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

#include "gcomm.h"
#include "fcntl.h"
#include "majorbbs.h"
#include "galme.h"
#include "galtext.h"
#include "gme.h"
#include "gmeutl.h"
#include "gmeloc.h"
#include "gmecore.h"

#define FILREV "$Revision: 43 $"

#define CNKPCYC 4                  /* "chunks" of file to copy per cycle   */

/* user data management declarations */

#define GMEUHDLBASE 0xFFFFFFF8UL
#define USERI2H(i) ((GMEUSERHANDLE)(GMEUHDLBASE-(i))) /* index to handle   */
#define USERH2I(h) ((INT)(GMEUHDLBASE-(h))) /* handle to index             */
#define INEXTRA(i) ((i) >= nterms) /* quickscan is in extra array          */

struct UserHandleInfo {            /* struct to track user info via handle */
     INT idx;                      /*   index of qsc (in chan or extra arr)*/
     INT ref;                      /*   reference count                    */
} * UserHdlArr=NULL;               /* array of handle tracking structs     */
size_t UserHdlArrSize;             /* number of elements in handle array   */

struct qscfg ** ExtraQscArr;       /* array of pointers to extra qsc bufs  */
size_t ExtraQscArrSize=0;          /* number of elements in extra qsc array*/

/* simple-send stuff */

static
struct sscache {                   /* simple send cache struct             */
     CHAR pWork[GMEWRKSZ];         /*   GME work space                     */
     struct message msg;           /*   new message structure              */
     CHAR attspc[GCSTRPTH];        /*   path+file name of att (if any)     */
     CHAR text[1];                 /*   message body text                  */
} *ssbuf;

struct dfaSegSpec sscMidKey[]={
     {fldoff(sscache,msg)+fldoff(message,msgid),sizeof(LONG),DFAST_BINARY,0,0}
};

struct dfaKeySpec sscKeys[]={
     {0,nelems(sscMidKey),sscMidKey}
};

static
INT sstskid=-1;                    /* simple send task ID                  */
static
GBOOL ssiuflg=FALSE;               /* simple send in use flag              */
static
DFAFILE *sscbb=NULL;               /* simple send cache file               */

/* miscellaneous local variables */

static
GBOOL gmeinit=FALSE,               /* has init__galme() been called yet?   */
      gmeshut=FALSE;               /* has final shutdown hook been called? */

INT ncflfn=0;                      /* number of registered conflict chkers */
cflfunc *cflfnar=NULL;             /* array of pointers to conflict chkers */

INT ngmelk=0;                      /* number of slots in lock array        */
struct gmelock *gmelkarr=NULL;     /* pointer to lock array                */

INT numsl=0;                       /* number of sysop dist lists           */
struct slinfo *losl=NULL;          /* array of sysop dist list info structs*/

INT ngmehooks=0;                   /* number of hooks                      */
struct gmehook *gmehooks=NULL;     /* pointer to hook array                */

INT ngmephooks=0;                  /* number of prioritized hooks          */
struct gmephook *gmephooks=NULL;   /* pointer to prioritized hook array    */

static ULONG cyccntr=1L;           /* system cycle counter                 */
static VOID (*oldsyscyc)(VOID);    /* old system cycle vector              */
static VOID (*oldfinrou)(VOID);    /* old module[0] shutdown vector        */

/* local functions */

static VOID countcyc(VOID);
static VOID ck4gpf(VOID);
static VOID emlnewu(VOID);
static VOID gmeAcctCreated(struct usracc const * pAcct);
static VOID gmecup(VOID);
static VOID gmedla(CHAR *uid);
static VOID gmesht(VOID);
static VOID bgmcupt(INT taskid);
static VOID iniUserData(VOID);
static VOID clsUserData(VOID);
static INT FindUser(CHAR const * uid,INT * piRef);
static INT LoadUser(struct usracc * pAcct,struct qscfg * pQsc);
static struct qscfg * RefQscPtr(INT iQsc);
static INT AllocUserHdl(INT iQsc);
static VOID FreeUserHdl(INT iRef);
static INT AllocExtraQsc(VOID);
static VOID FreeExtraQsc(INT iQsc);
static VOID inisimp(VOID);
static VOID clssimp(VOID);
static VOID cpy2cache(struct sscache *cache,VOID *pWork,
                      struct message *msg,const CHAR *text,const CHAR *filatt);
static VOID sndtask(INT taskid);
#if defined(EXTRA_LOGGING)
#define LOGFILE "galme.log"
static VOID logmsg(CHAR const * fmt,...);
static VOID vlogmsg(CHAR const * fmt,va_list ap);
#define LOG0(s)               logmsg(s)
#define LOG1(s,v1)            logmsg(s,v1)
#define LOG2(s,v1,v2)         logmsg(s,v1,v2)
#define LOG3(s,v1,v2,v3)      logmsg(s,v1,v2,v3)
#define LOG4(s,v1,v2,v3,v4)   logmsg(s,v1,v2,v3,v4)
#else
#define LOG0(s)               ((VOID)0)
#define LOG1(s,v1)            ((VOID)0)
#define LOG2(s,v1,v2)         ((VOID)0)
#define LOG3(s,v1,v2,v3)      ((VOID)0)
#define LOG4(s,v1,v2,v3,v4)   ((VOID)0)
#endif /* EXTRA_LOGGING */

VOID
init__galme(VOID)                  /* GME initialization function          */
{
     if (!gmeinit) {
          gmeinit=TRUE;
          iniutl();
          iniloc();
          inidft();
          inicore();
          iniUserData();
          inisimp();
          inilosl();
          hook_createacct(gmeAcctCreated);
          hook_cleanup(gmecup);
          hook_delacct(gmedla);
          hook_finalshutdown(gmesht);
          /* need to flush simpsnd() cache before anything else shuts down */
          oldfinrou=module[0]->finrou;
          module[0]->finrou=clssimp;
          if (needbgc()) {
               bgcipg=TRUE;
               initask(bgmcupt);
          }
          if (emlsdrou == NULL) {
               emlsdrou=emlnewu;
          }
          rtkick(1,ck4gpf);
          oldsyscyc=syscyc;
          syscyc=countcyc;
     }
}

VOID EXPORT
initwc__galme(VOID)
{
     init__galme();
}

static VOID
countcyc(VOID)                     /* count system cycles                  */
{
     ++cyccntr;
     (*oldsyscyc)();
}

ULONG
cyccnt(VOID)                       /* get system cycle count               */
{
     return(cyccntr);
}

static VOID
ck4gpf(VOID)                  /* send mail to sysop if GALEXCEP.FLG exists */
{
     CHAR *cp;

     if (fnd1st(&gmefb,"GALEXCEP.FLG",0)) {
          setmbk(gmemb);
          setmem(utlmsg,sizeof(struct message),0);
          utlmsg->forum=EMLID;
          if (!uidxst(cp=stpans(unpad(skpwht(rawmsg(GPUID)))))) {
               cp="Sysop";
          }
          stlcpy(utlmsg->from,cp,UIDSIZ);
          stlcpy(utlmsg->to,cp,UIDSIZ);
          stlcpy(utlmsg->topic,getmsg(GPTPC),TPCSIZ);
          utlmsg->flags=FILATT|FILAPV|FILIND;
          strcpy(utlmsg->attname,"GALEXCEP.OUT");
          stlcpy(utltxt,getmsg(GPNOTI),TXTLEN);
          rstmbk();
          inigmerq(utlwork);
          while (gsndmsg(utlwork,utlmsg,utltxt,"GALEXCEP.OUT") == GMEAGAIN) {
               /* this is OK here since it's only once, at startup */
          }
          unlink("GALEXCEP.FLG");
     }
}

static VOID
emlnewu(VOID)                      /* send E-mail regarding new user       */
{
     INT svcl;
     CHAR *tmps;

     setmbk(gmemb);
     svcl=clingo;
     clingo=0;
     if (supu2s) {
          setmem(utlmsg,sizeof(struct message),0);
          utlmsg->forum=EMLID;
          stlcpy(utlmsg->from,usaptr->userid,UIDSIZ);
          stlcpy(utlmsg->to,nuemto,UIDSIZ);
          stlcpy(utlmsg->topic,nuemtp,TPCSIZ);
          utlmsg->flags=NOMOD|NODEL;
          prfmsg(NUEMHD);
          shwusr(usaptr);
          new2ret(prf2str(utltxt,TXTLEN));
          inigmerq(utlwork);
          while (gsndmsg(utlwork,utlmsg,utltxt,NULL) == GMEAGAIN) {
               /* prefer not to do this but don't have option to cycle */
          }
          if (onsys(utlmsg->to)) {
               prfmlt(NEWUEM,usaptr->userid);
               injoth();
          }
     }
     if (supe2u) {
          setmem(utlmsg,sizeof(struct message),0);
          utlmsg->forum=EMLID;
          ininew(utlmsg);
          stlcpy(utlmsg->from,e2ufrm,UIDSIZ);
          stlcpy(utlmsg->to,usaptr->userid,UIDSIZ);
          stlcpy(utlmsg->topic,e2utpc,TPCSIZ);
          utlmsg->thrid=cmptid(utlmsg);
          tmps=NULL;
          utlmsg->flags=(e2urrr ? RECREQ : 0L);
          if (*e2uatt != '\0') {
               utlmsg->flags|=(FILATT|FILAPV|FILIND);
               stlcpy(utlmsg->attname,e2uanm,GCMAXFNM);
               tmps=e2uatt;
          }
          stlcpy(utltxt,getmsg(E2UTXT),TXTLEN);
          inigmerq(utlwork);
          while (gme1wnm(utlwork,utlmsg,utltxt,tmps) == GMEAGAIN) {
               /* prefer not to do this but don't have option to cycle */
          }
          *utlwork->cpyatt='\0';
          clsgmerq(utlwork);
     }
     clingo=svcl;
}

GBOOL
gmeoffl(VOID)                      /* is the GME running in offline mode?  */
{
     return(FALSE);
}

static VOID
gmeAcctCreated(                    /* account created notification handler */
struct usracc const * pAcct)       /*   completed user account info        */
{
     struct usracc tmpAcct;

     ASSERT(pAcct != NULL);
     ASSERT(islocal(pAcct->userid));
     /* have to copy since LoadUser() may modify */
     memcpy(&tmpAcct,pAcct,sizeof(struct usracc));
     LoadUser(&tmpAcct,utlqsc);
     gmeSaveQS(utlqsc);
}

static VOID
gmecup(VOID)                       /* GME cleanup routine                  */
{
     gmeclean(absdtdy());
}

static VOID
gmedla(CHAR *uid)                  /* GME delete account routine           */
{
     dfaSetBlk(qscbb);
     if (dfaAcqEQ(NULL,uid,0)) {
          dfaDelete();
     }
     dfaRstBlk();
     dfaSetBlk(qikbb);
     if (dfaAcqEQ(NULL,uid,0)) {
          dfaDelete();
     }
     dfaRstBlk();
     cleandla(uid);
}

static VOID
gmesht(VOID)                       /* GME shutdown routine                 */
{
#ifdef DEBUG
     INT i;
#endif

     clsUserData();
     clsloc(TRUE);
     clsutl();
#ifdef DEBUG
     for (i=0 ; i < ngmelk ; ++i) {
          if (gmelkarr[i].rqid != 0U) {
               shocst("MESSAGE LOCK LEFT ON:","Forum=%u, Msgid=%s",
                   gmelkarr[i].forum,l2as(gmelkarr[i].msgid));
          }
     }
#endif
     gmeshut=TRUE;
}

static VOID
bgmcupt(                           /* background message cleanup task      */
INT taskid)
{
     if (!pumpcup()) {
          mfytask(taskid,NULL);
          shocst("BACKGROUND CLEANUP COMPLETE",
                 "E-mail/Forums completed background cleanup for the day.");
     }
}

const CHAR *
gmexinf(VOID)                      /* get extended return information      */
{
     return(extinf);
}

CHAR *                             /*   copy of pointer to destination     */
prf2str(                           /* copy prfbuf contents to a string     */
CHAR *str,                         /*   string to copy into                */
UINT len)                          /*   length of string                   */
{
     stpans(prfbuf);
     stlcpy(str,prfbuf,len);
     clrprf();
     return(str);
}

static VOID
iniUserData(VOID)                  /* initialize user data tracking util   */
{
     UserHdlArrSize=((nterms/SMLBLK)+1)*SMLBLK;
     UserHdlArr=alczer(UserHdlArrSize*sizeof(struct UserHandleInfo));
}

static VOID
clsUserData(VOID)                  /* shut down user data tracking util    */
{
     INT i;                        /* generic array index variable         */

     /* flush any quickscans still loaded in extra array */
     for (i=0 ; i < UserHdlArrSize ; ++i) {
#if defined(EXTRA_LOGGING)
          if (UserHdlArr[i].ref != 0) {
               if (INEXTRA(UserHdlArr[i].idx)) {
                    logmsg("Qscan still loaded in extra: '%s' ref=%d h=%d"
                          ,RefQscPtr(UserHdlArr[i].idx)->userid,UserHdlArr[i].ref
                          ,i);
               }
               else {
                    logmsg("Qscan still loaded in channel: %d '%s' ref=%d h=%d"
                          ,UserHdlArr[i].idx,RefQscPtr(UserHdlArr[i].idx)->userid
                          ,UserHdlArr[i].ref,i);
               }
          }
#endif /* EXTRA_LOGGING */
          if (UserHdlArr[i].ref != 0 && INEXTRA(UserHdlArr[i].idx)) {
               gmeSaveQS(RefQscPtr(UserHdlArr[i].idx));
          }
     }

     /* free memory */
     if (UserHdlArr != NULL) {
          free(UserHdlArr);
          UserHdlArr=NULL;
          UserHdlArrSize=0;
     }
     if (ExtraQscArr != NULL) {

          /* free quickscan buffer blocks */
          for (i=0 ; i < ExtraQscArrSize/SMLBLK ; ++i) {
               free(ExtraQscArr[i*SMLBLK]);
          }

          /* free array of pointers to buffers */
          free(ExtraQscArr);
          ExtraQscArr=NULL;
          ExtraQscArrSize=0;
     }
}

INT                                /*   returns GME status code            */
inigmeu(VOID)                      /* initialize GME/user stuff at logon   */
{
     struct qscfg * qsc;           /* pointer to quickscan buffer          */
     INT wasLoaded;                /* quickscan was already loaded         */
     INT iRef;                     /* index of user handle info            */
     INT iQsc;                     /* index of extra quickscan buffer      */
     INT rc;                       /* return code                          */

     ASSERT(0 <= usrnum && usrnum < nterms);
     ASSERT(usaptr == uacoff(usrnum));
     ASSERT(islocal(usaptr->userid));
     qsc=uqsptr(usrnum);
     rc=GMEOK;

     /* if quickscan already loaded here, or not a valid user, exit */
     if (sameas(qsc->userid,usaptr->userid) || !islocal(usaptr->userid)) {
          LOG2("Improper call to inigmeu(): %d '%s'",usrnum,usaptr->userid);
          return(rc);
     }

     /* if quickscan was loaded in an extra buffer, get it from there */
     wasLoaded=FindUser(usaptr->userid,&iRef);
     if (wasLoaded && INEXTRA(iQsc=UserHdlArr[iRef].idx)) {

          /* move quickscan over from extra buffer */
          iQsc-=nterms;
          memcpy(qsc,ExtraQscArr[iQsc],qssiz);
          FreeExtraQsc(iQsc);

          /* update handle info */
          UserHdlArr[iRef].idx=usrnum;
          ++(UserHdlArr[iRef].ref);
          LOG4("Moved qscan from extra to channel: %d '%s' ref=%d h=%d"
              ,usrnum,usaptr->userid,UserHdlArr[iRef].ref,iRef);
     }
     /* else, load from disk */
     else {
          if (!wasLoaded) {   /* only if this is first time loading */
#if defined(EXTRA_LOGGING)
               iRef=
#endif /* EXTRA_LOGGING */
               AllocUserHdl(usrnum);
          }
          rc=LoadUser(usaptr,qsc);
          if (!wasLoaded) {   /* only if this is first time loading */
               LOG3("Loaded qscan into channel: %d '%s' h=%d"
                   ,usrnum,usaptr->userid,iRef);
               noticu(GMEHOOK_NOT_INIUSRL,qsc->userid);
          }
#if defined(EXTRA_LOGGING)
          else {
               logmsg("Qscan loaded on multiple channels: %d '%s'"
                     ,usrnum,usaptr->userid);
          }
#endif /* EXTRA_LOGGING */
     }

     noticu(GMEHOOK_NOT_INIUSR,usaptr->userid);
     return(rc);
}

VOID
clsgmeu(VOID)                      /* close GME/user stuff at logoff       */
{
     struct qscfg *qsc;            /* pointer to per-channel qscan buffer  */
     INT iRef;                     /* index of user handle info            */
     INT iQsc;                     /* index of extra quickscan buffer      */

     qsc=uqsptr(usrnum);
#if defined(EXTRA_LOGGING)
     if (!sameas(qsc->userid,usaptr->userid)) {
          logmsg("Improper call to clsgmeu(): %d qsc='%s' uap='%s'"
                ,usrnum,qsc->userid,usaptr->userid);
     }
#endif /* EXTRA_LOGGING */
     if (sameas(qsc->userid,usaptr->userid)) {
          noticu(GMEHOOK_NOT_CLSUSR,usaptr->userid);
          gmeSaveQS(qsc);     /* always save for backward compatibility */

          /* see if we're tracking this one (and it's in the right place) */
          if (FindUser(usaptr->userid,&iRef)
           && UserHdlArr[iRef].idx == usrnum) {

               /* if we're the last one using it, free it up */
               if (UserHdlArr[iRef].ref == 1) {
                    LOG3("Unloaded qscan from channel: %d '%s' h=%d"
                        ,usrnum,usaptr->userid,iRef);
                    noticu(GMEHOOK_NOT_CLSUSRL,usaptr->userid);
                    /* save again in case notification changed qscan */
                    gmeSaveQS(qsc);
                    FreeUserHdl(iRef);
               }
               /* else, move it into extra buf and decrement ref count */
               else {
                    iQsc=AllocExtraQsc();
                    memcpy(ExtraQscArr[iQsc],qsc,qssiz);
                    UserHdlArr[iRef].idx=iQsc+nterms;
                    --(UserHdlArr[iRef].ref);
                    LOG4("Moved qscan from channel to extra: %d '%s' ref=%d h=%d"
                        ,usrnum,usaptr->userid,UserHdlArr[iRef].ref,iRef);
               }
          }
#if defined(EXTRA_LOGGING)
          else {
               logmsg("Closing orphan qscan: %d '%s'",usrnum,usaptr->userid);
          }
#endif /* EXTRA_LOGGING */

          /* clear per-channel buffer */
          memset(qsc,0,qssiz);
     }
}

INT                                /*   returns GME status code            */
gmeUserOpen(                       /* open user in non-channel mode        */
struct usracc * pAcct,             /*   account of user to open            */
GMEUSERHANDLE * phUsr)             /*   buffer to receive user handle      */
{
     struct qscfg * pQsc;          /* ptr to qscan buffer to be returned   */
     INT iRef;                     /* index of user handle info            */
     INT iQsc;                     /* index of quickscan buffer            */
     INT rc;                       /* return code                          */

     ASSERT(pAcct != NULL);
     ASSERT(phUsr != NULL);

     /* if user is invalid, return error */
     if (!islocal(pAcct->userid)) {
          LOG1("Improper call to gmeUserOpen(): '%s'",pAcct->userid);
          return(GMEERR);
     }

     /* if user already loaded, just add reference */
     if (gmeUserReference(pAcct->userid,phUsr) == GMEOK) {
          return(GMEOK);
     }

     /* allocate buffer and handle */
     iQsc=AllocExtraQsc();
     pQsc=ExtraQscArr[iQsc];
     iRef=AllocUserHdl(iQsc+nterms);
     *phUsr=USERI2H(iRef);

     /* load info and tell everyone */
     LOG2("Loaded qscan into extra: '%s' h=%d",pAcct->userid,iRef);
     rc=LoadUser(pAcct,pQsc);
     noticu(GMEHOOK_NOT_INIUSRL,pQsc->userid);
     return(rc);
}

INT                                /*   returns GME status code            */
gmeUserReference(                  /* get reference to already-loaded user */
CHAR const * uid,                  /*   User-ID of user to reference       */
GMEUSERHANDLE * phUsr)             /*   buffer to receive user handle      */
{
     INT iRef;                     /* index of user handle info            */

     ASSERT(uid != NULL);
     ASSERT(phUsr != NULL);
     if (FindUser(uid,&iRef)) {
          ++(UserHdlArr[iRef].ref);
          *phUsr=USERI2H(iRef);
          LOG3("Inc references to qscan: '%s' ref=%d h=%d"
              ,uid,UserHdlArr[iRef].ref,iRef);
          return(GMEOK);
     }
     LOG1("Attempt to reference qscan failed: '%s'",uid);
     return(GMEERR);
}

struct qscfg *                     /*   returns NULL if invalid handle     */
gmeUserGetQS(                      /* get pointer to non-channel quickscan */
GMEUSERHANDLE hUsr)                /*   user handle                        */
{
     INT iRef;                     /* index of handle info                 */

     iRef=USERH2I(hUsr);
     if (0 <= iRef && iRef < UserHdlArrSize && UserHdlArr[iRef].ref != 0) {
          ASSERT(UserHdlArr[iRef].idx >= 0);
          return(RefQscPtr(UserHdlArr[iRef].idx));
     }
     return(NULL);
}

VOID
gmeUserClose(                      /* close a non-channel user             */
GMEUSERHANDLE hUsr)                /*   user handle                        */
{
     struct qscfg * pQsc;          /* ptr to qscan buffer to be returned   */
     INT iRef;                     /* index of handle info                 */
     INT iQsc;                     /* index of quickscan buffer            */

     iRef=USERH2I(hUsr);
     if (0 <= iRef && iRef < UserHdlArrSize && UserHdlArr[iRef].ref != 0) {
          ASSERT(UserHdlArr[iRef].idx >= 0);
          ASSERT(UserHdlArr[iRef].ref > 0);

          /* if this is the last reference, free it up */
          if (UserHdlArr[iRef].ref == 1) {
               iQsc=UserHdlArr[iRef].idx;
               ASSERT(INEXTRA(iQsc));
               /* save/free only if it's in extra buffer */
               if (INEXTRA(iQsc)) {
                    iQsc-=nterms;
                    pQsc=ExtraQscArr[iQsc];
                    LOG2("Unloaded qscan from extra: '%s' h=%d"
                        ,pQsc->userid,iRef);
                    noticu(GMEHOOK_NOT_CLSUSRL,pQsc->userid);
                    gmeSaveQS(pQsc);
                    FreeExtraQsc(iQsc);
                    FreeUserHdl(iRef);
               }
#if defined(EXTRA_LOGGING)
               else {
                    logmsg("Attempt to close channel qscan with "
                           "gmeUserClose(): %d '%s' h=%d"
                          ,iQsc,uqsptr(iQsc)->userid,iRef);
               }
#endif /* EXTRA_LOGGING */
          }
          /* otherwise, just decrement the reference count */
          else {
               --(UserHdlArr[iRef].ref);
               LOG3("Dec references to qscan: '%s' ref=%d h=%d"
                   ,RefQscPtr(UserHdlArr[iRef].idx)->userid
                   ,UserHdlArr[iRef].ref,iRef);
          }
     }
#if defined(EXTRA_LOGGING)
     else {
          logmsg("Improper call to gmeUserClose(), h=%d",iRef);
     }
#endif /* EXTRA_LOGGING */
}

struct qscfg *                     /*   returns qscan if loaded, else NULL */
GetOnlineQS(                       /* internal onsqsp() replacement        */
CHAR const * uid)                  /*   User-ID to find                    */
{
     INT iRef;                     /* index of handle info                 */

     ASSERT(uid != NULL);
     if (FindUser(uid,&iRef)) {
          return(RefQscPtr(UserHdlArr[iRef].idx));
     }
     return(NULL);
}

static INT                         /*   returns TRUE if found              */
FindUser(                          /* find a user's info if loaded         */
CHAR const * uid,                  /*   User-ID of user to find            */
INT * piRef)                       /*   buf to receive index of handle info*/
{
     struct qscfg * pQsc;          /* ptr to qscan buffer to be returned   */
     INT i;                        /* index into handle info array         */

     for (i=0 ; i < UserHdlArrSize ; ++i) {
          if (UserHdlArr[i].ref != 0) {
               pQsc=RefQscPtr(UserHdlArr[i].idx);
               if (sameas(uid,pQsc->userid)) {
                    *piRef=i;
                    return(TRUE);
               }
          }
     }
     return(FALSE);
}

static INT                         /*   returns GME status code            */
LoadUser(                          /* load/initialize user info            */
struct usracc * pAcct,             /*   user account                       */
struct qscfg * pQsc)               /*   buffer to receive quickscan        */
{
     struct fordef *fdef;
     INT i,idx,nfors,rc;

     ASSERT(pAcct != NULL);
     ASSERT(pQsc != NULL);
     ASSERT(islocal(pAcct->userid));
     rc=GMEOK;

     /* load/create record */
     gmeLoadQS(pQsc,pAcct->userid);

     /* make sure user's current forum is valid */
     if (!fidxst(pQsc->curfor)) {
          pQsc->curfor=dftfor;
          if (!inqs(pQsc,dftfor)) {
               i=absadqs(pQsc,dftfor);
               if (i != NOIDX) {
                    isethi(pQsc,i,-1L);
               }
          }
     }

     /* add new forums if requested */
     if (autqsc) {
          nfors=numforums();
          for (i=0 ; i < nfors ; i++) {
               fdef=idxdef(i);
               if (fdef->crdate >= pAcct->usedat && !inqs(pQsc,fdef->forum)) {
                    idx=add2qs(pQsc,fdef->forum);
                    if (idx != NOIDX) {
                         isethi(pQsc,idx,1L);
                    }
               }
          }
     }

     /* make sure user has a !QUICK record */
     dfaSetBlk(qikbb);
     if (!dfaQueryEQ(pAcct->userid,0)) {
          iniqik(qkdptr,pAcct->userid);
          dfaInsertV(qkdptr,fldoff(qikdat,list));
     }
     dfaRstBlk();

     /* make sure last e-mail read pointer is valid */
     if (pAcct->emllim > _highmsg) {
          pAcct->emllim=FIRSTM;
          rc=GMERST;
     }
     return(rc);
}

static struct qscfg *
RefQscPtr(                         /* get ptr to qscan from reference      */
INT iQsc)                          /*   index from handle info             */
{
     struct qscfg * pQsc;          /* ptr to qscan buffer to be returned   */

     ASSERT(iQsc >= 0);
     if (INEXTRA(iQsc)) {
          iQsc-=nterms;
          ASSERT(iQsc < ExtraQscArrSize);
          ASSERT(ExtraQscArr[iQsc] != NULL);
          pQsc=ExtraQscArr[iQsc];
     }
     else {
          pQsc=uqsptr(iQsc);
     }
     ASSERT(*pQsc->userid != '\0');
     return(pQsc);
}

static INT                         /*   returns index of tracking struct   */
AllocUserHdl(                      /* allocate a user info tracking handle */
INT iQsc)                          /*   index of quickscan record          */
{
     size_t newSize;               /* new size for reallocated hdl array   */
     INT iRef;                     /* index of allocated tracking struct   */

     /* find an unused handle */
     for (iRef=0 ; iRef < UserHdlArrSize ; ++iRef) {
          if (UserHdlArr[iRef].ref == 0) {
               break;
          }
     }

     /* otherwise allocate a new block of handles */
     if (iRef == UserHdlArrSize) {
          newSize=UserHdlArrSize+SMLBLK;
          UserHdlArr=alcrsz(UserHdlArr
                           ,(UserHdlArrSize*sizeof(struct UserHandleInfo))
                           ,(newSize*sizeof(struct UserHandleInfo)));
          memset(&UserHdlArr[UserHdlArrSize],0
                ,SMLBLK*sizeof(struct UserHandleInfo));
          UserHdlArrSize=newSize;
     }

     /* initialize allocated handle */
     ASSERT(iRef < UserHdlArrSize);
     UserHdlArr[iRef].idx=iQsc;
     UserHdlArr[iRef].ref=1;
     return(iRef);
}

static VOID
FreeUserHdl(                       /* free a user info tracking handle     */
INT iRef)                          /*   index of tracking struct           */
{
     ASSERT(0 <= iRef && iRef < UserHdlArrSize);
     UserHdlArr[iRef].idx=NOIDX;
     UserHdlArr[iRef].ref=0;
}

static INT                         /*   returns index of buffer pointer    */
AllocExtraQsc(VOID)                /* allocate an extra quickscan buffer   */
{
     CHAR * newBuf;                /* new set of buffers                   */
     size_t newSize;               /* new size for reallocated hdl array   */
     INT iQsc;                     /* index of allocated quickscan buffer  */
     INT iBuf;                     /* index of buffer being pointed at     */

     /* find an unused buffer */
     for (iQsc=0 ; iQsc < ExtraQscArrSize ; ++iQsc) {
          if (*(ExtraQscArr[iQsc]->userid) == '\0') {
               break;
          }
     }

     /* otherwise allocate a new block of buffers */
     if (iQsc == ExtraQscArrSize) {
          ASSERT((ExtraQscArrSize%SMLBLK) == 0);

          /* allocate a block buffers */
          newBuf=alczer(qssiz*SMLBLK);

          /* allocate a block of pointers */
          newSize=ExtraQscArrSize+SMLBLK;
          ExtraQscArr=alcrsz(ExtraQscArr
                            ,(ExtraQscArrSize*sizeof(struct qscfg *))
                            ,(newSize*sizeof(struct qscfg *)));

          /* point pointers at buffers */
          for (iBuf=0 ; iBuf < SMLBLK ; ++iBuf) {
               ExtraQscArr[ExtraQscArrSize+iBuf]
                    =(struct qscfg *)(newBuf+(iBuf*qssiz));
          }
          ExtraQscArrSize=newSize;
     }

     /* initialize allocated buffer */
     ASSERT(iQsc < ExtraQscArrSize);
     ExtraQscArr[iQsc]->userid[0]='\1'; /* just to tag as allocated */
     return(iQsc);
}

static VOID
FreeExtraQsc(                      /* free an extra quickscan buffer       */
INT iQsc)                          /*   index of tracking struct           */
{
     ASSERT(0 <= iQsc && iQsc < ExtraQscArrSize);
     memset(ExtraQscArr[iQsc],0,qssiz);
}

struct qscfg *
myqsptr(VOID)                      /* get current user's quickscan         */
{
     return((struct qscfg *)ptrblok(usrqs,usrnum));
}

struct qscfg *                     /*   pointer to quickscan data          */
othqsp(                            /* get a user's quickscan configuration */
const CHAR *uid)                   /*   User-ID to find                    */
{                                  /*   (for one-cycle use only)           */
     struct qscfg *tmpqsp;

     if ((tmpqsp=GetOnlineQS(uid)) != NULL) {
          return(tmpqsp);
     }
     dfaSetBlk(qscbb);
     if (dfaAcqEQ(utlqsc,uid,0)) {
          dfaRstBlk();
          return(utlqsc);
     }
     dfaRstBlk();
     return(NULL);
}

struct qscfg *                     /*   returns NULL if not found          */
onsqsp(                            /* get online user's quickscan          */
const CHAR *uid)                   /*   given User-ID                      */
{
     INT i;
     struct qscfg *tmpqsp;

     for (i=0 ; i < nterms ; ++i) {
          if (sameas(uid,(tmpqsp=uqsptr(i))->userid)) {
               return(tmpqsp);
          }
     }
     return(NULL);
}

VOID
setgmecb(                          /* set handler for GME status reports   */
VOID *pWork,                       /*   work area being used for request   */
VOID (*callback)(INT,INT))         /*   pointer to callback handler        */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     work->callback=callback;
}

VOID
callback(                          /* call callback handler if any         */
VOID *pWork,                       /*   being used by this work area       */
INT evt,                           /*   event to report                    */
INT res,                           /*   result code to report              */
const CHAR *info)                  /*   string to put in extinf            */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     if (work->callback != NULL) {
          if (info != NULL) {
               stlcpy(extinf,info,XINFSZ);
          }
          (*work->callback)(evt,res);
          *extinf='\0';
     }
}

GBOOL                              /*   returns TRUE if able to add to list*/
setcfl(                            /* set cooperative conflict checker     */
cflfunc cflchk)                    /*   function to check                  */
{
     if (ncflfn < MAXPARSZ) {
          if (alcpar((VOID ***)&cflfnar,ncflfn,SMLBLK)) {
               cflfnar[ncflfn++]=cflchk;
               return(TRUE);
          }
     }
     return(FALSE);
}

GBOOL                              /*   returns TRUE if a conflict         */
chkcfl(                            /* chk others for conflict w/my cur msg */
VOID *pWork)                       /*   work area being used to read       */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     if (work->rdctx.mid <= 0L) {
          return(FALSE);
     }
     // have to pass unaligned buffer !
     return(gmecfl(pWork,work->rdctx.fid,work->rdctx.mid));
}

GBOOL                              /*   returns TRUE if a conflict         */
chkmycfl(                          /* check my current message for conflict*/
VOID *pWork,                       /*   my work area being used to read    */
USHORT forum,                      /*   forum ID w/possible conflict       */
LONG msgid)                        /*   message ID w/possible conflict     */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     if (msgid == 0L) {
          return(forum == work->rdctx.fid);
     }
     return(msgid == work->rdctx.mid);
}

GBOOL                              /*   returns TRUE if a conflict         */
gencfl(                            /* generic conflict checker             */
VOID *pWork,                       /*   work area in use                   */
USHORT forum,                      /*   forum ID to check                  */
LONG msgid)                        /*   msg ID to check (0L for forum only)*/
{
     return(gmecfl(pWork,forum,msgid));
}

GBOOL                              /*   returns TRUE if a conflict         */
gmecfl(                            /* check for any conflict               */
VOID *pWork,                       /*   work area in use                   */
USHORT forum,                      /*   forum ID w/possible conflict       */
LONG msgid)                        /*   message ID w/possible conflict     */
{
     INT i;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(forum == EMLID || fidxst(forum));
     if (msgid == 0L) {
          for (i=0 ; i < ngmelk ; ++i) {
               if (gmelkarr[i].rqid != 0U
                && gmelkarr[i].rqid != work->rqid
                && gmelkarr[i].forum == forum) {
                    return(TRUE);
               }
          }
     }
     else {
          for (i=0 ; i < ngmelk ; ++i) {
               if (gmelkarr[i].rqid != 0U
                && gmelkarr[i].rqid != work->rqid
                && gmelkarr[i].msgid == msgid) {
                    return(TRUE);
               }
          }
     }
     for (i=0 ; i < ncflfn ; ++i) {
          // have to pass unaligned buffer !
          if ((*(cflfnar[i]))(pWork,forum,msgid)) {
               return(TRUE);
          }
     }
     return(FALSE);
}

GBOOL                              /*   was lock successful?               */
gmelok(                            /* put a lock on a message or forum     */
VOID *pWork,                       /*   work area associated w/lock        */
USHORT forum,                      /*   forum ID to lock                   */
LONG msgid)                        /*   message ID to lock                 */
{
     INT i;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     for (i=0 ; i < ngmelk && gmelkarr[i].rqid != 0U ; ++i) {
     }
     if (i < ngmelk) {
          gmelkarr[i].rqid=work->rqid;
          gmelkarr[i].forum=forum;
          gmelkarr[i].msgid=msgid;
          return(TRUE);
     }
     else {
          if (alcarr((VOID **)&gmelkarr,sizeof(struct gmelock),ngmelk,SMLBLK)) {
               gmelkarr[ngmelk].rqid=work->rqid;
               gmelkarr[ngmelk].forum=forum;
               gmelkarr[ngmelk].msgid=msgid;
               ++ngmelk;
               return(TRUE);
          }
#ifdef DEBUG
          else {
               catastro("Out of GME internal locks!");
          }
#endif
     }
     return(FALSE);
}

VOID
gmeulk(                            /* unlock a message or forum            */
VOID *pWork,                       /*   work area associated w/lock        */
USHORT forum,                      /*   forum ID to unlock                 */
LONG msgid)                        /*   message ID to unlock               */
{
     INT i;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     for (i=0 ; i < ngmelk ; ++i) {
          if (gmelkarr[i].rqid == work->rqid
           && gmelkarr[i].forum == forum
           && gmelkarr[i].msgid == msgid) {
               gmelkarr[i].rqid=0U;
               break;
          }
     }
}

VOID
gmeulkr(                           /* remove all locks assoc with a request*/
VOID *pWork)                       /*   work area associated w/request     */
{
     INT i;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     for (i=0 ; i < ngmelk ; ++i) {
          if (gmelkarr[i].rqid == work->rqid) {
               gmelkarr[i].rqid=0U;
          }
     }
}

INT                                /*   returns VAL code                   */
valadr(                            /* validate address                     */
VOID *pWork,                       /*   GME work space                     */
const CHAR *from,                  /*   User-ID writing the message        */
CHAR *to,                          /*   to address                         */
USHORT forum)                      /*   forum message being written in     */
{
     INT i,rc;
     SHORT tmpchg;
     USHORT tmpfid;
     struct qscfg *qsc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(to != NULL);
     ASSERT(forum == EMLID || fidxst(forum));
     work->flags&=~ADROK;
     rc=VALNO;
     if (massage(from,to,forum)) {
          if (forum == EMLID && uhskey(from,emlkey)) {
               if (isforum(to)) {
                    ASSERT(getAdrForID(to) != EMLID);
                    qsc=othqsp(from);
                    tmpfid=getAdrForID(to);
                    if (qforac(qsc,tmpfid) >= WRAXES) {
                         work->basechg=getdef(tmpfid)->chgmsg;
                         if (gtstcrd(from,work->basechg,FALSE)) {
                              work->flags|=ADROK;
                              rc=VALYES;
                         }
                         else {
                              rc=VALCRD;
                         }
                    }
                    else {
                         rc=VALACC;
                    }
               }
               else if (isdlst(to)) {
                    ASSERT(dlstxst(to)); /* massage() should check */
                    if (*to == '@') {
                         if ((work->fp=gcfsopen(dlstpfn(to),FOPRA,GSH_DENYNO))
                             == NULL) {
                              return(VALNO);
                         }
                         fgets((CHAR *)tmpbuf,TMPBSZ,work->fp);
                         if (!sameto(DLKRQS,(CHAR *)tmpbuf)) {
                              rc=VALNO;
                         }
                         else if (uhskey(from,
                                         unpad(skpwht(&((CHAR *)tmpbuf)
                                                      [sizeof(DLKRQS)-1])))) {
                              fscanf(work->fp,DLCHGS,&tmpchg);
                              work->basechg=tmpchg;
                              if (gtstcrd(from,work->basechg+emschg,FALSE)) {
                                   work->flags|=ADROK;
                                   rc=VALYES;
                              }
                              else {
                                   rc=VALCRD;
                              }
                         }
                         else {
                              rc=VALACC;
                         }
                         fclose(work->fp);
                         work->fp=NULL;
                    }
                    else if (sameas(to,"!quick")) {
                         if (uhskey(from,qikkey)) {
                              work->basechg=lstchg;
                              if (gtstcrd(from,work->basechg+emschg,FALSE)) {
                                   work->flags|=ADROK;
                                   rc=VALYES;
                              }
                              else {
                                   rc=VALCRD;
                              }
                         }
                         else {
                              rc=VALACC;
                         }
                    }
                    else {
                         ASSERT(sameas(to,"!mass"));
                         if (uhskey(from,massky)) {
                              work->basechg=lstchg;
                              if (gtstcrd(from,work->basechg+emschg,FALSE)) {
                                   work->flags|=ADROK;
                                   rc=VALYES;
                              }
                              else {
                                   rc=VALCRD;
                              }
                         }
                         else {
                              rc=VALACC;
                         }
                    }
               }
               else if (isexpa(to)) {
                    ASSERT(expidx(to) != NOIDX);
                    i=expidx(to);
                    if (uhskey(from,exphlr[i]->wrtkey)) {
                         work->basechg=emschg+exphlr[i]->wrtchg;
                         if (gtstcrd(from,work->basechg,FALSE)) {
                              work->flags|=ADROK;
                              rc=VALYES;
                         }
                         else {
                              rc=VALCRD;
                         }
                    }
                    else {
                         rc=VALACC;
                    }
               }
               else {
                    ASSERT(uidxst(to));
                    work->basechg=emschg;
                    if (gtstcrd(from,work->basechg,FALSE)) {
                         work->flags|=ADROK;
                         rc=VALYES;
                    }
                    else if (sameas(to,esysuid)) {
                         work->basechg=0;
                         work->flags|=ADROK;
                         rc=VALYES;
                    }
                    else {
                         rc=VALCRD;
                    }
               }
          }
          else if (forum == EMLID) {
               if (sameas(to,esysuid)) {
                    work->basechg=0;
                    work->flags|=ADROK;
                    rc=VALYES;
               }
               else {
                    rc=VALACC;
               }
          }
          else {
               ASSERT(fidxst(forum));
               qsc=othqsp(from);
               ASSERT(qsc != NULL);
               if (qforac(qsc,forum) >= WRAXES) {
                    work->basechg=getdef(forum)->chgmsg;
                    if (gtstcrd(from,work->basechg,FALSE)) {
                         work->flags|=ADROK;
                         rc=VALYES;
                    }
                    else {
                         rc=VALCRD;
                    }
               }
               else {
                    rc=VALACC;
               }
          }
     }
     return(rc);
}

INT                                /*   returns VAL code                   */
valatt(                            /* validate file attachments            */
VOID *pWork,                       /*   GME work space                     */
const CHAR *from,                  /*   User-ID writing the message        */
const CHAR *to,                    /*   to address                         */
USHORT forum)                      /*   forum message being written in     */
{
     INT rc,i;
     USHORT tmpfid;
     struct qscfg *qsc;
     struct fordef *tmpdef;
     struct exporter *tmpexp;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(to != NULL);
     ASSERT(forum == EMLID || fidxst(forum));
     work->flags&=~ATTOK;
     rc=VALNO;
     if (!(work->flags&ADROK)) {
          return(rc);
     }
     if (forum != EMLID || (forum == EMLID && isforum(to))) {
#ifdef DEBUG
          if (forum == EMLID) {
               ASSERT(getAdrForID(to) != EMLID);
          }
#endif
          qsc=othqsp(from);
          tmpfid=(forum == EMLID) ? getAdrForID(to) : forum;
          if (qforac(qsc,tmpfid) >= ULAXES) {
               tmpdef=getdef(tmpfid);
               work->attchg=tmpdef->chgatt;
               work->apkchg=tmpdef->chgupk;
               if (gtstcrd(from,work->basechg+work->attchg+work->apkchg,
                           FALSE)) {
                    work->flags|=ATTOK;
                    rc=VALYES;
               }
               else {
                    rc=VALCRD;
               }
          }
          else {
               rc=VALACC;
          }
     }
     else if (alweat) {
          if (uhskey(from,eatkey)) {
               if (isexpa(to)) {
                    ASSERT(expidx(to) != NOIDX);
                    i=expidx(to);
                    tmpexp=exphlr[i];
                    if (tmpexp->flags&EXPATT) {
                         if (uhskey(from,tmpexp->attkey)) {
                              work->attchg=eatchg+tmpexp->attchg;
                              work->apkchg=epkchg+tmpexp->apkchg;
                              if (gtstcrd(from,work->basechg+work->attchg
                               +work->apkchg,FALSE)) {
                                   work->flags|=ATTOK;
                                   rc=VALYES;
                              }
                              else {
                                   rc=VALCRD;
                              }
                         }
                         else {
                              rc=VALACC;
                         }
                    }
               }
               else if (isdlst(to)) {
                    work->attchg=0;
                    work->apkchg=0;
                    work->flags|=ATTOK;
                    rc=VALYES;
               }
               else {
                    work->attchg=eatchg;
                    work->apkchg=epkchg;
                    if (gtstcrd(from,work->basechg+work->attchg+work->apkchg,
                                FALSE)) {
                         work->flags|=ATTOK;
                         rc=VALYES;
                    }
                    else {
                         rc=VALCRD;
                    }
               }
          }
          else {
               rc=VALACC;
          }
     }
     return(rc);
}

INT                                /*   returns VAL code                   */
valrrr(                            /* validate return receipt request      */
VOID *pWork,                       /*   GME work space                     */
const CHAR *from,                  /*   User-ID writing the message        */
const CHAR *to,                    /*   to address                         */
USHORT forum)                      /*   forum message being written in     */
{
     INT rc;
     struct exporter *tmpexp;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(to != NULL);
     ASSERT(forum == EMLID || fidxst(forum));
     work->flags&=~RRROK;
     rc=VALNO;
     if ((work->flags&ADROK) && forum == EMLID
      && alwrrr && !isforum(to)) {
          if (uhskey(from,rrrkey)) {
               if (isexpa(to)) {
                    ASSERT(expidx(to) != NOIDX);
                    tmpexp=exphlr[expidx(to)];
                    if (tmpexp->flags&EXPRRR) {
                         if (uhskey(from,tmpexp->rrrkey)) {
                              work->rrrchg=rrrchg+tmpexp->rrrchg;
                              if (gtstcrd(from,work->basechg+work->rrrchg,
                                          FALSE)) {
                                   work->flags|=RRROK;
                                   rc=VALYES;
                              }
                              else {
                                   rc=VALCRD;
                              }
                         }
                         else {
                              rc=VALACC;
                         }
                    }
               }
               else if (isdlst(to)) {
                    work->rrrchg=0;
                    work->flags|=RRROK;
                    rc=VALYES;
               }
               else if (gtstcrd(from,work->basechg+(work->rrrchg=rrrchg),
                                FALSE)) {
                    work->flags|=RRROK;
                    rc=VALYES;
               }
               else {
                    rc=VALCRD;
               }
          }
          else {
               rc=VALACC;
          }
     }
     return(rc);
}

INT                                /*   returns VAL code                   */
valpri(                            /* validate priority messaging          */
VOID *pWork,                       /*   GME work space                     */
const CHAR *from,                  /*   User-ID writing the message        */
const CHAR *to,                    /*   to address                         */
USHORT forum)                      /*   forum message being written in     */
{
     INT rc;
     struct exporter *tmpexp;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(to != NULL);
     ASSERT(forum == EMLID || fidxst(forum));
     work->flags&=~PRIOK;
     rc=VALNO;
     if ((work->flags&ADROK) && forum == EMLID
      && alwpri && !isforum(to)) {
          if (uhskey(from,prikey)) {
               if (isexpa(to)) {
                    ASSERT(expidx(to) != NOIDX);
                    tmpexp=exphlr[expidx(to)];
                    if (tmpexp->flags&EXPPRI) {
                         if (uhskey(from,tmpexp->prikey)) {
                              work->prichg=prichg+tmpexp->prichg;
                              if (gtstcrd(from,work->basechg+work->prichg,
                                          FALSE)) {
                                   work->flags|=PRIOK;
                                   rc=VALYES;
                              }
                              else {
                                   rc=VALCRD;
                              }
                         }
                         else {
                              rc=VALACC;
                         }
                    }
               }
               else if (isdlst(to)) {
                    work->prichg=0;
                    work->flags|=PRIOK;
                    rc=VALYES;
               }
               else if (gtstcrd(from,work->basechg+(work->prichg=prichg),
                                FALSE)) {
                    work->flags|=PRIOK;
                    rc=VALYES;
               }
               else {
                    rc=VALCRD;
               }
          }
          else {
               rc=VALACC;
          }
     }
     return(rc);
}

INT                                /*   returns VAL code                   */
vfwdadr(                           /* validate address for forwarding      */
VOID *pWork,                       /*   GME work space                     */
const CHAR *from,                  /*   User-ID doing the forwarding       */
CHAR *to,                          /*   address to forward to              */
USHORT forum)                      /*   forum message being written in     */
{
     INT rc,i;
     USHORT tmpfid;
     struct qscfg *qsc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(to != NULL);
     ASSERT(forum == EMLID || fidxst(forum));
     work->flags&=~ADROK;
     rc=VALNO;
     if (massage(from,to,forum)) {
          if (forum == EMLID && uhskey(from,emlkey)) {
               if (isforum(to)) {
                    ASSERT(getAdrForID(to) != EMLID);
                    qsc=othqsp(from);
                    tmpfid=getAdrForID(to);
                    if (qforac(qsc,tmpfid) >= WRAXES) {
                         if (chgfwd) {
                              work->basechg=getdef(tmpfid)->chgmsg-emschg;
                              work->basechg=max(work->basechg,0L);
                         }
                         else {
                              work->basechg=0L;
                         }
                         if (gtstcrd(from,work->basechg,FALSE)) {
                              work->flags|=ADROK;
                              rc=VALYES;
                         }
                         else {
                              rc=VALCRD;
                         }
                    }
                    else {
                         rc=VALACC;
                    }
               }
               else if (isdlst(to)) {
                    rc=VALNO;
               }
               else if (isexpa(to)) {
                    ASSERT(expidx(to) != NOIDX);
                    i=expidx(to);
                    if (uhskey(from,exphlr[i]->wrtkey)) {
                         if (chgfwd) {
                              work->basechg=max(exphlr[i]->wrtchg,0L);
                         }
                         else {
                              work->basechg=0L;
                         }
                         if (gtstcrd(from,work->basechg,FALSE)) {
                              work->flags|=ADROK;
                              rc=VALYES;
                         }
                         else {
                              rc=VALCRD;
                         }
                    }
                    else {
                         rc=VALACC;
                    }
               }
               else {
                    ASSERT(uidxst(to));
                    work->basechg=0L;
                    work->flags|=ADROK;
                    rc=VALYES;
               }
          }
          else if (forum == EMLID) {
               if (sameas(to,esysuid)) {
                    work->basechg=0;
                    work->flags|=ADROK;
                    rc=VALYES;
               }
               else {
                    rc=VALACC;
               }
          }
          else {
               ASSERT(fidxst(forum));
               qsc=othqsp(from);
               if (qforac(qsc,forum) >= WRAXES) {
                    if (chgfwd) {
                         work->basechg=getdef(forum)->chgmsg-emschg;
                         work->basechg=max(work->basechg,0L);
                    }
                    else {
                         work->basechg=0L;
                    }
                    if (gtstcrd(from,work->basechg,FALSE)) {
                         work->flags|=ADROK;
                         rc=VALYES;
                    }
                    else {
                         rc=VALCRD;
                    }
               }
               else {
                    rc=VALACC;
               }
          }
     }
     return(rc);
}

INT                                /*   returns VAL code                   */
vfwdatt(                           /* validate attachment for forwarding   */
VOID *pWork,                       /*   GME work space                     */
const CHAR *from,                  /*   User-ID writing the message        */
const CHAR *to,                    /*   to address                         */
USHORT forum)                      /*   forum message being written in     */
{
     INT rc,i;
     USHORT tmpfid;
     struct qscfg *qsc;
     struct fordef *tmpdef;
     struct exporter *tmpexp;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(to != NULL);
     ASSERT(forum == EMLID || fidxst(forum));
     work->flags&=~ATTOK;
     rc=VALNO;
     if (!(work->flags&ADROK)) {
          return(rc);
     }
     if (forum != EMLID || (forum == EMLID && isforum(to))) {
#ifdef DEBUG
       if (forum == EMLID) {
            ASSERT(getAdrForID(to) != EMLID);
       }
#endif
          qsc=othqsp(from);
          tmpfid=(forum == EMLID) ? getAdrForID(to) : forum;
          if (qforac(qsc,tmpfid) >= ULAXES) {
               tmpdef=getdef(tmpfid);
               if (chgfwd) {
                    work->attchg=tmpdef->chgatt-eatchg;
                    work->apkchg=tmpdef->chgupk-epkchg;
                    work->attchg=max(work->attchg,0);
                    work->apkchg=max(work->apkchg,0);
               }
               else {
                    work->attchg=0;
                    work->apkchg=0;
               }
               if (gtstcrd(from,work->basechg+work->attchg+work->apkchg,
                           FALSE)) {
                    work->flags|=ATTOK;
                    rc=VALYES;
               }
               else {
                    rc=VALCRD;
               }
          }
          else {
               rc=VALACC;
          }
     }
     else if (alweat) {
          if (uhskey(from,eatkey)) {
               if (isexpa(to)) {
                    ASSERT(expidx(to) != NOIDX);
                    i=expidx(to);
                    tmpexp=exphlr[i];
                    if (tmpexp->flags&EXPATT) {
                         if (uhskey(from,tmpexp->attkey)) {
                              if (chgfwd) {
                                   work->attchg=max(tmpexp->attchg,0);
                                   work->apkchg=max(tmpexp->apkchg,0);
                              }
                              else {
                                   work->attchg=0;
                                   work->apkchg=0;
                              }
                              if (gtstcrd(from,work->basechg+work->attchg
                               +work->apkchg,FALSE)) {
                                   work->flags|=ATTOK;
                                   rc=VALYES;
                              }
                              else {
                                   rc=VALCRD;
                              }
                         }
                         else {
                              rc=VALACC;
                         }
                    }
               }
               else if (isdlst(to)) {
                    rc=VALNO;
               }
               else {
                    work->attchg=0;
                    work->apkchg=0;
                    work->flags|=ATTOK;
                    rc=VALYES;
               }
          }
          else {
               rc=VALACC;
          }
     }
     return(rc);
}

INT                                /*   returns VAL code                   */
vfwdpri(                           /* validate priority message for fwding */
VOID *pWork,                       /*   GME work space                     */
const CHAR *from,                  /*   User-ID writing the message        */
const CHAR *to,                    /*   to address                         */
USHORT forum)                      /*   forum message being written in     */
{
     INT rc;
     struct exporter *tmpexp;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(to != NULL);
     ASSERT(forum == EMLID || fidxst(forum));
     work->flags&=~PRIOK;
     rc=VALNO;
     if ((work->flags&ADROK) && forum == EMLID
      && alwpri && !isforum(to)) {
          if (uhskey(from,prikey)) {
               if (isexpa(to)) {
                    ASSERT(expidx(to) != NOIDX);
                    tmpexp=exphlr[expidx(to)];
                    if (tmpexp->flags&EXPPRI) {
                         if (uhskey(from,tmpexp->prikey)) {
                              if (chgfwd) {
                                   work->prichg=max(tmpexp->prichg,0);
                              }
                              else {
                                   work->prichg=0;
                              }
                              if (gtstcrd(from,work->basechg+work->prichg,
                                          FALSE)) {
                                   work->flags|=PRIOK;
                                   rc=VALYES;
                              }
                              else {
                                   rc=VALCRD;
                              }
                         }
                         else {
                              rc=VALACC;
                         }
                    }
               }
               else if (isdlst(to)) {
                    rc=VALNO;
               }
               else {
                    work->flags|=PRIOK;
                    rc=VALYES;
               }
          }
          else {
               rc=VALACC;
          }
     }
     return(rc);
}

VOID
setscan(                           /* set the scan context                 */
VOID *pWork,                       /*   for this work space                */
struct otscan *newscn)             /*   to this scan buffer                */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(newscn != NULL);
     work->curscn=newscn;
}

struct otscan *
getscan(                           /* get the current scan context buffer  */
VOID *pWork)                       /*   for this work space                */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     return(work->curscn);
}

VOID
gmeSetAppInfo(                     /* set app-defined info (when sending)  */
VOID *pWork,                       /*   for this work area                 */
const CHAR *appinf)                /*   to this buffer                     */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     work->appinf=appinf;
}

const CHAR *                       /*   returns pointer to temp buffer     */
gmeGetAppInfo(VOID)                /* get app-defined info (after reading) */
{
     if (curapi == NULL) {
          return("");
     }
     return(curapi);
}

VOID
gmePlainText(                      /* cvt fmtted to plain text if necessary*/
CHAR *text)                        /*   text buffer (must be TXTLEN long)  */
{
     if (isfmtted(text) && cvt2asc(text,utltxt,TXTLEN-1)) {
          text[0]='\r';
          stlcpy(&text[1],utltxt,TXTLEN-1);
     }
}

GBOOL                              /*   returns TRUE if hook set           */
hook_gme(                          /* set a GME function hook              */
INT hooktype,                      /*   type of hook to set                */
voidfunc hookfunc)                 /*   hook function pointer              */
{
     ASSERT(hooktype >= GMEHOOK_NOT_NEWMSG && hooktype <= GMEHOOK_MAX);
     ASSERT(hookfunc != NULL);
     if (alcarr((VOID **)&gmehooks,sizeof(struct gmehook),ngmehooks,SMLBLK)) {
          gmehooks[ngmehooks].hooktype=hooktype;
          gmehooks[ngmehooks].hookfunc=hookfunc;
          ++ngmehooks;
          return(TRUE);
     }
     return(FALSE);
}

static
INT                                /*   > 0 = target > test, etc.          */\
phookcmp(                          /* prioritized hook comparison function */
VOID const * target,               /*   target object                      */
VOID const * array,                /*   array object                       */
ULONG index)                       /*   index of array element to test     */
{
     return(((INT)target)-((struct gmephook const *)array)[index].hookpri);
}

GBOOL                              /*   returns TRUE if hook set           */
hook_gme_p(                        /* set a GME function hook w/priority   */
INT hooktype,                      /*   type of hook to set                */
voidfunc hookfunc,                 /*   hook function pointer              */
INT priority)                      /*   priority (0=highest, 255=lowest)   */
{
     INT i,cmp;

     ASSERT(hooktype > GMEHOOK_PRIBASE && hooktype <= GMEHOOK_PRIMAX);
     ASSERT(hookfunc != NULL);
     if (priority < GMEHOOKPRI_HIGHEST) {
          priority=GMEHOOKPRI_HIGHEST;
     }
     if (priority > GMEHOOKPRI_LOWEST) {
          priority=GMEHOOKPRI_LOWEST;
     }
     if (alcarr((VOID **)&gmephooks,sizeof(struct gmephook),ngmephooks,SMLBLK)) {
          i=binFindNear(&cmp,(VOID *)priority,gmephooks,ngmephooks,phookcmp);
          memmove(&gmephooks[i+1],&gmephooks[i]
                 ,(ngmephooks-i)*sizeof(struct gmephook));
          gmephooks[i].hookfunc=hookfunc;
          gmephooks[i].hooktype=hooktype;
          gmephooks[i].hookpri=priority;
          ++ngmephooks;
          return(TRUE);
     }
     return(FALSE);
}

VOID
noticu(                            /* handle user init/close notification  */
INT hookType,                      /*   type of hook to process            */
const CHAR *userid)                /*   User-ID being initialized          */
{
     INT i;

     i=NOIDX;
     while ((i=nexthook(hookType,i)) != NOIDX) {
          (*(gmehook_not_iniclsu)(gmehooks[i].hookfunc))(userid);
     }
}

VOID
notnwm(                            /* handle new message notification hook */
INT nwmtyp,                        /*   new message type code              */
VOID *pWork,                       /*   work area in use                   */
struct message *msg,               /*   header of new message              */
const CHAR *text)                  /*   message text                       */
{
     INT i;
     CHAR savehist[HSTSIZ];
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     curapi=(CHAR *)work->appinf;
     if (work->flags&FWDMSG) {
          stlcpy(extinf,work->auxto,UIDSIZ);
     }
     else {
          *extinf='\0';
     }
     i=NOIDX;
     while ((i=nexthook(GMEHOOK_NOT_NEWMSG,i)) != NOIDX) {
          (*(gmehook_not_newmsg)(gmehooks[i].hookfunc))(nwmtyp,msg,text);
     }
     if (newmsghook != NULL) {
          if (work->flags&FWDMSG) {
               stlcpy(extinf,msg->to,MAXADR);
               stlcpy(msg->to,work->auxto,UIDSIZ);
               stlcpy(savehist,msg->history,HSTSIZ);
               stlcpy(msg->history,work->auxhist,HSTSIZ);
               (*newmsghook)(nwmtyps(nwmtyp),msg,extinf);
               stlcpy(msg->to,extinf,MAXADR);
               stlcpy(msg->history,savehist,HSTSIZ);
          }
          else {
               (*newmsghook)(nwmtyps(nwmtyp),msg,NULL);
          }
     }
     curapi=utlapi;
}

INT                                /*   returns updated hook index         */
notnwml(                           /* low-level new message notification   */
INT curidx,                        /*   current hook index (NOIDX == first)*/
const struct message *msg,         /*   header of new message              */
const CHAR *text,                  /*   message text                       */
const CHAR *appinf)                /*   app-defined info ("" if none)      */
{
     INT i;

     curapi=(CHAR *)appinf;
     *extinf='\0';
     if ((i=nexthook(GMEHOOK_NOT_NEWMSGL,curidx)) != NOIDX) {
          (*(gmehook_not_newmsgl)(gmehooks[i].hookfunc))(msg,text);
     }
     curapi=utlapi;
     return(i);
}

INT                                /*   returns updated hook index         */
notnwf(                            /* handle new forum notification hooks  */
INT curidx,                        /*   current hook index (NOIDX == first)*/
const struct fordsk *newdef,       /*   new forum definition structure     */
const CHAR *desc,                  /*   descriptive text                   */
const CHAR *echoes)                /*   pointer to array of echo addresses */
{
     INT i;

     *utlapi='\0';
     *extinf='\0';
     if ((i=nexthook(GMEHOOK_NOT_NEWFOR,curidx)) != NOIDX) {
          (*(gmehook_not_newfor)(gmehooks[i].hookfunc))(newdef,desc,echoes);
     }
     return(i);
}

INT                                /*   returns updated hook index         */
notnwg(                            /* handle new group notification hooks  */
INT curidx,                        /*   current hook index (NOIDX == first)*/
const struct forgrp *grpbuf)       /*   new forum group structure          */
{
     INT i;

     *utlapi='\0';
     *extinf='\0';
     if ((i=nexthook(GMEHOOK_NOT_NEWGRP,curidx)) != NOIDX) {
          (*(gmehook_not_grpchg)(gmehooks[i].hookfunc))(grpbuf);
     }
     return(i);
}

VOID
notudm(                            /* handle message update notification   */
INT updtyp,                        /*   update type code                   */
VOID *pWork,                       /*   work area in use                   */
const struct message *msg,         /*   header of new message              */
const CHAR *text)                  /*   message text                       */
{
     INT i;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     stlcpy(extinf,work->rdctx.uid,UIDSIZ);
     i=NOIDX;
     while ((i=nexthook(GMEHOOK_NOT_UPDMSG,i)) != NOIDX) {
          (*(gmehook_not_updmsg)(gmehooks[i].hookfunc))(updtyp,msg,text);
     }
}

INT                                /*   returns updated hook index         */
notmdf(                            /* handle forum update notification     */
INT curidx,                        /*   current hook index (NOIDX == first)*/
const struct fordef *def,          /*   forum definition structure         */
const CHAR *desc,                  /*   descriptive text                   */
const CHAR *echoes)                /*   pointer to array of echo addresses */
{
     INT i;

     *utlapi='\0';
     *extinf='\0';
     if ((i=nexthook(GMEHOOK_NOT_UPDFOR,curidx)) != NOIDX) {
          (*(gmehook_not_updfor)(gmehooks[i].hookfunc))(def,desc,echoes);
     }
     return(i);
}

INT                                /*   returns updated hook index         */
notmodg(                           /* handle group modify notification     */
INT curidx,                        /*   current hook index (NOIDX == first)*/
const struct forgrp *grpbuf)       /*   new forum group structure          */
{
     INT i;

     *utlapi='\0';
     *extinf='\0';
     if ((i=nexthook(GMEHOOK_NOT_UPDGRP,curidx)) != NOIDX) {
          (*(gmehook_not_grpchg)(gmehooks[i].hookfunc))(grpbuf);
     }
     return(i);
}

VOID
notdlm(                            /* handle message delete notification   */
INT deltyp,                        /*   delete type code                   */
VOID *pWork,                       /*   work area in use                   */
struct message *msg,               /*   header of new message              */
const CHAR *text)                  /*   message text                       */
{
     INT i;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     stlcpy(extinf,work->rdctx.uid,UIDSIZ);
     i=NOIDX;
     while ((i=nexthook(GMEHOOK_NOT_DELMSG,i)) != NOIDX) {
          (*(gmehook_not_delmsg)(gmehooks[i].hookfunc))(deltyp,msg,text);
     }
}

VOID
notdlml(                           /* low-level message delete notification*/
USHORT forum,                      /*   forum ID                           */
LONG msgid)                        /*   message ID                         */
{
     INT i;

     *extinf='\0';
     curapi=NULL;
     i=NOIDX;
     while ((i=nexthook(GMEHOOK_NOT_DELMSGL,i)) != NOIDX) {
          (*(gmehook_not_delmsgl)(gmehooks[i].hookfunc))(forum,msgid);
     }
     curapi=utlapi;
}

VOID
notdlf(                            /* handle forum delete notification     */
const struct fordsk *def,          /*   forum definition structure         */
const CHAR *desc,                  /*   descriptive text                   */
const CHAR *echoes)                /*   pointer to array of echo addresses */
{
     INT i;

     *utlapi='\0';
     *extinf='\0';
     i=NOIDX;
     while ((i=nexthook(GMEHOOK_NOT_DELFOR,i)) != NOIDX) {
          (*(gmehook_not_delfor)(gmehooks[i].hookfunc))(def,desc,echoes);
     }
}

INT                                /*   returns updated hook index         */
notdlfl(                           /* low-level forum delete notification  */
int curidx,                        /*   current hook index (NOIDX == first)*/
USHORT forum)                      /*   forum ID of deleted forum          */
{
     INT i;

     *utlapi='\0';
     *extinf='\0';
     if ((i=nexthook(GMEHOOK_NOT_DELFORL,curidx)) != NOIDX) {
          (*(gmehook_not_delforl)(gmehooks[i].hookfunc))(forum);
     }
     return(i);
}

VOID
notdlg(                            /* handle group delete notification     */
const struct forgrp *grpbuf)       /*   new forum group structure          */
{
     INT i;

     *utlapi='\0';
     *extinf='\0';
     i=NOIDX;
     while ((i=nexthook(GMEHOOK_NOT_DELGRP,i)) != NOIDX) {
          (*(gmehook_not_grpchg)(gmehooks[i].hookfunc))(grpbuf);
     }
}

INT                                /*   returns updated hook index         */
notdlgl(                           /* low-level group delete notification  */
INT curidx,                        /*   current hook index (NOIDX == first)*/
USHORT grpid)                      /*   group ID of deleted group          */
{
     INT i;

     *utlapi='\0';
     *extinf='\0';
     if ((i=nexthook(GMEHOOK_NOT_DELGRPL,curidx)) != NOIDX) {
          (*(gmehook_not_delgrpl)(gmehooks[i].hookfunc))(grpid);
     }
     return(i);
}

INT                                /*   returns index or NOIDX if not found*/
nexthook(                          /* find next GME hook function          */
INT hooktype,                      /*   of a given type                    */
INT curidx)                        /*   after this index (NOIDX == first)  */
{
     INT i;

     for (i=(curidx == NOIDX ? 0 : curidx+1) ; i < ngmehooks ; ++i) {
          if (gmehooks[i].hooktype == hooktype) {
               return(i);
          }
     }
     return(NOIDX);
}

VOID
hdlafwd(                           /* call autoforward handlers            */
CHAR * fwdee,                      /*   buf containing proposed forwardee  */
struct message const * pMsg,       /*   message being sent                 */
VOID *pWork)                       /*   GME work space                     */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);
     INT i;

     curapi=NULL;
     stlcpy(extinf,work->rdctx.uid,UIDSIZ);
     i=NOIDX;
     while ((i=nextphook(GMEHOOK_HDL_AUTOFWD,i)) != NOIDX) {
          if ((*(gmehook_hdl_autofwd)(gmephooks[i].hookfunc))(fwdee,pMsg)) {
               break;
          }
     }
     curapi=utlapi;
     *extinf='\0';
}

INT                                /*   returns index or NOIDX if not found*/
nextphook(                         /* find next prioritized GME hook       */
INT hooktype,                      /*   of a given type                    */
INT curidx)                        /*   after this index (NOIDX == first)  */
{
     INT i;

     for (i=(curidx == NOIDX ? 0 : curidx+1) ; i < ngmephooks ; ++i) {
          if (gmephooks[i].hooktype == hooktype) {
               return(i);
          }
     }
     return(NOIDX);
}

const CHAR *
nwmtyps(                           /* get new message type string          */
INT nwmtyp)                        /*   given new message type code        */
{
     switch (nwmtyp) {
     case NEWMSG_WRITE:
          return(WRITENOT);
     case NEWMSG_REPLY:
          return(REPLYNOT);
     case NEWMSG_IMPRT:
          return(IMPRTNOT);
     case NEWMSG_DISTR:
          return(DISTRNOT);
     case NEWMSG_CCOPY:
          return(CCOPYNOT);
     case NEWMSG_COPY:
          return(MCOPYNOT);
     case NEWMSG_FORWD:
          return(FORWDNOT);
     case NEWMSG_RTRCP:
          return(RTRCPNOT);
     }
     ASSERT(FALSE);
     return("");
}

INT                                /*   returns VAL code                   */
wrtany(VOID)                       /* can current user E-mail anyone       */
{                                  /*   (other than Sysop)                 */
     if (haskey(emlkey)) {
          if (tstcrd(emschg)) {
               return(VALYES);
          }
          else {
               return(VALCRD);
          }
     }
     else {
          return(VALACC);
     }
}

INT                                /*   returns standard access level code */
foracc(                            /* get current user's forum access level*/
USHORT fid)                        /*   given forum ID                     */
{
     return(qforac(uqsptr(usrnum),fid));
}

INT                                /*   returns standard access level code */
gforac(                            /* get a user's forum access level      */
const CHAR *uid,                   /*   given any User-ID                  */
USHORT fid)                        /*   and forum ID                       */
{
     struct qscfg *qsc;

     qsc=othqsp(uid);
     if (qsc == NULL) {
          return(NOAXES);
     }
     return(qforac(qsc,fid));
}

INT                                /*   returns standard access level code */
qforac(                            /* get a user's forum access level      */
const struct qscfg *qsc,           /*   given quickscan                    */
USHORT fid)                        /*   and forum ID                       */
{
     INT i,acc;
     GBOOL priv;
     struct fordef *tmpdef;

     ASSERT(qsc != NULL);
     ASSERT(*qsc->userid != '\0');
     ASSERT(fid != EMLID && fidxst(fid));
     if ((tmpdef=getdef(fid)) == NULL) {
          return(NOAXES);
     }
     if (uhskey(qsc->userid,forsys)) {
          return(SYAXES);
     }
     if (*tmpdef->forlok == '\0') {
          priv=uhskey(qsc->userid,forprv);
     }
     else {
          priv=uhskey(qsc->userid,tmpdef->forlok);
     }
     if (sameas(qsc->userid,tmpdef->forop)) {
          acc=OPAXES;
     }
     else {
          if ((i=qsidx(qsc,fid)) != NOIDX) {
               acc=acclvl(qsc->accmsg+qsc->nforums*sizeof(struct fmidky),i);
               ASSERT(acc == NOTSET || acc <= COAXES);
          }
          if (i == NOIDX || acc == NOTSET) {
               acc=priv ? tmpdef->dfprv : tmpdef->dfnpv;
          }
     }
     if (!priv && acc > tmpdef->mxnpv) {
          acc=tmpdef->mxnpv;
     }
     if (fid == dftfor && acc == NOAXES) {
          acc=RDAXES;
     }
     return(acc);
}

INT                                /*   returns standard GME status codes  */
setaxes(                           /* set access for a user                */
USHORT fid,                        /*   forum to set access for            */
const CHAR *uid,                   /*   user to set access for             */
INT acc)                           /*   access to set to                   */
{
     INT i,curacc;
     struct qscfg *qsc;

     ASSERT(fidxst(fid));
     ASSERT(uid != NULL);
     ASSERT(uidxst(uid));
     ASSERT(acc == NOTSET || acc <= OPAXES);
     curacc=foracc(fid);
     if (curacc < OPAXES || (acc == OPAXES && curacc < SYAXES)) {
          return(GMEACC);
     }
     curacc=gforac(uid,fid);
     if (curacc == acc) {
          return(GMEOK);
     }
     if (acc == OPAXES) {
          setfop(fid,uid);
     }
     else {
          if (curacc == OPAXES) {
               setfop(fid,usaptr->userid);
          }
          qsc=othqsp(uid);
          if (qsc == NULL) {
               qsc=utlqsc;
               initqs(qsc,uid);
          }
          if (acc == NOTSET) {
               i=qsidx(qsc,fid);
               if (i != NOIDX) {
                    if (igethi(qsc,i) == 0L) {
                         idelqs(qsc,i);
                    }
                    else {
                         isetac(qsc,i,acc);
                    }
               }
          }
          else {
               i=absadqs(qsc,fid);
               if (i == NOIDX) {
                    return(GMEMEM);
               }
               isetac(qsc,i,acc);
          }
          dfaSetBlk(qscbb);
          if (dfaAcqEQ(NULL,uid,0)) {
               dfaUpdateV(qsc,qsrlen(qsc->nforums));
          }
          else {
               dfaInsertV(qsc,qsrlen(qsc->nforums));
          }
          dfaRstBlk();
     }
     return(GMEOK);
}

INT                                /*   returns standard GME status codes  */
cpyaxes(                           /* copy forum access                    */
const CHAR *dstusr,                /*   to this User-ID                    */
const CHAR *srcusr)                /*   from this User-ID                  */
{
     INT srcidx,dstidx,access;
     GBOOL newguy;
     CHAR *accarr;
     struct fmidky *fmarr;
     struct qscfg *srcqsc,*dstqsc;

     ASSERT(dstusr != NULL);
     ASSERT(srcusr != NULL);
     if (!haskey(forsys)) {
          return(GMEACC);
     }
     if ((srcqsc=othqsp(srcusr)) == NULL) {
          return(GMENSRC);
     }
     if (!uidxst(dstusr)) {
          return(GMENDST);
     }
     newguy=FALSE;
     if ((dstqsc=GetOnlineQS(dstusr)) == NULL) {
          dfaSetBlk(qscbb);
          ASSERT(srcqsc != qsdptr);
          dstqsc=qsdptr;
          if (!dfaAcqEQ(NULL,dstusr,0)) {
               newguy=TRUE;
               initqs(dstqsc,dstusr);
          }
          dfaRstBlk();
     }
     fmarr=(struct fmidky *)srcqsc->accmsg;
     accarr=(CHAR *)&fmarr[srcqsc->nforums];
     for (srcidx=0 ; srcidx < srcqsc->nforums ; ++srcidx) {
          access=acclvl(accarr,srcidx);
          if (access != NOTSET) {
               dstidx=absadqs(dstqsc,fmarr[srcidx].forum);
               if (dstidx != NOIDX) {
                    isetac(dstqsc,dstidx,access);
               }
          }
     }
     if (dstqsc == qsdptr) {
          dfaSetBlk(qscbb);
          if (newguy) {
               dfaInsertV(NULL,qsrlen(dstqsc->nforums));
          }
          else {
               dfaUpdateV(NULL,qsrlen(dstqsc->nforums));
          }
          dfaRstBlk();
     }
     return(GMEOK);
}

INT                                /*   returns standard GME status codes  */
chkread(                           /* check access/credits when reading    */
VOID *pWork)                       /*   GME work space (provided by caller)*/
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     if (work->rdctx.fid == EMLID) {
          if (work->rdctx.seq != ESQFRU
           && !gtstcrd(work->rdctx.uid,emrchg,FALSE)) {
               return(GMECRD);
          }
     }
     else {
          if (gforac(work->rdctx.uid,work->rdctx.fid) < RDAXES) {
               return(GMEACC);
          }
          if (!gtstcrd(work->rdctx.uid,getdef(work->rdctx.fid)->chgrdm,
                       FALSE)) {
               return(GMECRD);
          }
     }
     return(GMEOK);
}

INT                                /*   returns standard GME status codes  */
chkdel(                            /* check access before deleting         */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg)               /*   message to check                   */
{
     GBOOL toflg,frflg;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     toflg=sameas(msg->to,work->rdctx.uid);
     frflg=sameas(msg->from,work->rdctx.uid);
     if (work->rdctx.fid == EMLID) {
          if ((msg->flags&NODEL) && frflg && !toflg) {
               return(GMENDEL);
          }
          else if (!(frflg || toflg)) {
               return(GMENFND);
          }
     }
     else {
          if (gforac(work->rdctx.uid,work->rdctx.fid) < OPAXES && !frflg) {
               return(GMEACC);
          }
     }
     return(GMEOK);
}

INT                                /*   returns standard GME status codes  */
chksnd(                            /* check credits/access before sending  */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg,               /*   new message structure              */
const CHAR *filatt)                /*   file attachment (if any)           */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(msg != NULL);
     if (!(work->flags&ADROK)
      || (!(work->flags&ATTOK) && (msg->flags&FILATT))
      || (!(work->flags&RRROK) && (msg->flags&RECREQ))
      || (!(work->flags&PRIOK) && (msg->flags&PRIMSG))) {
          return(GMEERR);
     }
     if (msg->flags&FILATT) {
          ASSERT(filatt != NULL);
          ASSERT((msg->flags&FILIND)
              || sameto(attpth(msg->forum == EMLID && isforum(msg->to)
                             ? getAdrForID(msg->to) : msg->forum),
                        normspec((CHAR *)tmpbuf,filatt)));
          if (!fexist(filatt)) {
               return(GMEIVA);
          }
          work->attsiz=gmefb.ff_fsize;
     }
     addchg(pWork,msg);
     if (!gtstcrd(msg->from,work->basechg,FALSE)) {
          return(GMECRD);
     }
     return(GMEOK);
}

INT                                /*   returns standard GME status codes  */
chkcf(                             /* check credits/access for copy/forward*/
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg,               /*   new message structure              */
const CHAR *filatt)                /*   file attachment (if any)           */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(msg != NULL);
     if (!(work->flags&ADROK)
      || (!(work->flags&ATTOK) && (msg->flags&FILATT))
      || (!(work->flags&RRROK) && (msg->flags&RECREQ))
      || (!(work->flags&PRIOK) && (msg->flags&PRIMSG))) {
          return(GMEERR);
     }
     if (msg->flags&FILATT) {
          if (fexist(filatt)) {
               work->attsiz=gmefb.ff_fsize;
          }
          else {
               msg->flags&=~(FILATT|FILIND|FILAPV);
          }
     }
     addchg(pWork,msg);
     return(GMEOK);
}

VOID
addchg(                            /* add up charges for current message   */
VOID *pWork,                       /*   work area in use                   */
const struct message *msg)         /*   new message structure              */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     work->basechg=sendchg(pWork,msg,work->attsiz);
}

LONG
sendchg(                           /* total charges to send a message      */
VOID *pWork,                       /*   work area in use                   */
const struct message *msg,         /*   new message structure              */
LONG attsiz)                       /*   attachment size                    */
{
     LONG totchg;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     totchg=work->basechg;
     if (msg->flags&FILATT) {
          totchg+=work->attchg+(attsiz/1024L)*work->apkchg;
     }
     if (msg->flags&RECREQ) {
          totchg+=work->rrrchg;
     }
     if (msg->flags&PRIMSG) {
          totchg+=work->prichg;
     }
     return(totchg);
}

VOID
chkrqs(                            /* check recipient's quickscan          */
VOID *pWork,                       /*   work area in use                   */
struct message *msg)               /*   new message structure              */
{                                  /* (checks autofwd & forum to E-mail)   */
     struct qscfg *tmpqsp;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(msg != NULL);
     if (islocal(msg->to) && (tmpqsp=othqsp(msg->to)) != NULL) {
          if (msg->forum == EMLID) {
               if (!(msg->flags&GMFERR)) {
                    chkafwd(pWork,tmpqsp,msg);
               }
          }
          else if ((tmpqsp->flags&FORUM2)
                && qforac(tmpqsp,msg->forum) >= RDAXES) {
               work->flags|=CPY2E;
          }
     }
}

VOID
chkafwd(                           /* check for auto forwarding            */
VOID *pWork,                       /*   work area in use                   */
struct qscfg *rcpqsp,              /*   pointer to recipient's quickscan   */
struct message *msg)               /*   new message structure              */
{
     INT i,isFwd;
     LONG tmpflg,rcpchg;
     struct exporter *exptr;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);
     CHAR tmpFwdee[MAXADR];

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(msg != NULL);
     ASSERT(msg->forum == EMLID);
     ASSERT(uidxst(msg->to));

     isFwd=TRUE;
     tmpflg=msg->flags;
     stlcpy(tmpFwdee,rcpqsp->fwdee,MAXADR);
     if (*tmpFwdee == '\0') {
          isFwd=FALSE;
     }
     else if (isexpa(tmpFwdee)) {
          i=expidx(tmpFwdee);
          if (i != NOIDX && uhskey(msg->to,exphlr[i]->wrtkey)) {
               exptr=exphlr[i];
               work->rcpchg=0L;
               rcpchg=exptr->wrtchg;
               if (msg->flags&FILATT) {
                    if ((exptr->flags&EXPATT)
                     && uhskey(msg->to,exptr->attkey)) {
                         rcpchg+=exptr->attchg;
                         rcpchg+=exptr->apkchg*(work->attsiz/1024);
                    }
                    else {
                         noatt(msg);
                    }
               }
               if (msg->flags&RECREQ) {
                    if ((exptr->flags&EXPRRR)
                     && uhskey(msg->to,exptr->rrrkey)) {
                         rcpchg+=exptr->rrrchg;
                    }
                    else {
                         msg->flags&=~RECREQ;
                    }
               }
               if (msg->flags&PRIMSG) {
                    if ((exptr->flags&EXPPRI)
                     && uhskey(msg->to,exptr->prikey)) {
                         rcpchg+=exptr->prichg;
                    }
                    else {
                         msg->flags&=~PRIMSG;
                    }
               }
               if (rcpchg > 0L && chgfwd) {
                    if (gtstcrd(msg->to,rcpchg,FALSE)) {
                         work->rcpchg=rcpchg;
                    }
                    else {
                         msg->flags=tmpflg;
                         isFwd=FALSE;
                    }
               }
          }
          else {
               isFwd=FALSE;
          }
     }
     else if (!(islocal(tmpFwdee) && uidxst(tmpFwdee)
             && ((struct usracc *)accbb->data)->credat <= rcpqsp->fwdate)) {
          *rcpqsp->fwdee='\0';
          if (qsoffln(rcpqsp)) {   /* assumes no qscbb access since othqsp */
               dfaSetBlk(qscbb);
               dfaUpdateV(rcpqsp,qsrlen(rcpqsp->nforums));
               dfaRstBlk();
               work->flags|=CYCFLG;
               msg->flags=tmpflg;
          }
          isFwd=FALSE;
     }

     if (!isFwd) {
          *tmpFwdee='\0';
     }
     hdlafwd(tmpFwdee,msg,pWork);

     if (*tmpFwdee != '\0') {
          work->flags|=FWDMSG;
          setmem(&msg->rplto,sizeof(struct globid),0);
          addhist(msg->history,spr(AUTFWD,msg->to));
          stlcpy(work->auxto,msg->to,UIDSIZ);
          stlcpy(msg->to,tmpFwdee,MAXADR);
     }
}

VOID
rstafwd(                           /* restore msg->to after auto-forward   */
VOID *pWork,                       /*   work area in use                   */
struct message *msg)               /*   message structure                  */
{                                  /*   (also puts fwdee in extinf)        */
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(msg != NULL);
     stlcpy(extinf,msg->to,MAXADR);
     stlcpy((CHAR *)tmpbuf,msg->to,MAXADR);
     stlcpy(msg->to,work->auxto,UIDSIZ);
     stlcpy(work->auxto,(CHAR *)tmpbuf,UIDSIZ);
     stlcpy(msg->history,work->auxhist,HSTSIZ);
}

VOID
chkfor(                            /* check for E-mail to a forum          */
struct message *msg)               /*   new message structure              */
{
     CHAR *cp;

     ASSERT(msg != NULL);
     if (msg->forum == EMLID && isforum(msg->to)) {
          msg->forum=getAdrForID(msg->to);
          ASSERT(msg->forum != EMLID);
          if ((cp=strchr(msg->to,' ')) != NULL) {
               cp=unpad(skpwht(cp));
               movmem(cp,msg->to,strlen(cp)+1);
          }
          else {
               strcpy(msg->to,ALLINF);
          }
     }
}

LONG
normchg(                           /* compute charges for a "normal" msg   */
const struct message *msg,         /*   message header to check            */
LONG attsiz)                       /*   size of attachment (if any)        */
{
     LONG total;

     total=emschg;
     if (msg->flags&FILATT) {
          total+=eatchg+epkchg*(attsiz/1024);
     }
     if (msg->flags&RECREQ) {
          total+=rrrchg;
     }
     if (msg->flags&PRIMSG) {
          total+=prichg;
     }
     return(total);
}

VOID
wrtaud(                            /* do new-message audit stuff           */
VOID *pWork,                       /*   GME work space (provided by caller)*/
const struct message *msg)         /*   new message structure              */
{                                  /* (relies on AUDSIZ+20 buf in shocst)  */
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(msg != NULL);
     ASSERT(msg->forum == EMLID || fidxst(msg->forum));
     if (msg->forum == EMLID) {
          if (ewraud) {
               shocst("E-MAIL MESSAGE WRITTEN","%.29s wrote #%.10s to %.29s",
                      msg->from,l2as(msg->msgid),
                      work->flags&FWDMSG ? work->auxto : msg->to);
          }
          if (msg->flags&FILATT) {
               ++sv.uplds;
               if (eupaud) {
                    shocst("E-MAIL ATTACHMENT UPLOAD",
                           "%.29s uploaded %.12s to %.29s",
                           msg->from,msg->attname,
                           work->flags&FWDMSG ? work->auxto : msg->to);
               }
          }
     }
     else {
          if (fwraud) {
               shocst("FORUM MESSAGE WRITTEN","%.29s wrote in %.15s #%.10s",
                      msg->from,l2as(msg->msgid),getfnm(msg->forum));
          }
          if (msg->flags&FILATT) {
               ++sv.uplds;
               if (fupaud) {
                    shocst("FORUM ATTACHMENT UPLOAD",
                           "%.29s uploaded %.12s to %.15s",
                           msg->from,msg->attname,getfnm(msg->forum));
               }
          }
     }
}

const CHAR *
curuid(VOID)                      /* get current User-ID if any           */
{
     struct usracc *tmpuap;

     if (usrnum >= 0 && usrnum < nterms) {
          tmpuap=uacoff(usrnum);
          if (usaptr == tmpuap) {
               return(usaptr->userid);
          }
     }
     return("(System Process)");
}

VOID
ininew(                            /* initialize fields of a new message   */
struct message *msg)               /*   new message structure              */
{
     ASSERT(msg != NULL);
     iniamsg(msg);
     *msg->history='\0';
     setmem(&msg->rplto,sizeof(struct globid),0);
}

VOID
iniamsg(                           /* init auto-filled fields of a message */
struct message *msg)               /*   new message structure              */
{
     ASSERT(msg != NULL);
     msg->msgid=newmid();
     setggid(msg);
     msg->crdate=today();
     msg->crtime=now();
     msg->nrpl=0;
}

VOID
inicfmsg(                          /* init auto-filled fields for copy/fwd */
struct message *msg)               /*   message header structure           */
{
     ASSERT(msg != NULL);
     if (msg->forum == EMLID && !isexpa(msg->to)) {
          clrfrom(msg);
     }
     msg->msgid=newmid();
     setggid(msg);
     msg->nrpl=0;
     msg->thrid=0L;
     setmem(&msg->rplto,sizeof(struct globid),0);
}

VOID
setflgs(                           /* set only appropriate message flags   */
struct message *msg,               /*   for new message                    */
CHAR *userid)                      /*   user-id sending message            */
{
     LONG tmpflg;

     ASSERT(msg != NULL);
     ASSERT(msg->forum == EMLID || fidxst(msg->forum));
     tmpflg=(msg->flags&FILATT);
     if (tmpflg&FILATT) {
          tmpflg|=(msg->flags&FILIND);
          if (msg->forum == EMLID || gforac(userid,msg->forum) >= COAXES) {
               tmpflg|=FILAPV;
          }
     }
     tmpflg|=(msg->flags&RECREQ);
     tmpflg|=(msg->flags&PRIMSG);
     msg->flags=tmpflg;
}

VOID
setggid(                           /* set global ID as local system        */
struct message *msg)               /*   message structure to set up        */
{
     msg->gmid.sysid=gmeSysID();
     msg->gmid.msgid=msg->msgid;
}

GBOOL                              /*   returns TRUE if a cc: to send      */
nextcc(                            /* set up to send next cc:              */
VOID *pWork,                       /*   GME work space in use              */
struct message *msg,               /*   message header structure           */
const char *cclist)                /*   cc: list string                    */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     while (TRUE) {
          msg->forum=EMLID;
          msg->flags=(LONG)work->orgflg;
          clrwrt(pWork);
          if (work->ccidx == NOIDX) {
               return(FALSE);
          }
          work->ccidx=nxtccidx(msg->to,cclist,work->ccidx);
          callback(pWork,EVTSTRT,GMEOK,msg->to);
          if (isforum(msg->to)) {
               msg->flags&=~(RECREQ+PRIMSG);
          }
          if (stripit(pWork,msg)) {
               return(TRUE);
          }
     }
}

GBOOL                              /*   returns FALSE if can't send at all */
stripit(                           /* val a msg & strip unavail features   */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg)               /*   message header to strip            */
{
     switch (valadr(pWork,msg->from,msg->to,msg->forum)) {
     case VALNO:
          callback(pWork,EVTDONE,GMENFND,msg->to);
          return(FALSE);
     case VALACC:
          callback(pWork,EVTDONE,GMEACC,msg->to);
          return(FALSE);
     case VALCRD:
          callback(pWork,EVTDONE,GMECRD,msg->to);
          return(FALSE);
     }
     if (msg->flags&PRIMSG) {
          switch (valpri(pWork,msg->from,msg->to,msg->forum)) {
          case VALNO:
          case VALACC:
          case VALCRD:
               msg->flags&=~PRIMSG;
               break;
          }
     }
     if (msg->flags&RECREQ) {
          switch (valrrr(pWork,msg->from,msg->to,msg->forum)) {
          case VALNO:
          case VALACC:
          case VALCRD:
               msg->flags&=~RECREQ;
               break;
          }
     }
     if (msg->flags&FILATT) {
          switch (valatt(pWork,msg->from,msg->to,msg->forum)) {
          case VALNO:
          case VALACC:
          case VALCRD:
               noatt(msg);
               break;
          }
     }
     return(TRUE);
}

LONG
gmeSysID(VOID)                     /* get current GME System-ID            */
{
     return(gmeReg2SysID(bturno));
}

const CHAR *
gmeEmlSysUID(VOID)                 /* get E-mail sysop (default) User-ID   */
{
     return(esysuid);
}

GBOOL                              /*   returns FALSE if couldn't open     */
opn4cpy(                           /* open files for copy                  */
VOID *pWork,                       /*   work area to open files for        */
const CHAR *srcfil)                /*   source file name                   */
{                                  /*   (work->cpyatt is always dest)      */
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(srcfil != NULL);
     if (*work->cpyatt == '\0') {
          return(FALSE);
     }
     work->fp=fopen(srcfil,FOPRB);
     if (work->fp == NULL) {
          cantopen(srcfil,FALSE);
          return(FALSE);
     }
     work->fpout=fopen(work->cpyatt,FOPWB);
     if (work->fpout == NULL) {
          fclose(work->fp);
          cantopen(work->cpyatt,TRUE);
          return(FALSE);
     }
     fnd1st(&gmefb,srcfil,0);
     work->cfdate=gmefb.ff_fdate;
     work->cftime=gmefb.ff_ftime;
     return(TRUE);
}

INT                                /*   returns standard GME status codes  */
copychunk(                         /* cycled file copy utility             */
VOID *pWork)                       /*   work area in use                   */
{
     INT i;
     INT siz;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     for (i=0 ; i < CNKPCYC ; ++i) {
          if ((siz=(INT)fread(tmpbuf,1,TMPBSZ,work->fp)) > 0) {
               fwrite(tmpbuf,1,siz,work->fpout);
          }
          if (ferror(work->fpout) || ferror(work->fp)) {
               fclose(work->fp);
               work->fp=NULL;
               fclose(work->fpout);
               work->fpout=NULL;
               unlink(work->cpyatt);
               return(GMENOAT);
          }
          if (feof(work->fp)) {
               fclose(work->fp);
               work->fp=NULL;
               fclose(work->fpout);
               work->fpout=NULL;
               setFileTm(work->cpyatt,work->cftime,work->cfdate);
               return(GMEOK);
          }
     }
     return(GMEAGAIN);
}

GBOOL                              /*   TRUE if access > NOAXES            */
faccok(                            /* current user has access to Forum?    */
USHORT forum)                      /*   forum ID to get topic for          */
{
     struct qscfg *tmpqsp;

     tmpqsp=uqsptr(usrnum);
     if (*tmpqsp->userid != '\0') {
          return(qforac(tmpqsp,forum) > NOAXES);
     }
     return(FALSE);
}

INT                                /*   returns standard GME result codes  */
tagatt(                            /* create tag for message attachment    */
VOID *pWork,                       /*   GME work space (provided by caller)*/
const struct message *msg,         /*   message header structure           */
CHAR *tag)                         /*   file tag buffer to use             */
{
     struct gmetag tmptag;
     struct fordef *tmpdef;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     ASSERT(msg != NULL);
     ASSERT(msg->forum == EMLID || fidxst(msg->forum));
     ASSERT(msg->flags&FILATT);
     ASSERT(tag != NULL);
     if (fnd1st(&gmefb,dlname(msg),0)) {
          tmptag.fid=msg->forum;
          tmptag.mid=msg->msgid;
          tmptag.pos=(msg->forum == work->rdctx.fid ? work->rdfpos : 0L);
          if (tmptag.fid == EMLID) {
               tmptag.chg=edachg+edkchg*(gmefb.ff_fsize/1024);
               if (!gtstcrd(work->rdctx.uid,tmptag.chg,FALSE)) {
                    return(GMECRD);
               }
          }
          else {
               if (gforac(work->rdctx.uid,tmptag.fid) < DLAXES) {
                    return(GMEACC);
               }
               if (!(msg->flags&FILAPV)
                && gforac(work->rdctx.uid,msg->forum) < OPAXES) {
                    return(GMENAPV);
               }
               tmpdef=getdef(tmptag.fid);
               tmptag.chg=tmpdef->chgadl+tmpdef->chgdpk*(gmefb.ff_fsize/1024);
               if (!gtstcrd(work->rdctx.uid,tmptag.chg,FALSE)) {
                    return(GMECRD);
               }
          }
          memmove(tag,&tmptag,sizeof(struct gmetag));
          return(GMEOK);
     }
     return(GMENFND);
}

LONG
msgintg(                           /* what's the message ID associated     */
const CHAR *tag)                   /*   with this tag?                     */
{
     struct gmetag tmptag;

     memmove(&tmptag,tag,sizeof(struct gmetag));
     return(tmptag.mid);
}

USHORT
forintg(                           /* what's the forum ID associated       */
const CHAR *tag)                   /*   with this tag?                     */
{
     struct gmetag tmptag;

     memmove(&tmptag,tag,sizeof(struct gmetag));
     return(tmptag.fid);
}

GBOOL                              /*   ok to start download?              */
dlstart(                           /* current user starts tagged att dnload*/
CHAR *tag)                         /*   file tag structure in use          */
{
     return(gdlstart(usaptr->userid,tag));
}

VOID
dlabt(                             /* current user aborted att download    */
CHAR *tag)                         /*   file tag structure in use          */
{
     gdlabt(usaptr->userid,tag);
}

VOID
dldone(                            /* current user finished att download   */
CHAR *tag)                         /*   file tag structure in use          */
{
     gdldone(usaptr->userid,tag);
}

struct usracc const *              /*   returns NULL if not found          */
gmeGetTempAcct(                    /* get temporary copy of user's account */
CHAR const * userid)               /*   User-ID to get account for         */
{
     struct usracc * pAcctRet;
     static struct usracc AcctBuf;

     pAcctRet=NULL;
     if (onsysn(userid,TRUE)) {
          pAcctRet=othuap;
     }
     else {
          dfaSetBlk(accbb);
          if (dfaAcqEQ(&AcctBuf,userid,0)) {
               pAcctRet=&AcctBuf;
          }
          dfaRstBlk();
     }
     return(pAcctRet);
}

GBOOL                              /*   ok to start download?              */
gdlstart(                          /* user starts tagged att dnload        */
CHAR const * userid,               /*   User-ID doing download             */
CHAR * tag)                        /*   file tag structure in use          */
{
     struct usracc const * pAcct;
     LONG oldcrd;
     GBOOL ok;
     struct gmetag tmptag;

     ASSERT(tag != NULL);
     memcpy(&tmptag,tag,sizeof(struct gmetag));
     ASSERT(tmptag.fid == EMLID || fidxst(tmptag.fid));

     /* get user's current credits */
     pAcct=gmeGetTempAcct(userid);
     if (pAcct == NULL) {
          return(FALSE);
     }
     oldcrd=pAcct->creds;

     ok=gdedcrd(userid,tmptag.chg,FALSE,FALSE);
     if (ok) {

          /* compute number of credits to refund if download aborted */
          pAcct=gmeGetTempAcct(userid);
          ASSERT(pAcct != NULL);
          tmptag.chg=oldcrd-pAcct->creds;

          memcpy(tag,&tmptag,sizeof(struct gmetag));
     }
     return(ok);
}

VOID
gdlabt(                            /* user aborted attachment download     */
CHAR const * userid,               /*   User-ID doing download             */
CHAR * tag)                        /*   file tag structure in use          */
{
     struct gmetag tmptag;

     ASSERT(tag != NULL);
     memcpy(&tmptag,tag,sizeof(struct gmetag));
     ASSERT(tmptag.fid == EMLID || fidxst(tmptag.fid));
     crdusr((CHAR *)userid,l2as(tmptag.chg),FALSE,FALSE);
     dlaudabt(userid,tag);
}

VOID
gdldone(                           /* user finished attachment download    */
CHAR const * userid,               /*   User-ID doing download             */
CHAR * tag)                        /*   file tag structure in use          */
{
     ASSERT(tag != NULL);
     dlaud(userid,tag);
}

GBOOL                              /*   ok to download?                    */
gdlatt(                            /* tagged attachment downloaded for user*/
const CHAR *userid,                /*   User-ID attachment downloaded for  */
CHAR *tag)                         /*   file tag structure in use          */
{
     struct gmetag tmptag;

     ASSERT(tag != NULL);
     memcpy(&tmptag,tag,sizeof(struct gmetag));
     if (gdedcrd(userid,tmptag.chg,FALSE,FALSE)) {
          dlaud(userid,tag);
          return(TRUE);
     }
     return(FALSE);
}

VOID
dlaud(                             /* audit a download                     */
const CHAR *userid,                /*   User-ID attachment downloaded for  */
CHAR *tag)                         /*   tag used for download              */
{                                  /* relies on AUDSIZ+20 buf in shocst()  */
     struct gmetag tmptag;
     const struct message *tmpmsg;

     ASSERT(userid != NULL);
     ASSERT(tag != NULL);
     memcpy(&tmptag,tag,sizeof(struct gmetag));
     ASSERT(tmptag.fid == EMLID || fidxst(tmptag.fid));
     ++sv.dwnlds;
     if (tmptag.fid == EMLID) {
          if (ednaud) {
               if ((tmpmsg=tagmsg(tag)) != NULL) {
                    shocst("E-MAIL ATTACHMENT DOWNLOAD",
                           "%.29s downld %.12s from %.29s",
                           userid,tmpmsg->attname,tmpmsg->from);
               }
          }
     }
     else {
          if (fdnaud) {
               if ((tmpmsg=tagmsg(tag)) != NULL) {
                    shocst("FORUM ATTACHMENT DOWNLOAD",
                           "%.29s downld %.12s from %.15s",
                           userid,tmpmsg->attname,getfnm(tmpmsg->forum));
               }
          }
     }
}

VOID
dlaudabt(                          /* audit an aborted download            */
const CHAR *userid,                /*   User-ID attachment downloaded for  */
CHAR *tag)                         /*   tag used for download              */
{                                  /* relies on AUDSIZ+20 buf in shocst()  */
     struct gmetag tmptag;
     const struct message *tmpmsg;

     ASSERT(userid != NULL);
     ASSERT(tag != NULL);
     memcpy(&tmptag,tag,sizeof(struct gmetag));
     ASSERT(tmptag.fid == EMLID || fidxst(tmptag.fid));
     if (tmptag.fid == EMLID) {
          if (edaaud) {
               if ((tmpmsg=tagmsg(tag)) != NULL) {
                    shocst("E-MAIL ATT DOWNLOAD ABORTED",
                           "%.29s abortd %.12s from %.29s",
                           userid,tmpmsg->attname,tmpmsg->from);
               }
          }
     }
     else {
          if (fdaaud) {
               if ((tmpmsg=tagmsg(tag)) != NULL) {
                    shocst("FORUM ATT DOWNLOAD ABORTED",
                           "%.29s abortd %.12s from %.15s",
                           userid,tmpmsg->attname,getfnm(tmpmsg->forum));
               }
          }
     }
}

const struct message *             /*   pointer to temporary msg struct    */
tagmsg(                            /* get tagged message                   */
CHAR *tag)                         /*   file tag structure in use          */
{
     struct gmetag tmptag;

     ASSERT(tag != NULL);
     memmove(&tmptag,tag,sizeof(struct gmetag));
     ASSERT(tmptag.fid == EMLID || fidxst(tmptag.fid));
     inigmerq(utlwork);
     inormrd(utlwork,usaptr->userid,tmptag.fid,tmptag.mid);
     utlwork->rdfpos=tmptag.pos;
     if (grabmsg(utlwork,utlmsg,utltxt)) {
          clsgmerq(utlwork);
          return(utlmsg);
     }
     clsgmerq(utlwork);
     return(NULL);
}

LONG
firstnew(                          /* get first new message #              */
const CHAR *userid,                /*   for this user ID                   */
USHORT forum)                      /*   in this forum                      */
{                                  /*   NOTE: modifies othusn,othusp,etc.  */
     INT i;
     LONG tmphi;
     struct qscfg *qsc;

     ASSERT(uidxst(userid));
     ASSERT(forum == EMLID || fidxst(forum));
     if (forum == EMLID) {
          if (onsysn(userid,1)) {
               return(othuap->emllim);
          }
          else {
               dfaSetBlk(accbb);
               if (dfaAcqEQ(NULL,userid,0)) {
                    dfaRstBlk();
                    return(((struct usracc *)accbb->data)->emllim);
               }
               dfaRstBlk();
          }
     }
     else {
          qsc=othqsp(userid);
          if (qsc != NULL && (i=qsidx(qsc,forum)) != NOIDX) {
               tmphi=igethi(qsc,i);
               return(tmphi < 0 ? -tmphi : tmphi);
          }
     }
     return(0L);
}

INT                                /*   returns standard GME status codes  */
inidist(                           /* initialize sending a dist list       */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg)               /*   new message structure              */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(msg != NULL);
     ASSERT(isdlst(msg->to));
     stlcpy(work->dlstnam,msg->to,DLNMSZ);
     work->flags|=dlstyp(msg->to);
     switch (work->flags&(QIKLST|MASSLST|SYSLST)) {
     case QIKLST:
          if (!iniqksnd(pWork,msg->from)) {
               return(GMEMEM);
          }
          break;
     case MASSLST:
          if (!inimssnd(pWork)) {
               return(GMEERR);
          }
          break;
     case SYSLST:
          if (!inislsnd(pWork,msg->to)) {
               return(GMEERR);
          }
          break;
     default:
          ASSERT(FALSE);
     }
     return(GMEOK);
}

GBOOL                              /*   returns TRUE there was another     */
nxtdist(                           /* get next entry in dist list          */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg)               /*   message header structure           */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(msg != NULL);
     switch (work->flags&(QIKLST|MASSLST|SYSLST)) {
     case QIKLST:
          return(nxtqik(pWork,msg->to));
     case MASSLST:
          return(nxtmass(pWork,msg->to));
     case SYSLST:
          return(nxtsys(pWork,msg->to));
     default:
          ASSERT(FALSE);
     }
     return(FALSE);
}

VOID
clsdist(                           /* finish up distribution               */
VOID *pWork)                       /*   GME work space (provided by caller)*/
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     switch (work->flags&(QIKLST|MASSLST|SYSLST)) {
     case QIKLST:
          clsqik(pWork);
          break;
     case MASSLST:
          clsmass(pWork);
          break;
     case SYSLST:
          clssys(pWork);
          break;
     default:
          ASSERT(FALSE);
     }
}

GBOOL                              /*   returns TRUE if started OK         */
inimssnd(                          /* start up !MASS dist                  */
VOID *pWork)                       /*   work area to initialize            */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     dfaSetBlk(accbb);
     work->d.m.fpos=0L;
     work->d.m.more=dfaQueryLO(0);
     if (work->d.m.more) {
          work->d.m.fpos=dfaAbs();
     }
     dfaRstBlk();
     return(work->d.m.more);
}

GBOOL                              /*   returns TRUE if started OK         */
nxtmass(                           /* get next !MASS entry                 */
VOID *pWork,                       /*   work area to use                   */
CHAR *addr)                        /*   buffer for address                 */
{
     struct usracc *tmpacc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(work->d.m.fpos != 0L);
     dfaSetBlk(accbb);
     if (!work->d.m.more || !dfaAcqAbs(NULL,work->d.m.fpos,0)) {
          dfaRstBlk();
          return(FALSE);
     }
     tmpacc=(struct usracc *)(accbb->data);
     while (tmpacc->flags&DELTAG) {
          if (!dfaQueryNX()) {
               dfaRstBlk();
               return(FALSE);
          }
     }
     stlcpy(addr,tmpacc->userid,UIDSIZ);
     work->d.m.more=dfaQueryNX();
     if (work->d.m.more) {
          work->d.m.fpos=dfaAbs();
     }
     dfaRstBlk();
     return(TRUE);
}

VOID
clsmass(                           /* shut down !MASS distribution         */
VOID *pWork)                       /*   work area used                     */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     work->d.m.fpos=0L;
     work->d.m.more=FALSE;
}

VOID
inilosl(VOID)                      /* initialize list of sysop lists       */
{
     SHORT tmpchg;
     CHAR tmpnam[DLNMSZ];
     CHAR tmpkey[KEYSIZ];
     CHAR *cp;
     FILE *fp;
     struct ffblk fb;

     ASSERT(losl == NULL);
     numsl=0;
     if (fnd1st(&fb,dlstpfn("@*"),0)) {
          do {
               tmpnam[0]='@';
               stlcpy(&tmpnam[1],fb.ff_name,DLNMSZ-1);
               cp=strchr(tmpnam,'.');
               if (cp != NULL) {
                    *cp='\0';
               }
               fp=gcfsopen(dlstpfn(tmpnam),FOPRA,GSH_DENYNO);
               if (fp != NULL) {
                    if (rdlstinf(fp,tmpkey,&tmpchg)) {
                         if (!add2losl(tmpnam,tmpkey,tmpchg)) {
                              memcata();
                         }
                    }
                    fclose(fp);
               }
          } while (fndnxt(&fb));
     }
}

GBOOL                              /*   returns TRUE if added              */
add2losl(                          /* add to list of sysop dist lists      */
const CHAR *lstnam,                /*   list name                          */
const CHAR *lstkey,                /*   list key                           */
SHORT lstchg)                      /*   list surcharge                     */
{
     INT i;

     for (i=0 ; i < numsl && stricmp(losl[i].name,lstnam) < 0 ; ++i) {
     }
     if (i < numsl && sameas(losl[i].name,lstnam)) {
          return(FALSE);
     }
     if (alcarr((VOID **)&losl,sizeof(struct slinfo),numsl,SMLBLK)) {
          if (i < numsl) {
               movmem(&losl[i],&losl[i+1],(numsl-i)*sizeof(struct slinfo));
          }
          stlcpy(losl[i].name,lstnam,DLNMSZ);
          stlcpy(losl[i].key,lstkey,KEYSIZ);
          losl[i].surchg=lstchg;
          ++numsl;
          return(TRUE);
     }
     return(FALSE);
}

INT                                /*   returns standard GME status codes  */
nxtslst(                           /* get name of next sysop dist list     */
CHAR *lstnam,                      /*   buffer for list name               */
CHAR *lstkey,                      /*   buffer for list key                */
SHORT *surchg)                     /*   buffer for list surcharge          */
{
     INT i;

     ASSERT(lstnam != NULL);
     ASSERT(lstkey != NULL);
     ASSERT(surchg != NULL);
     for (i=0 ; i < numsl ; i++) {
          if (strcmpi(losl[i].name,lstnam) > 0) {
               stlcpy(lstnam,losl[i].name,DLNMSZ);
               stlcpy(lstkey,losl[i].key,KEYSIZ);
               *surchg=losl[i].surchg;
               return(GMEOK);
          }
     }
     return(GMENFND);
}

INT                                /*   returns standard GME status codes  */
getslst(                           /* get info about sysop list            */
const CHAR *lstnam,                /*   list name                          */
CHAR *lstkey,                      /*   buffer for list key                */
SHORT *surchg)                     /*   buffer for list surcharge          */
{
     INT i;

     ASSERT(lstnam != NULL);
     ASSERT(lstkey != NULL);
     ASSERT(surchg != NULL);
     for (i=0 ; i < numsl ; i++) {
          if (sameas(losl[i].name,lstnam)) {
               stlcpy(lstkey,losl[i].key,KEYSIZ);
               *surchg=losl[i].surchg;
               return(GMEOK);
          }
     }
     return(GMENFND);
}

INT                                /*   returns standard GME status codes  */
newslst(                           /* create a new sysop distribution list */
VOID *pWork,                       /*   GME work space                     */
const CHAR *lstnam,                /*   name for new list                  */
const CHAR *lstkey,                /*   key required to use list           */
SHORT surchg)                      /*   surcharge to use list              */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     if (!haskey(edstky)) {
          clsgmerq(pWork);
          return(GMEACC);
     }
     if (lstnam[0] != '@') {
          clsgmerq(pWork);
          return(GMEERR);
     }
     if (dlstxst(lstnam)) {
          clsgmerq(pWork);
          return(GMEDUP);
     }
     work->d.s.fp=gcfsopen(dlstpfn(lstnam),FOPWRA,GSH_DENYWR);
     if (work->d.s.fp == NULL) {
          clsgmerq(pWork);
          return(GMEUSE);
     }
     if (!add2losl(lstnam,lstkey,surchg)) {
          fclose(work->d.s.fp);
          clsgmerq(pWork);
          return(GMEMEM);
     }
     work->flags|=SYSLST;
     fprintf(work->d.s.fp,"%s%s\n",DLKRQS,lstkey);
     fprintf(work->d.s.fp,DLCHGS,surchg);
     work->d.s.fpos=ftell(work->d.s.fp);
     work->fpos=work->d.s.fpos;
     return(GMEOK);
}

INT                                /*   returns standard GME status codes  */
delslst(                           /* delete a sysop dist list             */
const CHAR *lstnam)                /*   name of list                       */
{
     INT i;
     FILE *fp;

     if (!haskey(edstky)) {
          return(GMEACC);
     }
     if (lstnam[0] != '@') {
          return(GMEERR);
     }
     i=dlstidx(lstnam);
     if (i == NOIDX) {
          return(GMENFND);
     }
     if ((fp=gcfsopen(dlstpfn(lstnam),FOPRWA,GSH_DENYWR)) == NULL) {
          return(GMEUSE);
     }
     fclose(fp);
     unlink(dlstpfn(lstnam));
     --numsl;
     if (i < numsl) {
          movmem(&losl[i+1],&losl[i],sizeof(struct slinfo)*(numsl-i));
     }
     return(GMEOK);
}

INT                                /*   returns standard GME status codes  */
edtslst(                           /* open a sysop dist list for editing   */
VOID *pWork,                       /*   work area to initialize            */
const CHAR *lstnam)                /*   name of list                       */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     if (!haskey(edstky)) {
          clsgmerq(pWork);
          return(GMEACC);
     }
     if (lstnam[0] != '@') {
          clsgmerq(pWork);
          return(GMEERR);
     }
     if (!dlstxst(lstnam)) {
          clsgmerq(pWork);
          return(GMENFND);
     }
     work->d.s.fp=gcfsopen(dlstpfn(lstnam),FOPRWA,GSH_DENYWR);
     if (work->d.s.fp == NULL) {
          clsgmerq(pWork);
          return(GMEUSE);
     }
     if (!rdlstinf(work->d.s.fp,tmpbuf,tmpbuf)) {
          fclose(work->d.s.fp);
          work->d.s.fp=NULL;
          clsgmerq(pWork);
          return(GMEERR);
     }
     work->flags|=SYSLST;
     work->d.s.fpos=ftell(work->d.s.fp);
     work->fpos=work->d.s.fpos;
     return(GMEOK);
}

INT                                /*   returns standard GME status codes  */
addslst(                           /* add an address to a sysop list       */
VOID *pWork,                       /*   GME work space                     */
const CHAR *addr)                  /*   address to add to list             */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     if (!(work->flags&SYSLST) || work->d.s.fp == NULL) {
          return(GMEERR);
     }
     fseek(work->d.s.fp,0L,SEEK_END);
     fprintf(work->d.s.fp,"%s\n",addr);
     return(GMEOK);
}

INT                                /*   returns standard GME status codes  */
rstlstp(                           /* set sysop list pointer to top of list*/
VOID *pWork)                       /*   GME work space                     */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     if (!(work->flags&SYSLST) || work->d.s.fp == NULL) {
          return(GMEERR);
     }
     work->d.s.fpos=work->fpos;
     return(GMEOK);
}

const CHAR *                       /*   returns pointer to temporary buffer*/
udlnam(VOID)                       /* path & file name to upload new list  */
{
     return(spr("%s\\%s",dlstpth,tmpanam(NULL)));
}

INT                                /*   returns standard GME status codes  */
setslst(                           /* import file to replace existing list */
const CHAR *lstnam,                /*   name for new list                  */
const CHAR *lstfil)                /*   file containing new list           */
{
     INT i;
     SHORT tmpchg;
     const CHAR *cp;
     CHAR tmpkey[KEYSIZ];
     FILE *fp;

     ASSERT(lstnam != NULL);
     ASSERT(lstfil != NULL);
     ASSERT(sameto(normspec(tmpbuf,dlstpth),
                   normspec((CHAR *)tmpbuf+GCMAXPTH,lstfil)));
     if (!haskey(edstky)) {
          return(GMEACC);
     }
     if (lstnam[0] != '@') {
          return(GMEERR);
     }
     if (!dlstxst(lstnam)) {
          return(GMENFND);
     }
     fp=gcfsopen(dlstpfn(lstnam),FOPRWA,GSH_DENYWR);
     if (fp == NULL) {
          return(GMEUSE);
     }
     fclose(fp);
     fp=fopen(lstfil,FOPRA);
     if (fp == NULL) {
          return(GMEERR);
     }
     if (!rdlstinf(fp,tmpkey,&tmpchg)) {
          fclose(fp);
          return(GMEERR);
     }
     fclose(fp);
     i=dlstidx(lstnam);
     ASSERT(i != NOIDX);
     stlcpy(losl[i].key,tmpkey,KEYSIZ);
     losl[i].surchg=tmpchg;
     cp=dlstpfn(lstnam);
     if (unlink(cp) != 0 || rename(lstfil,cp) != 0) {
          return(GMEERR);
     }
     return(GMEOK);
}

GBOOL                              /*   returns TRUE if successful         */
rdlstinf(                          /* return key/charge info from list file*/
FILE *fp,                          /*   stream to read from                */
CHAR *tmpkey,                      /*   key buffer                         */
SHORT *tmpchg)                     /*   charge buffer                      */
{
     CHAR *cp;

     fgets((CHAR *)tmpbuf,TMPBSZ,fp);
     if (sameto(DLKRQS,(CHAR *)tmpbuf) && fscanf(fp,DLCHGS,tmpchg) == 1) {
          cp=unpad(skpwht(&((CHAR *)tmpbuf)[sizeof(DLKRQS)-1]));
          stlcpy(tmpkey,cp,KEYSIZ);
          return(TRUE);
     }
     return(FALSE);
}

GBOOL                              /*   returns TRUE if started OK         */
inislsnd(                          /* start up sysop list dist             */
VOID *pWork,                       /*   work area to initialize            */
const CHAR *lstnam)                /*   name of list                       */
{
     INT dummy;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     if (lstnam[0] != '@' || !dlstxst(lstnam)
      || (work->d.s.fp=gcfsopen(dlstpfn(lstnam),FOPRA,GSH_DENYNO)) == NULL) {
          return(FALSE);
     }
     fgets((CHAR *)tmpbuf,TMPBSZ,work->d.s.fp);
     if (!sameto(DLKRQS,(CHAR *)tmpbuf)) {
          fclose(work->d.s.fp);
          work->d.s.fp=NULL;
          return(FALSE);
     }
     if (fscanf(work->d.s.fp,DLCHGS,&dummy) != 1) {
          fclose(work->d.s.fp);
          work->d.s.fp=NULL;
          return(FALSE);
     }
     work->d.s.fpos=ftell(work->d.s.fp);
     return(TRUE);
}

GBOOL
nxtsys(                            /* get next entry in sysop list         */
VOID *pWork,                       /*   work area to use                   */
CHAR *addr)                        /*   buffer for address                 */
{
     INT l;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     fseek(work->d.s.fp,work->d.s.fpos,SEEK_SET);
     if (fgets((CHAR *)tmpbuf,TMPBSZ,work->d.s.fp) != NULL) {
          work->d.s.fpos=ftell(work->d.s.fp);
          l=strlen((CHAR *)tmpbuf);
          if (((CHAR *)tmpbuf)[l-1] == '\n') {
               ((CHAR *)tmpbuf)[l-1]='\0';
          }
          stlcpy(addr,(CHAR *)tmpbuf,MAXADR);
          return(TRUE);
     }
     return(FALSE);
}

VOID
clssys(                            /* close sysop distribution list        */
VOID *pWork)                       /*   work area in use                   */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     if (work->d.s.fp != NULL) {
          fclose(work->d.s.fp);
          work->d.s.fp=NULL;
     }
}

GBOOL
dlstxst(                           /* does dist list exist?                */
const CHAR *name)                  /*   complete list name (incl @ or !)   */
{
     ASSERT(name != NULL);
     if (*name == '!') {
          return(sameas(name,"!quick") || sameas(name,"!mass"));
     }
     else {
          if (valslnm(name)) {
               return(dlstidx(name) != NOIDX);
          }
     }
     return(FALSE);
}

INT
dlstidx(                           /* get index of list in list of lists   */
const CHAR *name)                  /*   sysop list name                    */
{
     INT i;

     ASSERT(name != NULL);
     ASSERT(name[0] == '@');
     for (i=0 ; i < numsl ; ++i) {
          if (sameas(losl[i].name,name)) {
               return(i);
          }
     }
     return(NOIDX);
}

INT                                /*   returns standard GME status codes  */
slstfil(                           /* get info about sysop list            */
const CHAR *lstnam,                /*   list name                          */
CHAR *lstpath)                     /*   buffer for list path & file name   */
{
     if (haskey(edstky)) {
          if (dlstxst(lstnam)) {
               stlcpy(lstpath,dlstpfn(lstnam),GCMAXPTH);
               return(GMEOK);
          }
          return(GMENFND);
     }
     return(GMEACC);
}

const CHAR *                       /*   returns pointer to path & file name*/
dlstpfn(                           /* get path & file name of dist list    */
const CHAR *name)                  /*   complete name (including @)        */
{
     ASSERT(name != NULL);
     ASSERT(*name == '@');
     return(spr("%s"SLS"%s.dis",dlstpth,name+1));
}

VOID
cantopen(                          /* audit failed file open               */
const CHAR *fname,                 /*   file that couldn't be opened       */
GBOOL waswrt)                      /*   was the open for write             */
{
     if (waswrt) {
          shocst("GME FILE OPEN ERROR",
                 "unable to open \"%s\" for write",fname);
     }
     else {
          shocst("GME FILE OPEN ERROR",
                 "unable to open \"%s\" for read",fname);
     }
}

/* simple send functions */

static VOID
inisimp(VOID)                      /* init simple send stuff               */
{
#ifdef GCV30
     if (!dfaVirgin("galssc2","galssc2")) {
          catastro("Unable to copy simple-send cache: galssc2.vir");
     }
#else
     dfaCreateSpec("galssc2.dat",TRUE,fldoff(sscache,text)+1,4096,
                   DFACF_VARIABLE,0,nelems(sscKeys),sscKeys,NULL);
#endif
     
     sscbb=dfaOpen("galssc2.dat",fldoff(sscache,text)+TXTLEN,NULL);
     ssbuf=(struct sscache *)alcmem(fldoff(sscache,text)+TXTLEN);
}

static VOID
clssimp(VOID)                      /* close simple send stuff              */
{
     if (ssiuflg) {
          ssiuflg=FALSE;
          mfytask(sstskid,NULL);
          while (gsndmsg(ssbuf->pWork,&ssbuf->msg,ssbuf->text,ssbuf->attspc)
           == GMEAGAIN) {
          }
     }
     if (sscbb != NULL) {
          dfaSetBlk(sscbb);
          while (dfaAcqLO(ssbuf,0)) {
               dfaDelete();
               while (gsndmsg(ssbuf->pWork,&ssbuf->msg
                             ,ssbuf->text,ssbuf->attspc) == GMEAGAIN) {
               }
               dfaSetBlk(sscbb);
          }
          dfaClose(sscbb);
          sscbb=NULL;
     }
     (*oldfinrou)();
}

INT                                /*   returns standard GME status codes  */
simpsnd(                           /* simple send msg (non-user specific)  */
struct message *msg,               /*   message header structure           */
const CHAR *text,                  /*   message body text                  */
const CHAR *filatt)                /*   path+file name of att (if any)     */
{
     INT rc;

     ASSERT(msg != NULL);
     ASSERT(text != NULL);
     if (!gmeinit || gmeshut) {    /* called before init or after shutdown */
          return(GMEERR);
     }
     inigmerq(utlwork);
     if (sscbb == NULL) {          /* called during shutdown               */
          while ((rc=gsndmsg(utlwork,msg,text,filatt)) == GMEAGAIN) {
          }
          return(rc);
     }
     rc=gsndmsg(utlwork,msg,text,filatt);
     if (rc == GMEAGAIN) {
          if (ssiuflg) {
               dfaSetBlk(sscbb);
               cpy2cache((struct sscache *)(sscbb->data),utlwork,msg,text,filatt);
               dfaInsertV(NULL,fldoff(sscache,text)+strlen(text)+1);
               dfaRstBlk();
          }
          else {
               cpy2cache(ssbuf,utlwork,msg,text,filatt);
               ssiuflg=TRUE;
               sstskid=initask(sndtask);
          }
          setmem(utlwork,sizeof(struct gmework),0);
     }
     return(rc);
}

static VOID
cpy2cache(                         /* copy message info to cache buffer    */
struct sscache *cache,             /*   cache buffer                       */
VOID *pWork,                       /*   work area                          */
struct message *msg,               /*   message header                     */
const CHAR *text,                  /*   text buffer                        */
const CHAR *filatt)                /*   file attachment path+file name     */
{
     ASSERT(cache != NULL);
     ASSERT(pWork != NULL);
     ASSERT(msg != NULL);
     ASSERT(text != NULL);
     ASSERT(!(msg->flags&FILATT) || filatt != NULL);
     movmem(pWork,cache->pWork,sizeof(struct gmework));
     movmem(msg,&cache->msg,sizeof(struct message));
     stlcpy(cache->text,text,TXTLEN);
     if (msg->flags&FILATT) {
          stlcpy(cache->attspc,filatt,GCMAXPTH);
     }
     else {
          *cache->attspc='\0';
     }
}

static VOID
sndtask(                           /* background send handler              */
INT taskid)                        /*   task ID (from initask)             */
{
     ASSERT(taskid == sstskid);
     ASSERT(ssbuf != NULL);
     (VOID)taskid;
     if (gsndmsg(ssbuf->pWork,&ssbuf->msg,ssbuf->text,ssbuf->attspc)
      != GMEAGAIN) {
          dfaSetBlk(sscbb);
          if (dfaAcqLO(ssbuf,0)) {
               dfaDelete();
          }
          else {
               ssiuflg=FALSE;
               mfytask(sstskid,NULL);
          }
          dfaRstBlk();
     }
}

#ifdef EXTRA_LOGGING

static VOID
vlogmsg(                           /* output to log file                   */
CHAR const * fmt,
va_list ap)
{
     FILE * fp;

     if ((fp=fopen(LOGFILE,FOPAA)) != NULL) {
          fprintf(fp,"%s %s: ",ncdate(today()),nctime(now()));
          vfprintf(fp,fmt,ap);
          fprintf(fp,"\n");
          fclose(fp);
     }
}

static VOID
logmsg(                            /* output to log file                   */
CHAR const * fmt,
...)
{
     va_list ap;

     va_start(ap,fmt);
     vlogmsg(fmt,ap);
     va_end(ap);
}

#endif /* EXTRA_LOGGING */
