/***************************************************************************
 *                                                                         *
 *   GMECORE.C                                                             *
 *                                                                         *
 *   Copyright (c) 1994-1997 Galacticomm, Inc.    All Rights Reserved.     *
 *                                                                         *
 *   This is the Galacticomm Messaging Engine core.  It contains interface *
 *   functions for other modules to access GME and other core functions.   *
 *                                                                         *
 *                                            - J. Alvrus   6/13/94        *
 *                                                                         *
 ***************************************************************************/

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

#define FILREV "$Revision: 29 $"

/* GME State Codes */
#define DISTING  SRCHING+1
#define WRTPRIM  SRCHING+2
#define NXTDIST  SRCHING+3
#define SNDEXP   SRCHING+4
#define SNDIST   SRCHING+5
#define SNDECHO  SRCHING+6
#define SNDLOC   SRCHING+7
#define CPYEML   SRCHING+8
#define WRTECPY  SRCHING+9
#define UPDORG   SRCHING+10
#define STRTCPY  SRCHING+11
#define COPYATT  SRCHING+12
#define SENDOFF  SRCHING+13
#define DELETNG  SRCHING+14
#define GETOUT   SRCHING+15
#define GENRRR   SRCHING+16
#define REGET    SRCHING+17
#define GOWRITE  SRCHING+18
#define NXTECHO  SRCHING+19
#define STARTCC  SRCHING+20
#define NEXTCC   SRCHING+21
#define CCACPY   SRCHING+22
#define WRITCC   SRCHING+23
#define DISTCC   SRCHING+24

#define anycc(s) ((s) != NULL && *(s) != '\0')

CHAR *curapi=NULL;                 /* current app-defined info buffer      */

INT nexp=0;                        /* number of registered exporters       */
struct exporter **exphlr=NULL;     /* array of registered exporters        */

/* Forum CNF options */
SHORT forccr,                      /* dft forum credit consumption rate    */
      forlif,                      /* dft lifetime of a forum msg (days)   */
      fmschg,                      /* dft credit charge to write forum msg */
      fmrchg,                      /* dft credit charge to read forum msg  */
      fulchg,                      /* dft credit charge per file upload    */
      fkuchg,                      /* dft per-kbyte charge per file upload */
      fdlchg,                      /* dft credit charge per file download  */
      fkdchg;                      /* dft per-kbyte charge per file dnload */
GBOOL fopmfd,                      /* allow forum-ops to modify fordefs?   */
      fwraud,                      /* audit forum messages written?        */
      fupaud,                      /* audit forum attachment uploads?      */
      frdaud,                      /* audit forum message read?            */
      fdnaud,                      /* audit forum attachment downloads?    */
      fdaaud,                      /* audit aborted forum att downloads?   */
      fcrtaud,                     /* audit forum creations?               */
      fmodaud,                     /* audit forum modifications?           */
      fdelaud,                     /* audit forum deletions?               */
      gcrtaud,                     /* audit group creations?               */
      gmodaud,                     /* audit group modifications?           */
      gdelaud,                     /* audit group deletions?               */
      autqsc;                      /* auto add new forums to quickscan?    */
CHAR *forsys,                      /* Sysop privileged forum usage key     */
     *forprv;                      /* privileged forum usage key           */

/* E-mail CNF options */
INT dstdly;                        /* cycle-based distribution delay       */
SHORT emschg,                      /* credit charge per E-mail msg written */
      eatchg,                      /* additional charge for att            */
      epkchg,                      /* additional per-kbyte charge for att  */
      rrrchg,                      /* additional charge for return receipt */
      prichg,                      /* additional charge for priority msg   */
      lstchg,                      /* charge for using distribution list   */
      emrchg,                      /* credit charge per E-mail msg read    */
      edachg,                      /* charge for att download              */
      edkchg;                      /* per-kbyte charge for att download    */
GBOOL supu2s,                      /* signups generate E-mail to Sysop?    */
      supe2u,                      /* signups generate E-mail to user?     */
      e2urrr,                      /* ret. rec. on new-user E-mail?        */
      alweat,                      /* allow E-mail file attachments?       */
      alwrrr,                      /* allow E-mail return receipts?        */
      alwpri,                      /* allow priority E-mail messages?      */
      chgfwd,                      /* charge recipient for auto-forward    */
      ewraud,                      /* audit E-mail messages written?       */
      eupaud,                      /* audit E-mail attachment uploads?     */
      erdaud,                      /* audit E-mail messages read?          */
      ednaud,                      /* audit E-mail attachment downloads?   */
      edaaud,                      /* audit aborted E-mail att downloads?  */
      strxck;                      /* do strict exporter prefix checking   */
CHAR *esysuid,                     /* E-mail Sysop User-ID                 */
     *nuemto,                      /* User-ID new user e-mail sent to      */
     *nuemtp,                      /* topic of new-user E-mail to sysop    */
     *e2ufrm,                      /* User-ID new user e-mail is from      */
     *e2utpc,                      /* topic of new-user E-mail to user     */
     *e2uatt,                      /* att to E-mail to user                */
     *e2uanm,                      /* name of att to E-mail to user        */
     *emlkey,                      /* key reqd to write E-mail             */
     *eatkey,                      /* key reqd to upload E-mail attachments*/
     *rrrkey,                      /* key reqd to request return receipts  */
     *prikey,                      /* key reqd to send priority messages   */
     *qikkey,                      /* key reqd to use !QUICK list          */
     *massky,                      /* key reqd to use !MASS list           */
     *edstky,                      /* key reqd to edit sysop-defined lists */
     *rrtpc;                       /* return receipt topic                 */

/* other CNF options */
CHAR dlstpth[GCMAXPTH];            /* path to sysop distribution lists     */

/* special function hooks */
GBOOL                              /*   return TRUE if handled             */
(*hdlimphook)(                     /* redirect imported message hook       */
struct message *msg,               /*   message header                     */
const CHAR *text,                  /*   message text                       */
const CHAR *filatt);               /*   attachment path+file name          */

VOID
(*newmsghook)(                     /* new message notification hook        */
const CHAR *msgtyp,                /*   type of message                    */
const struct message *msg,         /*   message header                     */
const CHAR *fwdee);                /*   forwardee (if autoforwarded)       */

VOID
inicore(VOID)                      /* initialize GME core module           */
{
     curapi=utlapi;
     normspec(dlstpth,rawmsg(DLSTPTH));
     forccr=numopt(FORCCR,-32767,32767);
     forlif=numopt(FORLIF,-1,32767);
     fmschg=numopt(FMSCHG,-32767,32767);
     fmrchg=numopt(FMRCHG,-32767,32767);
     fulchg=numopt(FULCHG,-32767,32767);
     fkuchg=numopt(FKUCHG,-32767,32767);
     fdlchg=numopt(FDLCHG,-32767,32767);
     fkdchg=numopt(FKDCHG,-32767,32767);
     fopmfd=ynopt(FOPMFD);
     autqsc=ynopt(AUTQSC);
     forsys=stgopt(FORSYS);
     forprv=stgopt(FORPRV);
     fwraud=ynopt(FWRAUD);
     fupaud=ynopt(FUPAUD);
     frdaud=ynopt(FRDAUD);
     fdnaud=ynopt(FDNAUD);
     fdaaud=ynopt(FDAAUD);
     fcrtaud=ynopt(FCRTAUD);
     fmodaud=ynopt(FMODAUD);
     fdelaud=ynopt(FDELAUD);
     gcrtaud=ynopt(GCRTAUD);
     gmodaud=ynopt(GMODAUD);
     gdelaud=ynopt(GDELAUD);
     if (!uidxst(esysuid=strdup(stpans(unpad(skpwht(rawmsg(ESYSUID))))))) {
          shocst("BAD E-MAIL SYSOP USER-ID","Nonexistent User-ID in option ESYSUID: %s",esysuid);
          free(esysuid);
          esysuid="Sysop";
     }
     supu2s=ynopt(SUPU2S);
     if (supu2s) {
          if (!uidxst(nuemto=strdup(stpans(unpad(skpwht(rawmsg(NUEMTO))))))) {
               shocst("BAD NEW USER E-MAIL RECIPIENT","Nonexistent User-ID in option NUEMTO: %s",nuemto);
               free(nuemto);
               nuemto="Sysop";
          }
          nuemtp=stgopt(NUEMTP);
     }
     supe2u=ynopt(SUPE2U);
     if (supe2u) {
          if (!uidxst(e2ufrm=strdup(stpans(unpad(skpwht(rawmsg(E2UFRM))))))) {
               shocst("BAD NEW USER E-MAIL SENDER","Nonexistent User-ID in option E2UFRM: %s",e2ufrm);
               free(e2ufrm);
               e2ufrm="Sysop";
          }
          e2utpc=stgopt(E2UTPC);
          e2uatt=stgopt(E2UATT);
          e2uanm=stgopt(E2UANM);
          e2urrr=ynopt(E2URRR);
     }
     emlkey=stgopt(EMLKEY);
     emschg=numopt(EMSCHG,-32767,32767);
     eatkey=stgopt(EATKEY);
     eatchg=numopt(EATCHG,-32767,32767);
     epkchg=numopt(EPKCHG,-32767,32767);
     rrrkey=stgopt(RRRKEY);
     rrrchg=numopt(RRRCHG,-32767,32767);
     prikey=stgopt(PRIKEY);
     prichg=numopt(PRICHG,-32767,32767);
     qikkey=stgopt(QIKKEY);
     massky=stgopt(MASSKY);
     edstky=stgopt(EDSTKY);
     lstchg=numopt(LSTCHG,0,32767);
     dstdly=(CHAR)numopt(DSTDLY,0,255);
     alweat=ynopt(ALWEAT);
     alwrrr=ynopt(ALWRRR);
     alwpri=ynopt(ALWPRI);
     chgfwd=ynopt(CHGFWD);
     ewraud=ynopt(EWRAUD);
     eupaud=ynopt(EUPAUD);
     erdaud=ynopt(ERDAUD);
     ednaud=ynopt(EDNAUD);
     edaaud=ynopt(EDAAUD);
     stpans(rrtpc=stgopt(RRTPC));
     emrchg=numopt(EMRCHG,-32767,32767);
     edachg=numopt(EDACHG,-32767,32767);
     edkchg=numopt(EDKCHG,-32767,32767);
     strxck=ynopt(STRXCK);
}

INT                                /*   returns standard GME status codes  */
inifdef(                           /* initialize forum def w/defaults      */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct fordsk *newdef)             /*   memory area to initialize          */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(newdef != NULL);

     switch (work->state3) {
     case START:
          if (!haskey(forsys)) {
               clsgmerq(pWork);
               return(GMEACC);
          }
          gmeGetForDft(newdef,usaptr->userid);
     default:
          return(gmeRecommend(pWork,newdef->datfil));
     }
}

VOID
gmeGetForDft(                      /* get default forum configuration      */
struct fordsk *newdef,             /*   new forum definition structure     */
const char *forop)                 /*   Forum-Op to initialize with        */
{
     ASSERT(newdef != NULL);
     ASSERT(forop != NULL);
     ASSERT(islocal(forop));
     setmem(newdef,sizeof(struct fordsk),0);
     stlcpy(newdef->forop,(char *)forop,UIDSIZ);
     stlcpy(newdef->attpath,DFFAPTH,GCMAXPTH);
     stlcpy(newdef->forlok,forprv,KEYSIZ);
     newdef->dfnpv=DFDFNPV;
     newdef->dfprv=DFDFPRV;
     newdef->mxnpv=DFMXNPV;
     newdef->msglif=forlif;
     newdef->chgmsg=fmschg;
     newdef->chgatt=fulchg;
     newdef->chgadl=fdlchg;
     newdef->ccr=forccr;
     newdef->pfnlvl=DFTPFN;
}

INT                                /*   returns standard GME status codes  */
gmeRecommend(                      /* recommend a forum data file          */
VOID *pWork,                       /*   GME work space (provided by caller)*/
CHAR *datfil)                      /*   data file path+file name buffer    */
{
     int rc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(datfil != NULL);
     switch (work->state3) {
     case START:
          work->state3=SRCHING;
     case SRCHING:
          rc=recfdf(pWork,datfil);
          if (rc != GMEAGAIN) {
               clsgmerq(pWork);
          }
          return(rc);
     default:
          state_error(3,"gmeRecommend()",work->state3);
     }
     return(GMEERR);
}

INT                                /*   returns standard GME status codes  */
creatfor(                          /* create a new forum                   */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct fordsk *newdef,             /*   new forum definition structure     */
const CHAR *desc,                  /*   descriptive text                   */
const CHAR *echoes)                /*   pointer to array of echo addresses */
{                                  /*   (may be NULL if no echoes)         */
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);
     #ifdef DEBUG
          INT i;
          adr_t *tmpecho;
     #endif

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(newdef != NULL);
     ASSERT(desc != NULL);
     ASSERT(strlen(newdef->name) > 0 && strlen(newdef->name) < FORNSZ);
     ASSERT(strlen(newdef->forop) > 0 && strlen(newdef->forop) < UIDSIZ);
     ASSERT(strlen(newdef->datfil) > 0 && strlen(newdef->datfil) < GCMAXPTH);
     ASSERT(strlen(newdef->attpath) < MAXDIR);
     #ifdef DEBUG
          if (newdef->necho > 0) {
               ASSERT(newdef->necho <= MAXECHO);
               ASSERT(echoes != NULL);
               tmpecho=(adr_t *)echoes;
               for (i=0 ; i < newdef->necho ; ++i) {
                    ASSERT(strlen(tmpecho[i]) < MAXADR);
               }
          }
     #endif
     ASSERT(strlen(desc) < MAXFDESC);
     ASSERT(strlen(desc)+newdef->necho*MAXADR < MAXFDV);
     ASSERT(!(newdef->dfprv&1) && newdef->dfprv >= NOAXES
         && newdef->dfprv <= (haskey(forsys) ? OPAXES : COAXES));
     ASSERT(!(newdef->mxnpv&1) && newdef->mxnpv >= NOAXES
         && newdef->mxnpv <= (haskey(forsys) ? OPAXES : COAXES));
     ASSERT(!(newdef->dfnpv&1) && newdef->dfnpv >= NOAXES
         && newdef->dfnpv <= (haskey(forsys) ? OPAXES : COAXES));
     ASSERT(newdef->pfnlvl >= 0 && newdef->pfnlvl <= DFTPFN);
     ASSERT(newdef->necho >= 0 && newdef->necho < MAXECHO);
     switch (work->state3) {
     case START:
          if (!haskey(forsys)) {
               clsgmerq(pWork);
               return(GMEERR);
          }
     default:
          return(gmeGCreateFor(pWork,newdef,desc,echoes));
     }
}

INT                                /*   returns standard GME status codes  */
gmeGCreateFor(                     /* create a new forum (no access check) */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct fordsk *newdef,             /*   new forum definition structure     */
const CHAR *desc,                  /*   descriptive text                   */
const CHAR *echoes)                /*   pointer to array of echo addresses */
{                                  /*   (may be NULL if no echoes)         */
     INT rc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);
#ifdef DEBUG
     INT i;
     adr_t *tmpecho;
#endif

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(newdef != NULL);
     ASSERT(desc != NULL);
     ASSERT(strlen(newdef->name) > 0 && strlen(newdef->name) < FORNSZ);
     ASSERT(strlen(newdef->forop) > 0 && strlen(newdef->forop) < UIDSIZ);
     ASSERT(strlen(newdef->datfil) > 0 && strlen(newdef->datfil) < GCMAXPTH);
     ASSERT(strlen(newdef->attpath) < GCMAXPTH);
#ifdef DEBUG
     if (newdef->necho > 0) {
          ASSERT(newdef->necho <= MAXECHO);
          ASSERT(echoes != NULL);
          tmpecho=(adr_t *)echoes;
          for (i=0 ; i < newdef->necho ; ++i) {
               ASSERT(strlen(tmpecho[i]) < MAXADR);
          }
     }
#endif
     ASSERT(strlen(desc) < MAXFDESC);
     ASSERT(strlen(desc)+newdef->necho*MAXADR < MAXFDV);
     ASSERT(!(newdef->dfprv&1) && newdef->dfprv >= NOAXES
         && newdef->dfprv <= (haskey(forsys) ? OPAXES : COAXES));
     ASSERT(!(newdef->mxnpv&1) && newdef->mxnpv >= NOAXES
         && newdef->mxnpv <= (haskey(forsys) ? OPAXES : COAXES));
     ASSERT(!(newdef->dfnpv&1) && newdef->dfnpv >= NOAXES
         && newdef->dfnpv <= (haskey(forsys) ? OPAXES : COAXES));
     ASSERT(newdef->pfnlvl >= 0 && newdef->pfnlvl <= DFTPFN);
     ASSERT(newdef->necho >= 0 && newdef->necho < MAXECHO);
     switch (work->state3) {
     case START:
          if (fnmxst(newdef->name)) {
               clsgmerq(pWork);
               return(GMEDUP);
          }
          if (uidxst(newdef->forop)) {
               stlcpy(newdef->forop,accbb->key,UIDSIZ);
          }
          else {
               clsgmerq(pWork);
               return(GMENFND);
          }
          fnmcse(newdef->datfil);
          fnmcse(newdef->attpath);
          strupr(newdef->forlok);
          work->state3=WRITING;
     case WRITING:
          rc=gme1crf(pWork,newdef,desc,echoes);
          if (rc != GMEAGAIN) {
               if (rc > GMEAGAIN && fcrtaud) {
                    shocst("FORUM CREATED","%.29s created %.15s",
                           curuid(),newdef->name);
               }
               clsgmerq(pWork);
          }
          return(rc);
     default:
          state_error(3,"gmeGCreateFor()",work->state3);
     }
     return(GMEERR);
}

VOID
getecho(                           /* copy forum's echoes into work buffer */
USHORT forum,                      /*   forum ID to get                    */
CHAR *echoes)                      /*   pointer to work buffer             */
{
     struct fordef *defptr;

     ASSERT(fidxst(forum));
     ASSERT(echoes != NULL);
     defptr=getdef(forum);
     if (defptr->echoes == NULL) {
          *echoes='\0';
     }
     else {
          movmem(defptr->echoes,echoes,MAXADR*defptr->necho);
     }
}

VOID
getdesc(                           /* copy forum's desc into work buffer   */
USHORT forum,                      /*   forum ID to get                    */
CHAR *desc)                        /*   pointer to work buffer             */
{
     CHAR *tmpptr;

     ASSERT(fidxst(forum));
     ASSERT(desc != NULL);
     if ((tmpptr=gme1fdsc(forum)) == NULL) {
          *desc='\0';
     }
     else {
          stlcpy(desc,tmpptr,MAXFDESC);
     }
}

VOID
getallf(                           /* get all info about a forum           */
USHORT forum,                      /*   forum ID to get                    */
struct fordsk *workdef,            /*   on-disk forum definition format    */
CHAR *desc,                        /*   buffer for description             */
CHAR *echoes)                      /*   buffer for echoes                  */
{                                  /*   (desc & echoes can be NULL)        */
     ASSERT(fidxst(forum));
     ASSERT(workdef != NULL);
     gme1afi(forum,workdef,desc,echoes);
}

INT                                /*   returns standard GME status codes  */
cfgfacc(                           /* configure default access             */
USHORT forum,                      /*   for this forum                     */
const struct foracc *acc)          /*   forum access structure             */
{
     INT curhook;
     struct fordef *tmpdef;

     ASSERT(fidxst(forum));
     ASSERT(acc != NULL);
     if (foracc(forum) < OPAXES) {
          return(GMEACC);
     }
     tmpdef=getdef(forum);
     tmpdef->dfnpv=(CHAR)acc->dfnpv;
     tmpdef->dfprv=(CHAR)acc->dfprv;
     tmpdef->mxnpv=(CHAR)acc->mxnpv;
     stlcpy(tmpdef->forlok,acc->forlok,KEYSIZ);
     curhook=NOIDX;
     while ((curhook=notmdf(curhook,tmpdef,NULL,NULL)) != NOIDX) {
     }
     return(GMEOK);
}

INT                                /*   returns standard GME status codes  */
modfor(                            /* modify a forum                       */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct fordef *newdef,             /*   modified forum description         */
const CHAR *desc,                  /*   descriptive text                   */
const CHAR *echoes)                /*   pointer to array of echo addresses */
{                                  /*(desc & echoes may be NULL if no chng)*/
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);
     #ifdef DEBUG
     INT i;
     adr_t *tmpecho;
     #endif

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(newdef != NULL);
     ASSERT(strlen(newdef->name) > 0 && strlen(newdef->name) < FORNSZ);
     ASSERT(strlen(newdef->forop) > 0 && strlen(newdef->forop) < UIDSIZ);
     #ifdef DEBUG
          if (newdef->necho > 0) {
               ASSERT(newdef->necho <= MAXECHO);
               if (echoes != NULL) {
                    tmpecho=(adr_t *)echoes;
                    for (i=0 ; i < newdef->necho ; ++i) {
                         ASSERT(strlen(tmpecho[i]) < MAXADR);
                    }
               }
          }
          if (desc != NULL) {
               ASSERT(strlen(desc) < MAXFDESC);
               ASSERT(strlen(desc)+newdef->necho*MAXADR < MAXFDV);
          }
     #endif
     ASSERT(newdef->dfprv == getdef(newdef->forum)->dfprv
         || (!(newdef->dfprv&1)
             && newdef->dfprv <= (haskey(forsys) ? OPAXES : COAXES)));
     ASSERT(newdef->mxnpv == getdef(newdef->forum)->mxnpv
         || (!(newdef->mxnpv&1)
             && newdef->mxnpv <= (haskey(forsys) ? OPAXES : COAXES)));
     ASSERT(newdef->dfnpv == getdef(newdef->forum)->dfnpv
         || (!(newdef->dfnpv&1)
             && newdef->dfnpv <= (haskey(forsys) ? OPAXES : COAXES)));
     ASSERT(newdef->pfnlvl <= DFTPFN);
     ASSERT(newdef->necho >= 0 && newdef->necho < MAXECHO);
     switch (work->state3) {
     case START:
          if (foracc(newdef->forum) < (fopmfd ? OPAXES : SYAXES)) {
               clsgmerq(pWork);
               return(GMEACC);
          }
     default:
          return(gmeGModifyFor(pWork,newdef,desc,echoes));
     }
}

INT                                /*   returns standard GME status codes  */
gmeGModifyFor(                     /* modify a forum (no access check)     */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct fordef *newdef,             /*   modified forum description         */
const CHAR *desc,                  /*   descriptive text                   */
const CHAR *echoes)                /*   pointer to array of echo addresses */
{                                  /*(desc & echoes may be NULL if no chng)*/
     INT rc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);
#ifdef DEBUG
     INT i;
     adr_t *tmpecho;
#endif

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(newdef != NULL);
     ASSERT(strlen(newdef->name) > 0 && strlen(newdef->name) < FORNSZ);
     ASSERT(strlen(newdef->forop) > 0 && strlen(newdef->forop) < UIDSIZ);
#ifdef DEBUG
     if (newdef->necho > 0) {
          ASSERT(newdef->necho <= MAXECHO);
          if (echoes != NULL) {
               tmpecho=(adr_t *)echoes;
               for (i=0 ; i < newdef->necho ; ++i) {
                    ASSERT(strlen(tmpecho[i]) < MAXADR);
               }
          }
     }
     if (desc != NULL) {
          ASSERT(strlen(desc) < MAXFDESC);
          ASSERT(strlen(desc)+newdef->necho*MAXADR < MAXFDV);
     }
#endif
     ASSERT(newdef->dfprv == getdef(newdef->forum)->dfprv
         || (!(newdef->dfprv&1)
          && newdef->dfprv <= (haskey(forsys) ? OPAXES : COAXES)));
     ASSERT(newdef->mxnpv == getdef(newdef->forum)->mxnpv
         || (!(newdef->mxnpv&1)
          && newdef->mxnpv <= (haskey(forsys) ? OPAXES : COAXES)));
     ASSERT(newdef->dfnpv == getdef(newdef->forum)->dfnpv
         || (!(newdef->dfnpv&1)
          && newdef->dfnpv <= (haskey(forsys) ? OPAXES : COAXES)));
     ASSERT(newdef->pfnlvl <= DFTPFN);
     ASSERT(newdef->necho >= 0 && newdef->necho < MAXECHO);
     switch (work->state3) {
     case START:
          if (!fidxst(newdef->forum)) {
               clsgmerq(pWork);
               return(GMENFND);
          }
          if (gmecfl(pWork,newdef->forum,0L)) {
               clsgmerq(pWork);
               return(GMEUSE);
          }
          if (uidxst(newdef->forop)) {
               stlcpy(newdef->forop,accbb->key,UIDSIZ);
          }
          else {
               clsgmerq(pWork);
               return(GMENFND);
          }
          strupr(newdef->forlok);
          work->state3=WRITING;
     case WRITING:
          if ((rc=gme1modf(pWork,newdef,desc,echoes)) != GMEAGAIN) {
               if (rc > GMEAGAIN && fmodaud) {
                    shocst("FORUM MODIFIED","%.29s modified %.15s",
                           curuid(),newdef->name);
               }
               clsgmerq(pWork);
          }
          return(rc);
     default:
          state_error(3,"gmeGModifyFor()",work->state3);
     }
     return(GMEERR);
}

INT                                /*   returns standard GME status codes  */
delfor(                            /* delete a forum                       */
VOID *pWork,                       /*   GME work space (provided by caller)*/
USHORT forum)                      /*   forum ID to delete                 */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     switch (work->state3) {
     case START:
          if (!haskey(forsys)) {
               clsgmerq(pWork);
               return(GMEACC);
          }
     default:
          return(gmeGDeleteFor(pWork,forum));
     }
}

INT                                /*   returns standard GME status codes  */
gmeGDeleteFor(                     /* delete a forum (no access check)     */
VOID *pWork,                       /*   GME work space (provided by caller)*/
USHORT forum)                      /*   forum ID to delete                 */
{
     INT rc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     switch (work->state3) {
     case START:
          if (forum == dftfor) {
               clsgmerq(pWork);
               return(GMENDEL);
          }
          if (!fidxst(forum)) {
               clsgmerq(pWork);
               return(GMENFND);
          }
          if (gmecfl(pWork,forum,0L)) {
               clsgmerq(pWork);
               return(GMEUSE);
          }
          if (fdelaud) {
               stlcpy(work->auxto,getdef(forum)->name,FORNSZ);
          }
          work->state3=WRITING;
     case WRITING:
          if ((rc=gme1dlf(pWork,forum)) != GMEAGAIN) {
               if (rc > GMEAGAIN && fdelaud) {
                    shocst("FORUM DELETED","%.29s deleted %.15s",
                           curuid(),work->auxto);
               }
               clsgmerq(pWork);
          }
          return(rc);
     default:
          state_error(3,"gmeGDeleteFor()",work->state3);
     }
     return(GMEERR);
}

INT                                /*   returns standard GME status codes  */
gmeCreateGrp(                      /* create a new forum group             */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct forgrp *crtbuf)             /*   group information structure        */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     switch (work->state3) {
     case START:
          if (!haskey(forsys)) {
               clsgmerq(pWork);
               return(GMEACC);
          }
     default:
          return(gmeGCreateGrp(pWork,crtbuf));
     }
}

INT                                /*   returns standard GME status codes  */
gmeGCreateGrp(                     /* create a new forum group             */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct forgrp *crtbuf)             /*   group information structure        */
{
     INT rc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(crtbuf != NULL);
     ASSERT(crtbuf->nforums >= 0 && crtbuf->nforums <= gmeMaxGrpFor());
     ASSERT(valfornm(crtbuf->name));
     ASSERT(*crtbuf->key == '\0' || keynam(crtbuf->key));
     switch (work->state3) {
     case START:
          if (!valfornm(crtbuf->name)) {
               clsgmerq(pWork);
               return(GMEERR);
          }
          work->state3=WRITING;
     case WRITING:
          if ((rc=gme1crg(pWork,crtbuf)) != GMEAGAIN) {
               if (rc > GMEAGAIN && gcrtaud) {
                    shocst("FORUM GROUP CREATED",
                           "%.29s created %.15s (ID #%hu)",
                           curuid(),crtbuf->name,crtbuf->grpid);
               }
               clsgmerq(pWork);
          }
          return(rc);
     default:
          state_error(3,"gmeGCreateGrp()",work->state3);
     }
     clsgmerq(pWork);
     ASSERT(FALSE);
     return(GMEERR);
}

INT                                /*   returns standard GME status codes  */
gmeModifyGrp(                      /* modify a forum group                 */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct forgrp *grpbuf)             /*   group information structure        */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     switch (work->state3) {
     case START:
          if (!haskey(forsys)) {
               clsgmerq(pWork);
               return(GMEACC);
          }
     default:
          return(gmeGModifyGrp(pWork,grpbuf));
     }
}

INT                                /*   returns standard GME status codes  */
gmeGModifyGrp(                     /* modify a forum group (no access chk) */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct forgrp *grpbuf)             /*   group information structure        */
{
     INT rc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(grpbuf != NULL);
     ASSERT(grpbuf->nforums >= 0 && grpbuf->nforums <= gmeMaxGrpFor());
     ASSERT(valfornm(grpbuf->name));
     ASSERT(*grpbuf->key == '\0' || keynam(grpbuf->key));
     switch (work->state3) {
     case START:
          if (!valfornm(grpbuf->name)) {
               clsgmerq(pWork);
               return(GMEERR);
          }
          work->state3=WRITING;
     case WRITING:
          if (grpbuf->grpid == ROOTGRP) {
               rc=gme1mgf(pWork,ROOTGRP,grpbuf->nforums,grpbuf->forarr);
          }
          else {
               rc=gme1modg(pWork,TRUE,grpbuf);
          }
          if (rc != GMEAGAIN) {
               if (rc > GMEAGAIN && gmodaud) {
                    shocst("FORUM GROUP MODIFIED",
                           "%.29s modified %.15s (ID #%hu)",curuid(),
                           grpbuf->grpid == ROOTGRP ? "(root group)"
                                                    : grpbuf->name,
                           grpbuf->grpid);
               }
               clsgmerq(pWork);
          }
          return(rc);
     default:
          state_error(3,"gmeGModifyGrp()",work->state3);
     }
     clsgmerq(pWork);
     ASSERT(FALSE);
     return(GMEERR);
}

INT                                /*   returns standard GME status codes  */
gmeModGrpHdr(                      /* modify group header (not forums)     */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct forgrp *grpbuf)             /*   group information structure        */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     switch (work->state3) {
     case START:
          if (!haskey(forsys)) {
               clsgmerq(pWork);
               return(GMEACC);
          }
     default:
          return(gmeGModGrpHdr(pWork,grpbuf));
     }
}

INT                                /*   returns standard GME status codes  */
gmeGModGrpHdr(                     /* modify group header (no access chk)  */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct forgrp *grpbuf)             /*   group information structure        */
{
     INT rc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(grpbuf != NULL);
     ASSERT(valfornm(grpbuf->name));
     ASSERT(*grpbuf->key == '\0' || keynam(grpbuf->key));
     switch (work->state3) {
     case START:
          if (grpbuf->grpid == ROOTGRP || !valfornm(grpbuf->name)) {
               clsgmerq(pWork);
               return(GMEERR);
          }
          work->state3=WRITING;
     case WRITING:
          if ((rc=gme1modg(pWork,FALSE,grpbuf)) != GMEAGAIN) {
               if (rc > GMEAGAIN && gmodaud) {
                    shocst("FORUM GROUP MODIFIED",
                           "%.29s modified %.15s (ID #%hu)",
                           curuid(),grpbuf->name,grpbuf->grpid);
               }
               clsgmerq(pWork);
          }
          return(rc);
     default:
          state_error(3,"gmeGModGrpHdr()",work->state3);
     }
     clsgmerq(pWork);
     ASSERT(FALSE);
     return(GMEERR);
}

INT                                /*   returns standard GME status codes  */
gmeModGrpFor(                      /* modify forums in group               */
VOID *pWork,                       /*   GME work space (provided by caller)*/
USHORT grpid,                      /*   group ID to modify                 */
INT nforums,                       /*   number of forums in list           */
const USHORT *forlst)              /*   new list of forums                 */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     switch (work->state3) {
     case START:
          if (!haskey(forsys)) {
               clsgmerq(pWork);
               return(GMEACC);
          }
     default:
          return(gmeGModGrpFor(pWork,grpid,nforums,forlst));
     }
}

INT                                /*   returns standard GME status codes  */
gmeGModGrpFor(                     /* modify forums in group (no acc chk)  */
VOID *pWork,                       /*   GME work space (provided by caller)*/
USHORT grpid,                      /*   group ID to modify                 */
INT nforums,                       /*   number of forums in list           */
const USHORT *forlst)              /*   new list of forums                 */
{
     INT rc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(nforums >= 0 && nforums <= gmeMaxGrpFor());
     ASSERT(nforums == 0 || forlst != NULL);
     switch (work->state3) {
     case START:
          work->state3=WRITING;
     case WRITING:
          rc=gme1mgf(pWork,grpid,nforums,forlst);
          if (rc != GMEAGAIN) {
               if (rc > GMEAGAIN && gmodaud) {
                    shocst("FORUM GROUP MODIFIED",
                           "%.29s modified %.15s (ID #%hu)",curuid(),
                           grpid == ROOTGRP ? "(root group)"
                                            : getgrp(grpid)->name,
                           grpid);
               }
               clsgmerq(pWork);
          }
          return(rc);
     default:
          state_error(3,"gmeGModGrpFor()",work->state3);
     }
     clsgmerq(pWork);
     ASSERT(FALSE);
     return(GMEERR);
}

INT                                /*   returns standard GME status codes  */
gmeAddGrpFor(                      /* add a forum to a group               */
VOID *pWork,                       /*   GME work space (provided by caller)*/
USHORT grpid,                      /*   group ID to modify                 */
USHORT forum)                      /*   forum ID to add                    */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     switch (work->state3) {
     case START:
          if (!haskey(forsys)) {
               clsgmerq(pWork);
               return(GMEACC);
          }
     default:
          return(gmeGAddGrpFor(pWork,grpid,forum));
     }
}

INT                                /*   returns standard GME status codes  */
gmeGAddGrpFor(                     /* add a forum to a group (no acc chk)  */
VOID *pWork,                       /*   GME work space (provided by caller)*/
USHORT grpid,                      /*   group ID to modify                 */
USHORT forum)                      /*   forum ID to add                    */
{
     INT rc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     switch (work->state3) {
     case START:
          work->state3=WRITING;
     case WRITING:
          if ((rc=gme1adgf(pWork,grpid,forum)) != GMEAGAIN) {
               if (rc > GMEAGAIN && gmodaud) {
                    shocst("FORUM GROUP MODIFIED",
                           "%.29s modified %.15s (ID #%hu)",curuid(),
                           grpid == ROOTGRP ? "(root group)"
                                            : getgrp(grpid)->name,
                           grpid);
               }
               clsgmerq(pWork);
          }
          return(rc);
     default:
          state_error(3,"gmeGAddGrpFor()",work->state3);
     }
     clsgmerq(pWork);
     ASSERT(FALSE);
     return(GMEERR);
}

INT                                /*   returns standard GME status codes  */
gmeDelGrpFor(                      /* delete a forum from a group          */
VOID *pWork,                       /*   GME work space (provided by caller)*/
USHORT grpid,                      /*   group ID to modify                 */
USHORT forum)                      /*   forum ID to delete                 */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     switch (work->state3) {
     case START:
          if (!haskey(forsys)) {
               clsgmerq(pWork);
               return(GMEACC);
          }
     default:
          return(gmeGDelGrpFor(pWork,grpid,forum));
     }
}

INT                                /*   returns standard GME status codes  */
gmeGDelGrpFor(                     /* del a forum from a group (no acc chk)*/
VOID *pWork,                       /*   GME work space (provided by caller)*/
USHORT grpid,                      /*   group ID to modify                 */
USHORT forum)                      /*   forum ID to delete                 */
{
     INT rc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     switch (work->state3) {
     case START:
          work->state3=WRITING;
     case WRITING:
          if ((rc=gme1dlgf(pWork,grpid,forum)) != GMEAGAIN) {
               if (rc > GMEAGAIN && gmodaud) {
                    shocst("FORUM GROUP MODIFIED",
                           "%.29s modified %.15s (ID #%hu)",curuid(),
                           grpid == ROOTGRP ? "(root group)"
                                            : getgrp(grpid)->name,
                           grpid);
               }
               clsgmerq(pWork);
          }
          return(rc);
     default:
          state_error(3,"gmeGDelGrpFor()",work->state3);
     }
     clsgmerq(pWork);
     ASSERT(FALSE);
     return(GMEERR);
}

INT                                /*   returns standard GME status codes  */
gmeDeleteGrp(                      /* delete a group                       */
VOID *pWork,                       /*   GME work space (provided by caller)*/
USHORT grpid)                      /*   group ID to delete                 */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     switch (work->state3) {
     case START:
          if (!haskey(forsys)) {
               clsgmerq(pWork);
               return(GMEACC);
          }
     default:
          return(gmeGDeleteGrp(pWork,grpid));
     }
}

INT                                /*   returns standard GME status codes  */
gmeGDeleteGrp(                     /* delete a group (no access check)     */
VOID *pWork,                       /*   GME work space (provided by caller)*/
USHORT grpid)                      /*   group ID to delete                 */
{
     INT rc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     switch (work->state3) {
     case START:
          if (gdelaud) {
               stlcpy(work->auxto,getgrp(grpid)->name,FORNSZ);
          }
          work->state3=WRITING;
     case WRITING:
          if ((rc=gme1delg(pWork,grpid)) != GMEAGAIN) {
               if (rc > GMEAGAIN && gdelaud) {
                    shocst("FORUM GROUP DELETED",
                           "%.29s deleted %.15s (ID #%hu)",
                           curuid(),work->auxto,grpid);
               }
               clsgmerq(pWork);
          }
          return(rc);
     default:
          state_error(3,"gmeGDeleteGrp()",work->state3);
     }
     clsgmerq(pWork);
     ASSERT(FALSE);
     return(GMEERR);
}

INT                                /*   returns standard GME status codes  */
gsndmsg(                           /* send message (non-user specific)     */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg,               /*   new message structure              */
const CHAR *text,                  /*   message body text                  */
const CHAR *filatt)                /*   path+file name of att (if any)     */
{
     INT rc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(msg != NULL);
     ASSERT(text != NULL);
     ASSERT(strlen(text) < TXTLEN);
     ASSERT(msg->forum == EMLID || fidxst(msg->forum));
     switch (work->state3) {
     case START:
          chkfor(msg);
          chkrqs(pWork,msg);
          iniamsg(msg);
          work->state3=WRITING;
          if (msg->flags&FILATT) {
               if (msg->forum == EMLID && isexpa(msg->to)) {
                    stlcpy(work->cpyatt,expasp(msg->to,msg),GCMAXPTH);
                    if (msg->flags&FILIND) {
                         work->state3=STRTCPY;
                         return(GMEAGAIN);
                    }
                    if (sameas(normspec((CHAR *)tmpbuf,filatt),
                               normspec((CHAR *)tmpbuf+GCMAXPTH,work->cpyatt))) {
                         stlcpy(work->auxatt,work->cpyatt,GCMAXPTH);
                    }
                    else {
                         unlink(work->cpyatt);
                         if (rename(filatt,work->cpyatt) == 0) {
                              stlcpy(work->auxatt,work->cpyatt,GCMAXPTH);
                         }
                         else {
                              work->state3=STRTCPY;
                              return(GMEAGAIN);
                         }
                    }
               }
               else {
                    stlcpy(work->auxatt,filatt,GCMAXPTH);
                    *work->cpyatt='\0';
               }
          }
     case WRITING:
          rc=gme2wnm(pWork,msg,text,work->auxatt);
          if (rc != GMEAGAIN) {
               if (rc > GMEAGAIN) {
                    notnwm(NEWMSG_WRITE,pWork,msg,text);
               }
               clsgmerq(pWork);
          }
          return(rc);
     case STRTCPY:
          work->state3=COPYATT;
          if (!opn4cpy(pWork,filatt)) {
               clsgmerq(pWork);
               return(GMENOAT);
          }
     case COPYATT:
          rc=copychunk(pWork);
          if (rc > GMEAGAIN) {
               work->state3=WRITING;
               if (!(msg->flags&FILIND)) {
                    unlink(filatt);
               }
               stlcpy(work->auxatt,work->cpyatt,GCMAXPTH);
               rc=GMEAGAIN;
          }
          else if (rc < GMEAGAIN) {
               clsgmerq(pWork);
          }
          return(rc);
     default:
          state_error(3,"gsndmsg()",work->state3);
     }
     ASSERT(FALSE);
     clsgmerq(pWork);
     return(GMEERR);               /* this should never happen             */
}

INT                                /*   returns standard GME status codes  */
gmeSendMsg(                        /* send a new message from current user */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg,               /*   new message structure              */
const CHAR *text,                  /*   message body text                  */
const CHAR *filatt,                /*   path+file name of att (if any)     */
const CHAR *cclist)                /*   list of cc: addresses              */
{
     INT rc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(msg != NULL);
     ASSERT(text != NULL);
     ASSERT(strlen(text) < TXTLEN);
     ASSERT(msg->forum == EMLID || fidxst(msg->forum));
     while (TRUE) {
          switch (work->state3) {
          case START:
               if ((rc=chksnd(pWork,msg,filatt)) != GMEOK) {
                    clsgmerq(pWork);
                    return(rc);
               }
               work->orgflg=(INT)msg->flags;
               if (msg->flags&FILATT) {
                    stlcpy(work->orgatt,filatt,GCMAXPTH);
               }
               if (!(msg->forum == EMLID && isdlst(msg->to)) && anycc(cclist)) {
                    stlcpy(work->auxhist,msg->history,HSTSIZ);
               }
               ininew(msg);
               chkfor(msg);
               chkrqs(pWork,msg);
               setflgs(msg,msg->from);
               msg->thrid=0;
               work->orgmid=msg->msgid;
               work->state3=GOWRITE;
               if (msg->flags&FILATT) {
                    if (anycc(cclist)) {
                         gmelok(pWork,msg->forum,msg->msgid);
                    }
                    if (msg->forum == EMLID && isexpa(msg->to)) {
                         stlcpy(work->cpyatt,expasp(msg->to,msg),GCMAXPTH);
                         if ((msg->flags&FILIND) || anycc(cclist)) {
                              work->state3=STRTCPY;
                              return(GMEAGAIN);
                         }
                         if (sameas(normspec((CHAR *)tmpbuf,filatt),
                                    normspec((CHAR *)tmpbuf+GCMAXPTH,
                                             work->cpyatt))) {
                              stlcpy(work->auxatt,work->cpyatt,GCMAXPTH);
                         }
                         else {
                              unlink(work->cpyatt);
                              if (rename(filatt,work->cpyatt) == 0) {
                                   stlcpy(work->auxatt,work->cpyatt,GCMAXPTH);
                              }
                              else {
                                   work->state3=STRTCPY;
                                   return(GMEAGAIN);
                              }
                         }
                    }
                    else {
                         stlcpy(work->auxatt,filatt,GCMAXPTH);
                         *work->cpyatt='\0';
                    }
               }
               break;
          case STRTCPY:
               callback(pWork,EVTCPYS,GMEOK,work->cpyatt);
               work->state3=COPYATT;
               if (!opn4cpy(pWork,filatt)) {
                    clsgmerq(pWork);
                    return(GMENOAT);
               }
          case COPYATT:
               rc=copychunk(pWork);
               if (rc > GMEAGAIN) {
                    callback(pWork,EVTCPYD,GMEOK,work->cpyatt);
                    work->state3=GOWRITE;
                    stlcpy(work->auxatt,work->cpyatt,GCMAXPTH);
                    rc=GMEAGAIN;
               }
               else if (rc < GMEAGAIN) {
                    clsgmerq(pWork);
               }
               return(rc);
          case GOWRITE:
               if (msg->forum == EMLID && isdlst(msg->to)) {
                    work->state3=DISTING;
               }
               else {
                    callback(pWork,EVTSTRT,GMEOK,msg->to);
                    work->state3=WRITING;
                    if (!gdedcrd(msg->from,work->basechg,FALSE,FALSE)) {
                         clsgmerq(pWork);
                         return(GMECRD);
                    }
               }
               break;
          case DISTING:
               rc=gme2dst(pWork,msg,text,filatt);
               if (rc > GMEAGAIN) {
                    wrtaud(pWork,msg);
                    if (anycc(cclist)) {
                         callback(pWork,EVTDSTD,rc,NULL);
                         rc=GMEAGAIN;
                         work->state3=STARTCC;
                         msg->flags=(LONG)work->orgflg;
                         stlcpy(work->orgatt,dlname(msg),MAXADR);
                         clrwrt(pWork);
                    }
                    else {
                         clsgmerq(pWork);
                    }
               }
               else if (rc < GMEAGAIN) {
                    clsgmerq(pWork);
               }
               return(rc);
          case WRITING:
               rc=gme2wnm(pWork,msg,text,work->auxatt);
               if (rc > GMEAGAIN) {
                    wrtaud(pWork,msg);
                    notnwm(NEWMSG_WRITE,pWork,msg,text);
                    if (anycc(cclist)) {
                         clrwrt(pWork);
                         msg->flags=(LONG)work->orgflg;
                         if ((msg->flags&FILATT) && !isexpa(msg->to)) {
                              stlcpy(work->orgatt,dlname(msg),GCMAXPTH);
                         }
                         stlcpy(msg->history,work->auxhist,HSTSIZ);
                         if (work->flags&FWDMSG) {
                              rstafwd(pWork,msg);
                              callback(pWork,EVTDONE,GMEAFWD,NULL);
                         }
                         else {
                              callback(pWork,EVTDONE,rc,msg->to);
                         }
                         rc=GMEAGAIN;
                         work->state3=STARTCC;
                    }
                    else {
                         if (work->flags&FWDMSG) {
                              rstafwd(pWork,msg);
                              rc=GMEAFWD;
                         }
                         clsgmerq(pWork);
                    }
               }
               else if (rc < GMEAGAIN) {
                    if (!(work->flags&PRIMDONE)) {
                         gdedcrd(msg->from,-work->basechg,FALSE,TRUE);
                    }
                    clsgmerq(pWork);
               }
               return(rc);
          case STARTCC:            /* just so you know where it goes       */
          default:
               return(gme3cc(pWork,msg,text,cclist));
          }
     }
}

INT                                /*   returns standard GME status codes  */
reply(                             /* reply to current message in context  */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg,               /*   message header structure           */
const CHAR *text,                  /*   new message text                   */
const CHAR *filatt,                /*   path+file name of att (if any)     */
const CHAR *cclist)                /*   list of cc: addresses              */
{
     INT rc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     ASSERT(msg != NULL);
     ASSERT(text != NULL);
     ASSERT(strlen(text) < TXTLEN);
     ASSERT(msg->forum == EMLID || fidxst(msg->forum));
     while (TRUE) {
          switch (work->state3) {
          case START:
               ASSERT(msg->forum != EMLID
                || (!isforum(msg->to) && !isdlst(msg->to)));
               if ((rc=chksnd(pWork,msg,filatt)) != GMEOK) {
                    clsgmerq(pWork);
                    return(rc);
               }
               work->orgflg=(INT)msg->flags;
               if (msg->flags&FILATT) {
                    stlcpy(work->orgatt,filatt,GCMAXPTH);
               }
               addhist(msg->history,spr(RPLTO,l2as(work->rdctx.mid)));
               if (anycc(cclist)) {
                    stlcpy(work->auxhist,msg->history,HSTSIZ);
               }
               chkrqs(pWork,msg);
               if (msg->forum == EMLID && !isexpa(msg->to)) {
                    if (grabmsg(pWork,utlmsg,utltxt)) {
                         msg->rplto.sysid=(LONG)utlmsg->forum;
                         msg->rplto.msgid=utlmsg->msgid;
                    }
                    else {
                         msg->rplto.sysid=(LONG)work->rdctx.fid;
                         msg->rplto.msgid=work->rdctx.mid;
                    }
               }
               else {
                    msg->rplto=msg->gmid;
               }
               iniamsg(msg);
               setflgs(msg,msg->from);
               work->orgmid=msg->msgid;
               work->state3=GOWRITE;
               if (msg->flags&FILATT) {
                    if (anycc(cclist)) {
                         gmelok(pWork,msg->forum,msg->msgid);
                    }
                    if (msg->forum == EMLID && isexpa(msg->to)) {
                         stlcpy(work->cpyatt,expasp(msg->to,msg),GCMAXPTH);
                         if ((msg->flags&FILIND) || anycc(cclist)) {
                              work->state3=STRTCPY;
                              return(GMEAGAIN);
                         }
                         if (sameas(normspec((CHAR *)tmpbuf,filatt),
                                    normspec((CHAR *)tmpbuf+GCMAXPTH,
                                             work->cpyatt))) {
                              stlcpy(work->auxatt,work->cpyatt,GCMAXPTH);
                         }
                         else {
                              unlink(work->cpyatt);
                              if (rename(filatt,work->cpyatt) == 0) {
                                   stlcpy(work->auxatt,work->cpyatt,GCMAXPTH);
                              }
                              else {
                                   work->state3=STRTCPY;
                                   return(GMEAGAIN);
                              }
                         }
                    }
                    else {
                         stlcpy(work->auxatt,filatt,GCMAXPTH);
                         *work->cpyatt='\0';
                    }
               }
               break;
          case STRTCPY:
               callback(pWork,EVTCPYS,GMEOK,work->cpyatt);
               work->state3=COPYATT;
               if (!opn4cpy(pWork,filatt)) {
                    clsgmerq(pWork);
                    return(GMENOAT);
               }
          case COPYATT:
               rc=copychunk(pWork);
               if (rc > GMEAGAIN) {
                    callback(pWork,EVTCPYD,GMEOK,work->cpyatt);
                    work->state3=GOWRITE;
                    stlcpy(work->auxatt,work->cpyatt,GCMAXPTH);
                    rc=GMEAGAIN;
               }
               else if (rc < GMEAGAIN) {
                    clsgmerq(pWork);
               }
               return(rc);
          case GOWRITE:
               callback(pWork,EVTSTRT,GMEOK,msg->to);
               work->state3=WRITING;
               if (!gdedcrd(msg->from,work->basechg,FALSE,FALSE)) {
                    clsgmerq(work);
                    return(GMECRD);
               }
          case WRITING:
               rc=gme2wnm(pWork,msg,text,work->auxatt);
               if (rc > GMEAGAIN) {
                    wrtaud(pWork,msg);
                    notnwm(NEWMSG_REPLY,pWork,msg,text);
                    work->state3=UPDORG;
                    rc=GMEAGAIN;
               }
               else if (rc < GMEAGAIN) {
                    if (!(work->flags&PRIMDONE)) {
                         gdedcrd(msg->from,-work->basechg,FALSE,TRUE);
                    }
                    clsgmerq(pWork);
               }
               return(rc);
          case UPDORG:
               rc=GMEOK;
               if (grabmsg(pWork,utlmsg,utltxt)) {
                    if (utlmsg->forum != EMLID
                     && utlmsg->forum == msg->forum) {
                         ++utlmsg->nrpl;
                         if (updmsg(pWork,utlmsg,utltxt,utlapi)) {
                              notudm(UPDMSG_REPLY,pWork,utlmsg,utltxt);
                         }
                    }
                    if (work->rdctx.fid == EMLID
                     && (othqsp(msg->from)->flags&CLARPL)) {
                         if (utlmsg->forum == EMLID) {
                              clrto(utlmsg);
                              if (utlmsg->flags&FRCLR) {
                                   clrfrom(utlmsg);
                              }
                              updmsg(pWork,utlmsg,utltxt,utlapi);
                         }
                         else {
                              gme1dlm(pWork);
                         }
                    }
               }
               if (anycc(cclist)) {
                    if ((msg->flags&FILATT) && !isexpa(msg->to)) {
                         stlcpy(work->orgatt,dlname(msg),GCMAXPTH);
                    }
                    if (work->flags&FWDMSG) {
                         rstafwd(pWork,msg);
                         callback(pWork,EVTDONE,GMEAFWD,NULL);
                    }
                    else {
                         callback(pWork,EVTDONE,rc,msg->to);
                    }
                    work->state3=STARTCC;
                    return(GMEAGAIN);
               }
               else {
                    rc=GMEOK;
                    if (work->flags&FWDMSG) {
                         rstafwd(pWork,msg);
                         rc=GMEAFWD;
                    }
                    clsgmerq(pWork);
                    return(rc);
               }
          case STARTCC:            /* just so you know where it goes       */
          default:
               return(gme3cc(pWork,msg,text,cclist));
          }
     }
}

INT                                /*   returns standard GME status codes  */
gme3cc(                            /* send cc:s when writing or replying   */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg,               /*   message header structure           */
const CHAR *text,                  /*   message text                       */
const CHAR *cclist)                /*   list of cc: addresses              */
{
     INT rc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     while (TRUE) {
          switch (work->state3) {
          case STARTCC:
               callback(pWork,EVTCCST,GMEOK,NULL);
               addhist(msg->history,spr(CCOF,l2as(work->orgmid)));
               stlcpy(work->auxhist,msg->history,HSTSIZ);
               setmem(&msg->rplto,sizeof(struct globid),0);
               work->ccidx=0;
               work->state3=NEXTCC;
          case NEXTCC:
               if (!nextcc(pWork,msg,cclist)) {
                    clsgmerq(pWork);
                    return(GMEOK);
               }
               addchg(pWork,msg);
               if (!gtstcrd(msg->from,work->basechg,FALSE)) {
                    if (msg->forum == EMLID) {
                         callback(pWork,EVTDONE,GMECRD,msg->to);
                    }
                    else {
                         callback(pWork,EVTDONE,GMECRD,
                                  spr("/%s",getfnm(msg->forum)));
                    }
                    break;
               }
               chkfor(msg);
               chkrqs(pWork,msg);
               iniamsg(msg);
               setflgs(msg,msg->from);
               msg->thrid=0L;
               if (msg->flags&FILATT) {
                    if (!(msg->flags&FILIND) || isexpa(msg->to)) {
                         if (isexpa(msg->to)) {
                              stlcpy(work->cpyatt,expasp(msg->to,msg),GCMAXPTH);
                         }
                         else {
                              stlcpy(work->cpyatt,ulname(msg),GCMAXPTH);
                         }
                         work->state3=CCACPY;
                         callback(pWork,EVTCPYS,GMEOK,work->cpyatt);
                         if (!opn4cpy(pWork,work->orgatt)) {
                              callback(pWork,EVTCPYD,GMENOAT,work->cpyatt);
                              *work->cpyatt='\0';
                              noatt(msg);
                              work->state3=SENDOFF;
                         }
                    }
                    else {
                         work->state3=SENDOFF;
                         stlcpy(work->auxatt,work->orgatt,GCMAXPTH);
                         *work->cpyatt='\0';
                    }
               }
               else {
                    work->state3=SENDOFF;
                    *work->cpyatt='\0';
               }
               break;
          case CCACPY:
               rc=copychunk(pWork);
               if (rc != GMEAGAIN) {
                    callback(pWork,EVTCPYD,rc,work->cpyatt);
                    if (rc < GMEAGAIN) {
                         noatt(msg);
                    }
                    work->state3=SENDOFF;
                    rc=GMEAGAIN;
               }
               return(rc);
          case SENDOFF:
               if (isdlst(msg->to)) {
                    work->state3=DISTCC;
                    if (*work->cpyatt != '\0') {
                         stlcpy(work->auxatt,work->cpyatt,GCMAXPTH);
                         *work->cpyatt='\0';
                    }
               }
               else {
                    if (gdedcrd(msg->from,work->basechg,FALSE,FALSE)) {
                         if (*work->cpyatt != '\0') {
                              stlcpy(work->auxatt,work->cpyatt,GCMAXPTH);
                              *work->cpyatt='\0';
                         }
                         work->flags&=~PRIMDONE;
                         work->state3=WRITCC;
                    }
                    else {
                         if (msg->forum == EMLID) {
                              callback(pWork,EVTDONE,GMECRD,msg->to);
                         }
                         else {
                              callback(pWork,EVTDONE,GMECRD,
                                       spr("/%s",getfnm(msg->forum)));
                         }
                         unlink(work->cpyatt);
                         work->state3=NEXTCC;
                         return(GMEAGAIN);
                    }
               }
               break;
          case WRITCC:
               rc=gme2wnm(pWork,msg,text,work->auxatt);
               if (rc != GMEAGAIN) {
                    if (rc > GMEAGAIN) {
                         notnwm(NEWMSG_CCOPY,pWork,msg,text);
                         if (work->flags&FWDMSG) {
                              rstafwd(pWork,msg);
                              callback(pWork,EVTDONE,GMEAFWD,NULL);
                         }
                         else if (msg->forum == EMLID) {
                              callback(pWork,EVTDONE,rc,msg->to);
                         }
                         else {
                              callback(pWork,EVTDONE,rc,
                                       spr("/%s",getfnm(msg->forum)));
                         }
                    }
                    else if (rc < GMEAGAIN) {
                         if (msg->forum == EMLID) {
                              callback(pWork,EVTDONE,rc,msg->to);
                         }
                         else {
                              callback(pWork,EVTDONE,rc,
                                       spr("/%s",getfnm(msg->forum)));
                         }
                         if (!(work->flags&PRIMDONE)) {
                              gdedcrd(msg->from,-work->basechg,FALSE,TRUE);
                              if ((msg->flags&FILATT) && !(msg->flags&FILIND)) {
                                   unlink(work->auxatt);
                              }
                         }
                    }
                    work->state3=NEXTCC;
                    rc=GMEAGAIN;
                    stlcpy(msg->history,work->auxhist,HSTSIZ);
               }
               return(rc);
          case DISTCC:
               rc=gme2dst(pWork,msg,text,work->auxatt);
               if (rc != GMEAGAIN) {
                    callback(pWork,EVTDSTD,rc,NULL);
                    if (rc < GMEAGAIN) {
                         unlink(work->auxatt);
                    }
                    work->state3=NEXTCC;
                    rc=GMEAGAIN;
                    stlcpy(work->auxhist,msg->history,HSTSIZ);
               }
               return(rc);
          default:
               state_error(3,"gme3cc()",work->state3);
          }
     }
}

INT                                /*   returns standard GME status codes  */
copymsg(                           /* send copy of current msg in context  */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg,               /*   message header buffer              */
const CHAR *text)                  /*   message text buffer                */
{
     INT rc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     ASSERT(msg != NULL);
     ASSERT(text != NULL);
     ASSERT(strlen(text) < TXTLEN);
     ASSERT(msg->forum == EMLID || fidxst(msg->forum));
     while (TRUE) {
          switch (work->state3) {
          case START:
               if (!grabmsg(pWork,utlmsg,utltxt)) {
                    clrwrt(pWork);
                    work->state3=START;
                    return(GMEERR);
               }
               work->flags&=~PRIMDONE;
               if (msg->flags&FILATT) {
                    stlcpy(work->auxatt,dlname(utlmsg),GCMAXPTH);
               }
               if (msg->forum == EMLID && isdlst(msg->to)) {
                    work->state3=START;
                    clrwrt(pWork);
                    return(GMENCFL);
               }
               if ((rc=chkcf(pWork,msg,work->auxatt)) != GMEOK) {
                    work->state3=START;
                    clrwrt(pWork);
                    return(rc);
               }
               if (!gdedcrd(work->rdctx.uid,work->basechg,FALSE,FALSE)) {
                    work->state3=START;
                    clrwrt(pWork);
                    return(GMECRD);
               }
               addhist(msg->history,spr(COPYBY,work->rdctx.uid));
               chkfor(msg);
               chkrqs(pWork,msg);
               setflgs(msg,work->rdctx.uid);
               inicfmsg(msg);
               ASSERT(work->appinf == NULL);
               if (*utlapi != '\0') {
                    work->flags|=HASAPI;
                    work->appinf=utlapi;
               }
               work->flags&=~CYCFLG;
               work->state3=WRITING;
               if (msg->flags&FILATT) {
                    if (!(msg->flags&FILIND)
                     || (msg->forum == EMLID && isexpa(msg->to))) {
                         if (msg->forum == EMLID && isexpa(msg->to)) {
                              stlcpy(work->cpyatt,expasp(msg->to,msg),GCMAXPTH);
                         }
                         else {
                              stlcpy(work->cpyatt,ulname(msg),GCMAXPTH);
                         }
                         callback(pWork,EVTCPYS,GMEOK,work->cpyatt);
                         work->state3=COPYATT;
                         if (!opn4cpy(pWork,work->auxatt)) {
                              rc=GMENOAT;
                              work->state3=GETOUT;
                         }
                    }
               }
               break;
          case COPYATT:
               rc=copychunk(pWork);
               if (rc > GMEAGAIN) {
                    callback(pWork,EVTCPYD,GMEOK,work->cpyatt);
                    stlcpy(work->auxatt,work->cpyatt,GCMAXPTH);
                    work->state3=WRITING;
                    rc=GMEAGAIN;
               }
               else if (rc < GMEAGAIN) {
                    work->state3=GETOUT;
                    break;
               }
               else {
                    work->flags|=CYCFLG;
               }
               return(rc);
          case WRITING:
               if ((work->flags&HASAPI) && (work->flags&CYCFLG)
                && !grabmsg(pWork,utlmsg,utltxt)) {
                    rc=GMEERR;
                    work->state3=GETOUT;
                    break;
               }
               work->flags&=~CYCFLG;
               rc=gme2wnm(pWork,msg,text,work->auxatt);
               if (rc == GMEAGAIN) {
                    work->flags|=CYCFLG;
               }
               else {
                    work->state3=GETOUT;
                    if (rc > GMEAGAIN) {
                         notnwm(NEWMSG_COPY,pWork,msg,text);
                         if (work->flags&FWDMSG) {
                              rstafwd(pWork,msg);
                              rc=GMEAFWD;
                         }
                    }
                    break;
               }
               return(rc);
          case GETOUT:
               uclfrom(msg);
               if (rc < GMEAGAIN && !(work->flags&PRIMDONE)) {
                    gdedcrd(work->rdctx.uid,-work->basechg,FALSE,TRUE);
                    unlink(work->cpyatt);
               }
               clrwrt(pWork);
               work->state3=START;
               return(rc);
          default:
               state_error(3,"copymsg()",work->state3);
          }
     }
}

INT                                /*   returns standard GME status codes  */
fwdmsg(                            /* forward current message in context   */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg,               /*   message header structure           */
const CHAR *text)                  /*   message text                       */
{
     INT rc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);
     GBOOL therenow;

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     ASSERT(msg != NULL);
     ASSERT(text != NULL);
     ASSERT(strlen(text) < TXTLEN);
     ASSERT(msg->forum == EMLID || fidxst(msg->forum));
     while (TRUE) {
          switch (work->state3) {
          case START:
               if (!grabmsg(pWork,utlmsg,utltxt)) {
                    clrwrt(pWork);
                    work->state3=START;
                    return(GMEERR);
               }
               work->flags&=~PRIMDONE;
               if (msg->flags&FILATT) {
                    stlcpy(work->auxatt,dlname(utlmsg),GCMAXPTH);
               }
               if (msg->forum == EMLID && isdlst(msg->to)) {
                    clrwrt(pWork);
                    work->state3=START;
                    return(GMENCFL);
               }
               if (gmecfl(pWork,utlmsg->forum,utlmsg->msgid)) {
                    clrwrt(pWork);
                    work->state3=START;
                    return(GMEUSE);
               }
               if ((rc=chkdel(pWork,utlmsg)) != GMEOK) {
                    clrwrt(pWork);
                    work->state3=START;
                    return(rc);
               }
               if ((rc=chkcf(pWork,msg,work->auxatt)) != GMEOK) {
                    clrwrt(pWork);
                    work->state3=START;
                    return(rc);
               }
               if (!gdedcrd(work->rdctx.uid,work->basechg,FALSE,FALSE)) {
                    clrwrt(pWork);
                    work->state3=START;
                    return(GMECRD);
               }
               addhist(msg->history,spr(FWDBY,work->rdctx.uid));
               chkfor(msg);
               chkrqs(pWork,msg);
               setflgs(msg,work->rdctx.uid);
               inicfmsg(msg);
               ASSERT(work->appinf == NULL);
               if (*utlapi != '\0') {
                    work->flags|=HASAPI;
                    work->appinf=utlapi;
               }
               work->flags&=~CYCFLG;
               work->state3=WRITING;
               if (msg->flags&FILATT) {
                    if (!(msg->flags&FILIND)
                     || (msg->forum == EMLID && isexpa(msg->to))) {
                         if (msg->forum == EMLID && isexpa(msg->to)) {
                              stlcpy(work->cpyatt,expasp(msg->to,msg),GCMAXPTH);
                         }
                         else {
                              stlcpy(work->cpyatt,ulname(msg),GCMAXPTH);
                         }
                         therenow=sameas(normspec((CHAR *)tmpbuf,work->auxatt),
                                         normspec((CHAR *)tmpbuf+GCMAXPTH,
                                                  work->cpyatt));
                         if (!(msg->flags&FILIND) && !therenow) {
                              unlink(work->cpyatt);
                         }
                         if (!(msg->flags&FILIND)
                          && (therenow
                           || rename(work->auxatt,work->cpyatt) == 0)) {
                              stlcpy(work->auxatt,work->cpyatt,GCMAXPTH);
                         }
                         else {
                              callback(pWork,EVTCPYS,GMEOK,work->cpyatt);
                              work->state3=COPYATT;
                              if (!opn4cpy(pWork,work->auxatt)) {
                                   rc=GMENOAT;
                                   work->state3=GETOUT;
                              }
                         }
                    }
               }
               break;
          case COPYATT:
               rc=copychunk(pWork);
               if (rc > GMEAGAIN) {
                    callback(pWork,EVTCPYD,GMEOK,work->cpyatt);
                    stlcpy(work->auxatt,work->cpyatt,GCMAXPTH);
                    work->state3=WRITING;
                    rc=GMEAGAIN;
               }
               else if (rc < GMEAGAIN) {
                    work->state3=GETOUT;
                    break;
               }
               else {
                    work->flags|=CYCFLG;
               }
               return(rc);
          case WRITING:
               if ((work->flags&HASAPI) && (work->flags&CYCFLG)
                && !grabmsg(pWork,utlmsg,utltxt)) {
                    rc=GMEERR;
                    work->state3=GETOUT;
                    break;
               }
               work->flags&=~CYCFLG;
               rc=gme2wnm(pWork,msg,text,work->auxatt);
               if (rc == GMEAGAIN) {
                    work->flags|=CYCFLG;
               }
               else {
                    if (rc > GMEAGAIN) {
                         notnwm(NEWMSG_FORWD,pWork,msg,text);
                         work->state3=DELETNG;
                         rc=GMEAGAIN;
                    }
                    else {
                         work->state3=GETOUT;
                         break;
                    }
               }
               return(rc);
          case DELETNG:
               if (!grabmsg(pWork,utlmsg,utltxt)) {
                    rc=GMEERR;
               }
               else if (gmecfl(pWork,utlmsg->forum,utlmsg->msgid)) {
                    rc=GMEUSE;
               }
               else if (gme1dlm(pWork)) {
                    notdlm(DELMSG_FORWD,pWork,utlmsg,utltxt);
                    if (work->flags&FWDMSG) {
                         rstafwd(pWork,msg);
                         rc=GMEAFWD;
                    }
                    else {
                         rc=GMEOK;
                    }
               }
               else {
                    rc=GMEERR;
               }
          case GETOUT:
               uclfrom(msg);
               if (rc < GMEAGAIN && !(work->flags&PRIMDONE)) {
                    gdedcrd(work->rdctx.uid,-work->basechg,FALSE,TRUE);
                    unlink(work->cpyatt);
               }
               clrwrt(pWork);
               work->state3=START;
               return(rc);
          default:
               state_error(3,"fwdmsg()",work->state3);
          }
     }
}

INT                                /*   returns standard GME status codes  */
gmeGForward(                       /* forward a message (not user-specific)*/
VOID *pWork,                       /*   GME work space (provided by caller)*/
GBOOL delorig,                     /*   delete original (forward vs. copy) */
struct message *msg,               /*   message header buffer              */
const CHAR *text)                  /*   message text buffer                */
{
     INT rc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);
     GBOOL therenow;

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     ASSERT(msg != NULL);
     ASSERT(text != NULL);
     ASSERT(strlen(text) < TXTLEN);
     ASSERT(msg->forum == EMLID || fidxst(msg->forum));
     while (TRUE) {
          switch (work->state3) {
          case START:
               if (msg->forum == EMLID && isdlst(msg->to)) {
                    clrwrt(pWork);
                    return(GMENCFL);
               }
               if (!grabmsg(pWork,utlmsg,utltxt)) {
                    clrwrt(pWork);
                    return(GMEERR);
               }
               if (delorig && gmecfl(pWork,utlmsg->forum,utlmsg->msgid)) {
                    clrwrt(pWork);
                    work->state3=START;
                    return(GMEUSE);
               }
               work->flags&=~PRIMDONE;
               if (*work->rdctx.uid != '\0') {
                    addhist(msg->history,
                            spr(delorig ? FWDBY : COPYBY,work->rdctx.uid));
               }
               chkfor(msg);
               chkrqs(pWork,msg);
               msg->flags&=NONSYSF;
               inicfmsg(msg);
               ASSERT(work->appinf == NULL);
               if (*utlapi != '\0') {
                    work->flags|=HASAPI;
                    work->appinf=utlapi;
               }
               work->flags&=~CYCFLG;
               work->state3=WRITING;
               if (msg->flags&FILATT) {
                    stlcpy(work->auxatt,dlname(utlmsg),GCMAXPTH);
                    if (!fexist(work->auxatt)) {
                         *work->auxatt='\0';
                         msg->flags&=~(FILATT|FILIND|FILAPV);
                    }
                    else if (!(msg->flags&FILIND)
                     || (msg->forum == EMLID && isexpa(msg->to))) {
                         if (msg->forum == EMLID && isexpa(msg->to)) {
                              stlcpy(work->cpyatt,expasp(msg->to,msg),GCMAXPTH);
                         }
                         else {
                              stlcpy(work->cpyatt,ulname(msg),GCMAXPTH);
                         }
                         therenow=FALSE;
                         if (delorig && !(msg->flags&FILIND)) {
                              therenow=samepath(work->auxatt,work->cpyatt);
                              unlink(work->cpyatt);
                              if (!therenow) {
                                   therenow=rename(work->auxatt,work->cpyatt) == 0;
                                   if (therenow) {
                                        stlcpy(work->auxatt,work->cpyatt,GCMAXPTH);
                                   }
                              }
                         }
                         if (!therenow) {
                              callback(pWork,EVTCPYS,GMEOK,work->cpyatt);
                              work->state3=COPYATT;
                              if (!opn4cpy(pWork,work->auxatt)) {
                                   rc=GMENOAT;
                                   work->state3=GETOUT;
                              }
                         }
                    }
               }
               break;
          case COPYATT:
               rc=copychunk(pWork);
               if (rc > GMEAGAIN) {
                    callback(pWork,EVTCPYD,GMEOK,work->cpyatt);
                    stlcpy(work->auxatt,work->cpyatt,GCMAXPTH);
                    work->state3=WRITING;
                    rc=GMEAGAIN;
               }
               else if (rc < GMEAGAIN) {
                    work->state3=GETOUT;
                    break;
               }
               else {
                    work->flags|=CYCFLG;
               }
               return(rc);
          case WRITING:
               if ((work->flags&HASAPI) && (work->flags&CYCFLG)
                && !grabmsg(pWork,utlmsg,utltxt)) {
                    rc=GMEERR;
                    work->state3=GETOUT;
                    break;
               }
               work->flags&=~CYCFLG;
               rc=gme2wnm(pWork,msg,text,work->auxatt);
               if (rc == GMEAGAIN) {
                    work->flags|=CYCFLG;
               }
               else {
                    work->state3=GETOUT;
                    if (rc > GMEAGAIN) {
                         if (delorig) {
                              notnwm(NEWMSG_FORWD,pWork,msg,text);
                              work->state3=DELETNG;
                              return(GMEAGAIN);
                         }
                         else {
                              notnwm(NEWMSG_COPY,pWork,msg,text);
                         }
                    }
                    break;
               }
               return(rc);
          case DELETNG:
               if (grabmsg(pWork,utlmsg,utltxt)
                && !gmecfl(pWork,utlmsg->forum,utlmsg->msgid)
                && gme1dlm(pWork)) {
                    notdlm(DELMSG_FORWD,pWork,utlmsg,utltxt);
               }
               rc=GMEOK;
          case GETOUT:
               uclfrom(msg);
               ASSERT(rc != GMEAGAIN);
               if (rc > GMEAGAIN) {
                    if (work->flags&FWDMSG) {
                         rstafwd(pWork,msg);
                         rc=GMEAFWD;
                    }
               }
               else if (!(work->flags&PRIMDONE)) {
                    unlink(work->cpyatt);
               }
               clrwrt(pWork);
               work->state3=START;
               return(rc);
          default:
               state_error(3,"gmeGForward()",work->state3);
          }
     }
}

INT                                /*   returns standard GME status codes  */
gme2dst(                           /* send dist list                       */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg,               /*   new message structure              */
const CHAR *text,                  /*   message body text                  */
const CHAR *filatt)                /*   path+file name of att (if any)     */
{
     INT rc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(msg != NULL);
     ASSERT(text != NULL);
     ASSERT(strlen(text) < TXTLEN);
     while (TRUE) {
          switch (work->state2) {
          case START:
               ASSERT(msg->forum == EMLID);
               callback(pWork,EVTDSTS,GMEOK,msg->to);
               if ((rc=inidist(pWork,msg)) != GMEOK) {
                    work->state2=START;
                    return(rc);
               }
               if (!gdedcrd(msg->from,work->basechg,FALSE,FALSE)) {
                    work->state2=START;
                    return(GMECRD);
               }
               work->dstprim=msg->msgid;
               msg->thrid=cmptid(msg);
               work->tmpflg=(INT)msg->flags;
               stlcpy(work->auxhist,msg->history,HSTSIZ);
               work->state2=WRTPRIM;
               gmelok(pWork,msg->forum,msg->msgid);
               return(GMEAGAIN);
          case WRTPRIM:
               rc=gme1wnm(pWork,msg,text,filatt);
               if (rc < GMEAGAIN) {
                    gdedcrd(msg->from,-work->basechg,FALSE,TRUE);
                    clsdist(pWork);
                    work->state2=START;
               }
               else if (rc > GMEAGAIN) {
                    work->flags|=PRIMDONE;
                    if (msg->flags&FILATT) {
                         if (msg->flags&FILIND) {
                              stlcpy(work->auxatt,filatt,GCMAXPTH);
                         }
                         else {
                              stlcpy(work->auxatt,dlname(msg),GCMAXPTH);
                              work->tmpflg|=(INT)FILIND;
                         }
                    }
                    work->state2=DSTDLY;
                    work->dstdly=0;
                    rc=GMEAGAIN;
               }
               return(rc);
          case DSTDLY:
               if (work->dstdly++ < dstdly) {
                    return(GMEAGAIN);
               }
          case NXTDIST:
               if (nxtdist(pWork,msg)) {
                    callback(pWork,EVTSTRT,GMEOK,msg->to);
                    work->flags&=~(ADROK|ATTOK|RRROK|PRIOK|FWDMSG|CPY2E);
                    *work->cpyatt='\0';
                    msg->forum=EMLID;
                    msg->flags=(LONG)work->tmpflg;
                    if (isdlst(msg->to)) {
                         callback(pWork,EVTDONE,GMENCFL,msg->to);
                    }
                    if (!isdlst(msg->to) && stripit(pWork,msg)) {
                         stlcpy(msg->history,work->auxhist,HSTSIZ);
                         if (work->flags&SYSLST) {
                              addhist(msg->history,spr(SDLMSG,work->dlstnam));
                         }
                         else {
                              addhist(msg->history,DSTMSG);
                         }
                         chkfor(msg);
                         chkrqs(pWork,msg);
                         addchg(pWork,msg);
                         iniamsg(msg);
                         setflgs(msg,msg->from);
                         msg->thrid=cmptid(msg);
                         if (gdedcrd(msg->from,work->basechg,FALSE,FALSE)) {
                              if ((work->flags&FWDMSG) && work->rcpchg != 0L
                               && !gdedcrd(work->auxto,work->rcpchg,FALSE,FALSE)) {
                                   stlcpy(msg->to,work->auxto,UIDSIZ);
                                   work->flags&=~FWDMSG;
                              }
                              if (isexpa(msg->to)) {
                                   if (msg->flags&FILATT) {
                                        work->state2=STRTCPY;
                                   }
                                   else {
                                        work->state2=SNDEXP;
                                   }
                              }
                              else {
                                   work->state2=SNDIST;
                              }
                         }
                         else {
                              if (msg->forum == EMLID) {
                                   callback(pWork,EVTDONE,GMECRD,msg->to);
                              }
                              else {
                                   callback(pWork,EVTDONE,GMECRD,
                                            spr("/%s",getfnm(msg->forum)));
                              }
                         }
                    }
                    rc=GMEAGAIN;
               }
               else {
                    msg->forum=EMLID;
                    msg->msgid=work->dstprim;
                    stlcpy(msg->to,work->dlstnam,DLNMSZ);
                    stlcpy(msg->history,work->auxhist,HSTSIZ);
                    msg->flags=(LONG)work->tmpflg;
                    clsdist(pWork);
                    work->state2=START;
                    rc=GMEOK;
               }
               return(rc);
          case STRTCPY:
               work->state2=COPYATT;
               stlcpy(work->cpyatt,expasp(msg->to,msg),GCMAXPTH);
               callback(pWork,EVTCPYS,GMEOK,work->cpyatt);
               if (!opn4cpy(pWork,work->auxatt)) {
                    noatt(msg);
                    work->state2=SNDEXP;
                    *work->cpyatt='\0';
                    break;
               }
               break;
          case COPYATT:
               rc=copychunk(pWork);
               if (rc > GMEAGAIN) {
                    callback(pWork,EVTCPYD,GMEOK,work->cpyatt);
                    if (isexpa(msg->to)) {
                         work->state2=SNDEXP;
                    }
                    else {
                         work->state2=SNDIST;
                    }
                    rc=GMEAGAIN;
               }
               else if (rc < GMEAGAIN) {
                    work->state2=START;
               }
               return(rc);
          case SNDEXP:
               rc=sndexp(pWork,msg->to,msg,text,work->cpyatt);
               if (rc > GMEAGAIN) {
                    notnwm(NEWMSG_DISTR,pWork,msg,text);
                    *work->cpyatt='\0';
               }
               if ((work->flags&FWDMSG) && rc > GMEAGAIN) {
                    rstafwd(pWork,msg);
                    callback(pWork,EVTDONE,GMEAFWD,NULL);
               }
               else {
                    callback(pWork,EVTDONE,rc,msg->to);
               }
               if (rc <= GMEAGAIN) {
                    unlink(work->cpyatt);
                    if ((work->flags&FWDMSG) && work->rcpchg != 0) {
                         gdedcrd(work->auxto,-work->rcpchg,FALSE,TRUE);
                    }
               }
               work->state2=DSTDLY;
               work->dstdly=0;
               return(GMEAGAIN);
          case SNDIST:
               rc=gme1wap(pWork,EMLID,work->dstprim,FALSE,msg,work->auxatt);
               if (rc != GMEAGAIN) {
                    if (rc > GMEAGAIN) {
                         notnwm(NEWMSG_DISTR,pWork,msg,text);
                    }
                    if ((work->flags&FWDMSG) && rc > GMEAGAIN) {
                         rstafwd(pWork,msg);
                         callback(pWork,EVTDONE,GMEAFWD,NULL);
                    }
                    else if (msg->forum == EMLID) {
                         callback(pWork,EVTDONE,rc,msg->to);
                    }
                    else {
                         callback(pWork,EVTDONE,rc,
                                  spr("/%s",getfnm(msg->forum)));
                    }
                    if (rc < GMEAGAIN) {
                         gdedcrd(msg->from,-work->basechg,FALSE,TRUE);
                    }
                    if (msg->forum != EMLID && iniecho(pWork,msg)) {
                         work->state2=SNDECHO;
                    }
                    else {
                         work->state2=DSTDLY;
                         work->dstdly=0;
                    }
                    rc=GMEAGAIN;
               }
               return(rc);
          case SNDECHO:
               rc=gme1echo(pWork,msg,text,work->auxatt);
               if (rc != GMEAGAIN) {
                    work->state2=DSTDLY;
                    work->dstdly=0;
               }
               return(GMEAGAIN);
          default:
               state_error(2,"gme2dst()",work->state2);
          }
     }
}

INT                                /*   returns standard GME status codes  */
gme2wnm(                           /* send a message                       */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg,               /*   new message structure              */
const CHAR *text,                  /*   message body text                  */
const CHAR *filatt)                /*   path+file name of att (if any)     */
{
     INT rc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(msg != NULL);
     ASSERT(text != NULL);
     ASSERT(strlen(text) < TXTLEN);
     ASSERT(msg->forum == EMLID || fidxst(msg->forum));
     while (TRUE) {
          switch (work->state2) {
          case START:
               if (msg->thrid == 0L) {
                    msg->thrid=cmptid(msg);
               }
               if ((work->flags&FWDMSG) && work->rcpchg != 0L
                && !gdedcrd(work->auxto,work->rcpchg,FALSE,FALSE)) {
                    stlcpy(msg->to,work->auxto,UIDSIZ);
                    work->flags&=~FWDMSG;
               }
               if (msg->forum == EMLID && isexpa(msg->to)) {
                    work->state2=SNDEXP;
               }
               else {
                    work->state2=SNDLOC;
               }
               break;
          case SNDEXP:
               rc=sndexp(pWork,msg->to,msg,text,filatt);
               if (rc > GMEAGAIN) {
                    *work->cpyatt='\0';
                    work->flags|=PRIMDONE;
               }
               work->state2=START;
               return(rc);
          case SNDLOC:
               rc=gme1wnm(pWork,msg,text,filatt);
               if (rc != GMEAGAIN) {
                    work->state2=START;
                    if (rc > GMEAGAIN) {
                         work->flags|=PRIMDONE;
                         if (msg->forum != EMLID) {
                              if (iniecho(pWork,msg)) {
                                   work->state2=SNDECHO;
                                   stlcpy(work->auxatt,dlname(msg),GCMAXPTH);
                                   rc=GMEAGAIN;
                              }
                              else if (work->flags&CPY2E) {
                                   work->state2=CPYEML;
                                   rc=GMEAGAIN;
                              }
                         }
                    }
               }
               return(rc);
          case SNDECHO:
               rc=gme1echo(pWork,msg,text,work->auxatt);
               if (rc != GMEAGAIN) {
                    work->state2=START;
                    if (work->flags&CPY2E) {
                         work->state2=CPYEML;
                         rc=GMEAGAIN;
                    }
               }
               return(rc);
          case CPYEML:
               work->state2=WRTECPY;
               work->orgfor=msg->forum;
               msg->forum=EMLID;
               clrfrom(msg);
          case WRTECPY:
               rc=gme1wap(pWork,work->orgfor,msg->msgid,TRUE,msg,work->auxatt);
               if (rc != GMEAGAIN) {
                    work->state2=START;
                    msg->forum=work->orgfor;
                    uclfrom(msg);
               }
               return(rc);
          default:
               state_error(2,"gme2wnm()",work->state2);
          }
     }
}

INT                                /*   returns standard GME status codes  */
readpar(                           /* read parent of message               */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg,               /*   message header structure buffer    */
CHAR *text)                        /*   message body text buffer           */
{
     INT rc;

     rc=readparf(pWork,msg,text);
     if (rc > GMEAGAIN) {
          gmePlainText(text);
     }
     return(rc);
}

INT                                /*   returns standard GME status codes  */
thrinfo(                           /* read info on a thread (orig message) */
VOID *pWork,                       /*   GME work space (provided by caller)*/
INT direct,                        /*   direction (0=same, 1=next, -1=prev)*/
USHORT *nmsgs,                     /*   number of messages in thread       */
struct message *msg,               /*   message header structure buffer    */
CHAR *text)                        /*   message body text buffer           */
{
     INT rc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     ASSERT(direct == 0 || direct == -1 || direct == 1);
     ASSERT(work->rdctx.fid == EMLID || nmsgs != NULL);
     ASSERT(msg != NULL);
     ASSERT(text != NULL);
     switch (work->state3) {
     case START:
          if (work->rdctx.fid != EMLID
           && gforac(work->rdctx.uid,work->rdctx.fid) < RDAXES) {
               work->state3=START;
               return(GMEACC);
          }
     default:
          rc=gmeGThreadInfo(pWork,direct,nmsgs,msg,text);
          if (rc > GMEAGAIN) {
               gmePlainText(text);
          }
          return(rc);
     }
}

INT                                /*   returns standard GME status codes  */
gmeGThreadInfo(                    /* read thread info (not user-specific) */
VOID *pWork,                       /*   GME work space (provided by caller)*/
INT direct,                        /*   direction (0=same, 1=next, -1=prev)*/
USHORT *nmsgs,                     /*   number of messages in thread       */
struct message *msg,               /*   message header structure buffer    */
CHAR *text)                        /*   message body text buffer           */
{
     INT rc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     ASSERT(direct == 0 || direct == -1 || direct == 1);
     ASSERT(work->rdctx.fid == EMLID || nmsgs != NULL);
     ASSERT(msg != NULL);
     ASSERT(text != NULL);
     switch (work->state3) {
     case START:
          work->state3=READING;
     case READING:
          rc=gme1rti(pWork,direct,nmsgs,msg,text);
          if (rc != GMEAGAIN) {
               work->state3=START;
          }
          return(rc);
     default:
          state_error(3,"gmeGThreadInfo()",work->state3);
     }
     ASSERT(FALSE);
     clsgmerq(pWork);
     return(GMEERR);     /* this should never happen */
}

INT                                /*   returns standard GME status codes  */
readmsg(                           /* read current message in context      */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg,               /*   message header structure buffer    */
CHAR *text)                        /*   message body text buffer           */
{
     INT rc;

     rc=frdmsg(pWork,RDEXCT,msg,text);
     if (rc > GMEAGAIN) {
          gmePlainText(text);
     }
     return(rc);
}

INT                                /*   returns standard GME status codes  */
nearmsg(                           /* read nearest to cur msg in context   */
VOID *pWork,                       /*   GME work space (provided by caller)*/
INT nearop,                        /*   get near "style" to use            */
struct message *msg,               /*   message header structure buffer    */
CHAR *text)                        /*   message body text buffer           */
{
     INT rc;

     ASSERT(nearop == RDLEGT || nearop == RDLTGE
         || nearop == RDGELT || nearop == RDGTLE);
     rc=frdmsg(pWork,nearop,msg,text);
     if (rc > GMEAGAIN) {
          gmePlainText(text);
     }
     return(rc);
}

INT                                /*   returns standard GME status codes  */
nextmsg(                           /* read next message in context         */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg,               /*   message header structure buffer    */
CHAR *text)                        /*   message body text buffer           */
{
     INT rc;

     rc=frdmsg(pWork,RDNEXT,msg,text);
     if (rc > GMEAGAIN) {
          gmePlainText(text);
     }
     return(rc);
}

INT                                /*   returns standard GME status codes  */
prevmsg(                           /* read previous message in context     */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg,               /*   message header structure buffer    */
CHAR *text)                        /*   message body text buffer           */
{
     INT rc;
     rc=frdmsg(pWork,RDPREV,msg,text);
     if (rc > GMEAGAIN) {
          gmePlainText(text);
     }
     return(rc);
}

INT                                /*   returns standard GME status codes  */
nextseq(                           /* read next message in a sequence      */
VOID *pWork,                       /*   GME work space (provided by caller)*/
INT sequence,                      /*   sequence to use                    */
struct message *msg,               /*   message header structure buffer    */
CHAR *text)                        /*   message body text buffer           */
{
     INT rc;

     rc=frdseq(pWork,RDNEXT,sequence,msg,text);
     if (rc > GMEAGAIN) {
          gmePlainText(text);
     }
     return(rc);
}

INT                                /*   returns standard GME status codes  */
prevseq(                           /* read previous message in a sequence  */
VOID *pWork,                       /*   GME work space (provided by caller)*/
INT sequence,                      /*   sequence to use                    */
struct message *msg,               /*   message header structure buffer    */
CHAR *text)                        /*   message body text buffer           */
{
     INT rc;

     rc=frdseq(pWork,RDPREV,sequence,msg,text);
     if (rc > GMEAGAIN) {
          gmePlainText(text);
     }
     return(rc);
}

INT                                /*   returns standard GME status codes  */
frdmsg(                            /* read message in context (keep format)*/
VOID *pWork,                       /*   GME work space (provided by caller)*/
INT direct,                        /*   read direction code                */
struct message *msg,               /*   message header structure buffer    */
CHAR *text)                        /*   message body text buffer           */
{
     INT rc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     ASSERT(msg != NULL);
     ASSERT(text != NULL);
     switch (work->state3) {
          case START:
          rc=chkread(pWork);
          ASSERT(rc != GMEAGAIN);
          if (rc < GMEAGAIN) {
               return(rc);
          }
     default:
          return(grdmsg(pWork,direct,msg,text));
     }
}

INT                                /*   returns standard GME status codes  */
frdseq(                            /* read message in a sequence           */
VOID *pWork,                       /*   GME work space (provided by caller)*/
INT direct,                        /*   read direction code                */
INT sequence,                      /*   sequence to use                    */
struct message *msg,               /*   message header structure buffer    */
CHAR *text)                        /*   message body text buffer           */
{
     INT rc,savseq;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     ASSERT(msg != NULL);
     ASSERT(text != NULL);
     switch (work->state3) {
     case START:
          rc=chkread(pWork);
          ASSERT(rc != GMEAGAIN);
          if (rc < GMEAGAIN) {
               return(rc);
          }
     default:
          savseq=work->rdctx.seq;
          work->rdctx.seq=sequence;
          rc=grdmsg(pWork,direct,msg,text);
          work->rdctx.seq=savseq;
          return(rc);
     }
}

INT                                /*   returns standard GME status codes  */
readparf(                          /* read parent of message (keep format) */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg,               /*   message header structure buffer    */
CHAR *text)                        /*   message body text buffer           */
{
     INT rc;
     USHORT tmpfid;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     ASSERT(msg != NULL);
     ASSERT(text != NULL);
     switch (work->state3) {
     case START:
          rc=chkread(pWork);
          ASSERT(rc != GMEAGAIN);
          if (rc < GMEAGAIN) {
               return(rc);
          }
          gmeulkr(pWork);
          if (grabmsg(pWork,utlmsg,utltxt)) {
               if (utlmsg->rplto.msgid != 0L) {
                    if (work->rdctx.fid == EMLID) {
                         tmpfid=(USHORT)utlmsg->rplto.sysid;
                         if (tmpfid != EMLID) {
                              if (!fidxst(tmpfid)) {
                                   return(GMENFND);
                              }
                              if (gforac(work->rdctx.uid,tmpfid) < RDAXES) {
                                   return(GMEACC);
                              }
                         }
                    }
                    return(gme1rdg(pWork,&utlmsg->rplto,msg,text));
               }
               return(GMENFND);
          }
          return(GMEERR);
     default:
          state_error(3,"readparf()",work->state3);
     }
     ASSERT(FALSE);
     clsgmerq(pWork);
     return(GMEERR);     /* this should never happen */
}

INT                                /*   returns standard GME status codes  */
gmeGReadGlob(                      /* read forum msg given global ID       */
VOID *pWork,                       /*   GME work space (provided by caller)*/
const struct globid *globid,       /*   global ID to read                  */
struct message *msg,               /*   message header structure buffer    */
CHAR *text)                        /*   message body text buffer           */
{
     INT rc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     ASSERT(globid->sysid != 0 && globid->msgid != 0);
     ASSERT(msg != NULL);
     ASSERT(text != NULL);
     switch (work->state3) {
     case START:
          if (work->rdctx.fid == EMLID) {
               return(GMEERR);
          }
          gmeulkr(pWork);
          work->state3=READING;
     case READING:
          if ((rc=gme1rdg(pWork,globid,msg,text)) != GMEAGAIN) {
               work->state3=START;
          }
          return(rc);
     default:
          state_error(3,"gmeGReadGlob()",work->state3);
     }
     ASSERT(FALSE);
     clsgmerq(pWork);
     return(GMEERR);     /* this should never happen */
}

INT                                /*   returns standard GME status codes  */
grdmsg(                            /* generic read message in context      */
VOID *pWork,                       /*   GME work space (provided by caller)*/
INT direct,                        /*   read direction code                */
struct message *msg,               /*   message header structure buffer    */
CHAR *text)                        /*   message body text buffer           */
{
     INT rc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     ASSERT(msg != NULL);
     ASSERT(text != NULL);
     while (TRUE) {
          switch (work->state3) {
          case START:
               gmeulkr(pWork);
               if (work->rdctx.seq == FSQSCN) {
                    work->state3=SRCHING;
               }
               else {
                    work->state3=READING;
               }
               break;
          case READING:
               rc=gme1rdm(pWork,direct,msg,text);
               if (rc != GMEAGAIN) {
                    work->state3=START;
               }
               return(rc);
          case SRCHING:
               rc=gme2scn(pWork,direct,msg,text);
               if (rc != GMEAGAIN) {
                    work->state3=START;
               }
               return(rc);
          default:
               state_error(3,"grdmsg()",work->state3);
          }
     }
}

INT                                /*   returns standard GME status codes  */
gme2scn(                           /* scan for a message                   */
VOID *pWork,                       /*   GME work space (provided by caller)*/
INT direct,                        /*   read direction code                */
struct message *msg,               /*   message header structure buffer    */
CHAR *text)                        /*   message body text buffer           */
{
     INT rc;
     LONG tmpmid;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     ASSERT(work->rdctx.seq == FSQSCN);
     ASSERT(work->curscn != NULL);
     ASSERT(msg != NULL);
     ASSERT(text != NULL);
     switch (work->state2) {
     case START:
          work->orgfor=work->rdctx.fid;
          work->orgmid=work->rdctx.mid;
          work->state2=SRCHING;
     case SRCHING:
          work->rdctx.seq=FSQFOR;
          switch (direct) {
          case RDEXCT:
               rc=gme1rdm(pWork,direct,msg,text);
               if (rc != GMEAGAIN) {
                    work->state2=START;
                    if (rc == GMEOK && !chkscn(pWork,msg,text)) {
                         work->state2=START;
                         rc=GMENFND;
                    }
               }
               break;
          case RDNEXT:
               switch (rc=gme1rdm(pWork,direct,msg,text)) {
               case GMEAGAIN:
                    break;
               case GMEOK:
                    tmpmid=fstscnm(work->rdctx.uid,work->curscn,msg->forum);
                    if (msg->msgid <= tmpmid || !chkscn(pWork,msg,text)) {
                         if (msg->msgid <= tmpmid) {
                              msgctx(pWork,tmpmid);
                         }
                         callback(pWork,EVTNEWM,GMEOK,NULL);
                         rc=GMEAGAIN;
                    }
                    break;
               case GMENFND:
                    if (nxtscnf(pWork)) {
                         callback(pWork,EVTNEWF,GMEOK,getfnm(work->rdctx.fid));
                         rc=GMEAGAIN;
                    }
                    break;
               default:
                    work->state2=START;
                    work->rdctx.fid=work->orgfor;
                    work->rdctx.mid=work->orgmid;
                    work->rdfpos=0L;
                    break;
               }
               break;
          case RDPREV:
               switch (rc=gme1rdm(pWork,direct,msg,text)) {
               case GMEAGAIN:
                    break;
               case GMEOK:
                    tmpmid=fstscnm(work->rdctx.uid,work->curscn,msg->forum);
                    if (msg->msgid > tmpmid) {
                         if (!chkscn(pWork,msg,text)) {
                              callback(pWork,EVTNEWM,GMEOK,NULL);
                              rc=GMEAGAIN;
                         }
                         break;
                    }
                    else {
                         rc=GMENFND;
                    }
               case GMENFND:
                    if (prvscnf(pWork)) {
                         callback(pWork,EVTNEWF,GMEOK,getfnm(work->rdctx.fid));
                         rc=GMEAGAIN;
                    }
                    break;
               default:
                    work->state2=START;
                    work->rdctx.fid=work->orgfor;
                    work->rdctx.mid=work->orgmid;
                    work->rdfpos=0L;
                    break;
               }
               break;
          case RDLEGT:
          case RDLTGE:
          case RDGELT:
          case RDGTLE:
               rc=GMEERR;
               break;
          default:
               ASSERT(FALSE);
               clsgmerq(pWork);
               rc=GMEERR;
               break;
          }
          work->rdctx.seq=FSQSCN;
          if (rc != GMEAGAIN) {
               work->state2=START;
          }
          return(rc);
     default:
          state_error(2,"gme2scn()",work->state2);
     }
     ASSERT(FALSE);
     clsgmerq(pWork);
     return(GMEERR);               /* this should never happen             */
}

GBOOL                              /*   returns TRUE if another forum      */
nxtscnf(                           /* set up for next forum in scan        */
VOID *pWork)                       /*   GME work space (provided by caller)*/
{
     INT i,j;
     INT nfors,nfortot;
     USHORT *forlst;
     struct fordef *ford;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     ASSERT(work->curscn != NULL);
     nfors=work->curscn->nforums;
     forlst=work->curscn->forlst;
     if (work->curscn->flags&SCALL) {
          ASSERT(fididx(work->rdctx.fid) != NOIDX);
          nfortot=numforums();
          for (i=fididx(work->rdctx.fid)+1 ; i < nfortot ; i++) {
               ford=idxdef(i);
               ASSERT(ford != NULL);
               if (gforac(work->rdctx.uid,ford->forum) >= RDAXES) {
                    for (j=0 ; j < nfors ; j++) {
                         if (forlst[j] == ford->forum) {
                              break;
                         }
                    }
                    if (j == nfors) {
                         work->rdctx.fid=ford->forum;
                         break;
                    }
               }
          }
          if (i == nfortot) {
               return(FALSE);
          }
     }
     else {
          if (work->rdctx.fid == forlst[nfors-1]) {
               return(FALSE);
          }
          for (i=0 ; i < nfors-1 ; ++i) {
               if (work->rdctx.fid == forlst[i]) {
                    break;
               }
          }
          if (i == nfors-1) {
               ASSERT(FALSE);
               return(FALSE);
          }
          for (++i ; i < nfors ; ++i) {
               if (fidxst(forlst[i])
                && gforac(work->rdctx.uid,forlst[i]) >= RDAXES) {
                    work->rdctx.fid=forlst[i];
                    break;
               }
          }
          if (i == nfors) {
               return(FALSE);
          }
     }
     msgctx(pWork,fstscnm(work->rdctx.uid,work->curscn,work->rdctx.fid));
     return(TRUE);
}

GBOOL                              /*   returns TRUE if another forum      */
prvscnf(                           /* set up for previous forum in scan    */
VOID *pWork)                       /*   GME work space (provided by caller)*/
{
     INT i,j;
     INT nfors;
     USHORT *forlst;
     struct fordef *ford;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     ASSERT(work->curscn != NULL);
     nfors=work->curscn->nforums;
     forlst=work->curscn->forlst;
     if (work->curscn->flags&SCALL) {
          ASSERT(fididx(work->rdctx.fid) != NOIDX);
          for (i=fididx(work->rdctx.fid)-1 ; i > -1 ; i--) {
               ford=idxdef(i);
               ASSERT(ford != NULL);
               if (gforac(work->rdctx.uid,ford->forum) >= RDAXES) {
                    for (j=0 ; j < nfors ; j++) {
                         if (forlst[j] == ford->forum) {
                              break;
                         }
                    }
                    if (j == nfors) {
                         work->rdctx.fid=ford->forum;
                         break;
                    }
               }
          }
          if (i == -1) {
               return(FALSE);
          }
     }
     else {
          if (work->rdctx.fid == forlst[0]) {
               return(FALSE);
          }
          for (i=1 ; i < nfors ; ++i) {
               if (work->rdctx.fid == forlst[i]) {
                    break;
               }
          }
          if (i == nfors) {
               ASSERT(FALSE);
               return(FALSE);
          }
          for (--i ; i >= 0 ; --i) {
               if (fidxst(forlst[i])
                && gforac(work->rdctx.uid,forlst[i]) >= RDAXES) {
                    work->rdctx.fid=forlst[i];
                    break;
               }
          }
          if (i < 0) {
               return(FALSE);
          }
     }
     msgctx(pWork,LASTM);
     return(TRUE);
}

USHORT                             /*   returns EMLID of no forums         */
fstscnf(                           /* get first forum ID                   */
const CHAR *userid,                /*   User-ID doing scan                 */
const struct otscan *ots)          /*   in this search                     */
{
     INT i;
     const struct fordef *tmpdef;

     ASSERT(ots != NULL);
     if (ots->flags&SCALL) {
          tmpdef=nxtdefp("");
          while (tmpdef != NULL) {
               if (gforac(userid,tmpdef->forum) >= RDAXES
                && scnfidx(ots,tmpdef->forum) == NOIDX) {
                    return(tmpdef->forum);
               }
               tmpdef=nxtdefp(tmpdef->name);
          }
     }
     else {
          for (i=0 ; i < ots->nforums ; ++i) {
               ASSERT(fidxst(ots->forlst[i]));
               if (gforac(userid,ots->forlst[i]) >= RDAXES) {
                    return(ots->forlst[i]);
               }
          }
     }
     return(EMLID);
}

INT                                /*   returns NOIDX if not found         */
scnfidx(                           /* get index of forum                   */
const struct otscan *ots,          /*   in this scan                       */
USHORT forum)                      /*   forum ID to find                   */
{
     INT i;

     for (i=0 ; i < ots->nforums ; ++i) {
          if (ots->forlst[i] == forum) {
               return(i);
          }
     }
     return(NOIDX);
}

LONG
fstscnm(                           /* get first message #                  */
const CHAR *userid,                /*   User-ID doing scan                 */
const struct otscan *ots,          /*   in this scan                       */
USHORT forum)                      /*   in this forum (in scan)            */
{
     LONG stmid,tmpmid;

     stmid=ots->stmsgid;
     if (ots->flags&SCNEW) {
          tmpmid=firstnew(userid,forum);
          if (tmpmid > stmid) {
               stmid=tmpmid;
          }
     }
     return(stmid);
}

GBOOL
chkscn(                            /* check if message is in one-time scan */
VOID *pWork,                       /*   GME work space (provided by caller)*/
const struct message *msg,         /*   message header structure buffer    */
const CHAR *text)                  /*   message body text buffer           */
{
     CHAR kwdlst[MAXSKWD];
     CHAR srchstr[MAXSKWD];
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     ASSERT(work->curscn != NULL);
     ASSERT(msg != NULL);
     ASSERT(text != NULL);
     if ((work->curscn->flags&SCNEW)
      && msg->msgid <= firstnew(work->rdctx.uid,msg->forum)) {
          return(FALSE);
     }
     if ((work->curscn->flags&SCATT) && !(msg->flags&FILAPV)) {
          return(FALSE);
     }
     if ((work->curscn->flags&SCTOU) && !sameas(msg->to,work->rdctx.uid)) {
          return(FALSE);
     }
     if ((work->curscn->flags&SCFRU) && !sameas(msg->from,work->rdctx.uid)) {
          return(FALSE);
     }
     if (msg->msgid <= work->curscn->stmsgid) {
          return(FALSE);
     }
     if (*work->curscn->keywds != '\0') {
          if (!parsrch(work->curscn->keywds,kwdlst,srchstr)) {
               return(FALSE);
          }
          if (isfmtted(text) && cvt2asc(text,utltxt,TXTLEN)) {
               return(chkwds(hdrstr(msg),utltxt,kwdlst,srchstr));
          }
          return(chkwds(hdrstr(msg),text,kwdlst,srchstr));
     }
     return(TRUE);
}

GBOOL                              /*   returns FALSE if invalid search str*/
parsrch(                           /* parse search string                  */
const CHAR *usrstr,                /*   string user typed in               */
CHAR *kwdbuf,                      /*   buffer to store keywords in        */
CHAR *search)                      /*   buffer to store search template    */
{
     INT loop,loop2;
     CHAR *current,*cp;
     CHAR tmpstr[MAXSKWD];

     ASSERT(usrstr != NULL);
     ASSERT(kwdbuf != NULL);
     ASSERT(search != NULL);
     if (strlen(usrstr) >= MAXSKWD) {
          return(FALSE);
     }
     stlcpy(tmpstr,usrstr,MAXSKWD);
     unpad(tmpstr);
     while (strsrep(tmpstr,"?","")) {
     }
     while (samend(tmpstr," OR")) {
          tmpstr[strlen(tmpstr)-3]='\0';
     }
     while (samend(tmpstr," AND")) {
          tmpstr[strlen(tmpstr)-4]='\0';
     }
     while (samend(tmpstr," XOR")) {
          tmpstr[strlen(tmpstr)-4]='\0';
     }
     while (samend(tmpstr," NOT")) {
          tmpstr[strlen(tmpstr)-4]='\0';
     }
     while (strsrep(tmpstr," AND "," & ")) {
     }
     while (strsrep(tmpstr," OR "," | ")) {
     }
     while (strsrep(tmpstr," XOR "," ^ ")) {
     }
     while (strsrep(tmpstr," NOT "," ! ")) {
     }
     if (sameto("NOT ",tmpstr)) {
          strsrep(tmpstr,"NOT ","!");
     }
     stlcpy(kwdbuf,tmpstr,MAXSKWD);
     unpad(kwdbuf);
     strrpl(kwdbuf,'&',' ');
     strrpl(kwdbuf,'|',' ');
     strrpl(kwdbuf,'^',' ');
     strrpl(kwdbuf,'!',' ');
     strrpl(kwdbuf,'(',' ');
     strrpl(kwdbuf,')',' ');
     while (strsrep(kwdbuf,"  "," ")) {
     }
     while (*kwdbuf == ' ') {
          movmem(&kwdbuf[1],kwdbuf,strlen(&kwdbuf[1])+1);
     }
     stlcpy(search,tmpstr,MAXSKWD);
     for (loop=0 ; TRUE ; ++loop) {
          current=itemidxd(kwdbuf,loop," ");
          if (*current == '\0') {
               break;
          }
          strsrep(search,current,"?");
     }
     do {
          loop2=0;
          while (strsrep(search," ","")) {
               loop2=1;
          }
          while (strsrep(search,"??","?&?")) {
               loop2=1;
          }
          while (strsrep(search,"?!","?&!")) {
               loop2=1;
          }
          while (strsrep(search,"&&","&")) {
               loop2=1;
          }
          while (strsrep(search,"!&","!")) {
               loop2=1;
          }
          while (strsrep(search,"&|","|")) {
               loop2=1;
          }
          while (strsrep(search,"|&","|")) {
               loop2=1;
          }
          while (strsrep(search,"&^","^")) {
               loop2=1;
          }
          while (strsrep(search,"^&","^")) {
               loop2=1;
          }
          while (strsrep(search,"!!","")) {
               loop2=1;
          }
          while (strsrep(search,"()","")) {
               loop2=1;
          }
          while (strsrep(search,"(&","(")) {
               loop2=1;
          }
          while (strsrep(search,"(|","(")) {
               loop2=1;
          }
          while (strsrep(search,"(^","(")) {
               loop2=1;
          }
          while (strsrep(search,"&)",")")) {
               loop2=1;
          }
          while (strsrep(search,"|)",")")) {
               loop2=1;
          }
          while (strsrep(search,"||","|")) {
               loop2=1;
          }
          while (strsrep(search,"^)",")")) {
               loop2=1;
          }
          while (strsrep(search,"^^","^")) {
               loop2=1;
          }
     } while (loop2);
     loop2=0;
     for (cp=search ; *cp != '\0' ; cp++) {
          if (*cp == '(') {
               loop2++;
          }
          else if (*cp == ')') {
               loop2--;
          }
          if (loop2 < 0) {
               break;
          }
     }
     if (loop2 != 0) {
          return(FALSE);
     }
     while (search[0] == '|') {
          movmem(&search[1],search,strlen(&search[1])+1);
     }
     while (search[0] == '^') {
          movmem(&search[1],search,strlen(&search[1])+1);
     }
     while (search[0] == '&') {
          movmem(&search[1],search,strlen(&search[1])+1);
     }
     while (search[strlen(search)-1] == '!') {
          search[strlen(search)-1]='\0';
     }
     return(TRUE);
}

const CHAR *                       /*   returns ptr to string buffer       */
hdrstr(                            /* generate msg header string for search*/
const struct message *msg)         /*   message header structure buffer    */
{
     static char retbuf[1024];

     ASSERT(msg != NULL);
     ASSERT(fidxst(msg->forum));
     setmbk(gmemb);
     clrprf();
     prfmsg(SCNHDR,
            datlin(msg->crdate,msg->crtime),
            getfnm(msg->forum),
            msg->from,
            l2as(msg->msgid),
            msg->to,
            (msg->flags&EXEMPT) ? "*EXEMPT*" : "",
            msg->topic,
            (msg->flags&FILATT) ? spr("File: %s",msg->attname) : "",
            *msg->history == '\0' ? "" : spr("(%s)",msg->history),
            nrstr(msg->nrpl));
     rstmbk();
     prf2str(retbuf,sizeof(retbuf));
     return(retbuf);
}

const CHAR *
datlin(                            /* build date/time string               */
USHORT date,                       /*   from DOS packed date               */
USHORT time)                       /*   and DOS packed time                */
{
     static CHAR retval[40];

     strcpy(retval,prnday(date,9));
     strcat(retval,", ");
     strcat(retval,prndat(22,date,','));
     strcat(retval,"  ");
     strcat(retval,prntim(2,time));
     return(retval);
}

const CHAR *
nrstr(                             /* generate "number of replies" string  */
INT nr)                            /*   number of replies                  */
{
     ASSERT(nr >= 0);
     switch (nr) {
     case 0:
          return("");
     case 1:
          return("(1 reply)");
     default:
          return(spr("(%d replies)",nr));
     }
}

GBOOL
chkwds(                            /* check if a string matches keywords   */
const CHAR *hstr,                  /*   message header to check            */
const CHAR *tstr,                  /*   message text to check              */
const CHAR *kwds,                  /*   ' '-delimited keyword list         */
const CHAR *srch)                  /*   search template string             */
{
     INT i;
     CHAR *cp,*wd;
     CHAR templat[MAXSKWD];

     ASSERT(hstr != NULL);
     ASSERT(tstr != NULL);
     ASSERT(kwds != NULL);
     ASSERT(srch != NULL);
     stlcpy(templat,srch,MAXSKWD);
     for (i=0,cp=templat; TRUE ; ++i) {
          cp=strchr(cp,'?');
          wd=itemidxd(kwds,i," ");
          if (cp == NULL || *wd == '\0') {
               break;
          }
          if (wrdinstr(wd,hstr) || wrdinstr(wd,tstr)) {
               *cp='1';
          }
          else {
               *cp='0';
          }
     }
     return(evalkwd(templat));
}

GBOOL
wrdinstr(                          /* is a word in a string?               */
const CHAR *wordstr,               /*   word to search for                 */
const CHAR *srchstr)               /*   string to search in                */
{
     INT i,wordlen,srchlen,looplim;

     ASSERT(wordstr != NULL);
     ASSERT(srchstr != NULL);
     if (sameto(wordstr,srchstr)) {
          return(TRUE);
     }
     wordlen=strlen(wordstr);
     srchlen=strlen(srchstr);
     if (wordlen > srchlen) {
          return(FALSE);
     }
     looplim=srchlen-wordlen+1;
     for (i=1,++srchstr ; i < looplim ; ++i,++srchstr) {
          if (sameto(wordstr,srchstr) && isspace(srchstr[-1])) {
               return(TRUE);
          }
     }
     return(FALSE);
}

GBOOL
evalkwd(                           /* reduces and/or/not primitive to y/n  */
CHAR *p)                           /*   keyword primitive (!0&(0|!(1^0)))  */
{
     CHAR image[MAXSKWD];

     while (strlen(p) > 1) {
          stlcpy(image,p,MAXSKWD);
          do {
               do {
                    do {
                         do {
                              do {
                              } while (strsrep(p,"(1)","1")
                                    || strsrep(p,"(0)","0"));
                         } while (strsrep(p,"!1","0") || strsrep(p,"!0","1"));
                    } while (strsrep(p,"1&1","1") || strsrep(p,"1&0","0")
                          || strsrep(p,"0&1","0") || strsrep(p,"0&0","0"));
               } while (strsrep(p,"1^1","0") || strsrep(p,"1^0","1")
                     || strsrep(p,"0^1","1") || strsrep(p,"0^0","0"));
          } while (strsrep(p,"1|1","1") || strsrep(p,"1|0","1")
                || strsrep(p,"0|1","1") || strsrep(p,"0|0","0"));
          if ((strchr(p,'1') == NULL && strchr(p,'0') == NULL)
           || sameas(p,image)) {
               return(FALSE);
          }
     }
     return(*p == '1');
}

INT                                /*   returns standard GME status codes  */
markread(                          /* mark a message as read               */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg,               /*   message header structure buffer    */
CHAR *text)                        /*   message body text buffer           */
{
     INT rc;

     rc=markreadf(pWork,msg,text);
     if (rc > GMEOK) {             /* return receipt was generated         */
          gmePlainText(text);
     }
     return(rc);
}

INT                                /*   returns standard GME status codes  */
markreadf(                         /* mark a message as read (keep format) */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg,               /*   message header structure buffer    */
CHAR *text)                        /*   message body text buffer           */
{
     INT i,rc;
     LONG tmphi;
     struct qscfg *qsc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     ASSERT(msg != NULL);
     ASSERT(text != NULL);
     switch (work->state3) {
     case START:
          if (msg->forum == EMLID) {
               if (work->rdctx.seq != ESQFRU) {
                    gdedcrd(work->rdctx.uid,emrchg,FALSE,TRUE);
               }
          }
          else {
               ASSERT(fidxst(msg->forum));
               gdedcrd(work->rdctx.uid,getdef(msg->forum)->chgrdm,FALSE,TRUE);
          }
          if (work->rdctx.fid == EMLID) {
               if (erdaud) {
                    shocst("E-MAIL MESSAGE READ",
                           "%.29s read #%.10s from %.29s",
                           work->rdctx.uid,l2as(msg->msgid),msg->from);
               }
               if (work->rdctx.seq == ESQTOU
                || (work->rdctx.seq == ESQTHR
                 && sameas(msg->to,work->rdctx.uid))) {
                    if (onsysn(work->rdctx.uid,TRUE)) {
                         if (msg->msgid > othuap->emllim) {
                              othuap->emllim=msg->msgid;
                         }
                    }
                    else {
                         dfaSetBlk(accbb);
                         if (dfaAcqEQ(NULL,work->rdctx.uid,0)) {
                              othuap=(struct usracc *)accbb->data;
                              if (msg->msgid > othuap->emllim) {
                                   othuap->emllim=msg->msgid;
                                   dfaUpdate(NULL);
                              }
                         }
                         dfaRstBlk();
                    }
                    if (msg->flags&RECREQ) {
                         work->state3=GENRRR;
                         formrr(msg,text);
                         chkrqs(pWork,msg);
                    }
                    else {
                         work->state3=START;
                         return(GMEOK);
                    }
               }
               else {
                    work->state3=START;
                    return(GMEOK);
               }
          }
          else {
               if (frdaud) {
                    shocst("FORUM MESSAGE READ","%.29s read from %.15s #%.10s",
                           work->rdctx.uid,getfnm(msg->forum),l2as(msg->msgid));
               }
               qsc=othqsp(work->rdctx.uid);
               if (qsc == NULL) {
                    return(GMEERR);
               }
               i=qsidx(qsc,msg->forum);
               if (i == NOIDX) {
                    i=absadqs(qsc,msg->forum);
                    if (i != NOIDX) {
                         isethi(qsc,i,-msg->msgid);
                         if (qsoffln(qsc)) {
                              dfaSetBlk(qscbb);
                              dfaUpdateV(qsc,qsrlen(qsc->nforums));
                              dfaRstBlk();
                         }
                    }
               }
               else {
                    tmphi=igethi(qsc,i);
                    if (tmphi <= 0L) {
                         if (msg->msgid > -tmphi) {
                              isethi(qsc,i,-msg->msgid);
                              if (qsoffln(qsc)) {
                                   dfaSetBlk(qscbb);
                                   dfaUpdateV(qsc,qsrlen(qsc->nforums));
                                   dfaRstBlk();
                              }
                         }
                    }
                    else if (msg->msgid > tmphi) {
                         isethi(qsc,i,msg->msgid);
                         if (qsoffln(qsc)) {
                              dfaSetBlk(qscbb);
                              dfaUpdateV(qsc,qsrlen(qsc->nforums));
                              dfaRstBlk();
                         }
                    }
               }
               work->state3=START;
               return(GMEOK);
          }
     case GENRRR:
          rc=gme2wnm(pWork,msg,text,NULL);
          if (rc != GMEAGAIN) {
               if (rc > GMEAGAIN) {
                    notnwm(NEWMSG_RTRCP,pWork,msg,text);
                    work->state3=REGET;
                    return(GMEAGAIN);
               }
               work->state3=START;
          }
          return(rc);
     case REGET:
          work->state3=START;
          if (work->flags&FWDMSG) {
               rstafwd(pWork,msg);
               rc=GMEAFWD;
          }
          else {
               rc=GMERRG;
          }
          if (grabmsg(pWork,msg,text)) {
               msg->flags&=~RECREQ;
               if (msg->flags&FRCLR) {
                    clrfrom(msg);
               }
               updmsg(pWork,msg,text,utlapi);
               uclfrom(msg);
               clrwrt(pWork);
               return(rc);
          }
          return(GMENRGM);
     default:
          state_error(3,"markread()",work->state3);
     }
     ASSERT(FALSE);
     clsgmerq(pWork);
     return(GMEERR);     /* this should never happen */
}

VOID
formrr(                            /* form return receipt message          */
struct message *msg,               /*   message header structure to use    */
CHAR *text)                        /*   text buffer to use                 */
{
     ASSERT(msg != NULL);
     ASSERT(text != NULL);
     setmbk(gmemb);
     msg->forum=EMLID;
     stlcpy(text,msg->from,MAXADR);
     stlcpy(msg->from,msg->to,MAXADR);
     stlcpy(msg->to,text,MAXADR);
     sprintf(text,getmsg(RRTEXT),msg->from,l2as(msg->msgid),
             ncedatl(msg->crdate),nctime(msg->crtime),msg->topic);
     stlcpy(msg->topic,spr(rrtpc,l2as(msg->msgid)),TPCSIZ);
     msg->flags=(NOMOD|NODEL);
     ininew(msg);
     msg->thrid=cmptid(msg);
     rstmbk();
}

INT                                /*   returns standard GME status codes  */
delmsg(                            /* delete current message in context    */
VOID *pWork)                       /*   GME work space (provided by caller)*/
{
     INT rc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     switch (work->state3) {
     case START:
          if (!grabmsg(pWork,utlmsg,utltxt)) {
               return(GMENFND);
          }
          if ((rc=chkdel(pWork,utlmsg)) != GMEOK) {
               return(rc);
          }
     default:
          return(gmeGDeleteMsg(pWork));
     }
}

INT                                /*   returns standard GME status codes  */
gmeGDeleteMsg(                     /* delete current msg (non-user spec)   */
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));
     switch (work->state3) {
     case START:
          if (chkcfl(pWork)) {
               return(GMEUSE);
          }
          if (!grabmsg(pWork,utlmsg,utltxt)) {
               return(GMENFND);
          }
          notdlm(DELMSG_NORM,pWork,utlmsg,utltxt);
          if (gme1dlm(pWork)) {
               return(GMEOK);
          }
          return(GMEERR);
     default:
          state_error(3,"gmeGDeleteMsg()",work->state3);
     }
     ASSERT(FALSE);
     clsgmerq(pWork);
     return(GMEERR);     /* this should never happen */
}

INT                                /*   returns standard GME status codes  */
modmsg(                            /* modify current message in context    */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg,               /*   message header structure buffer    */
const CHAR *text)                  /*   message body text buffer           */
{
     GBOOL frflg;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     ASSERT(msg != NULL);
     ASSERT(text != NULL);
     switch (work->state3) {
     case START:
          if (!grabmsg(pWork,utlmsg,utltxt)) {
               return(GMENFND);
          }
          frflg=sameas(utlmsg->from,work->rdctx.uid);
          if (work->rdctx.fid == EMLID) {
               if (!frflg) {
                    return(GMENFND);
               }
               if (utlmsg->flags&NOMOD) {
                    return(GMENMOD);
               }
          }
          else {
               if (gforac(work->rdctx.uid,work->rdctx.fid) < OPAXES && !frflg) {
                    return(GMEACC);
               }
          }
     default:
          return(gmeGModifyMsg(pWork,msg,text));
     }
}

INT                                /*   returns standard GME status codes  */
gmeGModifyMsg(                     /* modify current msg (not user-spec)   */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg,               /*   message header structure buffer    */
const CHAR *text)                  /*   message body text buffer           */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     ASSERT(msg != NULL);
     ASSERT(text != NULL);
     switch (work->state3) {
     case START:
          if (chkcfl(pWork)) {
               return(GMEUSE);
          }
          if (!grabmsg(pWork,utlmsg,utltxt)) {
               return(GMENFND);
          }
          stlcpy(utlmsg->topic,msg->topic,TPCSIZ);
          if (utlmsg->flags&FRCLR) {
               clrfrom(utlmsg);
          }
          if (utlmsg->flags&TOCLR) {
               clrto(utlmsg);
          }
          if (updmsg(pWork,utlmsg,text,utlapi)) {
               notudm(UPDMSG_MODFY,pWork,utlmsg,text);
               return(GMEOK);
          }
          return(GMEERR);
     default:
          state_error(3,"gmeGModifyMsg()",work->state3);
     }
     ASSERT(FALSE);
     clsgmerq(pWork);
     return(GMEERR);     /* this should never happen */
}

INT                                /*   returns standard GME status codes  */
gmeModAppInfo(                     /* modify app-defined info (after read) */
VOID *pWork,                       /*   work area in use                   */
const char *appinf)                /*   app-defined info buffer            */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     switch (work->state3) {
     case START:
          if (chkcfl(pWork)) {
               work->state3=START;
               return(GMEUSE);
          }
          if (!grabmsg(pWork,utlmsg,utltxt)) {
               return(GMENFND);
          }
          work->state3=WRITING;
     case WRITING:
          work->state3=START;
          if (updmsg(pWork,utlmsg,utltxt,appinf)) {
               curapi=(char *)appinf;
               notudm(UPDMSG_APINF,pWork,utlmsg,utltxt);
               curapi=utlapi;
               return(GMEOK);
          }
          return(GMEERR);
     default:
          state_error(3,"gmeModAppInfo()",work->state3);
     }
     ASSERT(FALSE);
     clsgmerq(pWork);
     return(GMEERR);     /* this should never happen */
}

INT                                /*   returns standard GME status codes  */
aprvmsg(                           /* approve/unapprove current message    */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg,               /*   message header structure buffer    */
const CHAR *text,                  /*   message body text buffer           */
GBOOL approve)                     /*   TRUE=approve, FALSE=unapprove      */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     (VOID)text;
     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     ASSERT(work->rdfpos != NULL);
     switch (work->state3) {
     case START:
          if (msg->forum == EMLID) {
               return(GMEERR);
          }
          if (gforac(work->rdctx.uid,msg->forum) < OPAXES) {
               return(GMEACC);
          }
     default:
          return(gmeGApprove(pWork,msg,approve));
     }
}

INT                                /*   returns standard GME status codes  */
gmeGApprove(                       /* approve/unapprove msg (not user-spec)*/
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg,               /*   message header structure buffer    */
GBOOL approve)                     /*   TRUE=approve, FALSE=unapprove      */
{
     struct fordef *tmpdef;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     ASSERT(work->rdfpos != NULL);
     switch (work->state3) {
     case START:
          if (msg->forum == EMLID) {
               return(GMEERR);
          }
          if (grabmsg(pWork,utlmsg,utltxt)) {
               if (!(utlmsg->flags&FILATT)) {
                    return(GMENOAT);
               }
               if (approve) {
                    utlmsg->flags|=FILAPV;
               }
               else {
                    utlmsg->flags&=~FILAPV;
               }
               if (updmsg(pWork,utlmsg,utltxt,utlapi)) {
                    tmpdef=getdef(utlmsg->forum);
                    if (approve) {
                         --tmpdef->nw4app;
                         ++tmpdef->nfiles;
                    }
                    else {
                         ++tmpdef->nw4app;
                         --tmpdef->nfiles;
                    }
                    notudm(UPDMSG_APRV,pWork,utlmsg,utltxt);
                    return(GMEOK);
               }
          }
          return(GMEERR);
     default:
          state_error(3,"gmeGApprove()",work->state3);
     }
     ASSERT(FALSE);
     clsgmerq(pWork);
     return(GMEERR);     /* this should never happen */
}

INT                                /*   returns standard GME status codes  */
exmtmsg(                           /* exempt/unexempt current message      */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg,               /*   message header structure buffer    */
const CHAR *text,                  /*   message body text buffer           */
GBOOL exempt)                      /*   TRUE=exempt, FALSE=unexempt        */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     (VOID)text;
     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     ASSERT(work->rdfpos != NULL);
     switch (work->state3) {
     case START:
          if (msg->forum == EMLID) {
               return(GMEERR);
          }
          if (gforac(work->rdctx.uid,msg->forum) < OPAXES) {
               return(GMEACC);
          }
     default:
          return(gmeGExempt(pWork,msg,exempt));
     }
}

INT                                /*   returns standard GME status codes  */
gmeGExempt(                        /* exempt/unexempt msg (not user-spec)  */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg,               /*   message header structure buffer    */
GBOOL exempt)                      /*   TRUE=exempt, FALSE=unexempt        */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     ASSERT(work->rdfpos != NULL);
     switch (work->state3) {
     case START:
          if (msg->forum == EMLID) {
               return(GMEERR);
          }
          if (grabmsg(pWork,utlmsg,utltxt)) {
               if (exempt) {
                    utlmsg->flags|=EXEMPT;
               }
               else {
                    utlmsg->flags&=~EXEMPT;
               }
               if (updmsg(pWork,utlmsg,utltxt,utlapi)) {
                    notudm(UPDMSG_EXMT,pWork,utlmsg,utltxt);
                    return(GMEOK);
               }
          }
          return(GMEERR);
     default:
          state_error(3,"gmeGExempt()",work->state3);
     }
     ASSERT(FALSE);
     clsgmerq(pWork);
     return(GMEERR);     /* this should never happen */
}

INT                                /*   returns VAL code                   */
setafwd(                           /* set auto-forwardee for current user  */
CHAR *newfwde)                     /*   new auto-forwardee                 */
{
     INT rc;
     struct qscfg *tmpqsp;

     tmpqsp=uqsptr(usrnum);
     if (*newfwde == '\0') {
          setmem(tmpqsp->fwdee,MAXADR,0);
          return(VALYES);
     }
     if (strlen(newfwde) >= MAXADR || isdlst(newfwde) || isforum(newfwde)) {
          return(VALNO);
     }
     inigmerq(utlwork);
     rc=valadr(utlwork,usaptr->userid,newfwde,EMLID);
     clsgmerq(utlwork);
     if (rc == VALCRD) {
          rc=VALYES;
     }
     if (rc == VALYES) {
          tmpqsp->fwdate=today();
          stlcpy(tmpqsp->fwdee,newfwde,MAXADR);
     }
     return(rc);
}

GBOOL
iniecho(                           /* start up forum echoing               */
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(msg->forum != EMLID);
     if (work->flags&NOECHO) {
          return(FALSE);
     }
     if (getdef(msg->forum)->necho > 0) {
          work->echonum=0;
          return(TRUE);
     }
     return(FALSE);
}

INT                                /*   returns standard GME status codes  */
gme1echo(                          /* send message to forum echoes         */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg,               /*   new message structure              */
const CHAR *text,                  /*   message body text                  */
const CHAR *filatt)                /*   path+file name of att (if any)     */
{
     INT rc;
     LONG tmpflg;
     CHAR *curecho;
     struct fordef *tmpdef;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(msg != NULL);
     ASSERT(msg->forum != EMLID);
     ASSERT(text != NULL);
     while (TRUE) {
          switch (work->state1) {
          case START:
               work->state1=NXTECHO;
          case NXTECHO:
               tmpdef=getdef(msg->forum);
               ASSERT(work->echonum < tmpdef->necho);
               tmpflg=msg->flags;
               curecho=((adr_t *)tmpdef->echoes)[work->echonum];
               if (expidx(curecho) == NOIDX) {
                    invecho(curecho,getfnm(msg->forum));
                    work->state1=GETOUT;
                    break;
               }
               work->state1=WRITING;
               if (msg->flags&FILATT) {
                    ASSERT(filatt != NULL);
                    stlcpy(work->cpyatt,expasp(curecho,msg),GCMAXPTH);
                    work->state1=COPYATT;
                    if (!opn4cpy(pWork,filatt)) {
                         noatt(msg);
                         work->state1=WRITING;
                         *work->cpyatt='\0';
                    }
               }
               break;
          case COPYATT:
               rc=copychunk(pWork);
               if (rc > GMEAGAIN) {
                    work->state1=GOWRITE;
                    rc=GMEAGAIN;
               }
               else if (rc < GMEAGAIN) {
                    work->state1=GETOUT;
                    break;
               }
               return(rc);
          case GOWRITE:
               tmpdef=getdef(msg->forum);
               curecho=((adr_t *)tmpdef->echoes)[work->echonum];
               tmpflg=msg->flags;
          case WRITING:
               if (sndexp(pWork,curecho,msg,text,work->cpyatt) <= GMEAGAIN) {
                    invecho(curecho,getfnm(msg->forum));
                    unlink(work->cpyatt);
               }
               *work->cpyatt='\0';
               if (!(msg->flags&FILATT)) {
                    msg->flags=tmpflg;
               }
          case GETOUT:
               if (++work->echonum < tmpdef->necho) {
                    work->state1=NXTECHO;
                    rc=GMEAGAIN;
               }
               else {
                    rc=GMEOK;
                    work->state1=START;
               }
               return(rc);
          default:
               state_error(1,"gme1echo()",work->state1);
          }
     }
}

VOID
invecho(                           /* audit invalid echo address           */
const CHAR *addr,                  /*   echo address                       */
const CHAR *fnam)                  /*   forum name                         */
{
     sprintf((CHAR *)tmpbuf,"Forum: %s, Addr: %s",fnam,addr);
     ((CHAR *)tmpbuf)[AUDSIZ-67+1]='\0'; /* 67 from shocst() */
     shocst("INVALID ECHO ADDRESS",(CHAR *)tmpbuf);
}

GBOOL                              /*   returns TRUE if a valid address    */
massage(                           /* convert address to final format      */
const CHAR *from,                  /*   User-ID of sender                  */
CHAR *to,                          /*   to address                         */
USHORT dest)                       /*   destination (forum ID or E-mail)   */
{
     CHAR *tmp;

     ASSERT(from != NULL);
     ASSERT(uidxst(from));
     ASSERT(to != NULL);
     ASSERT(strlen(to) < MAXADR);
#ifdef DEBUG
     if (dest != EMLID) {
          ASSERT(fidxst(dest));
     }
#endif
     if ((tmp=skpwht(to)) != to) {
          movmem(tmp,to,strlen(tmp)+1);
     }
     unpad(to);
     if (dest == EMLID) {
          return(fixadr(from,to));
     }
     else {
          if (*to == '\0') {
               strcpy(to,ALLINF);
               return(TRUE);
          }
          if (sameas(to,ALLINF)) {
               return(TRUE);
          }
          if (uidxst(to)) {
               stlcpy(to,accbb->key,UIDSIZ);
               return(TRUE);
          }
          if (getdef(dest)->necho != 0) {
               return(TRUE);
          }
          return(TRUE);
     }
}

GBOOL
adrxst(                            /* does this address exist              */
const CHAR *adr)                   /*   address to check                   */
{
     if (isforum(adr) && forAdrXst(adr)) {
          return(TRUE);
     }
     else if (isdlst(adr)) {
          return(dlstxst(adr));
     }
     else if (uidxst(adr)) {
          return(TRUE);
     }
     return(valexa(adr));
}

GBOOL                              /*   returns TRUE if address exists     */
fixadr(                            /* fix up an address                    */
const CHAR *from,                  /*   User-ID of sender (NULL for any)   */
CHAR *adr)                         /*   address to fix up                  */
{
     INT i,fnlen;
     CHAR *fnp,*adrp;

     ASSERT(adr != NULL);
     if (isforum(adr)) {
          if ((i=fnmidx(extAdrForNam(adr))) != NOIDX) {
               fnp=idxdef(i)->name;
               fnlen=strlen(fnp);
               ASSERT(fnlen < FORNSZ);
               movmem(fnp,adr+1,fnlen);
               adrp=unpad(skpwht(adr+fnlen+1));
               if (*adrp == '\0') {
                    adr[fnlen+1]='\0';
               }
               else {
                    if (uidxst(adrp)) {
                         stlcpy(adrp,accbb->key,UIDSIZ);
                    }
                    movmem(adrp,adr+fnlen+2,strlen(adrp)+1);
               }
               return(TRUE);
          }
          return(FALSE);
     }
     else if (isdlst(adr)) {
          if (dlstxst(adr)) {
               strupr(adr);
               return(TRUE);
          }
          return(FALSE);
     }
     else if (islocal(adr) && uidxst(adr)) {
          stlcpy(adr,accbb->key,UIDSIZ);
          return(TRUE);
     }
     else {
          return(masexa(from,adr));
     }
}

VOID
gmeForceEcho(                      /* force imported message to be echoed  */
VOID *pWork)                       /*   work space to be used for impmsg() */
{
     ((struct gmework *)BYTEALIGN(pWork))->flags|=YESECHO;
}

INT                                /*   returns standard GME status codes  */
impmsg(                            /* import a message                     */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg,               /*   new message structure              */
const CHAR *text,                  /*   message body text                  */
const CHAR *filatt)                /*   path+file name of att (if any)     */
{
     INT rc;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     while (TRUE) {
          switch (work->state3) {
          case START:
               if (msg->forum != EMLID && !fidxst(msg->forum)) {
                    clsgmerq(pWork);
                    return(GMEERR);
               }
               if (hdlimphook != NULL) {
                    if ((*hdlimphook)(msg,text,filatt)) {
                         clsgmerq(pWork);
                         return(GMEOK);
                    }
               }
               if (uidxst(msg->to)) {
                    stlcpy(msg->to,((struct usracc *)(accbb->data))->userid,
                           UIDSIZ);
               }
               else if (msg->forum == EMLID && !valexa(msg->to)) {
                    clsgmerq(pWork);
                    return(GMENFND);
               }
               msg->nrpl=0;
               if (msg->crdate == 0U && msg->crtime == 0U) {
                    msg->crdate=today();
                    msg->crtime=now();
               }
               msg->flags&=NONSYSF;
               if (msg->gmid.sysid == 0L || msg->gmid.msgid == 0L) {
                    msg->msgid=newmid();
                    setggid(msg);
               }
               else if (gidxst(msg->forum,&msg->gmid)) {
                    clsgmerq(pWork);
                    return(GMEDUP);
               }
               else {
                    msg->msgid=newmid();
               }
               if (msg->rplto.sysid != 0L && msg->rplto.msgid != 0L
                && msg->forum != EMLID) {
                    inormrd(pWork,"",msg->forum,0);
                    if (gme1rdg(pWork,&msg->rplto,utlmsg,utltxt) == GMEOK) {
                         msg->thrid=utlmsg->thrid;
                         addhist(msg->history,spr(RPLTO,l2as(utlmsg->msgid)));
                    }
               }
               work->state3=GOWRITE;
          case GOWRITE:
               chkrqs(pWork,msg);
               if (!(work->flags&YESECHO)) {
                    work->flags|=NOECHO;
               }
               work->state3=WRITING;
               if (msg->flags&FILATT) {
                    if (*msg->attname == '\0') {
                         stlcpy(msg->attname,attnam(msg->msgid),GCMAXFNM);
                    }
                    if (msg->forum == EMLID && isexpa(msg->to)) {
                         stlcpy(work->cpyatt,expasp(msg->to,msg),GCMAXPTH);
                         if (msg->flags&FILIND) {
                              work->state3=STRTCPY;
                         }
                         else if (sameas(normspec((CHAR *)tmpbuf,filatt),
                                         normspec((CHAR *)tmpbuf+GCMAXPTH,
                                                  work->cpyatt))) {
                              stlcpy(work->auxatt,work->cpyatt,GCMAXPTH);
                         }
                         else {
                              unlink(work->cpyatt);
                              if (rename(filatt,work->cpyatt) == 0) {
                                   stlcpy(work->auxatt,work->cpyatt,GCMAXPTH);
                              }
                              else {
                                   work->state3=STRTCPY;
                              }
                         }
                    }
                    else {
                         stlcpy(work->cpyatt,ulname(msg),GCMAXPTH);
                         if (msg->flags&FILIND) {
                              stlcpy(work->auxatt,filatt,GCMAXPTH);
                         }
                         else {
                              unlink(work->cpyatt);
                              if (rename(filatt,work->cpyatt) == 0) {
                                   stlcpy(work->auxatt,work->cpyatt,GCMAXPTH);
                              }
                              else {
                                   work->state3=STRTCPY;
                              }
                         }
                    }
               }
               break;
          case STRTCPY:
               work->state3=COPYATT;
               if (!opn4cpy(pWork,filatt)) {
                    clsgmerq(pWork);
                    return(GMENOAT);
               }
          case COPYATT:
               rc=copychunk(pWork);
               if (rc > GMEAGAIN) {
                    work->state3=WRITING;
                    if (!(msg->flags&FILIND)) {
                         unlink(filatt);
                    }
                    stlcpy(work->auxatt,work->cpyatt,GCMAXPTH);
                    rc=GMEAGAIN;
               }
               else if (rc < GMEAGAIN) {
                    clsgmerq(pWork);
               }
               return(rc);
          case WRITING:
               rc=gme2wnm(pWork,msg,text,work->auxatt);
               if (rc != GMEAGAIN) {
                    if (rc > GMEAGAIN) {
                         if (work->rdfpos != 0L
                          && grabmsg(pWork,utlmsg,utltxt)) {
                              ++utlmsg->nrpl;
                              if (updmsg(pWork,utlmsg,utltxt,utlapi)) {
                                   notudm(UPDMSG_REPLY,pWork,utlmsg,utltxt);
                              }
                         }
                         notnwm(NEWMSG_IMPRT,pWork,msg,text);
                         if (work->flags&FWDMSG) {
                              rstafwd(pWork,msg);
                              rc=GMEAFWD;
                         }
                    }
                    clsgmerq(pWork);
               }
               return(rc);
          default:
               state_error(3,"impmsg()",work->state3);
          }
     }
}

INT                                /*   returns standard GME status codes  */
gmeGExportMsg(                     /* send a message directly to exporter  */
const CHAR *to,                    /*   address to send message to         */
struct message *msg,               /*   new message structure              */
const CHAR *text,                  /*   message body text                  */
const CHAR *filatt)                /*   path+file name of att (if any)     */
{
     memset(utlwork,0,GMEWRKSZ);
     msg->msgid=newmid();
     setggid(msg);
     if (msg->crdate == 0U && msg->crtime == 0U) {
          msg->crdate=today();
          msg->crtime=now();
     }
     return(sndexp(utlwork,to,msg,text,filatt));
}

VOID
register_exp(                      /* register an exporter                 */
struct exporter *exp)              /*   pointer to exporter control block  */
{
     INT i,cmp;

     if (!valpfx(exp->prefix)) {
          catastro("Invalid exporter prefix: \"%s\"",exp->prefix);
     }
     for (i=0 ; i < nexp ; ++i) {
          cmp=stricmp(exphlr[i]->prefix,exp->prefix);
          if (cmp == 0) {
               catastro("Conflicting exporter prefixes: \"%s\"",exp->prefix);
          }
          else if (cmp > 0) {
               break;
          }
     }
     alcpar((VOID **)&exphlr,nexp,SMLBLK);
     movmem(&exphlr[i],&exphlr[i+1],sizeof(struct exporter *)*(nexp-i));
     exphlr[i]=exp;
     ++nexp;
}

GBOOL
expavl(VOID)                       /* are any exporters available?         */
{
     INT i;

     for (i=0 ; i < nexp ; ++i) {
          if (haskey(exphlr[i]->wrtkey)) {
               return(TRUE);
          }
     }
     return(FALSE);
}

GBOOL
valexa(                            /* is this a valid E-mail export addr?  */
const CHAR *to)                    /*   address to check                   */
{
     INT i;

     ASSERT(to != NULL);
     ASSERT(strlen(to) < MAXADR);
     if (nexp == 0) {
          return(FALSE);
     }
     if (isexpa(to)) {
          i=expidx(to);
          if (i != NOIDX) {
               return((*exphlr[i]->valadr)(skppfx(to)));
          }
     }
     return(FALSE);
}

GBOOL                              /*   returns TRUE if a valid address    */
masexa(                            /* convert export address to proper form*/
const CHAR *from,                  /*   User-ID of sender (NULL for any)   */
CHAR *to)                          /*   address to check                   */
{
     INT i,numyes;
     UINT len;
     struct exporter *tmpexp,*lastexp;

     ASSERT(to != NULL);
     ASSERT(strlen(to) < MAXADR);
     if (nexp == 0) {
          return(FALSE);
     }
     if (isexpa(to)) {
          i=expidx(to);
          if (i != NOIDX) {
               return((from == NULL ? TRUE : uhskey(from,exphlr[i]->wrtkey))
                   && (*exphlr[i]->valadr)(skppfx(to)));
          }
          if (strxck) {
               return(FALSE);
          }
     }
     for (i=0,numyes=0 ; i < nexp ; ++i) {
          tmpexp=exphlr[i];
          if ((from == NULL || uhskey(from,tmpexp->wrtkey))
           && (*tmpexp->valadr)(to)) {
               ++numyes;
               lastexp=tmpexp;
          }
     }
     if (numyes == 1) {
          i=strlen(lastexp->prefix);
          len=strlen(to)+1;
          if (len > MAXADR-i-1) {
               len=MAXADR-i-1;
               to[len-1]='\0';
          }
          movmem(to,to+i+1,len);
          strcpy(to,lastexp->prefix);
          to[i]=':';
          return(TRUE);
     }
     return(FALSE);
}

INT
numexp(VOID)                       /* get number of exporters on system    */
{
     return(nexp);
}

INT                                /*   returns standard GME status codes  */
sndexp(                            /* send a message an exporter           */
VOID *pWork,                       /*   work area in use                   */
const CHAR *to,                    /*   address to send message to         */
const struct message *msg,         /*   new message structure              */
const CHAR *text,                  /*   message body text                  */
const CHAR *filatt)                /*   path+file name of att (if any)     */
{
     INT i;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     if (to == NULL || *to == '\0') {
          to=msg->to;
     }
     i=expidx(to);
     if (i == NOIDX) {
          return(GMEERR);
     }
     if (!(exphlr[i]->flags&EXPFMT) && isfmtted(text)
      && cvt2asc(text,utltxt+1,TXTLEN)) {
          utltxt[0]='\r';
          text=utltxt;
     }
     if (work->flags&FWDMSG) {
          stlcpy(extinf,work->auxto,UIDSIZ);
     }
     else {
          stlcpy(extinf,work->rdctx.uid,UIDSIZ);
     }
     curapi=(char *)work->appinf;
     i=(*exphlr[i]->sndmsg)(skppfx(to),msg,text,filatt);
     curapi=utlapi;
     *extinf='\0';
     return(i);
}

INT                                /*   returns NOIDX if not found         */
expidx(                            /* index of exporter in handler array   */
const CHAR *to)                    /*   address containing prefix          */
{
     INT i;

     ASSERT(to != NULL);
     for (i=0 ; i < nexp ; ++i) {
          if (thisexp(exphlr[i],to)) {
               return(i);
          }
     }
     return(NOIDX);
}

const CHAR *
expasp(                            /* get exporter attachment file spec    */
const CHAR *to,                    /*   address message is being sent to   */
const struct message *msg)         /*   given message header structure     */
{
     INT i;

     if (to == NULL || *to == '\0') {
          to=msg->to;
     }
     ASSERT(expidx(to) != NOIDX);
     i=expidx(to);
     if (exphlr[i]->flags&EXPATT) {
          return((*exphlr[i]->attspc)(skppfx(to),msg));
     }
     return("");
}

const struct expinfo *             /*   returns pointer to static buffer   */
expinf(                            /* get info on an exporter              */
INT idx)                           /*   given exporter index               */
{
     static struct expinfo expinfo;

     ASSERT(idx >= 0 && idx < nexp);
     return(exp2inf(exphlr[idx],&expinfo));
}

const CHAR *                       /*   ptr to temp area (may be msgbuf)   */
exphlp(                            /* get help message for an exporter     */
INT idx)                           /*   given exporter index               */
{
     ASSERT(idx >= 0 && idx < nexp);
     return((*exphlr[idx]->helpmsg)());
}
