/***************************************************************************
 *                                                                         *
 *   GMEUTL.C                                                              *
 *                                                                         *
 *   Copyright (c) 1994-1997 Galacticomm, Inc.    All Rights Reserved.     *
 *                                                                         *
 *   This file contains common and utility functions used by and for GME   *
 *   both online and offline.                                              *
 *                                                                         *
 *                                           - J. Alvrus   6/9/94          *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "majorbbs.h"
#include "galme.h"
#include "gme.h"
#include "gmeutl.h"
#include "gmeloc.h"

#define FILREV "$Revision: 28 $"

#define GMEMBN "galme.mcv"         /* GME CNF options file name            */

UINT  _reclen,                     /* message file record length           */
      _txtlen=0U;                  /* max message text length              */

INT _maxqsf=0;                     /* max forums in a user's quickscan     */
INT qssiz;                         /* size of in-memory quickscan          */
USHORT dftfor;                     /* default forum ID                     */
SHORT dfpref;                      /* default user preferences             */

HMCVFILE gmemb=NULL;               /* GME CNF options file block handle    */

CHAR *utltxt;                      /* GME utility message text buffer      */
struct message *utlmsg;            /* GME utility message header structure */
struct gmework *utlwork;           /* GME utility work area                */
struct fordef *utldef;             /* GME utility forum definition         */

CHAR *extinf;                      /* extended return information buffer   */

UINT hirqidx=0U;                   /* highest request array index          */
CHAR *gmerqarr=NULL;               /* pointer to open request array        */

UINT                               /*   returns max message text length    */
txtlen(VOID)                       /* init__ callable get-text-length      */
{
     UINT tmplen;

     if (_txtlen == 0U) {
          if (gmemb == NULL) {
               gmemb=opnmsg(GMEMBN);
          }
          else {
               setmbk(gmemb);
          }
          tmplen=(UINT)lngopt(MSGSIZ,1024L,16384L);
          _txtlen=tmplen-sizeof(struct msgdsk)-2*MAXADR;
          _reclen=tmplen+_txtlen;
          ASSERT((INT)_reclen > 0);     /* no wrapping */
          rstmbk();
     }
     return(_txtlen);
}

INT                                /*   returns max # forums allowed in qs */
maxqsf(VOID)                       /* init__ callable get-max-qscan-forums */
{
     if (_maxqsf == 0) {
          if (gmemb == NULL) {
               gmemb=opnmsg(GMEMBN);
          }
          else {
               setmbk(gmemb);
          }
          _maxqsf=numopt(MAXQSF2,1,MAXNQSF);
          qssiz=fldoff(qscfg,accmsg)+_maxqsf*sizeof(struct fmidky)+(_maxqsf+1)/2;
#ifdef GCWINNT
          /* adjust qssiz for alignment */
          qssiz=((qssiz+(ALLOC_BOUNDARY-1))/ALLOC_BOUNDARY)*ALLOC_BOUNDARY;
#endif /* GCWINNT */
          rstmbk();
     }
     return(_maxqsf);
}

VOID
iniutl(VOID)                       /* initialize GME utilities             */
{
     ASSERT(GMEWRKSZ >= sizeof(struct gmework));
     txtlen();                     /* these will open .MCV file and ensure */
     maxqsf();                     /* TXTLEN and MAXQSF are initialized    */
     setmbk(gmemb);
     gmerqarr=(CHAR *)alczer(BIGBLK);
     hirqidx=BIGBLK;
     utlwork=(struct gmework *)alczer(sizeof(struct gmework));
     utlmsg=(struct message *)alcmem(sizeof(struct message));
     utltxt=alcmem(TXTLEN);
     utldef=(struct fordef *)alcmem(sizeof(struct fordef));
     extinf=alcmem(XINFSZ);
}

VOID
inidft(VOID)                       /* initialize default forum/preferences */
{
     setmbk(gmemb);
     if ((dftfor=getfid(getmsg(DFTFOR))) == EMLID) {
          catastro("Default forum \"%s\" does not exist!  "
                   "Check configuration option DFTFOR",getmsg(DFTFOR));
     }
     dfpref=0;
     if (ynopt(DFCARP)) {
          dfpref|=CLARPL;
     }
     switch (tokopt(DFLONP,"NEVER","PROMPT","ALWAYS",NULL)) {
     case 3:
          dfpref|=GORTIN;
     case 2:
          dfpref|=P4NEWM;
     }
     if (ynopt(DFFORP)) {
          dfpref|=FORUM2;
     }
     switch (tokopt(DFQUOP,"NEVER","PROMPT","ALWAYS",NULL)) {
     case 3:
          dfpref|=ALWQUO;
     case 2:
          dfpref|=MSGQUO;
     }
     if (!ynopt(DFBRWP)) {
          dfpref|=CMBHDR;
     }
     switch (tokopt(DFCMTP,"NEVER","PROMPT","ALWAYS",NULL)) {
     case 3:
          dfpref|=ALWCMT;
     case 2:
          dfpref|=CFWCMT;
     }
     rstmbk();
}

VOID
clsutl(VOID)                       /* close GME utilities                  */
{
     if (gmemb != NULL) {
          clsmsg(gmemb);
          gmemb=NULL;
     }
}

VOID
inigmerq(                          /* initialize GME request               */
VOID *pWork)                       /*   work area to be used for request   */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERTM(!gmerqopn(pWork),spr("rqid=%u st1=%d st2=%d st3=%d",
             work->rqid,work->state1,work->state2,work->state3));
     if (gmerqopn(pWork)) {
          clsgmerq(pWork);
     }
     setmem(pWork,sizeof(struct gmework),0);
     work->state1=START;
     work->state2=START;
     work->state3=START;
     addrq(pWork);
}

VOID
clsgmerq(                          /* close a GME request                  */
VOID *pWork)                       /*   work area being used for request   */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     if (gmerqopn(pWork)) {
          if (!gmeoffl()) {
               gmeulkr(pWork);
          }
          if (work->fp != NULL) {
               fclose(work->fp);
          }
          if (work->fpout != NULL) {
               fclose(work->fpout);
          }
          unlink(work->cpyatt);
          if (!gmeoffl()) {
               if (work->flags&(QIKLST|MASSLST|SYSLST)) {
                    clsdist(pWork);
               }
          }
          if (!((LONG)work->orgflg&FILIND) && istmp(work->orgatt)) {
               unlink(work->orgatt);
          }
          remrq(pWork);
     }
     setmem(pWork,sizeof(struct gmework),0);
     work->state1=START;
     work->state2=START;
     work->state3=START;
}

VOID
addrq(                             /* add request to list of requests      */
VOID *pWork)                       /*   pointer to request work area       */
{
     UINT idx,bit;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(!gmerqopn(pWork));
     for (idx=0 ; (idx < hirqidx) && (~gmerqarr[idx] == 0) ; ++idx) {
     }
     if ((LONG)idx*8L >= MAXGMERQ+1) {
          catastro("Too many simultaneous GME requests");
     }
     if (idx == hirqidx) {
          ASSERT(hirqidx+BIGBLK < MAXBLK);
          gmerqarr=(CHAR *)alcrsz(gmerqarr,hirqidx,hirqidx+BIGBLK);
          setmem(&gmerqarr[hirqidx],BIGBLK,0);
          hirqidx+=BIGBLK;
     }
     for (bit=0 ; gmerqarr[idx]&(1<<bit) ; ++bit) {
          ASSERT(bit < 8);
     }
     gmerqarr[idx]|=1<<bit;
     work->rqid=idx*8+bit+1;
     work->antirq=~work->rqid;
}

VOID
remrq(                             /* remove request from list             */
VOID *pWork)                       /*   pointer to request work area       */
{
     UINT idx,bit;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     idx=(work->rqid-1)>>3;        /* idx = rqid / 8 */
     bit=(work->rqid-1)&7;         /* bit = rqid % 8 */
     gmerqarr[idx]&=~(1<<bit);
}

GBOOL
gmerqopn(                          /* is this request open?                */
VOID *pWork)                       /*   pointer to work area               */
{
     UINT idx,bit;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     if (work->antirq == ~work->rqid
      && work->rqid > 0 && work->rqid < hirqidx*8) {
          idx=(work->rqid-1)>>3;   /* idx = rqid / 8 */
          bit=(work->rqid-1)&7;    /* bit = rqid % 8 */
          return(gmerqarr[idx]&(1<<bit));
     }
     return(FALSE);
}

const struct fordef *              /*   pointer to temporary buffer        */
getdefp(                           /* get pointer to forum definition      */
USHORT forum)                      /*   forum ID of def to get             */
{
     return(getdefb(forum,utldef));
}

struct fordef *                    /*   pointer to destination buffer      */
getdefb(                           /* copy forum def into a work buffer    */
USHORT forum,                      /*   forum ID of def to get             */
struct fordef *workdef)            /*   pointer to work buffer             */
{
     struct fordef *tmpdef;

     ASSERT(workdef != NULL);
     tmpdef=getdef(forum);
     if (tmpdef != NULL) {
          movmem(tmpdef,workdef,sizeof(struct fordef));
          return(workdef);
     }
     return(NULL);
}

const struct fordef *              /*   pointer to temporary buffer        */
fiddefp(                           /* ptr to forum def in forum ID order   */
INT idx)                           /*   index of forum ID                  */
{
     return(fiddefb(idx,utldef));
}

struct fordef *                    /*   pointer to destination buffer      */
fiddefb(                           /* get forum def in forum ID order      */
INT idx,                           /*   index of forum ID                  */
struct fordef *workdef)            /*   pointer to work buffer             */
{
     struct fordef *tmpdef;

     ASSERT(workdef != NULL);
     tmpdef=fiddef(idx);
     if (tmpdef != NULL) {
          movmem(tmpdef,workdef,sizeof(struct fordef));
          return(workdef);
     }
     return(NULL);
}

const struct fordef *              /*   pointer to temporary buffer        */
seqdefp(                           /* get pointer to forum def in sequence */
USHORT seqid)                      /*   sequence ID of forum               */
{
     return(seqdefb(seqid,utldef));
}

struct fordef *                    /*   pointer to destination buffer      */
seqdefb(                           /* get forum def into buffer in sequence*/
USHORT seqid,                      /*   sequence ID of forum               */
struct fordef *workdef)            /*   pointer to work buffer             */
{
     struct fordef *tmpdef;

     ASSERT(workdef != NULL);
     tmpdef=seqdef(seqid);
     if (tmpdef != NULL) {
          movmem(tmpdef,workdef,sizeof(struct fordef));
          return(workdef);
     }
     return(NULL);
}

const struct fordef *              /*   pointer to temporary buffer        */
nxtdefp(                           /* get ptr to next forum definition     */
const CHAR *name)                  /*   given name to start with           */
{
     return(nxtdefb(name,utldef));
}

struct fordef *                    /*   pointer to destination buffer      */
nxtdefb(                           /* copy next forum def into a work buf  */
const CHAR *name,                  /*   given name to start with           */
struct fordef *workdef)            /*   pointer to work buffer             */
{
     INT i;

     ASSERT(workdef != NULL);
     i=nxtfnmi(name);
     if (i != NOIDX) {
          movmem(idxdef(i),workdef,sizeof(struct fordef));
          return(workdef);
     }
     return(NULL);
}

const struct fordef *              /*   pointer to temporary buffer        */
prvdefp(                           /* get ptr to prev forum definition     */
const CHAR *name)                  /*   given name to start with           */
{
     return(prvdefb(name,utldef));
}

struct fordef *                    /*   pointer to destination buffer      */
prvdefb(                           /* copy prev forum def into a work buf  */
const CHAR *name,                  /*   given name to start with           */
struct fordef *workdef)            /*   pointer to work buffer             */
{
     INT i;

     ASSERT(workdef != NULL);
     i=prvfnmi(name);
     if (i != NOIDX) {
          movmem(idxdef(i),workdef,sizeof(struct fordef));
          return(workdef);
     }
     return(NULL);
}

USHORT                             /*   returns ROOTGRP on failure         */
gmeGetGrpID(                       /* get group ID from path               */
const CHAR *path)                  /*   group path to trace                */
{
     const struct forgrp *grpptr;
     const CHAR *cp;
     size_t len;
     USHORT grpid;
     CHAR name[FORNSZ];

     grpid=ROOTGRP;
     while (*path != '\0') {
          cp=strchr(path,FORIDC);
          if (cp == NULL) {
               stlcpy(name,path,FORNSZ);
               path+=strlen(path);
          }
          else {
               len=(size_t)(cp-path);
               len=min(len,FORNSZ-1);
               stlcpy(name,path,len+1);
               path=cp+1;
          }
          if ((grpptr=gmeNamedGrpP(grpid,name)) == NULL) {
               return(ROOTGRP);
          }
          grpid=grpptr->grpid;
     }
     return(grpid);
}

const struct forgrp *              /*   pointer to temporary buffer        */
gmeGetGrpP(                        /* get pointer to group info            */
USHORT grpid)                      /*   group ID to get                    */
{
     return(gmeGetGrpB(grpid,utlgrp));
}

struct forgrp *                    /*   pointer to destination buffer      */
gmeGetGrpB(                        /* copy group info into a buffer        */
USHORT grpid,                      /*   group ID to get                    */
struct forgrp *grpbuf)             /*   pointer to buffer                  */
{
     struct forgrp *gp;

     ASSERT(grpbuf != NULL);
     if ((gp=getgrp(grpid)) != NULL) {
          movmem(gp,grpbuf,grprlen(gp->nforums));
          return(grpbuf);
     }
     return(NULL);
}

const struct forgrp *              /*   pointer to temporary buffer        */
gmeNamedGrpP(                      /* get next group in a group            */
USHORT pargrp,                     /*   parent group ID                    */
const CHAR *name)                  /*   name of group to get after         */
{
     ASSERT(name != NULL);
     return(gmeNamedGrpB(pargrp,name,utlgrp));
}

struct forgrp *                    /*   pointer to destination buffer      */
gmeNamedGrpB(                      /* get next group in a group            */
USHORT pargrp,                     /*   parent group ID                    */
const CHAR *name,                  /*   name of group to get after         */
struct forgrp *grpbuf)             /*   pointer to buffer                  */
{
     struct forgrp *gp;

     ASSERT(name != NULL);
     ASSERT(grpbuf != NULL);
     if ((gp=xctgrp(pargrp,name)) != NULL) {
          movmem(gp,grpbuf,grprlen(gp->nforums));
          return(grpbuf);
     }
     return(NULL);
}

const struct forgrp *              /*   pointer to temporary buffer        */
gmeNextGrpP(                       /* get next group in a group            */
USHORT pargrp,                     /*   parent group ID                    */
const CHAR *name)                  /*   name of group to get after         */
{
     ASSERT(name != NULL);
     return(gmeNextGrpB(pargrp,name,utlgrp));
}

struct forgrp *                    /*   pointer to destination buffer      */
gmeNextGrpB(                       /* get next group in a group            */
USHORT pargrp,                     /*   parent group ID                    */
const CHAR *name,                  /*   name of group to get after         */
struct forgrp *grpbuf)             /*   pointer to buffer                  */
{
     struct forgrp *gp;

     ASSERT(name != NULL);
     ASSERT(grpbuf != NULL);
     if ((gp=nxtgrp(pargrp,name)) != NULL) {
          movmem(gp,grpbuf,grprlen(gp->nforums));
          return(grpbuf);
     }
     return(NULL);
}

const struct forgrp *              /*   pointer to temporary buffer        */
gmePrevGrpP(                       /* get previous group in a group        */
USHORT pargrp,                     /*   parent group ID                    */
const CHAR *name)                  /*   name of group to get before        */
{
     ASSERT(name != NULL);
     return(gmePrevGrpB(pargrp,name,utlgrp));
}

struct forgrp *                    /*   pointer to destination buffer      */
gmePrevGrpB(                       /* get previous group in a group        */
USHORT pargrp,                     /*   parent group ID                    */
const CHAR *name,                  /*   name of group to get before        */
struct forgrp *grpbuf)             /*   pointer to buffer                  */
{
     struct forgrp *gp;

     ASSERT(name != NULL);
     ASSERT(grpbuf != NULL);
     if ((gp=prvgrp(pargrp,name)) != NULL) {
          movmem(gp,grpbuf,grprlen(gp->nforums));
          return(grpbuf);
     }
     return(NULL);
}

const struct fordef *              /*   pointer to temporary buffer        */
gmeGetGrpFDefP(                    /* get def of named forum in a group    */
USHORT grpid,                      /*   group ID forum is in               */
const CHAR *name)                  /*   name of forum to find              */
{
     ASSERT(name != NULL);
     return(gmeGetGrpFDefB(grpid,name,utldef));
}

struct fordef *                    /*   copy of pointer to buffer          */
gmeGetGrpFDefB(                    /* get def of named forum in a group    */
USHORT grpid,                      /*   group ID forum is in               */
const CHAR *name,                  /*   name of forum to find              */
struct fordef *defbuf)             /*   pointer to buffer                  */
{
     INT i;
     struct forgrp *gp;

     ASSERT(gmeGrpExist(grpid));
     ASSERT(name != NULL);
     ASSERT(defbuf != NULL);
     if ((gp=getgrp(grpid)) != NULL) {
          if ((i=gmeGetGrpFIdx(gp,name)) != NOIDX) {
               return(getdefb(gp->forarr[i],defbuf));
          }
     }
     return(NULL);
}

const struct fordef *              /*   pointer to temporary buffer        */
gmeNextGrpFDefP(                   /* get def of next forum in a group     */
USHORT grpid,                      /*   group ID forum is in               */
const CHAR *name)                  /*   name of forum to find after        */
{
     ASSERT(name != NULL);
     return(gmeNextGrpFDefB(grpid,name,utldef));
}

struct fordef *                    /*   copy of pointer to buffer          */
gmeNextGrpFDefB(                   /* get def of next forum in a group     */
USHORT grpid,                      /*   group ID forum is in               */
const CHAR *name,                  /*   name of forum to find after        */
struct fordef *defbuf)             /*   pointer to buffer                  */
{
     INT i;
     struct forgrp *gp;

     ASSERT(gmeGrpExist(grpid));
     ASSERT(name != NULL);
     ASSERT(defbuf != NULL);
     if ((gp=getgrp(grpid)) != NULL) {
          if ((i=gmeNextGrpFIdx(gp,name)) != NOIDX) {
               return(getdefb(gp->forarr[i],defbuf));
          }
     }
     return(NULL);
}

const struct fordef *              /*   pointer to temporary buffer        */
gmePrevGrpFDefP(                   /* get def of previous forum in a group */
USHORT grpid,                      /*   group ID forum is in               */
const CHAR *name)                  /*   name of forum to find before       */
{
     ASSERT(name != NULL);
     return(gmePrevGrpFDefB(grpid,name,utldef));
}

struct fordef *                    /*   copy of pointer to buffer          */
gmePrevGrpFDefB(                   /* get def of previous forum in a group */
USHORT grpid,                      /*   group ID forum is in               */
const CHAR *name,                  /*   name of forum to find before       */
struct fordef *defbuf)             /*   pointer to buffer                  */
{
     INT i;
     struct forgrp *gp;

     ASSERT(gmeGrpExist(grpid));
     ASSERT(name != NULL);
     ASSERT(defbuf != NULL);
     if ((gp=getgrp(grpid)) != NULL) {
          if ((i=gmePrevGrpFIdx(gp,name)) != NOIDX) {
               return(getdefb(gp->forarr[i],defbuf));
          }
     }
     return(NULL);
}

INT                                /*   (returns NOIDX if not found)       */
gmeGetGrpFIdx(                     /* get index of named forum in group    */
const struct forgrp *grpbuf,       /*   group info buffer to find in       */
const CHAR *name)                  /*   name of forum to find              */
{
     INT i,cond;

     ASSERT(grpbuf != NULL);
     ASSERT(name != NULL);
     i=neargrpi(&cond,name,grpbuf);
     if (cond == 0) {
          return(i);
     }
     return(NOIDX);
}

INT                                /*   (returns NOIDX if not found)       */
gmeNextGrpFIdx(                    /* get index of next forum in group     */
const struct forgrp *grpbuf,       /*   group to find in                   */
const CHAR *name)                  /*   after this forum name              */
{
     INT i,cond;

     ASSERT(grpbuf != NULL);
     ASSERT(name != NULL);
     i=neargrpi(&cond,name,grpbuf);
     if (cond > 0) {
          return(i);
     }
     if (cond <= 0 && i < grpbuf->nforums-1) {
          return(i+1);
     }
     return(NOIDX);
}

INT                                /*   (returns NOIDX if not found)       */
gmePrevGrpFIdx(                    /* get index of previous forum in group */
const struct forgrp *grpbuf,       /*   group info buffer to find in       */
const CHAR *name)                  /*   before this forum name             */
{
     INT i,cond;

     ASSERT(grpbuf != NULL);
     ASSERT(name != NULL);
     i=neargrpi(&cond,name,grpbuf);
     if (cond < 0) {
          return(i);
     }
     if (cond >= 0 && i > 0) {
          return(i-1);
     }
     return(NOIDX);
}

GBOOL                              /*   returns TRUE if change made        */
addgrpf(                           /* add a forum to a group               */
struct forgrp *grpptr,             /*   group info record                  */
USHORT forum)                      /*   forum ID to add                    */
{
     INT i,comp;

     ASSERT(grpptr != NULL);
     ASSERT(fidxst(forum));
     i=0;
     if (grpptr->nforums != 0) {
          i=neargrpi(&comp,getdef(forum)->name,grpptr);
          if (comp == 0) {
               return(FALSE);
          }
          if (comp < 0) {
               ++i;
          }
          ASSERT(i <= grpptr->nforums);
          movmem(&grpptr->forarr[i],&grpptr->forarr[i+1],
                 (grpptr->nforums-i)*sizeof(USHORT));
     }
     grpptr->forarr[i]=forum;
     ++grpptr->nforums;
     return(TRUE);
}

GBOOL                              /*   returns TRUE if change made        */
delgrpf(                           /* delete a forum from a group          */
struct forgrp *grpptr,             /*   group info record                  */
USHORT forum)                      /*   forum ID to add                    */
{
     INT i,nforums;
     USHORT *forlst;

     ASSERT(grpptr != NULL);
     if (grpptr->nforums == 0) {
          return(FALSE);
     }
     nforums=grpptr->nforums;
     forlst=grpptr->forarr;
     for (i=0 ; i < nforums ; ++i) {
          if (forlst[i] == forum) {
               --grpptr->nforums;
               movmem(&forlst[i+1],&forlst[i],(nforums-i-1)*sizeof(USHORT));
               return(TRUE);
          }
     }
     return(FALSE);
}

INT
neargrpi(                          /* get index of "nearest" forum in group*/
INT *cond,                         /*   buffer for result of last stricmp()*/
const CHAR *name,                  /*   name to search for                 */
const struct forgrp *grpbuf)       /*   group info buffer                  */
{
     INT lo,md,hi,comp;
     const USHORT *forlst;

     ASSERT(name != NULL);
     forlst=grpbuf->forarr;
     comp=1;
     md=-1;
     lo=0;
     hi=grpbuf->nforums-1;
     while (lo <= hi) {
          md=lo+((hi-lo)>>1);
          if ((comp=stricmp(getdef(forlst[md])->name,name)) < 0) {
               lo=md+1;
          }
          else if (comp > 0) {
               hi=md-1;
          }
          else {
               break;
          }
     }
     *cond=comp;
     return(md);
}

GBOOL
valfornm(                          /* is this a valid forum name?          */
const CHAR *name)                  /*   name to check                      */
{
     INT len;

     for (len=0 ; *name != '\0' ; ++len,++name) {
          if (len == FORNSZ || !isascii(*name)
           || strchr(" /@;",*name) != NULL) {
               return(FALSE);
          }
     }
     return(len > 0);
}

USHORT
getAdrForID(                       /* get forum ID given forum address     */
const CHAR *faddr)                 /*   forum-style address                */
{
     ASSERT(faddr != NULL);
     ASSERT(strlen(faddr) < MAXADR);
     ASSERT(isforum(faddr));
     return(getfid(extAdrForNam(faddr)));
}

const CHAR *                       /*   returns pointer to static buffer   */
extAdrForNam(                      /* extract forum name from address      */
const CHAR *faddr)                 /*   forum-style address                */
{
     INT i;
     static CHAR buf[FORNSZ];

     ASSERT(faddr != NULL);
     ASSERT(strlen(faddr) < MAXADR);
     ASSERT(isforum(faddr));
     for (i=0,++faddr ; i < FORNSZ && *faddr != '\0' && *faddr != ' ' ; ++i) {
          buf[i]=*faddr++;
     }
     buf[i]='\0';
     return(buf);
}

const CHAR *                       /*   returns NULL if doesn't exist      */
getfnm(                            /* get forum name                       */
USHORT fid)                        /*   given forum ID                     */
{
     struct fordef *tmpdef;
     static CHAR tmpnam[FORNSZ];

     if (fid == EMLID || (tmpdef=getdef(fid)) == NULL) {
          return(NULL);
     }
     return(stlcpy(tmpnam,tmpdef->name,FORNSZ));
}

const CHAR *                       /*   returns NULL if doesn't exist      */
getftpc(                           /* get forum topic                      */
USHORT fid)                        /*   given forum ID                     */
{
     struct fordef *tmpdef;
     static CHAR tmptpc[TPCSIZ];

     if (fid == EMLID || (tmpdef=getdef(fid)) == NULL) {
          return(NULL);
     }
     return(stlcpy(tmptpc,tmpdef->topic,TPCSIZ));
}

VOID
gmeLoadQS(                         /* load a user's quickscan              */
struct qscfg *qsc,                 /*   pointer to quickscan buffer        */
const CHAR *uid)                   /*   User-ID to load                    */
{                                  /*   qsc buf must hold MAXQSF forums    */
     INT i;
     USHORT qslen;

     dfaSetBlk(qscbb);
     if (dfaAcqEQ(NULL,uid,0)) {
          if ((qslen=dfaLastLen()) > qssiz) {
               while (qsdptr->nforums > MAXQSF && (i=qsdelf(qsdptr)) != NOIDX) {
                    idelqs(qsdptr,i);
               }
               while (qsdptr->nforums > MAXQSF && (i=qsoldm(qsdptr)) != NOIDX) {
                    idelqs(qsdptr,i);
               }
               while (qsdptr->nforums > MAXQSF && (i=qsoldr(qsdptr)) != NOIDX) {
                    idelqs(qsdptr,i);
               }
               while (qsdptr->nforums > MAXQSF) {
                    idelqs(qsdptr,qsoldest(qsdptr));
               }
               qslen=qsrlen(qsdptr->nforums);
          }
          movmem(qsdptr,qsc,qslen);
     }
     else {
          initqs(qsc,(CHAR *)uid);
     }
     dfaRstBlk();
}

VOID
gmeSaveQS(                         /* save a quickscan                     */
const struct qscfg *qsc)           /*   quickscan to save                  */
{
     dfaSetBlk(qscbb);
     if (dfaAcqEQ(NULL,qsc->userid,0)) {
          if (memcmp(qsdptr,qsc,qsrlen(qsc->nforums)) != 0) {
               dfaUpdateV(qsc,qsrlen(qsc->nforums));
          }
     }
     else {
          dfaInsertV(qsc,qsrlen(qsc->nforums));
     }
     dfaRstBlk();
}

VOID
initqs(                            /* initialize a new user's quickscan    */
struct qscfg *qsc,                 /*   pointer to quickscan buffer        */
const CHAR *uid)                   /*   User-ID to initialize              */
{
     setmem(qsc,qssiz,0);
     qsc->flags=dfpref|NEWMSG;
     qsc->curfor=dftfor;
     stlcpy(qsc->userid,uid,UIDSIZ);
     isethi(qsc,add2qs(qsc,dftfor),1L);
}

GBOOL                              /*   returns FALSE if unable to add     */
sfinqs(                            /* set forum in quickscan               */
struct qscfg *qsc,                 /*   quickscan record                   */
USHORT fid,                        /*   forum to set                       */
GBOOL turnon)                      /*   TRUE = make forum "in" quickscan   */
{
     INT i;
     LONG tmphi;

     ASSERT(qsc != NULL);
     i=qsidx(qsc,fid);
     if (turnon) {
          if (i == NOIDX) {
               i=absadqs(qsc,fid);
               if (i == NOIDX) {
                    return(FALSE);
               }
               isethi(qsc,i,1L);
          }
          else {
               tmphi=igethi(qsc,i);
               if (tmphi == 0L) {
                    isethi(qsc,i,1L);
               }
               else if (tmphi < 0L) {
                    isethi(qsc,i,-tmphi);
               }
          }
     }
     else if (i != NOIDX) {
          tmphi=igethi(qsc,i);
          if (tmphi > 0L) {
               isethi(qsc,i,-tmphi);
          }
     }
     return(TRUE);
}

struct otscan *                    /*   returns copy of pointer to dest    */
qsc2ots(                           /* copy quickscan to a one-time scan buf*/
const struct qscfg *qsc,           /*   quickscan to copy                  */
struct otscan *ots)                /*   one-time scan buffer               */
{
     INT i;
     USHORT fid;

     ASSERT(qsc != NULL);
     ASSERT(ots != NULL);
     stlcpy(ots->keywds,qsc->kwds,MAXSKWD);
     ots->stmsgid=qsc->stmsg;
     ots->flags=(qsc->flags&NEWMSG) ? SCNEW : 0;
     if (qsc->flags&WATONL) {
          ots->flags|=SCATT;
     }
     if (qsc->flags&TOMEONL) {
          ots->flags|=SCTOU;
     }
     if (qsc->flags&FRMEONL) {
          ots->flags|=SCFRU;
     }
     ots->nforums=0;
     for (i=0 ; i < qsc->nforums ; ++i) {
          if (fidxst(fid=igetfid(qsc,i)) && igethi(qsc,i) > 0L) {
               addf2ots(ots,fid);
          }
     }
     return(ots);
}

VOID
addf2ots(                          /* add forum to scan list in seq order  */
struct otscan *ots,                /*   one-time scan structure to update  */
USHORT forum)                      /*   forum to add                       */
{
     INT i;
     USHORT seq;

     ASSERT(ots != NULL);
     ASSERT(fidxst(forum));
     seq=getdef(forum)->seqid;
     for (i=0 ; i < ots->nforums ; ++i) {
          if (getdef(ots->forlst[i])->seqid > seq) {
               break;
          }
     }
     movmem(&ots->forlst[i],&ots->forlst[i+1],
            sizeof(USHORT)*(ots->nforums-i));
     ots->forlst[i]=forum;
     ++ots->nforums;
}

INT                                /*   index of new entry (NOIDX if error)*/
absadqs(                           /* add forum to qs, del others for room */
struct qscfg *qsc,                 /*   quickscan record                   */
USHORT fid)                        /*   forum ID to add                    */
{
     INT i;

     ASSERT(qsc != NULL);
     if ((i=add2qs(qsc,fid)) == NOIDX) {
          if ((i=qsdelf(qsc)) == NOIDX) {
               if ((i=qsoldm(qsc)) == NOIDX) {
                    return(NOIDX);
               }
          }
          idelqs(qsc,i);
          i=add2qs(qsc,fid);
     }
     return(i);
}

INT                                /*   index in qs arrays (NOIDX if err)  */
qsidx(                             /* get index of forum in quickscan      */
const struct qscfg *qsc,           /*   quickscan record                   */
USHORT fid)                        /*   forum ID to get                    */
{
     INT i;
     const struct fmidky *fm;

     fm=(const struct fmidky *)qsc->accmsg;
     for (i=0 ; i < qsc->nforums ; ++i) {
          if (fm[i].forum == fid) {
               return(i);
          }
     }
     return(NOIDX);
}

INT                                /*   index in qs arrays (NOIDX if err)  */
add2qs(                            /* add slot for forum to quickscan      */
struct qscfg *qsc,                 /*   quickscan record                   */
USHORT fid)                        /*   forum ID to add                    */
{
     INT i,j;
     CHAR *acc;
     USHORT lastfid;
     struct fmidky *fm;

     ASSERT(qsc != NULL);
     lastfid=EMLID;
     fm=(struct fmidky *)qsc->accmsg;
     for (i=0 ; i < qsc->nforums ; ++i) {
          if (fid == fm[i].forum) {
               return(i);
          }
          else if (fid > lastfid && fid < fm[i].forum) {
               break;
          }
          lastfid=fm[i].forum;
     }
     if (qsc->nforums < MAXQSF) {
          acc=(CHAR *)&fm[qsc->nforums];
          movmem(acc,acc+sizeof(struct fmidky),(qsc->nforums+1)/2);
          acc=(CHAR *)&fm[qsc->nforums+1];
          if (i < qsc->nforums) {
               movmem(&fm[i],&fm[i+1],sizeof(struct fmidky)*(qsc->nforums-i));
               for (j=qsc->nforums ; j > i ; --j) {
                    wracc(acc,acclvl(acc,j-1),j);
               }
          }
          fm[i].forum=fid;
          fm[i].msgid=0L;
          wracc(acc,NOTSET,i);
          ++qsc->nforums;
          return(i);
     }
     return(NOIDX);
}

VOID
delqs(                             /* delete an entry from the quickscan   */
struct qscfg *qsc,                 /*   pointer to quickscan               */
USHORT forum)                      /*   forum ID to remove from quickscan  */
{
     INT i;

     ASSERT(qsc != NULL);
     i=qsidx(qsc,forum);
     if (i != NOIDX) {
          idelqs(qsc,i);
     }
}

GBOOL                              /*   returns TRUE if able to set        */
sethi(                             /* set hi message in quickscan          */
struct qscfg *qsc,                 /*   pointer to quickscan               */
USHORT forum,                      /*   forum ID to set for                */
LONG msgid)                        /*   message ID to set as high message  */
{
     INT i;

     ASSERT(qsc != NULL);
     i=qsidx(qsc,forum);
     if (i == NOIDX) {
          i=add2qs(qsc,forum);
          if (i != NOIDX) {
               isethi(qsc,i,msgid);
               return(TRUE);
          }
     }
     else {
          isethi(qsc,i,msgid);
          return(TRUE);
     }
     return(FALSE);
}

GBOOL                              /*   returns TRUE if able to set        */
setac(                             /* set forum acc in quickscan           */
struct qscfg *qsc,                 /*   pointer to quickscan               */
USHORT forum,                      /*   forum ID to set for                */
INT acc)                           /*   access level to set                */
{
     INT i;

     ASSERT(qsc != NULL);
     ASSERT(acc >= 0 && acc <= NOTSET);
     i=qsidx(qsc,forum);
     if (i == NOIDX) {
          i=add2qs(qsc,forum);
          if (i != NOIDX) {
               isetac(qsc,i,acc);
               return(TRUE);
          }
     }
     else {
          isetac(qsc,i,acc);
          return(TRUE);
     }
     return(FALSE);
}

LONG
gethi(                             /* get hi message in quickscan          */
const struct qscfg *qsc,           /*   pointer to quickscan               */
USHORT forum)                      /*   forum ID to get for                */
{
     INT i;

     ASSERT(qsc != NULL);
     i=qsidx(qsc,forum);
     if (i != NOIDX) {
          return(igethi(qsc,i));
     }
     return(FIRSTM);
}

VOID
idelqs(                            /* delete an entry from the quickscan   */
struct qscfg *qsc,                 /*   pointer to quickscan               */
INT idx)                           /*   index of entry to delete           */
{
     INT i;
     CHAR *acc;
     struct fmidky *fm;

     ASSERT(qsc != NULL);
     ASSERT(idx >= 0 && idx < qsc->nforums);
     fm=(struct fmidky *)qsc->accmsg;
     movmem(&fm[idx+1],&fm[idx],
            sizeof(struct fmidky)*(qsc->nforums-idx-1)+(qsc->nforums+1)/2);
     --qsc->nforums;
     acc=(CHAR *)&fm[qsc->nforums];
     for (i=idx ; i < qsc->nforums ; ++i) {
          wracc(acc,acclvl(acc,i+1),i);
     }
}

VOID
isethi(                            /* set hi message in quickscan (w/index)*/
struct qscfg *qsc,                 /*   pointer to quickscan               */
INT idx,                           /*   index of forum to set              */
LONG msgid)                        /*   message ID to set as high message  */
{
     struct fmidky *fm;

     ASSERT(qsc != NULL);
     ASSERT(idx >= 0 && idx < qsc->nforums);
     fm=(struct fmidky *)qsc->accmsg;
     fm[idx].msgid=msgid;
}

VOID
isetac(                            /* set forum acc in quickscan (w/index) */
struct qscfg *qsc,                 /*   pointer to quickscan               */
INT idx,                           /*   index of forum to set              */
INT acc)                           /*   access level to set                */
{
     CHAR *acar;

     ASSERT(qsc != NULL);
     ASSERT(idx >= 0 && idx < qsc->nforums);
     ASSERT(acc >= 0 && acc <= NOTSET);
     ASSERT(acc != OPAXES && acc != SYAXES);
     acar=qsc->accmsg+sizeof(struct fmidky)*qsc->nforums;
     wracc(acar,acc,idx);
}

USHORT
igetfid(                           /* get forum ID in quickscan (w/index)  */
const struct qscfg *qsc,           /*   pointer to quickscan               */
INT idx)                           /*   index of forum to get              */
{
     const struct fmidky *fm;

     ASSERT(qsc != NULL);
     ASSERT(idx >= 0 && idx < qsc->nforums);
     fm=(const struct fmidky *)qsc->accmsg;
     return(fm[idx].forum);
}

LONG
igethi(                            /* get hi message in quickscan (w/index)*/
const struct qscfg *qsc,           /*   pointer to quickscan               */
INT idx)                           /*   index of forum to get              */
{
     const struct fmidky *fm;

     ASSERT(qsc != NULL);
     ASSERT(idx >= 0 && idx < qsc->nforums);
     fm=(const struct fmidky *)qsc->accmsg;
     return(fm[idx].msgid);
}

INT
igetac(                            /* get forum acc in quickscan (w/index) */
const struct qscfg *qsc,           /*   pointer to quickscan               */
INT idx)                           /*   index of forum to get              */
{
     const CHAR *acar;

     ASSERT(qsc != NULL);
     ASSERT(idx >= 0 && idx < qsc->nforums);
     acar=qsc->accmsg+sizeof(struct fmidky)*qsc->nforums;
     return(acclvl(acar,idx));
}

INT                                /*   returns index or NOIDX if none     */
qsdelf(                            /* get first non-existent forum         */
const struct qscfg *qsc)           /*   pointer to quickscan               */
{
     INT i;
     const struct fmidky *fm;

     ASSERT(qsc != NULL);
     fm=(const struct fmidky *)qsc->accmsg;
     for (i=0 ; i < qsc->nforums ; ++i) {
          if (!fidxst(fm[i].forum)) {
               return(i);
          }
     }
     return(NOIDX);
}

INT                                /*   returns index or NOIDX if none     */
qsoldm(                            /* get oldest "marker" entry            */
const struct qscfg *qsc)           /*   pointer to quickscan               */
{
     INT i,lasti;
     LONG curhi,lowhi;
     const struct fmidky *fm;
     const CHAR *acc;

     ASSERT(qsc != NULL);
     fm=(const struct fmidky *)qsc->accmsg;
     acc=(const CHAR *)&fm[qsc->nforums];
     lowhi=-LASTM;
     lasti=NOIDX;
     for (i=0 ; i < qsc->nforums ; ++i) {
          curhi=fm[i].msgid;
          if (curhi <= 0L && curhi > lowhi && acclvl(acc,i) == NOTSET) {
               lowhi=curhi;
               lasti=i;
          }
     }
     return(lasti);
}

INT                                /*   returns index or NOIDX if none     */
qsoldr(                            /* get oldest "real" entry              */
const struct qscfg *qsc)           /*   pointer to quickscan               */
{
     INT i,lasti;
     LONG curhi,lowhi;
     const struct fmidky *fm;
     const CHAR *acc;

     ASSERT(qsc != NULL);
     fm=(const struct fmidky *)qsc->accmsg;
     acc=(const CHAR *)&fm[qsc->nforums];
     lowhi=LASTM;
     lasti=NOIDX;
     for (i=0 ; i < qsc->nforums ; ++i) {
          curhi=fm[i].msgid;
          if (curhi > 0L && curhi < lowhi && acclvl(acc,i) == NOTSET) {
               lowhi=curhi;
               lasti=i;
          }
     }
     return(lasti);
}

INT                                /*   returns index or NOIDX if none     */
qsoldest(                          /* get oldest real entry w/ or w/out acc*/
const struct qscfg *qsc)           /*   pointer to quickscan               */
{
     INT i,lasti;
     LONG curhi,lowhi;
     const struct fmidky *fm;

     ASSERT(qsc != NULL);
     fm=(const struct fmidky *)qsc->accmsg;
     lowhi=LASTM;
     lasti=NOIDX;
     for (i=0 ; i < qsc->nforums ; ++i) {
          curhi=fm[i].msgid;
          if (curhi > 0L && curhi < lowhi) {
               lowhi=curhi;
               lasti=i;
          }
     }
     return(lasti);
}

GBOOL
islocal(                           /* is this a valid local address        */
const CHAR *adr)                   /*   address to check                   */
{
     INT i;

     ASSERT(adr != NULL);
     if (isdlst(adr) || isforum(adr) || isexpa(adr)) {
          return(FALSE);
     }
     for (i=0 ; adr[i] != '\0' ; ++i) {
          if (i >= UIDSIZ || !isuidc(adr[i])) {
               return(FALSE);
          }
     }
     return(TRUE);
}

struct qscfg *
uqsptr(                            /* get online user's quickscan          */
INT unum)                          /*   given user number                  */
{
     return((struct qscfg *)ptrblok(usrqs,unum));
}

VOID
wracc(                             /* write-access-level in compressd array*/
CHAR *acarpt,                      /*   pointer to access-level array      */
INT value,                         /*   value to write                     */
INT idx)                           /*   index in array                     */
{
     CHAR *cp;

     ASSERT(acarpt != NULL);
     ASSERT(value <= COAXES || value == NOTSET);
     cp=&acarpt[idx>>1];
     if (idx&1) {
          *cp&=0x0F;
          *cp|=value<<4;
     }
     else {
          *cp&=0xF0;
          *cp|=value;
     }
}

INT
acclvl(                            /* get acc level from compressed array  */
const CHAR *acarpt,                /*   pointer to array                   */
INT idx)                           /*   index of access level              */
{
     ASSERT(acarpt != NULL);
     if (idx&1) {
          return(acarpt[idx>>1]>>4);
     }
     else {
          return(acarpt[idx>>1]&0x0F);
     }
}

const CHAR *
accstr(                            /* string describing forum access level */
INT acclvl)                        /*   access level to describe           */
{
     static CHAR *accdsc[]={"Zero","","Read","","Download","","Write","",
                            "Upload","","Co-Op","","Forum-Op","","Sysop",
                            "<unassigned>"};

     ASSERT(acclvl <= NOTSET && (!(acclvl&1) || acclvl == NOTSET));
     return(accdsc[acclvl]);
}

LONG
gmeReg2SysID(                      /* get GME System-ID from reg #         */
const CHAR *regno)                 /*   system registration number         */
{
     ASSERT(regno != NULL);
     ASSERT(strlen(regno) == 8);
     ASSERT(alldgs((CHAR *)regno));
     return(((LONG)(((regno[0]-'0')*10+(regno[1]-'0'))|0x80))
          |(((LONG)(((regno[2]-'0')*10+(regno[3]-'0'))|0x80))<<8)
          |(((LONG)(((regno[4]-'0')*10+(regno[5]-'0'))|0x80))<<16)
          |(((LONG)(((regno[6]-'0')*10+(regno[7]-'0'))|0x80))<<24));
}

#ifdef DEBUG
GBOOL
valSysID(                          /* check for valid System-ID            */
LONG sysid)                        /*   System-ID to check                 */
{
     return((((ULONG)sysid)&0x80) == 0x80
         && (((ULONG)sysid)&0x7F) < 100
         && ((((ULONG)sysid)>>8)&0x80) == 0x80
         && ((((ULONG)sysid)>>8)&0x7F) < 100
         && ((((ULONG)sysid)>>16)&0x80) == 0x80
         && ((((ULONG)sysid)>>16)&0x7F) < 100
         && ((((ULONG)sysid)>>24)&0x80) == 0x80
         && ((((ULONG)sysid)>>24)&0x7F) < 100);
}
#endif // DEBUG

CHAR *                             /*   returns pointer to destination     */
gmeSysID2Reg(                      /* get reg # from GME System-ID         */
LONG sysid,                        /*   GME System-ID to convert           */
CHAR *regno)                       /*   buffer for reg # (size >= 9)       */
{
     CHAR *cp;
     ULONG usid;

     ASSERT(regno != NULL);
     ASSERT(valSysID(sysid));
     cp=regno;
     usid=(ULONG)sysid;
     while (cp < regno+8) {
          *cp++=(CHAR)((usid&0x7F)/10)+'0';
          *cp++=(CHAR)((usid&0x7F)%10)+'0';
          usid>>=8;
     }
     *cp='\0';
     return(regno);
}

INT                                /*   returns standard GME status code   */
val2gme(                           /* convert a VAL code to status code    */
INT valcode)                       /*   VAL code to convert                */
{
     switch (valcode) {
     case VALYES:
          return(GMEOK);
     case VALNO:
          return(GMEERR);
     case VALACC:
          return(GMEACC);
     case VALCRD:
          return(GMECRD);
     }
     return(GMEAGAIN);
}

VOID
clrwrt(                            /* clr write-specific flds of work area */
VOID *pWork)                       /*   GME work space in use              */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     work->flags&=~(ADROK|ATTOK|RRROK|PRIOK|FWDMSG|CPY2E|SYSLST|QIKLST
                   |MASSLST|CYCFLG|HASAPI|PRIMDONE);
     work->basechg=0L;
     work->rcpchg=0L;
     work->attchg=0;
     work->apkchg=0;
     work->rrrchg=0;
     work->prichg=0;
     work->appinf=NULL;
     *work->cpyatt='\0';
}

VOID
addhist(                           /* add to message history               */
CHAR *history,                     /*   current history string             */
const CHAR *newhist)               /*   string to add                      */
{
     CHAR histbuf[HSTSIZ*2];

     stlcpy(histbuf,newhist,HSTSIZ);
     if (*history != '\0') {
          stzcat(histbuf,", ",HSTSIZ*2);
          stzcat(histbuf,history,HSTSIZ*2);
          if (strlen(histbuf) > HSTSIZ-1) {
               histbuf[HSTSIZ-2]='*';
               histbuf[HSTSIZ-1]='\0';
          }
     }
     stlcpy(history,histbuf,HSTSIZ);
}

LONG
cmptid(                            /* compute thread ID                    */
const struct message *msg)         /*   message header                     */
{
     if (msg->forum == EMLID) {
          return(emltid(msg));
     }
     else {
          return(fortid(msg));
     }
}

LONG
emltid(                            /* generate an E-mail thread ID         */
const struct message *msg)         /*   message header (to,from,topic)     */
{
     CHAR *adr1,*adr2,*cp1,*cp2;

     ASSERT(msg != NULL);
     adr1=(CHAR *)tmpbuf;
     adr2=adr1+UIDSIZ;
     if (isexpa(msg->from)) {
          cp1=strchr(msg->from,':');
          ASSERT(cp1 != NULL);
          for (cp2=cp1+1 ; *cp2 == ' ' ; ++cp2) {
          }
          ASSERT(UIDSIZ >= (INT)(cp1-msg->from+2));
          stlcpy(adr1,msg->from,(INT)(cp1-msg->from+2));
          stlcat(adr1,cp2,UIDSIZ);
     }
     else {
          stlcpy(adr1,msg->from,UIDSIZ);
     }
     if (isexpa(msg->to)) {
          cp1=strchr(msg->to,':');
          ASSERT(cp1 != NULL);
          for (cp2=cp1+1 ; *cp2 == ' ' ; ++cp2) {
          }
          ASSERT(UIDSIZ >= (INT)(cp1-msg->to+2));
          stlcpy(adr2,msg->to,(INT)(cp1-msg->to+2));
          stlcat(adr2,cp2,UIDSIZ);
     }
     else {
          stlcpy(adr2,msg->to,UIDSIZ);
     }
     if (strcmp(adr1,adr2) < 0) {
          ASSERT(strlen(adr2) < UIDSIZ);
          movmem(adr2,adr1+strlen(adr1),strlen(adr2)+1);
     }
     else {
          stlcat(adr2,adr1,2*UIDSIZ-1);
          ASSERT(strlen(adr2) < 2*UIDSIZ-1);
          movmem(adr2,adr1,strlen(adr2)+1);
     }
     stlcat(adr1,msg->topic,2*UIDSIZ+TPCSIZ-2);
     strupr(adr1);
     return(crc32(adr1,strlen(adr1)));
}

LONG
fortid(                            /* generate a forum thread ID           */
const struct message *msg)         /*   message header (topic)             */
{
     CHAR *s;

     ASSERT(msg != NULL);
     s=(CHAR *)tmpbuf;
     stlcpy(s,msg->topic,TPCSIZ);
     strupr(s);
     return(crc32(s,strlen(s)));
}

const CHAR *                       /*   path & file name to place file in  */
ulname(                            /* path & file name to upload att to    */
const struct message *msg)         /*   header of message to attach to     */
{
     USHORT tmpfid;

     ASSERT(msg != NULL);
     ASSERT(msg->forum == EMLID || fidxst(msg->forum));
     tmpfid=msg->forum;
     if (tmpfid == EMLID && isforum(msg->to)) {
          tmpfid=getAdrForID(msg->to);
          ASSERT(tmpfid != EMLID);
     }
     return(spr("%s"SLS"%s",attpth(tmpfid),tmpanam(NULL)));
}

const CHAR *                       /*   path & file name of attachment     */
dlname(                            /* path & file name to dnload att from  */
const struct message *msg)         /*   header of message with attachment  */
{
     return(gdlname((msg->flags&FILIND) != 0L,msg->forum,msg->msgid));
}

const CHAR *                       /*   path & file name of attachment     */
gdlname(                           /* gen path & file name to dnload att   */
GBOOL indirect,                    /*   attachment is indirect             */
USHORT forum,                      /*   forum message is in                */
LONG msgid)                        /*   message ID                         */
{
     static CHAR nambuf[GCMAXPTH];

     if (indirect) {
          indasp(nambuf,forum,msgid);
     }
     else {
          dirasp(nambuf,forum,msgid);
     }
     return(nambuf);
}

CHAR *                             /*   pointer to string buffer           */
indasp(                            /* get path+file name of indirect att   */
CHAR *buf,                         /*   string buffer for path             */
USHORT forum,                      /*   forum message is in                */
LONG msgid)                        /*   message ID of message              */
{
     FILE *fp;

     buf[0]='\0';
     if ((fp=fopen(attpfn(forum,msgid),FOPRA)) != NULL) {
          fgets(buf,GCMAXPTH,fp);
          fclose(fp);
     }
     return(buf);
}

CHAR *                             /*   pointer to string buffer           */
dirasp(                            /* get path+file name of direct att     */
CHAR *buf,                         /*   string buffer for path             */
USHORT forum,                      /*   forum message is in                */
LONG msgid)                        /*   message ID of message              */
{
     stlcpy(buf,attpfn(forum,msgid),GCMAXPTH);
     return(buf);
}

GBOOL
valpfx(                            /* is this a valid exporter prefix?     */
const CHAR *prefix)                /*   prefix to check                    */
{
     return(isalnum(prefix[0]) && isalnum(prefix[1])
         && (prefix[2] == '\0' || (isalnum(prefix[2]) && prefix[3] == '\0')));
}

GBOOL
isexpa(                            /* is this an exporter-style address    */
const CHAR *adr)                   /*   address to check                   */
{
     CHAR *cp;

     cp=strchr(adr,':');
     if (cp != NULL) {
          for ( ; adr < cp ; ++adr) {
               if (!isalnum(*adr)) {
                    return(FALSE);
               }
          }
          return(TRUE);
     }
     return(FALSE);
}

CHAR *
skppfx(                            /* skip over exporter prefix (if any)   */
const CHAR *adr)                   /*   address with prefix                */
{
     const CHAR *cp;

     ASSERT(adr != NULL);
     cp=strchr(adr,':');
     if (cp == NULL || (cp-adr) >= PFXSIZ) {
          cp=adr-1;
     }
     return(skpwht(cp+1));
}

GBOOL
thisexp(                           /* does address refer to this exporter? */
const struct exporter *exp,        /*   pointer to exporter control block  */
const CHAR *to)                    /*   address to check                   */
{
     return(to[strlen(exp->prefix)] == ':' && sameto(exp->prefix,to));
}

struct expinfo *                   /*   returns pointer to destination     */
exp2inf(                           /* copy exporter struct to expinfo      */
const struct exporter *exp,        /*   exporter structure                 */
struct expinfo *info)              /*   expinfo structure                  */
{
     ASSERT(exp != NULL);
     ASSERT(info != NULL);
     stlcpy(info->prefix,exp->prefix,PFXSIZ);
     stlcpy(info->name,exp->name,EXPNSZ);
     stlcpy(info->desc,exp->desc,EXPDSZ);
     stlcpy(info->exmp,exp->exmp,MAXADR);
     stlcpy(info->wrtkey,exp->wrtkey,KEYSIZ);
     info->wrtchg=exp->wrtchg;
     stlcpy(info->attkey,exp->attkey,KEYSIZ);
     info->attchg=exp->attchg;
     info->apkchg=exp->apkchg;
     stlcpy(info->rrrkey,exp->rrrkey,KEYSIZ);
     info->rrrchg=exp->rrrchg;
     stlcpy(info->prikey,exp->prikey,KEYSIZ);
     info->prichg=exp->prichg;
     info->flags=exp->flags;
     return(info);
}

GBOOL                              /*   returns TRUE if a valid file name  */
valfnm(                            /* check for valid file name            */
const CHAR *name)                  /*   string to check                    */
{
     const CHAR *cp;
     INT i;

     for (i=1,cp=name ; *cp != '\0' && *cp != '.' ; ++i,++cp) {
          if (i > 8 || !isvalfc(*cp)) {
               return(FALSE);
          }
     }
     if (*cp++ == '.') {
          for (i=1 ; *cp != '\0' ; ++i,++cp) {
               if (i > 3 || *cp == '.' || !isvalfc(*cp)) {
                    return(FALSE);
               }
          }
     }
     if (rsvnam(name)) {
          return(FALSE);
     }
     return(TRUE);
}

INT                                /*   returns appropriate GME work flag  */
dlstyp(                            /* get dist list type                   */
const CHAR *to)                    /*   given to address                   */
{
     ASSERT(to != NULL);
     if (!isdlst(to)) {
          return(0);
     }
     if (sameas(to,"!quick")) {
          return(QIKLST);
     }
     if (sameas(to,"!mass")) {
          return(MASSLST);
     }
     ASSERT(*to == '@');
     return(SYSLST);
}

GBOOL
valslnm(                           /* is this a valid sysop dist list name?*/
const CHAR *name)                  /*   complete name (including @)        */
{
     INT i;

     ASSERT(name != NULL);
     if (*name != '@') {
          return(FALSE);
     }
     for (i=1 ; name[i] != '\0' ; i++) {
          if (!isalnum(name[i]) || i >= DLNMSZ-1) {
               return(FALSE);
          }
     }
     return(i > 1);
}

INT                                /*   returns new number of echoes       */
addecho(                           /* add an echo to list                  */
CHAR *echoes,                      /*   echo list buffer                   */
const CHAR *newadr,                /*   echo address to add                */
INT necho)                         /*   current number of echoes           */
{
     adr_t *tmpecho;

     ASSERT(echoes != NULL);
     ASSERT(newadr != NULL);
     ASSERT(necho < MAXECHO);
     tmpecho=(adr_t *)echoes;
     stlcpy(tmpecho[necho],newadr,MAXADR);
     return(necho+1);
}

INT                                /*   returns new number of echoes       */
delecho(                           /* delete an echo from list             */
CHAR *echoes,                      /*   echo list buffer                   */
INT echonum,                       /*   index of echo address to delete    */
INT necho)                         /*   current number of echoes           */
{
     adr_t *tmpecho;

     ASSERT(echoes != NULL);
     ASSERT(necho < MAXECHO);
     ASSERT(echonum < necho);
     tmpecho=(adr_t *)echoes;
     if (echonum < necho-1) {
          movmem(tmpecho[echonum+1],tmpecho[echonum],
                 MAXADR*(necho-echonum-1));
     }
     return(necho-1);
}

VOID
clrfrom(                           /* "clear" from field of message        */
struct message *msg)               /*   message to clear                   */
{
     msg->flags|=FRCLR;
     msg->from[0]=lwclch(msg->from[0]);
}

VOID
uclfrom(                           /* un-"clear" from field of message     */
struct message *msg)               /*   message to unclear                 */
{
     if (msg->flags&FRCLR) {
          msg->from[0]=lwclch(msg->from[0]);
     }
}

VOID
clrto(                             /* "clear" to field of message          */
struct message *msg)               /*   message to clear                   */
{
     msg->flags|=TOCLR;
     msg->to[0]=lwclch(msg->to[0]);
}

VOID
uclto(                             /* un-"clear" to field of message       */
struct message *msg)               /*   message to unclear                 */
{
     if (msg->flags&TOCLR) {
          msg->to[0]=lwclch(msg->to[0]);
     }
}

CHAR
lwclch(                            /* reversable clobber/unclobber of char */
CHAR c)                            /*   character to clobber/unclobber     */
{
     static CHAR xform[256]={
            0, 32, 34, 39, 40, 41, 44, 45, 46, 48, 49, 50, 51, 52, 53, 54,
           55, 56, 57, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77,
            1, 78,  2, 79, 80, 81, 82,  3,  4,  5, 42, 83,  6,  7,  8, 47,
            9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 84, 85, 86, 87, 88, 89,
           90, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 33, 35,
           36, 37, 38, 43, 58, 59, 60, 61, 62, 63, 64, 95, 97, 98, 99, 91,
          100, 92, 93, 94, 96,123,124,125,126,127,166,167,168,169,170,171,
          172,173,174,175,176,177,178,179,180,181,182,101,102,103,104,105,
          183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,
          199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,
          215,216,217,218,219,220,106,107,108,109,110,111,112,113,114,115,
          116,117,118,119,120,121,122,128,129,130,131,132,133,134,135,136,
          137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,
          153,154,155,156,157,158,159,160,161,162,163,164,165,224,225,226,
          221,222,223,240,241,242,243,244,245,246,247,248,249,250,251,252,
          227,228,229,230,231,232,233,234,235,236,237,238,239,  0,  0,  0
     };

     return(xform[(INT)c]);
}

CHAR *                             /*   ptr to next entry (NULL if done)   */
parscc(                            /* parse next cc: from list             */
CHAR *addr,                        /*   buffer to put cc: address into     */
const CHAR *list)                  /*   ';'-delimited cc: list             */
{
     CHAR *cp;
     UINT size;

     cp=strchr(list,';');
     if (cp == NULL) {
          stlcpy(addr,skpwht(list),MAXADR);
          unpad(addr);
     }
     else {
          ++cp;
          list=skpwht(list);
          size=(UINT)(cp-list);
          size=min(size,MAXADR);
          stlcpy(addr,list,size);
          unpad(addr);
     }
     return(cp);
}

INT                                /*   i of next entry (NOIDX if done)  */
nxtccidx(                          /* parse next cc: from list             */
CHAR *addr,                        /*   buffer to put cc: address into     */
const CHAR *list,                  /*   ';'-delimited list of addresses    */
INT curidx)                        /*   current index in cc: list          */
{
     CHAR *scp,*ecp;
     UINT size;

     scp=skpwht(list+curidx);
     ecp=strchr(scp,';');
     if (ecp == NULL) {
          unpad(stlcpy(addr,scp,MAXADR));
          return(NOIDX);
     }
     ++ecp;
     size=(UINT)(ecp-scp);
     size=min(size,MAXADR);
     unpad(stlcpy(addr,scp,size));
     return((INT)(ecp-list));
}

LONG
newmid(VOID)                       /* generate a new message ID            */
{
     ++sv.msgtot;
     return(++_highmsg);
}

GBOOL                              /*   returns TRUE if directory exists   */
fmdir(                             /* find or make directory               */
const CHAR *dirname)               /*   directory name                     */
{
     ASSERT(dirname != NULL);
     ASSERT(strlen(dirname) < GCMAXPTH);
     if (!dexist(dirname)) {
          return(makedir(dirname));
     }
     return(TRUE);
}

GBOOL                              /*   returns TRUE if successful         */
makedir(                           /* make nested directories              */
const CHAR *dirname)               /*   directory name(s)                  */
{
     const CHAR *tmpptr;
     CHAR tmpdir[GCMAXPTH];

     ASSERT(dirname != NULL);
     ASSERT(strlen(dirname) < GCMAXPTH);
     if (*dirname == '\0') {
          return(FALSE);
     }
     tmpptr=strchr(dirname,':');
     if (tmpptr == NULL) {
          tmpptr=dirname;
     }
     else {
          ++tmpptr;
          if (*tmpptr == '\0') {
               return(FALSE);
          }
     }
     if (*tmpptr == SL) {
          ++tmpptr;
     }
     do {
          tmpptr=strchr(tmpptr+1,SL);
          if (tmpptr == NULL) {
               strcpy(tmpdir,dirname);
          }
          else {
               stlcpy(tmpdir,dirname,(INT)(tmpptr-dirname)+1);
          }
          if (!dexist(tmpdir)) {
               if (MKDIR(tmpdir) != 0) {
                    return(FALSE);
               }
          }
     } while (tmpptr != NULL);
     return(TRUE);
}

GBOOL                              /*   returns TRUE if directory exists   */
dexist(                            /* check if directory exists            */
const CHAR *dirname)               /*   name of directory (no trailing \)  */
{
     CHAR fulpth[GCMAXPTH];

     ASSERT(dirname != NULL);
     ASSERT(strlen(dirname) < GCMAXPTH);
     if ((strchr(dirname,'*') == NULL) && (strchr(dirname,'?') == NULL)) {
          normspec(fulpth,dirname);
          return(isdir(fulpth));
     }
     return(FALSE);
}

GBOOL                              /*   returns TRUE if file exists        */
fexist(                            /* check if file exists                 */
const CHAR *name)                  /*   path+file name to find             */
{
     ASSERT(name != NULL);
     return((strchr(name,'*') == NULL) && (strchr(name,'?') == NULL)
         && isfile(name));
}

GBOOL
samepath(                          /* are two path+file names the same dir?*/
const CHAR *path1,                 /*   one path+file name                 */
const CHAR *path2)                 /*   second path+file name              */
{
     CHAR pathbuf1[GCMAXPTH],pathbuf2[GCMAXPTH];

     normspec(tmpbuf,(CHAR *)path1);
     fileparts(GCPART_PATH,tmpbuf,pathbuf1,sizeof(pathbuf1));
     normspec(tmpbuf,(CHAR *)path2);
     fileparts(GCPART_PATH,tmpbuf,pathbuf2,sizeof(pathbuf2));
     return(sameas(pathbuf1,pathbuf2));
}

CHAR *                             /*   returns pointer to file name       */
tmpanam(                           /* create temp attachment file name     */
CHAR *dest)                        /*   dest. buf or NULL for internal buf */
{
     INT i;
     CHAR *src;
     static CHAR tmpdest[GCMAXFNM];
     static ULONG counter=0;

     if (dest == NULL) {
          dest=tmpdest;
     }
     src=(CHAR *)&counter;
     for (i=0 ; i < 8 ; ++i) {
          if (i&1) {
               dest[i]='A'+((*src)>>4);
               ++src;
          }
          else {
               dest[i]='A'+((*src)&0x0F);
          }
     }
     ++counter;
     strcpy(&dest[8],".TMP");
     return(dest);
}

GBOOL
istmp(                             /* is this a temporary attachment file  */
const CHAR *fname)                 /*   path+file name to check            */
{
     CHAR *cp,fptmp[GCMAXFNM];

     cp=fileparts(GCPART_FNAM,fname,fptmp,GCMAXFNM);
     return(cp != NULL && (cp=strchr(cp,'.')) != NULL && sameas(cp,".tmp"));
}

GBOOL                              /*   was able to allocate?              */
alcarr(                            /* alloc another item in array in blocks*/
VOID **ppar,                       /*   pointer to pointer to array        */
UINT itmsiz,                       /*   size of individual array items     */
UINT nitems,                       /*   # items currently in array         */
UINT blksiz)                       /*   # items in each block              */
{                                  /*   (assumes ++nitems after each call) */
     ULONG newlen;
     VOID *tmp;

     ASSERT(ppar != NULL);
     if (nitems >= MAXBLK/itmsiz) {
          return(FALSE);
     }
     if (nitems%blksiz == 0) {
          newlen=(ULONG)itmsiz*blksiz*(nitems/blksiz+1);
          if (newlen > MAXBLK) {
               newlen=MAXBLK;
          }
          tmp=malloc((UINT)newlen);
          if (tmp == NULL) {
               return(FALSE);
          }
          if (*ppar != NULL) {
               movmem(*ppar,tmp,itmsiz*nitems);
               free(*ppar);
          }
          *ppar=tmp;
#ifdef DEBUG
          setmem(&((CHAR *)(*ppar))[itmsiz*nitems],itmsiz*blksiz,0);
#endif
     }
     return(TRUE);
}

CHAR *                             /*   returns pointer to string          */
zpad(                              /* zero pad a string                    */
CHAR *s,                           /*   pointer to string                  */
UINT len)                          /*   length of string buffer            */
{
     UINT i;

     i=strlen(s);
     setmem(s+i,len-i,0);
     return(s);
}

CHAR *                             /*   copy of pointer to string          */
new2ret(                           /* replace '\n' with '\r' in string     */
CHAR *str)                         /*   string to replace                  */
{
     return(strrpl(str,'\n','\r'));
}

const CHAR *                       /*   returns NULL if none               */
tpcfnm(                            /* extract file name from topic         */
const CHAR *tpc)                   /*   topic string                       */
{
     INT i;
     const CHAR *cp;
     static CHAR fname[GCMAXFNM];

     cp=tpc;
     for (i=0 ; *cp != '\0' && *cp != '.' && !isspace(*cp) ; cp++,i++) {
     }
     if (i < 1 || i > 8 || *cp != '.') {
          return(NULL);
     }
     cp++;
     for (i=0 ; *cp != '\0' && *cp != '.' && !isspace(*cp) ; cp++,i++) {
     }
     if (i > 3 || *cp == '.') {
          return(NULL);
     }
     stlcpy(fname,tpc,(INT)(cp-tpc+1));
     fnmcse(fname);
     return(valfnm(fname) ? fname : NULL);
}

CHAR *                             /*   returns pointer to destination     */
gmeGetAppSect(                     /* extract an app-defined info section  */
CHAR *dst,                         /*   destination buffer                 */
const CHAR *src,                   /*   source buffer                      */
const CHAR *sect)                  /*   section name                       */
{
     UINT len;
     CHAR *scp,*ecp;

     ASSERT(dst != NULL);
     ASSERT(src != NULL);
     ASSERT(sect != NULL);
     scp=findsect(src,sect);
     if (scp == NULL) {
          *dst='\0';
     }
     else {
          ecp=strstr(scp,"\n[");
          if (ecp == NULL) {
               len=strlen(scp);
          }
          else {
               len=(UINT)(ecp-scp+1);
          }
          ASSERT(strcmp(tmpbuf,mksectnam(utltxt,sect)) == 0);
          ASSERT(strlen(tmpbuf) <= len);
          len-=strlen(tmpbuf);
          scp+=strlen(tmpbuf);
          movmem(scp,dst,len);
          dst[len]='\0';
     }
     return(dst);
}

GBOOL                              /*   returns TRUE if update successful  */
gmeSetAppSect(                     /* set contents of app info section     */
CHAR *dst,                         /*   destination buffer                 */
const CHAR *src,                   /*   new contents ("" to remove)        */
const CHAR *sect)                  /*   section name                       */
{
     UINT dstlen,adjust,oldlen;
     CHAR *scp,*ecp;

     ASSERT(dst != NULL);
     ASSERT(src != NULL);
     ASSERT(sect != NULL);
     if (*src == '\0') {
          remsect(dst,sect);
          return(TRUE);
     }
     dstlen=strlen(dst);
     adjust=0;
     if (src[strlen(src)-1] != '\n') {
          ++adjust;
     }
     scp=findsect(dst,sect);
     if (scp == NULL) {
          oldlen=0U;
          if (*dst != '\0' && dst[dstlen-1] != '\n') {
               ++adjust;
          }
     }
     else {
          ecp=strstr(scp,"\n[");
          if (ecp == NULL) {
               oldlen=strlen(scp);
          }
          else {
               oldlen=(UINT)(ecp-scp+1);
          }
     }
     ASSERT(strcmp(tmpbuf,mksectnam(utltxt,sect)) == 0);
     if (dstlen-oldlen+strlen(tmpbuf)+strlen(src)+adjust < TXTLEN) {
          remsect(dst,sect);
          addsect(dst,src,sect);
          return(TRUE);
     }
     return(FALSE);
}

CHAR *                             /*   pointer to start of section name   */
findsect(                          /* find a section in app-defined info   */
const CHAR *src,                   /*   app-defined info buffer            */
const CHAR *sect)                  /*   section name                       */
{
     CHAR *cp;

     ASSERT(src != NULL);
     ASSERT(sect != NULL);
     cp=strstr(src,mksectnam(tmpbuf,sect));
     if (cp != NULL && (cp == src || *(cp-1) == '\n')) {
          return(cp);
     }
     return(NULL);
}

CHAR *                             /*   returns pointer to destination     */
addsect(                           /* add a new section                    */
char *dst,                         /*   buffer to add to                   */
const CHAR *src,                   /*   contents of new section            */
const CHAR *sect)                  /*   new section name                   */
{
     UINT len;

     ASSERT(dst != NULL);
     ASSERT(src != NULL);
     ASSERT(sect != NULL);
     stlcat(dst,mksectnam(tmpbuf,sect),TXTLEN);
     stlcat(dst,(CHAR *)src,TXTLEN);
     len=strlen(dst);
     if (len < TXTLEN-1 && dst[len-1] != '\n') {
          dst[len]='\n';
          dst[len+1]='\0';
     }
     return(dst);
}

CHAR *                             /*   returns pointer to destination     */
remsect(                           /* remove a section                     */
CHAR *dst,                         /*   buffer containing app-defined info */
const CHAR *sect)                  /*   section name                       */
{
     CHAR *scp,*ecp;

     scp=findsect(dst,sect);
     if (scp != NULL) {
          ecp=strstr(scp,"\n[");
          if (ecp == NULL) {
               *scp='\0';
          }
          else {
               ++ecp;
               movmem(ecp,scp,strlen(ecp)+1);
          }
     }
     return(dst);
}

CHAR *                             /*   returns pointer to name string     */
mksectnam(                         /* make section name string             */
CHAR *buf,                         /*   buffer to put name string into     */
const CHAR *sect)                  /*   section name                       */
{
     sprintf(buf,"[%s]\n",sect);
     return(buf);
}

VOID
inormrd(                           /* initialize "normal" read context     */
VOID *pWork,                       /*   work area to initialize            */
const CHAR *userid,                /*   user ID doing reading              */
USHORT forum,                      /*   forum to read from                 */
LONG msgid)                        /*   message to start at                */
{
     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(forum == EMLID || fidxst(forum));
     inictx(pWork,userid,forum == EMLID ? ESQTOU : FSQFOR,forum,msgid,0L);
}

VOID
forctx(                            /* change forum of read context         */
VOID *pWork,                       /*   work area in use                   */
USHORT forum,                      /*   forum to change to                 */
INT sequence)                      /*   new sequence to establish          */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(seqok(sequence,forum));
     work->rdctx.fid=forum;
     work->rdctx.seq=sequence;
     work->rdfpos=0L;
     gmeulkr(pWork);
}

VOID
seqctx(                            /* change sequence of read context      */
VOID *pWork,                       /*   work area in use                   */
INT sequence)                      /*   sequence to change to              */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(seqok(sequence,work->rdctx.fid));
     work->rdctx.seq=sequence;
     work->rdfpos=0L;
     gmeulkr(pWork);
}

VOID
msgctx(                            /* change cur message of read context   */
VOID *pWork,                       /*   work area in use                   */
LONG msgid)                        /*   message ID to change to            */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     work->rdctx.mid=msgid;
     work->rdfpos=0L;
     gmeulkr(pWork);
}

VOID
thrctx(                            /* change thread of read context        */
VOID *pWork,                       /*   work area in use                   */
LONG thrid)                        /*   thread ID to change to             */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     work->rdctx.tid=thrid;
     work->rdfpos=0L;
     gmeulkr(pWork);
}

VOID
inictx(                            /* initialize read context              */
VOID *pWork,                       /*   work area to initialize            */
const CHAR *userid,                /*   user ID doing reading              */
INT sequence,                      /*   sequence to use                    */
USHORT forum,                      /*   forum to read from                 */
LONG msgid,                        /*   message to start at                */
LONG thrid)                        /*   thread to use (if any)             */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(seqok(sequence,forum));
     ASSERT(forum == EMLID || fidxst(forum));
     ASSERT(msgid >= 0L);
     work->rdctx.seq=sequence;
     work->rdctx.fid=forum;
     work->rdctx.mid=msgid;
     work->rdctx.tid=thrid;
     stlcpy(work->rdctx.uid,userid,UIDSIZ);
     work->rdfpos=0L;
}

#ifdef DEBUG
GBOOL
seqok(                             /* check for valid sequence code        */
INT sequence,                      /*   sequence code                      */
USHORT forum)                      /*   forum ID                           */
{
     switch (sequence) {
     case ESQTOU:
     case ESQFRU:
     case ESQTHR:
          return(forum == EMLID);
     case FSQFOR:
     case FSQTHR:
     case FSQSCN:
          return(forum != EMLID);
     default:
          return(FALSE);
     }
}

GBOOL
ctxok(                             /* check for valid context setup        */
const struct rdctx *ctx)           /*   context structure                  */
{
     ASSERT(ctx != NULL);
     if (seqok(ctx->seq,ctx->fid)
      && (ctx->fid == EMLID || fidxst(ctx->fid))) {
          return(TRUE);
     }
     return(FALSE);
}
#endif
