/***************************************************************************
 *                                                                         *
 *   AAEFU.C                                                               *
 *                                                                         *
 *   Copyright (c) 1988-1997 Galacticomm, Inc.    All Rights Reserved.     *
 *                                                                         *
 *   ASCII/ANSI E-mail and Forums utilities and common functions.          *
 *                                                                         *
 *                                                - J. Alvrus 8/2/94       *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "majorbbs.h"
#include "gme.h"
#include "filexfer.h"
#include "galmsg.h"
#include "emlfor.h"
#include "aaef.h"
#include "efhooks.h"

#define FILREV "$Revision: 18 $"

struct efvda *efvda=NULL;

VOID *efwork;                      /* GME request work area in VDA         */
struct qikdat *qikbuf;             /* !QUICK struct in VDA                 */
struct fordef *fdef;               /* forum def struct in VDA              */
struct fordsk *fdsk;               /* on-disk forum struct in VDA          */
CHAR *desc,                        /* forum description buf in VDA         */
     *msgatr,                      /* ANSI message body color string       */
     *tagkey;                      /* key req. to tag attachments          */
adr_t *echo;                       /* forum echoes buffer in VDA           */
GBOOL cb4hdr;                      /* A/A clear before message (non-browse)*/
SHORT cmthdrsz;                    /* size of combined comment headers     */
SHORT ccmax;                       /* max # CCs to allow for A/A           */
UINT msgbyts;                      /* max msg text size (backward compat.) */

static quotfunc quoter=NULL;       /* current message quoting function     */
static ccadfunc ccadder=NULL;      /* current cc: list adder function      */

VOID edreply(VOID);
VOID quotem(VOID);
VOID dftquo(struct message *qmsg,CHAR *qtxt,UINT qlen);
USHORT linlen(CHAR *ptr);
GBOOL ccok(VOID);
INT efuphl(INT fupcode);
VOID alofsnd(VOID);
VOID alofbeg(VOID);
VOID add2lst(CHAR *newadr);
CHAR *addcclst(CHAR *text,CHAR *list,CHAR *to);
CHAR *dftadcc(CHAR *text,CHAR *list,CHAR *to);
VOID sendit(VOID);
VOID wrtprg(INT evt,INT rc);
VOID wnotify(SHORT rc);
VOID dnotify(SHORT rc);
VOID sendone(SHORT rc);
GBOOL meddone(SHORT edflgs);
VOID modit(VOID);
INT efdnhl(INT tshcode);
VOID addcmt(VOID);
GBOOL cmtdone(SHORT edflgs);
VOID fwdcpycb(INT evt,INT rc);
static CHAR *nrstr(SHORT nr);
CHAR *adrfld(CHAR *adr,CHAR *buf,SHORT maxlen);
CHAR *hststr(CHAR *history);
static CHAR *datlin(USHORT date,USHORT time);
CHAR *fnmlin(USHORT forum);
CHAR *cfortvar(VOID);
GBOOL foppkey(INT unum,const CHAR *lock);
USHORT ucurfor(INT unum);

VOID
iniaa(VOID)                        /* initialize ASCII/ANSI stuff          */
{
     UINT tmpvsz;

     msgbyts=txtlen();
     tmpvsz=(UINT)(efvda->u.m.msgtxt-(CHAR *)efvda)+txtlen()-1;
     dclvda(max(sizeof(struct efvda),tmpvsz));
     cb4hdr=ynopt(CB4HDR);
     tagkey=stgopt(TAGKEY);
     msgatr=stgopt(MSGATR);
     cmthdrsz=strlen(abvcmt)+strlen(blwcmt)+2*UIDSIZ;
     ccmax=numopt(CCMAX,0,32767);
     if (!setcfl(efcflck)) {
          catastro("Not enough memory to register E-mail/Forums conflict checker");
     }
     if (quoter == NULL) {
          setquoter(dftquo);
     }
     if (ccadder == NULL) {
          setccadder(dftadcc);
     }
     iniaaeml();
     iniaafor();
     register_textvar("CURRENT_FORUM",cfortvar);
     register_pseudok("_FORUMOP",foppkey);
}

GBOOL
efcflck(                           /* E-mail/Forums conflict checker       */
VOID *work,                        /*   work area to check                 */
USHORT forum,                      /*   forum to check                     */
LONG msgid)                        /*   message ID to check                */
{
     INT i,tmpusn,savusn;
     struct user *tmpusp,*savusp;
     struct qscfg *tmpqsp;
     struct efvda *tmpvda;

     savusn=usrnum;
     savusp=usrptr;
     if (msgid == 0L) {
          for (tmpusn=0 ; tmpusn < nterms ; tmpusn++) {
            tmpusp=usroff(tmpusn);
               if (tmpusp->usrcls > SUPIPG) {
                    tmpvda=(struct efvda *)vdaoff(tmpusn);
                    if ((tmpusp->state == emlstate || tmpusp->state == forstate
                     || (peruflg[tmpusn]&(INEDIT|INFTF|INFSD|INEDHLP)))
                     && work != tmpvda->efwork) {
                         tmpqsp=uqsptr(tmpusn);
                         if (forum == tmpqsp->curfor) {
                              usrnum=savusn;
                              usrptr=savusp;
                              return(TRUE);
                         }
                         if (usrscn[tmpusn] != NULL) {
                              for (i=0 ; i < MAXPRV ; ++i) {
                                   if (forum == tmpvda->prvlst[i].fid) {
                                        usrnum=savusn;
                                        usrptr=savusp;
                                        return(TRUE);
                                   }
                              }
                         }
                    }
                    usrnum=tmpusn;
                    usrptr=tmpusp;
                    setftu();
                    for (i=0 ; i < ftuptr->numftg ; i++) {
                         if (ftgusr[i].tshndl == efdnhl
                          && forintg(ftgusr[i].tagspc) == forum) {
                              usrnum=savusn;
                              usrptr=savusp;
                              return(TRUE);
                         }
                    }
               }
          }
     }
     else {
          for (tmpusn=0 ; tmpusn < nterms ; tmpusn++) {
            tmpusp=usroff(tmpusn);
               if (tmpusp->usrcls > SUPIPG) {
                    tmpvda=(struct efvda *)vdaoff(tmpusn);
                    if ((tmpusp->state == emlstate || tmpusp->state == forstate
                     || (peruflg[tmpusn]&(INEDIT|INFTF)))
                     && work != tmpvda->efwork) {
                         if (msgid == tmpvda->curmid
                          || msgid == tmpvda->prethr) {
                              usrnum=savusn;
                              usrptr=savusp;
                              return(TRUE);
                         }
                    }
                    usrnum=tmpusn;
                    usrptr=tmpusp;
                    setftu();
                    for (i=0 ; i < ftuptr->numftg ; i++) {
                         if (ftgusr[i].tshndl == efdnhl
                    && msgintg(ftgusr[i].tagspc) == msgid) {
                              usrnum=savusn;
                              usrptr=savusp;
                              return(TRUE);
                         }
                    }
               }
          }
     }
     usrnum=savusn;
     usrptr=savusp;
     return(FALSE);
}

USHORT                             /*   updated spinner position           */
spin(                              /* output a spinner                     */
USHORT count)                      /*   spinner position index             */
{
     static CHAR spinner[]={"/-\\|"};

     ++count;
     if (count > 3) {
          count=0;
     }
     prf("\b%c",spinner[count]);
     return(count);
}

VOID
setmeup(VOID)                      /* set up E-mail/Forums files/pointers  */
{
     setmbk(efmb);
     efvda=(struct efvda *)vdaptr;
     efwork=efvda->efwork;
     qsptr=myqsptr();
     msg=&efvda->u.m.msg;
     msgtxt=efvda->u.m.msgtxt;
     filatt=efvda->u.m.filatt;
     qikbuf=&efvda->u.q.qik;
     fdef=&efvda->u.f.d.def;
     fdsk=&efvda->u.f.d.dsk;
     echo=(adr_t *)efvda->u.f.v.echo;
     desc=(CHAR *)echo;
}

GBOOL                              /*   returns TRUE if user warned        */
pfnwrn(VOID)                       /* warn user about profanity            */
{
     GBOOL rc=FALSE;

     if (usrptr->pfnacc > MAXPFN) {
          byenow(MUCH2P);
          return(TRUE);
     }
     if (usrptr->pfnacc > WRNPFN && pfnlvl > 0) {
          prfmsg(RAUNCH);
          rc=TRUE;
     }
     else if (pfnlvl > 2) {
          prfmsg(PFNMSG);
          rc=TRUE;
     }
     if (rc) {
          outprf(usrnum);
          cncall();
          injacr();
     }
     return(rc);
}

VOID
gostt(                             /* prompt and change substate           */
INT stt)                           /*   state to go to                     */
{
     struct otscan *ots;

     if (isripu() || shortm) {
          switch (stt) {
          case EMAINS:
               stt=EMAINM;
               break;
          case SPECFS:
               stt=SPECFN;
               break;
          case LSPECFS:
               stt=LSPECFN;
               break;
          case FMAINS:
               stt=FMAINM;
               break;
          case FINDS:
               stt=FINDM;
               break;
          case FOPMNS:
               stt=FOPMNU;
               break;
          case OPRMNS:
               stt=OPRMNU;
               break;
          }
     }
     switch (stt) {
     default:
#ifdef DEBUG
       catastro("Update your state list in gostt() (state=%d)",stt);
#endif
     case LONRDN:
     case EMAINM:
     case EMAINS:
     case ERTFQN:
     case ERTFQL:
     case ERFQF:
     case GONUM:
     case ESCAN:
     case SCNEND:
     case SCNENDX:
     case SCNBEG:
     case SCNBEGX:
     case ERDTO:
     case ERDFR:
     case ERDPAR:
     case EFREAD:
     case EFOREAD:
     case USEQUO:
     case WHOFWD:
     case RAFABT:
     case WHOCPY:
     case CMTPMT:
     case EWRTTO:
     case WHO2CC:
     case MODWHT:
     case DELMSN:
     case SPECFN:
     case SPECFS:
     case LSPECFN:
     case LSPECFS:
     case ENTFWD:
     case CHGFWD:
     case NEMPRF:
     case FMTPRF:
     case RMDPRF:
     case QUOPRF:
     case CARPRF:
     case CMTPRF:
     case L2EDPMT:
     case ACCLVK:
     case SYSENT:
     case QIKPMT:
     case STARTF:
     case STARTN:
     case FTDSCN:
     case FTSCAN:
     case FDSCAN:
     case FSCAN:
     case FSCBEG:
     case FSCBEGX:
     case FSCEND:
     case FSCENDX:
     case THRFBP:
     case FREAD:
     case FAREAD:
     case FTREAD:
     case FOREAD:
     case FORMTO:
     case FORETO:
     case FAUTH:
     case FOPRDA:
     case FOPRDU:
     case FOPRDX:
     case FINDM:
     case FINDS:
     case OTADF:
     case OTRMF:
     case OTNMSG:
     case OTATONL:
     case OT2UONL:
     case OTFUONL:
     case OTSMSG:
     case OTKWDS:
     case LSTWHAT:
     case LISTYP:
     case STRTLST:
     case QCADD:
     case QCDEL:
     case QSNMSG:
     case QSATONL:
     case QS2UONL:
     case QSFUONL:
     case QSSMSG:
     case QSKWDS:
     case WHATFOR:
     case FOPMNU:
     case FOPMNS:
     case OPRMNS:
     case FCRTNAM:
     case FSDCHG:
     case EDECHO:
     case ECH2ADD:
     case ECH2DEL:
     case FOR2DEL:
     case SETDFAC:
     case UID2SET:
     case FORUID:
     case SYSUID:
     case FSETACH:
     case SSETACH:
     case UCPYSRC:
     case UCPDST:
     case UCPDST2:
          prfmsg(stt);
          break;
     case SRCHMNU:
          ots=usrscn[usrnum];
          prfmsg(stt,ynstr(ots->flags,SCNEW),ynstr(ots->flags,SCATT),
                 ynstr(ots->flags,SCTOU),ynstr(ots->flags,SCFRU),
                 l2as(ots->stmsgid),ots->keywds);
          break;
     case QSCFGM:
          prfmsg(stt,ynstr(qsptr->flags,NEWMSG),ynstr(qsptr->flags,WATONL),
                 ynstr(qsptr->flags,TOMEONL),ynstr(qsptr->flags,FRMEONL),
                 l2as(qsptr->stmsg),qsptr->kwds);
          break;
     case OPRMNU:
          prfmsg(stt,curfnm(),curftpc());
          break;
     case CRTFORM:
          prfmsg(stt,fdsk->name,fdsk->topic);
          break;
     case EDTFMNU:
          prfmsg(stt,fdef->name,fdef->topic);
          break;
     case CNFDEL:
          prfmsg(stt,fdef->name,fdef->topic,fdef->nmsgs,fdef->nfiles);
          break;
     case CFGACCM:
          prfmsg(stt,curfnm(),curftpc(),accstr(efvda->u.a.dfnpv),
                 accstr(efvda->u.a.dfprv),accstr(efvda->u.a.mxnpv),
                 efvda->u.a.forlok);
          break;
     case MODLOC:
          prfmsg(stt,forprv);
          break;
     case FMAINM:
     case FMAINS:
          gfmain(stt == FMAINS);
          break;
     case POSTWRT:
          shopwm();
          break;
     case NAMATT:
          if (msg->attname[0] == '\0') {
               prfmsg(NAMATN);
          }
          else {
               prfmsg(NAMATT,msg->attname);
          }
          break;
     case DLNOW:
          prfmsg(ATTNOT,msg->attname,l2as(efvda->attsiz));
          prfmsg(stt);
          break;
     case LSTCHG:
          prfmsg(stt,MINLCH,MAXLCH);
          break;
     case QIKEDT:
          prfmsg(stt,efvda->count+1);
          break;
     case SETPRF:
          dsetprf(stt);
          break;
     }
     usrptr->substt=stt;
}

VOID
errhlp(                            /* display error/help message & reprompt*/
INT ehmsg,                         /*   error/help message to display      */
INT repmt)                         /*   reprompt with this message         */
{
     cncall();
     clrinp();
     prfmsg(ehmsg);
     gostt(repmt);
}

VOID
startlst(                          /* fire up a cycled listing process     */
GBOOL (*nxtlst)(VOID),             /*   function to output next line       */
VOID (*whndun)(SHORT rc))          /*   function to call when finished     */
{
     usrptr->substt=LISTING;
     efvda->cflags&=~LSTDONE;
     efvda->nxtlst=nxtlst;
     efvda->whndun=whndun;
     lister();
}

VOID
lister(VOID)                       /* cycled show-a-list process           */
{
     if (efvda->cflags&LSTDONE) {
          if (btuoba(usrnum) < OUTSIZ-LISTIL) {
               docyc(FALSE);
          }
          else {
               (*efvda->whndun)(FALSE);
               chkcyc();
          }
     }
     else {
          if (btuoba(usrnum) < MAXLSI) {
               docyc(FALSE);
          }
          else if ((*efvda->nxtlst)()) {
               outprf(usrnum);
               clrprf();
               docyc(FALSE);
          }
          else {
               efvda->cflags|=LSTDONE;
               if (btuoba(usrnum) < OUTSIZ-LISTIL) {
                    docyc(FALSE);
               }
               else {
                    (*efvda->whndun)(FALSE);
                    chkcyc();
               }
          }
     }
}

VOID
wrthlp(VOID)                       /* show help for who-to-write prompt    */
{
     SHORT i,nexp;
     const struct expinfo *tmpinf;

     switch (wrtany()) {
     case VALACC:
          prfmsg(NWRTACC);
          break;
     case VALCRD:
          prfmsg(NWRTCRD);
          break;
     case VALYES:
          prfmsg(WEHELP);
          if (expavl()) {
               nexp=numexp();
               for (i=0 ; i < nexp ; ++i) {
                    tmpinf=expinf(i);
                    if (haskey(tmpinf->wrtkey)) {
                         prfmsg(WEXHLP,tmpinf->name,tmpinf->prefix,
                                tmpinf->exmp);
                    }
               }
          }
          prfmsg(WEHLP2);
          break;
     }
}

VOID
startwrt(                          /* start message write process          */
VOID (*whndun)(SHORT rc))          /*   function to invoke when finished   */
{
     stlcpy(msg->from,usaptr->userid,MAXADR);
     efvda->whndun=whndun;
     efvda->numcc=0;
     efvda->flags&=~(CCLST|LOFUPL);
     efvda->flags&=~ISRPL;
     goedit(ED_CLRTXT+ED_CLRTOP);
}

VOID
startrpl(                          /* start reply process                  */
VOID (*whndun)(SHORT rc))          /*   function to invoke when finished   */
{
     stlcpy(msg->to,msg->from,MAXADR);
     stlcpy(msg->from,usaptr->userid,MAXADR);
     efvda->whndun=whndun;
     efvda->numcc=0;
     efvda->flags&=~(CCLST|LOFUPL);
     efvda->flags|=ISRPL;
     edreply();
}

VOID
edreply(VOID)                      /* start editing reply w/possible quote */
{
     if ((qsptr->flags&MSGQUO)) {
          if (qsptr->flags&ALWQUO) {
               if (!(qsptr->flags&USRSET)) {
                    prfmsg(QUOUSD);
                    outprf(usrnum);
               }
               quotem();
               goedit(0);
          }
          else {
               gostt(USEQUO);
          }
     }
     else {
          goedit(ED_CLRTXT);
     }
}

VOID
susequo(VOID)                      /* state: use quoting when replying?    */
{
     switch (cncyesno()) {
     case 'Y':
          quotem();
          goedit(0);
          break;
     case 'N':
          goedit(ED_CLRTXT);
          break;
     default:
          cncall();
          prfmsg(YORN);
          gostt(USEQUO);
          break;
     }
}

VOID
goedit(                            /* start editing a message              */
INT flags)                         /*   flags for editor                   */
{
     INT pflvl;
     USHORT dstfor;

     peruflg[usrnum]|=INEDIT;
     efvda->savstt=usrptr->state;
     lf2cr(msgtxt);
     zpad(msgtxt,TXTLEN);          /* to aVOID possible FSE comprbuf() err */
     bgnedt(TXTLEN,msgtxt,TPCSIZ,msg->topic,edtdone,flags);
     edtimr(efimr);
     dstfor=msg->forum;
     if (msg->forum == EMLID && isforum(msg->to)) {
          dstfor=getAdrForID(msg->to);
     }
     if (dstfor != EMLID) {
          pflvl=getdefp(dstfor)->pfnlvl;
          if (pflvl != DFTPFN) {
               edtpfn(pflvl);
          }
     }
}

GBOOL                              /*   returns TRUE if found spec'd msg   */
efimr(                             /* import message into editor           */
LONG msgid)                        /*   message ID to import               */
{
     SHORT rc;
     VOID *tmpwork;
     struct message *tmpmsg;

     tmpwork=((struct efvda *)vdatmp)->efwork;
     setmem(tmpwork,GMEWRKSZ,0);
     tmpmsg=&((struct efvda *)vdatmp)->u.m.msg;
     msgtxt=((struct efvda *)vdaptr)->u.m.msgtxt;
     inigmerq(tmpwork);
     if (peruflg[usrnum]&INEMAIL) {
          inormrd(tmpwork,usaptr->userid,EMLID,msgid);
          while ((rc=readmsg(tmpwork,tmpmsg,msgtxt)) == GMEAGAIN) {
               /* exact reads should not require more than 1 cycle */
          }
          if (rc != GMEOK) {
               seqctx(tmpwork,ESQFRU);
               while ((rc=readmsg(tmpwork,tmpmsg,msgtxt)) == GMEAGAIN) {
               /* exact reads should not require more than 1 cycle */
               }
          }
     }
     else {
          inormrd(tmpwork,usaptr->userid,myqsptr()->curfor,msgid);
          while ((rc=readmsg(tmpwork,tmpmsg,msgtxt)) == GMEAGAIN) {
               /* exact reads should not require more than 1 cycle */
          }
     }
     clsgmerq(tmpwork);
     return(rc == GMEOK);
}

GBOOL                              /*   returns TRUE to stay in module     */
edtdone(                           /* finished editing message             */
SHORT edflgs)                      /*   edit result flags                  */
{
     setmeup();
     peruflg[usrnum]&=~INEDIT;
     usrptr->state=efvda->savstt;
     if (edflgs&ED_QUITEX) {
          prfmsg(WABORT);
          (*efvda->whndun)(TRUE);
     }
     else {
          chkitout();
          if ((efvda->flags&(ATTOK|RRROK|PRIOK)) || ccok()) {
               gostt(POSTWRT);
          }
          else {
               sendit();
          }
     }
     efvda->dftchr=getdft();
     outprf(usrnum);
     return(TRUE);
}

VOID
chkitout(VOID)                     /* check available post-write options   */
{
     efvda->flags&=~(ATTOK+RRROK+PRIOK);
     if (valatt(efwork,msg->from,msg->to,msg->forum) == VALYES) {
          efvda->flags|=ATTOK;
     }
     if (valrrr(efwork,msg->from,msg->to,msg->forum) == VALYES) {
          efvda->flags|=RRROK;
     }
     if (valpri(efwork,msg->from,msg->to,msg->forum) == VALYES) {
          efvda->flags|=PRIOK;
     }
}

VOID
shopwm(VOID)                       /* show post-write menu                 */
{
     prfmsg(PWHDR);
     if (efvda->flags&ATTOK) {
          if (msg->flags&FILATT) {
               prfmsg(PWDET,msg->attname);
          }
          else {
               prfmsg(PWATT);
          }
     }
     if (efvda->flags&RRROK) {
          if (msg->flags&RECREQ) {
               prfmsg(PWCRR);
          }
          else {
               prfmsg(PWRRR);
          }
     }
     if (efvda->flags&PRIOK) {
          if (msg->flags&PRIMSG) {
               prfmsg(PWCHP);
          }
          else {
               prfmsg(PWRHP);
          }
     }
     if (ccok()) {
          prfmsg(PWCPY);
          if (efvda->numcc > 0) {
               if (efvda->flags&CCLST) {
                    prfmsg(PWNLC);
               }
               else {
                    prfmsg(PWYLC);
               }
          }
     }
     prfmsg(PWMOD);
     prfmsg(POSTWRT);
}

VOID
shopwh(VOID)                       /* show post-write help                 */
{
     prfmsg(PWHHDR);
     if (efvda->flags&ATTOK) {
          prfmsg(PWAHLP);
     }
     if (efvda->flags&RRROK) {
          prfmsg(PWRHLP);
     }
     if (efvda->flags&PRIOK) {
          prfmsg(PWPHLP);
     }
     if (ccok()) {
          prfmsg(PWCHLP);
     }
     prfmsg(PWHTLR);
}

GBOOL
ccok(VOID)                         /* can user send cc: of this message?   */
{
     return(((peruflg[usrnum]&INEMAIL) && alwcpy && wrtany() != VALACC
          && (ccmax > 0 || (usrptr->flags&MASTER))) ? TRUE : FALSE);
}

VOID
spostwrt(VOID)                     /* state: E-mail/Forums post-write menu */
{
     switch (cncchr()) {
     case 'A':
          if ((efvda->flags&ATTOK) && !(msg->flags&FILATT)) {
               efupload();
          }
          else {
               errhlp(CNOTIL,POSTWRT);
          }
          break;
     case 'D':
          if ((efvda->flags&ATTOK) && (msg->flags&FILATT)) {
               prfmsg(DETING,msg->attname);
               if (!(msg->flags&FILIND)) {
                    unlink(filatt);
               }
               msg->flags&=~(FILATT|FILIND);
               setmem(msg->attname,GCMAXFNM,0);
               setmem(filatt,GCMAXPTH,0);
               shopwm();
          }
          else {
               errhlp(CNOTIL,POSTWRT);
          }
          break;
     case 'R':
          if (efvda->flags&RRROK) {
               if (msg->flags&RECREQ) {
                    msg->flags&=~RECREQ;
               }
               else {
                    msg->flags|=RECREQ;
               }
          }
          else {
               prfmsg(CNOTIL);
          }
          shopwm();
          break;
     case 'P':
          if (efvda->flags&PRIOK) {
               if (msg->flags&PRIMSG) {
                    msg->flags&=~PRIMSG;
               }
               else {
                    msg->flags|=PRIMSG;
               }
          }
          else {
               prfmsg(CNOTIL);
          }
          shopwm();
          break;
     case 'C':
          if (ccok()) {
               gostt(WHO2CC);
          }
          else {
               errhlp(CNOTIL,POSTWRT);
          }
          break;
     case 'L':
          if (ccok() && efvda->numcc > 0) {
               if (efvda->flags&CCLST) {
                    efvda->flags&=~CCLST;
               }
               else {
                    efvda->flags|=CCLST;
               }
          }
          else {
               prfmsg(CNOTIL);
          }
          shopwm();
          break;
     case 'M':
          goedit(0);
          break;
     case '?':
          shopwh();
          shopwm();
          break;
     case '.':
          if (efvda->flags&CCLST) {
               addcclst(msgtxt,efvda->cclist,msg->to);
          }
          sendit();
          break;
     default:
          errhlp(CNOTIL,POSTWRT);
          break;
     }
}

VOID
efupload(VOID)                     /* E-mail/Forums upload utility         */
{
     efvda->flags&=~FUPOK;
     stlcpy(filatt,ulname(msg),GCMAXPTH);
     ASSERT(!fexist(filatt));
     peruflg[usrnum]|=INFTF;
     efvda->savstt=usrptr->state;
     if (morcnc()) {
          fileup("attachment",cncall(),efuphl);
     }
     else {
          fileup("attachment","?",efuphl);
     }
     ftuptr->flags|=FTFREF;
}

INT                                /*   response code                      */
efuphl(                            /* E-mail/Forums upload handler         */
INT fupcode)                       /*   FTF action code                    */
{
     SHORT rc=0;
     CHAR fptmp[GCMAXFNM];

     setmeup();
     switch (fupcode) {
     case FUPPTH:
          break;
     case FUPBEG:
          if (ftfscb->actfil > 0) {
               strcpy(ftfbuf,"You may only upload one attachment per message.");
          }
          else {
               if (ftuptr->flags&FTBANG) {
                   ftuptr->flags&=~FTBANG;
                   efvda->flags|=LOFUPL;
               }
               stlcpy(msg->attname,ftfscb->fname,GCMAXFNM);
               fnmcse(msg->attname);
               stlcpy(ftfbuf,filatt,GCMAXPTH);
               rc=1;
          }
          break;
     case FUPREF:
          msg->flags|=FILIND;
          unlink(filatt);
          stlcpy(filatt,ftfbuf,GCMAXPTH);
          stlcpy(msg->attname,fileparts(GCPART_FNAM,filatt,fptmp,GCMAXFNM),
           GCMAXFNM);
          fnmcse(msg->attname);
          break;
     case FUPEND:
          efvda->flags|=FUPOK;
          msg->flags|=FILATT;
          if (msg->flags&FILIND) {
               ftfbuf[0]='\0';
          }
          else {
               stlcpy(ftfbuf,filatt,GCMAXPTH);
          }
          break;
     case FUPSKP:
          efvda->flags&=~FUPOK;
          msg->flags&=~(FILATT+FILIND);
          unlink(filatt);
          break;
     case FUPFIN:
          peruflg[usrnum]&=~INFTF;
          usrptr->state=efvda->savstt;
          if (efvda->flags&FUPOK) {
               if (efvda->flags&LOFUPL) {
                   alofsnd();
               }
               else {
                   gostt(NAMATT);
               }
          }
          else {
               errhlp(NUPLD,POSTWRT);
          }
          efvda->dftchr=getdft();
          rc=1;
          break;
     case FUPHUP:
          break;
     default:
          ASSERT(FALSE);
     }
     return(rc);
}

VOID
alofsnd(VOID)                      /* logoff-after-upload start send       */
{
     INT savbrk;
     CHAR fname[GCMAXPTH];

     if (msg->attname[0] == '\0') {
          fileparts(GCPART_FNAM,filatt,fname,sizeof(fname));
          stlcpy(msg->attname,fname,GCMAXFNM);
     }
     if (efvda->flags&CCLST) {
          addcclst(msgtxt,efvda->cclist,msg->to);
     }
     savbrk=usaptr->scnbrk;
     usaptr->scnbrk=CTNUOS;
     rstrxf();
     usaptr->scnbrk=savbrk;
     sendit();
}

VOID
alofbeg(VOID)                      /* begin logoff-after-upload            */
{
     cycall();
     efvda->cflags&=~DIDCYC;
     prf(getasc(alwait == 0 ? ALOFFAST : AUTOLOF),alwait);
     efvda->count=0;
     usrptr->substt=AUTOLOF;
     btuech(usrnum,0);
     outprf(usrnum);
     btuinj(usrnum,CYCLE);
}

VOID
clofwait(VOID)                     /* handle cycling during logoff wait    */
{
     if (efvda->count == 0) {
          if (btuoba(usrnum) >= OUTSIZ-1) {
               efvda->count=(INT)((UINT)(hrtval()>>16)+alwait);
          }
     }
     else if ((UINT)(hrtval()>>16) > (UINT)efvda->count) {
          usrptr->substt=ALOFNOW;
          if (alwait != 0) {
               prfmsg(ALOFNOW);
               outprf(usrnum);
          }
          btuinj(usrnum,RING);
          return;
     }
     btuinj(usrnum,CYCLE);
}

VOID
slofwait(VOID)                     /* handle user input during logoff wait */
{
     efvda->cflags&=~DIDCYC;
     cncall();
     prfmsg(ALOFABT);
     rstrxf();
     echon();
     (*efvda->whndun)(FALSE);
     efvda->dftchr=getdft();
     outprf(usrnum);
     clrprf();
}

VOID
snamatt(VOID)                      /* state: enter name of attachment      */
{
     CHAR *tmpnam;

     if (morcnc() == '.') {
          cncchr();
          if (*msg->attname == '\0') {
               errhlp(NOBLANK,NAMATT);
          }
          else {
               gostt(POSTWRT);
          }
     }
     else if (morcnc() == '?') {
          cncchr();
          errhlp(ATNMHLP,NAMATT);
     }
     else {
          tmpnam=cncwrd();
          if (valfnm(tmpnam)) {
               stlcpy(msg->attname,fnmcse(tmpnam),GCMAXFNM);
               gostt(POSTWRT);
          }
          else {
               errhlp(INVANM,NAMATT);
          }
     }
}

VOID
swhotocc(VOID)                     /* state: to whom to send cc            */
{
     SHORT rc;
     CHAR tmpadr[MAXADR];

     ASSERT(efvda->numcc <= ccmax || (usrptr->flags&MASTER));
     stlcpy(tmpadr,cncall(),MAXADR);
     if (sameas(tmpadr,"?")) {
          clrxrf();
          if (efvda->numcc == 0) {
               prfmsg(NOCCLST);
          }
          else {
               prfmsg(CCLIST,efvda->cclist);
          }
          gostt(POSTWRT);
          return;
     }
     if (efvda->numcc == ccmax && !(usrptr->flags&MASTER)) {
          prfmsg(CCDONE,ccmax);
          gostt(POSTWRT);
          return;
     }
     if (islocal(tmpadr)) {
          switch (hdluid(tmpadr)) {
          case UIDFND:
               stlcpy(tmpadr,uidxrf.userid,MAXADR);
               break;
          case UIDPMT:
               gostt(WHO2CC);
               return;
          case UIDCAL:
               return;
          default:
               ASSERT(FALSE);
          }
     }
     setmem(vdatmp,GMEWRKSZ,0);
     inigmerq(vdatmp);
     rc=valadr(vdatmp,usaptr->userid,tmpadr,msg->forum);
     clsgmerq(vdatmp);
     switch (rc) {
     case VALYES:
          add2lst(tmpadr);
          gostt(POSTWRT);
          break;
     case VALNO:
          errhlp(NOSUCH,POSTWRT);
          break;
     case VALACC:
          errhlp(NOWACC,POSTWRT);
          break;
     case VALCRD:
          cncall();
          prfmsg(NOWCRD);
          howbuy();
          gostt(POSTWRT);
          break;
     }
}

VOID
add2lst(                           /* add an address to cc: list           */
CHAR *newadr)                      /*   new address to add                 */
{
     CHAR *tmplst;

     ASSERT(newadr != NULL);
     ASSERT(strlen(newadr) < MAXADR);
     if (efvda->cclist == NULL) {
          efvda->cclist=alczer(MAXADR);
          if (efvda->cclist == NULL) {
               errhlp(NOADCC,POSTWRT);
               return;
          }
          efvda->cclstsz=MAXADR;
          stlcpy(efvda->cclist,newadr,MAXADR);
     }
     else {
          if (strlen(newadr)+strlen(efvda->cclist) > efvda->cclstsz) {
               tmplst=alcrsz(efvda->cclist,efvda->cclstsz,
                             efvda->cclstsz+MAXADR);
               if (tmplst == NULL) {
                    errhlp(NOADCC,POSTWRT);
                    return;
               }
               efvda->cclstsz+=MAXADR;
               efvda->cclist=tmplst;
          }
          tmplst=efvda->cclist+strlen(efvda->cclist);
          *tmplst++=';';
          stlcpy(tmplst,newadr,MAXADR);
     }
     ++efvda->numcc;
}

ccadfunc                           /*   old cc: list adder                 */
setccadder(                        /* set add-cc:-list function            */
ccadfunc newccadder)               /*   new cc: list adder                 */
{
     ccadfunc tmp;

     tmp=ccadder;
     ccadder=newccadder;
     return(tmp);
}

CHAR *                             /*   copy of pointer to text            */
addcclst(                          /* add cc: list to message text         */
CHAR *text,                        /*   message text buffer                */
CHAR *list,                        /*   ';'-delimited cc: list string      */
CHAR *to)                          /*   primary recipient address          */
{
     return((*ccadder)(text,list,to));
}

CHAR *                             /*   copy of pointer to text            */
dftadcc(                           /* default add cc: list to msg function */
CHAR *text,                        /*   message text buffer                */
CHAR *list,                        /*   ';'-delimited cc: list string      */
CHAR *to)                          /*   primary recipient address          */
{
     CHAR *cp;
     INT txtlen,chunk;

     ASSERT(text != NULL);
     ASSERT(strlen(text) < TXTLEN);
     ASSERT(list != NULL);
     ASSERT(*list != '\0');
     txtlen=strlen(text);
     while (!(text[txtlen-1] == '\r' && text[txtlen-2] == '\r')
         && txtlen < TXTLEN) {
          text[txtlen++]='\r';
     }
     if (txtlen < TXTLEN-sizeof("to: ")) {
          strcpy(&text[txtlen],"to: ");
          txtlen+=sizeof("to: ")-1;
          stlcpy(&text[txtlen],to,TXTLEN-txtlen);
          txtlen=strlen(text);
     }
     else {
          text[txtlen]='\0';
          return(text);
     }
     if (txtlen < TXTLEN-sizeof("\rcc: ")) {
          strcpy(&text[txtlen],"\rcc: ");
          txtlen+=sizeof("\rcc: ")-1;
          do {
               cp=skpwht(list);
               list=strchr(cp,';');
               chunk=(INT)(list-cp);
               if (list == NULL) {
                    stlcpy(&text[txtlen],cp,TXTLEN-txtlen);
               }
               else if (txtlen < TXTLEN-(chunk+sizeof("\r    ")-1)) {
                    movmem(cp,&text[txtlen],chunk);
                    txtlen+=chunk;
                    ++list;
                    movmem("\r    ",&text[txtlen],sizeof("\r    ")-1);
                    txtlen+=sizeof("\r    ")-1;
               }
               else {
                    break;
               }
          } while (list != NULL);
     }
     else {
          text[txtlen]='\0';
     }
     return(text);
}

VOID
sendit(VOID)                       /* initiate message send process        */
{
     cncall();
     prfmsg(SNDSTRT);
     setgmecb(efwork,wrtprg);
     efvda->tmpmid=0L;
     efvda->cflags&=~(WDSTING|WCCING|WCPYING);
     if (efvda->flags&ISRPL) {
          usrptr->substt=RPLING;
     }
     else {
          usrptr->substt=SENDING;
     }
     docyc(FALSE);
}

VOID
sender(VOID)                       /* cycled message send process          */
{
     SHORT rc;

     switch (rc=gmeSendMsg(efwork,msg,msgtxt,filatt,efvda->cclist)) {
     case GMEAGAIN:
          if (efvda->cflags&WCPYING) {
               efvda->count=spin(efvda->count);
               outprf(usrnum);
          }
          docyc(FALSE);
          break;
     default:
          sendone(rc);
          break;
     }
}

VOID
replyr(VOID)                       /* cycled reply process                 */
{
     SHORT rc;

     switch (rc=reply(efwork,msg,msgtxt,filatt,efvda->cclist)) {
     case GMEAGAIN:
          if (efvda->cflags&WCPYING) {
               efvda->count=spin(efvda->count);
               outprf(usrnum);
          }
          docyc(FALSE);
          break;
     default:
          sendone(rc);
          break;
     }
}

VOID
sendone(                           /* done sending a message               */
SHORT rc)                          /*   result code from GME               */
{
     switch (rc) {
     case GMEAFWD:
     case GMEOK:
          if (!(efvda->cflags&(WDSTING|WCCING))) {
               wnotify(rc);
          }
          break;
     default:
          prfmsg(SENDERR,rc);
          break;
     }
     prfmsg(SENDTLR);
     if (efvda->cclist != NULL) {
          free(efvda->cclist);
          efvda->cclist=NULL;
     }
     efvda->cflags&=~(WDSTING|WCCING);
     if (efvda->flags&LOFUPL) {
          alofbeg();
     }
     else {
          (*efvda->whndun)(FALSE);
          chkcyc();
     }
}

VOID
wrtprg(                            /* show progress of write operation     */
INT evt,                           /*   current event                      */
INT rc)                            /*   result of last write operation     */
{
     switch (evt) {
     case EVTCPYS:
          prfmsg(CPYATT);
          outprf(usrnum);
          efvda->cflags|=WCPYING;
          break;
     case EVTCPYD:
          prf("\b\n");
          outprf(usrnum);
          efvda->cflags&=~WCPYING;
          break;
     case EVTDSTS:
          prfmsg(DSTSTRT,gmexinf());
          outprf(usrnum);
          efvda->cflags|=WDSTING;
          break;
     case EVTDSTD:
          efvda->cflags&=~WDSTING;
          break;
     case EVTCCST:
          prfmsg(CCSTART);
          outprf(usrnum);
          efvda->cflags|=WCCING;
          break;
     case EVTDONE:
          if (efvda->tmpmid == 0L) {
               efvda->tmpmid=msg->msgid;
          }
          if (rc > GMEAGAIN) {
               if (efvda->cflags&WCCING) {
                    cnotify(rc);
               }
               else if (efvda->cflags&WDSTING) {
                    dnotify(rc);
               }
               else {
                    wnotify(rc);
               }
          }
          else if (!(efvda->cflags&WDSTING)) {
               switch (rc) {
               case GMECRD:
                    if (efvda->cflags&WCCING) {
                         prfmsg(CCNOCRD,msg->to);
                         outprf(usrnum);
                         break;
                    }
               default:
                    prfmsg(SENDERR,rc);
                    outprf(usrnum);
                    break;
               }
          }
          break;
     }
}

VOID
wnotify(                           /* do notification of message write     */
SHORT rc)                          /*   result of write operation          */
{
     if (msg->forum != EMLID && (msg->flags&FILATT)) {
          if (msg->flags&FILAPV) {
               prfmsg(FILAVL);
          }
          else {
               prfmsg(FILNVL);
          }
     }
     prfmsg(SENTOK,l2as(msg->msgid));
     outprf(usrnum);
     if (rc == GMEAFWD) {
          wrtnot(gmexinf(),msg);
     }
     else {
          if (wrtnot(msg->to,msg)) {
               prfmsg(SNOTIFD,msg->to);
               outprf(usrnum);
          }
     }
}

VOID
dnotify(                           /* do notification of distributed msg   */
SHORT rc)                          /*   result of write operation          */
{
     if (msg->forum != EMLID && (msg->flags&FILATT)) {
          if (msg->flags&FILAPV) {
               prfmsg(FILAVL);
          }
          else {
               prfmsg(FILNVL);
          }
     }
     if (msg->forum == EMLID) {
          prfmsg(DISTOK,l2as(msg->msgid),msg->to);
     }
     else {
          prfmsg(DISTOK,l2as(msg->msgid),gmexinf());
     }
     outprf(usrnum);
     if (rc == GMEAFWD) {
          wrtnot(gmexinf(),msg);
     }
     else {
          if (wrtnot(msg->to,msg)) {
               prfmsg(SNOTIFD,msg->to);
               outprf(usrnum);
          }
     }
}

VOID
fnotify(                           /* do notification of message forward   */
SHORT rc)                          /*   result of forward operation        */
{
     if (msg->forum != EMLID && (msg->flags&FILATT)) {
          if (msg->flags&FILAPV) {
               prfmsg(FWDCAP);
          }
          else {
               prfmsg(FWDCNA);
          }
     }
     if (msg->forum == EMLID) {
          prfmsg(FWDCNF,l2as(efvda->curmid),l2as(msg->msgid),msg->to);
     }
     else {
          prfmsg(FWDCNF,l2as(efvda->curmid),l2as(msg->msgid),
                 spr("/%s",getfnm(msg->forum)));
     }
     outprf(usrnum);
     if (rc == GMEAFWD) {
          fwdnot(gmexinf());
     }
     else {
          if (fwdnot(msg->to)) {
               prfmsg(SNOTIFD,msg->to);
               outprf(usrnum);
          }
     }
}

VOID
cnotify(                           /* do notification of message copy      */
SHORT rc)                          /*   result of copy operation           */
{
     if (msg->forum != EMLID && (msg->flags&FILATT)) {
          if (msg->flags&FILAPV) {
               prfmsg(FWDCAP);
          }
          else {
               prfmsg(FWDCNA);
          }
     }
     if (msg->forum == EMLID) {
          prfmsg(CPYCNF,l2as(efvda->tmpmid),l2as(msg->msgid),msg->to);
     }
     else {
          prfmsg(CPYCNF,l2as(efvda->tmpmid),l2as(msg->msgid),
                 spr("/%s",getfnm(msg->forum)));
     }
     outprf(usrnum);
     if (rc == GMEAFWD) {
          cpynot(gmexinf());
     }
     else {
          if (cpynot(msg->to)) {
               prfmsg(SNOTIFD,msg->to);
               outprf(usrnum);
          }
     }
}

VOID
startmod(                          /* initiate modification of message     */
VOID (*whndun)(SHORT rc))          /*   function to call when finished     */
{
     INT pflvl;

     peruflg[usrnum]|=INEDIT;
     efvda->savstt=usrptr->state;
     efvda->whndun=whndun;
     lf2cr(msgtxt);
     zpad(msgtxt,TXTLEN);          /* to aVOID possible FSE comprbuf() err */
     bgnedt(TXTLEN,msgtxt,TPCSIZ,msg->topic,meddone,0);
     edtimr(efimr);
     if (msg->forum != EMLID) {
          pflvl=getdefp(msg->forum)->pfnlvl;
          if (pflvl != DFTPFN) {
               edtpfn(pflvl);
          }
     }
}

GBOOL                              /*   returns TRUE to stay in module     */
meddone(                           /* finished editing msg being modified  */
SHORT edflgs)                      /*   edit result flags                  */
{
     setmeup();
     peruflg[usrnum]&=~INEDIT;
     usrptr->state=efvda->savstt;
     if (edflgs&ED_QUITEX) {
          prfmsg(WABORT);
          (*efvda->whndun)(GMEAGAIN);
     }
     else {
          modit();
     }
     efvda->dftchr=getdft();
     outprf(usrnum);
     return(TRUE);
}

VOID
modit(VOID)                        /* initiate message modify process      */
{
     cncall();
     usrptr->substt=MODING;
     docyc(FALSE);
}

VOID
cmodr(VOID)                        /* cycled message modify process        */
{
     SHORT rc;

     switch (rc=modmsg(efwork,msg,msgtxt)) {
     case GMEAGAIN:
          docyc(FALSE);
          break;
     default:
          (*efvda->whndun)(rc);
          chkcyc();
          break;
     }
}

VOID
getprnt(                           /* initiate read parent message process */
VOID (*whndun)(SHORT rc))          /*   function to call when finished     */
{
     usrptr->substt=CRDPRNT;
     efvda->whndun=whndun;
     cgetprnt();
}

VOID
cgetprnt(VOID)                     /* cycled read message parent process   */
{
     SHORT rc;

     switch (rc=readpar(efwork,msg,msgtxt)) {
     case GMEAGAIN:
          docyc(TRUE);
          return;
     default:
          (*efvda->whndun)(rc);
          break;
     }
     chkcyc();
}

VOID
getnear(                           /* initiate read near message process   */
SHORT nearop,                      /*   get near "style" to use            */
VOID (*whndun)(SHORT rc))          /*   function to call when finished     */
{
     usrptr->substt=CRDNEAR;
     efvda->nearop=nearop;
     efvda->whndun=whndun;
     cgetnear();
}

VOID
cgetnear(VOID)                     /* cycled read near message process     */
{
     SHORT rc;

     switch (rc=nearmsg(efwork,efvda->nearop,msg,msgtxt)) {
     case GMEAGAIN:
          docyc(TRUE);
          return;
     default:
          (*efvda->whndun)(rc);
          break;
     }
     chkcyc();
}

VOID
reget(                             /* initiate re-read of current message  */
VOID (*whndun)(SHORT rc))          /*   function to call when finished     */
{
     usrptr->substt=CREREAD;
     efvda->whndun=whndun;
     creget();
}

VOID
creget(VOID)                       /* cycled re-read of current message    */
{
     SHORT rc;

     switch (rc=readmsg(efwork,msg,msgtxt)) {
     case GMEAGAIN:
          docyc(TRUE);
          return;
     default:
          (*efvda->whndun)(rc);
          break;
     }
     chkcyc();
}

VOID
getnext(                           /* initiate read next message process   */
VOID (*whndun)(SHORT rc))          /*   function to call when finished     */
{
     usrptr->substt=CRDNEXT;
     efvda->whndun=whndun;
     cygetnext();
}

VOID cygetnext(VOID)               /* cycled read next message process     */
{
     SHORT rc;

     switch (rc=nextmsg(efwork,msg,msgtxt)) {
     case GMEAGAIN:
          docyc(!srchipg());
          return;
     default:
          (*efvda->whndun)(rc);
     }
     chkcyc();
}

VOID
getprev(                           /* initiate get previous message process*/
VOID (*whndun)(SHORT rc))          /*   function to call when finished     */
{
     usrptr->substt=CRDPREV;
     efvda->whndun=whndun;
     cgetprev();
}

VOID cgetprev(VOID)                /* cycled get previous message process  */
{
     SHORT rc;

     switch (rc=prevmsg(efwork,msg,msgtxt)) {
     case GMEAGAIN:
          docyc(!srchipg());
          return;
     default:
          (*efvda->whndun)(rc);
          break;
     }
     chkcyc();
}

GBOOL
srchipg(VOID)                      /* is there a message search in progress*/
{
     return((usrscn[usrnum] != NULL) ? TRUE : FALSE);
}

VOID
xmtext(                            /* transmit text of message             */
VOID (*whndun)(SHORT rc))          /*   function to call when finished     */
{
     CHAR *cp;

     for (cp=msgtxt ; *cp != '\0' ; cp++) {
          if (*cp == '\r' && isalnum(*(cp+1))) {
               *cp='\n';
          }
     }
     btulok(usrnum,FALSE);
     efvda->whndun=whndun;
     efvda->txtptr=msgtxt;
     usrptr->substt=XMTING;
     btuxmt(usrnum,msgatr);
     if (usrptr->flags&NOINJO) {
          efvda->cflags&=~INJOSAV;
     }
     else {
          efvda->cflags|=INJOSAV;
     }
     cxmtxt();
}

VOID
cxmtxt(VOID)                       /* cycled text transmit process         */
{
     UINT outavl,lftlen,outlen;

     outavl=btuoba(usrnum);
     lftlen=strlen(efvda->txtptr);
     outlen=min(1024,lftlen);
     if (outlen > outavl-2) {
          usrptr->flags|=NOINJO;
          docyc(FALSE);
          return;
     }
     if (*efvda->txtptr != '\0') {
          stlcpy(prfbuf,efvda->txtptr,outlen+1);
          btuxmt(usrnum,prfbuf);
          clrprf();
          efvda->txtptr+=outlen;
     }
     if (btuoba(usrnum) > OUTSIZ-128) {
          btuxmt(usrnum,"\r");
          xmtdone(FALSE);
     }
     else {
          if (*efvda->txtptr != '\0') {
               usrptr->flags|=NOINJO;
          }
          else if (efvda->cflags&INJOSAV) {
               usrptr->flags&=~NOINJO;
          }
          docyc(FALSE);
     }
}

VOID
xmtdone(                           /* done sending message text            */
GBOOL aborted)                     /*   TRUE if user aborted output        */
{
     if (efvda->cflags&INJOSAV) {
          usrptr->flags&=~NOINJO;
     }
     (*efvda->whndun)(aborted);
     chkcyc();
}

VOID
markoff(                           /* mark message read, generate ret.rcp. */
VOID (*whndun)(SHORT rc))          /*   function to call when finished     */
{
     usrptr->substt=CMARKRD;
     efvda->whndun=whndun;
     cmarkoff();
}

VOID
cmarkoff(VOID)                     /* cycled mark-message-read process     */
{
     SHORT rc;

     switch (rc=markread(efwork,msg,msgtxt)) {
     case GMEAGAIN:
          docyc(TRUE);
          return;
     default:
          (*efvda->whndun)(rc);
          break;
     }
     chkcyc();
}

VOID
sdlnow(                            /* state: query to download attachment  */
VOID (*whndun)(SHORT rc))          /*   function to call when done         */
{
     switch (cncyesno()) {
     case 'Y':
          prfmsg(DLHDR);
          efdnload(whndun);
          break;
     case 'N':
          (*whndun)(0);
          break;
     default:
          errhlp(YORN,usrptr->substt);
          break;
     }
}

VOID
efdnload(                          /* E-mail/Forums download utility       */
VOID (*whndun)(SHORT rc))          /*   function to call when finished     */
{
     SHORT rc;

     efvda->whndun=whndun;
     peruflg[usrnum]|=INFTF;
     efvda->savstt=usrptr->state;
     if (ftgnew()) {
          switch (rc=tagatt(efwork,msg,ftgptr->tagspc)) {
          case GMEOK:
               ftgptr->flags=haskey(tagkey) ? FTGABL : 0;
               ftgptr->tshndl=efdnhl;
               break;
          default:
               switch (rc) {
               case GMEACC:
                    prfmsg(NDLACC);
                    break;
               case GMECRD:
                    prfmsg(NDLCRD);
                    break;
               case GMENFND:
                    prfmsg(NDLFIL,l2as(msg->msgid));
                    break;
               default:
                    ASSERT(FALSE);
               }
               peruflg[usrnum]&=~INFTF;
               (*whndun)(0);
               return;
          }
     }
     if (morcnc()) {
          ftgsbm(cncall());
     }
     else {
          ftgsbm("?");
     }
     if (usrptr->state == efvda->savstt && (peruflg[usrnum]&INFTF)) {
          peruflg[usrnum]&=~INFTF;
          (*whndun)(0);
     }
}

INT                                /*   result code                        */
efdnhl(                            /* Handle downloads for E-mail & Forums */
INT tshcode)                       /*   action code                        */
{
     INT rc=FALSE;
     const CHAR *cp;
     FILE *fp;
     const struct message *tmpmsg;

     setmeup();
     switch(tshcode) {
     case TSHDSC:
          tmpmsg=tagmsg(ftgptr->tagspc);
          if (tmpmsg == NULL) {
               stlcpy(tshmsg,stpans(getmsg(DLAGON)),TSHLEN+1);
               break;
          }
          sprintf(tshmsg,"file \"%0.39s\" attached to message #%ld",
                  tmpmsg->attname,tmpmsg->msgid);
          if (tmpmsg->forum != EMLID) {
               sprintf(vdatmp,stpans(getmsg(DLAFOR)),tmpmsg->attname,
                       tmpmsg->msgid,getfnm(tmpmsg->forum));
          }
          else if (sameas(usaptr->userid,tmpmsg->from)) {
               sprintf(vdatmp,stpans(getmsg(DLAFRM)),tmpmsg->attname,
                       tmpmsg->msgid,tmpmsg->to);
          }
          else {
               sprintf(vdatmp,stpans(getmsg(DLATO)),tmpmsg->attname,
                       tmpmsg->msgid,tmpmsg->from);
          }
          stlcpy(tshmsg,vdatmp,TSHLEN+1);
          break;
     case TSHVIS:
          tmpmsg=tagmsg(ftgptr->tagspc);
          if (tmpmsg != NULL) {
               fp=fopen(dlname(tmpmsg),FOPRB);
               if (fp != NULL) {
                    fread(tshmsg,1,TSHLEN,fp);
                    fclose(fp);
               }
          }
          rc=TRUE;
          break;
     case TSHSCN:
          break;
     case TSHNXT:
          break;
     case TSHBEG:
          tmpmsg=tagmsg(ftgptr->tagspc);
          if (tmpmsg == NULL) {
               stlcpy(tshmsg,stpans(getmsg(DLADEL)),TSHLEN+1);
          }
          else if (!fnd1st(&effb,cp=dlname(tmpmsg),0)) {
               sprintf(vdatmp,stpans(getmsg(DLAFGON)),tmpmsg->msgid);
               stlcpy(tshmsg,vdatmp,TSHLEN+1);
          }
          else if (!dlstart(ftgptr->tagspc)) {
               stlcpy(tshmsg,stpans(getmsg(DLACRD)),TSHLEN+1);
          }
          else {
               stlcpy(tshmsg,cp,TSHLEN+1);
               stlcpy(ftfscb->fname,tmpmsg->attname,GCMAXFNM);
               rc=TRUE;
          }
          break;
     case TSHEND:
          dldone(ftgptr->tagspc);
          break;
     case TSHSKP:
          dlabt(ftgptr->tagspc);
          break;
     case TSHFIN:
          usrptr->state=efvda->savstt;
          peruflg[usrnum]&=~INFTF;
          (*efvda->whndun)(0);
          efvda->dftchr=getdft();
          rc=TRUE;
          break;
     case TSHHUP:
          break;
     case TSHUNT:
          break;
     default:
          ASSERT(FALSE);
     }
     return(rc);
}

quotfunc                      /*   old message quoter                      */
setquoter(                    /* set message quoter function               */
quotfunc newquoter)           /*   new message quoter                      */
{
     quotfunc tmp;

     tmp=quoter;
     quoter=newquoter;
     return(tmp);
}

VOID
quotem(VOID)                  /* "quote" the current in-memory message     */
{
     (*quoter)(msg,msgtxt,TXTLEN);
}

VOID
dftquo(                       /* default message quoter                    */
struct message *qmsg,         /*   header of message to quote              */
CHAR *qtxt,                   /*   text buffer to quote                    */
UINT qlen)                    /*   length of text buffer                   */
{
     UINT len;
     CHAR *curlin,inits[]="  >";

     if ((curlin=strstr(qmsg->to,"{MBBS:")) != NULL) {
          curlin=skpwht(curlin+strlen("{MBBS:"));
     }
     else if (isexpa(qmsg->to)) {
          curlin=strchr(qmsg->to,':')+1;
     }
     else {
          curlin=qmsg->to;
     }
     inits[0]=toupper(curlin[0]);
     if (strchr(curlin,' ') == NULL) {
          inits[1]=toupper(curlin[1]);
     }
     else {
          inits[1]=toupper(*lastwd(curlin));
     }
     stpans(qtxt);
     curlin=qtxt;
     len=0;
     if (*curlin == '\n') {
          do {
               len++;
          } while (*(curlin+len) == '\n');
          movmem(curlin+len,curlin,strlen(curlin+len)+1);
     }
     while (strlen(qtxt)+strlen(inits) < qlen) {
          if ((len=linlen(curlin)) != 0) {
               movmem(curlin,curlin+strlen(inits),strlen(curlin)+1);
               movmem(inits,curlin,strlen(inits));
               if ((len+=strlen(inits)) > 79) {
                    movmem(curlin+len,curlin+79,strlen(curlin+len)+1);
                    len=79;
               }
          }
          if (*(curlin=curlin+len) == '\0' || *++curlin == '\0') {
               break;
          }
     }
}

USHORT
linlen(                            /* find length of line (up to first \r) */
CHAR *ptr)                         /* pointer to line to get length of     */
{
     USHORT retval;

     for (retval=0 ; *ptr != '\r' && *ptr != '\0'; retval++,ptr++) {
          if (*ptr == '\n') {
               *ptr='\r';
               break;
          }
     }
     return(retval);
}

VOID
deletem(                           /* initiate cycled delete-message       */
VOID (*whndun)(SHORT rc))          /*   function to call when finished     */
{
     efvda->whndun=whndun;
     usrptr->substt=DELING;
     docyc(TRUE);
}

VOID
cdelm(VOID)                        /* cycled delete message process        */
{
     SHORT rc;

     switch (rc=delmsg(efwork)) {
     case GMEAGAIN:
          docyc(TRUE);
          return;
     default:
          (*efvda->whndun)(rc);
          break;
     }
     chkcyc();
}

VOID
cfhlp(VOID)                        /* show help for copy/forward prompt    */
{
     INT i,nexp;
     const struct expinfo *tmpinf;

     switch (wrtany()) {
     case VALACC:
          prfmsg(NWRTACC);
          break;
     case VALCRD:
          prfmsg(NWRTCRD);
          break;
     case VALYES:
          prfmsg(CFHLP);
          if (expavl()) {
               nexp=numexp();
               for (i=0 ; i < nexp ; ++i) {
                    tmpinf=expinf(i);
                    if (haskey(tmpinf->wrtkey)) {
                         prfmsg(WEXHLP,tmpinf->name,tmpinf->prefix,
                                tmpinf->exmp);
                    }
               }
          }
          prfmsg(CFHLP2);
          break;
     }
}

VOID
comment(                           /* check for comments when copy/fwd     */
VOID (*whndun)(SHORT rc))          /*   function to call when finished     */
{
     if ((qsptr->flags&CFWCMT) && strlen(msgtxt)+cmthdrsz+LINLEN < TXTLEN) {
          efvda->whndun=whndun;
          if (qsptr->flags&ALWCMT) {
               if (!(qsptr->flags&USRSET)) {
                    prfmsg(CMTNOW);
                    outprf(usrnum);
               }
               addcmt();
          }
          else {
               gostt(CMTPMT);
          }
     }
     else {
          (*whndun)(FALSE);
     }
}

VOID
scmtpmt(VOID)                      /* state: add comments when copy/fwd?   */
{
     switch (cncyesno()) {
     case 'Y':
          addcmt();
          break;
     case 'N':
          (*efvda->whndun)(FALSE);
          break;
     default:
          errhlp(YORN,CMTPMT);
          break;
     }
}

VOID
addcmt(VOID)                       /* start up editor to add comments      */
{
     CHAR *cp;
     INT pflvl;
     UINT txtlen;
     USHORT dstfor;

     txtlen=strlen(msgtxt);
     efvda->cmtspc=TXTLEN-txtlen-1;
     movmem(msgtxt,&msgtxt[efvda->cmtspc],txtlen+1);
     sprintf(msgtxt,abvcmt,usaptr->userid);
     cp=msgtxt+strlen(msgtxt);
     txtlen=efvda->cmtspc-cmthdrsz;
     setmem(cp,txtlen,0);          /* to avoid possible FSE comprbuf() err */
     bgnedt(txtlen,cp,TPCSIZ,msg->topic,cmtdone,ED_CLRTXT+ED_FIXTOP);
     dstfor=msg->forum;
     if (msg->forum == EMLID && isforum(msg->to)) {
          dstfor=getAdrForID(msg->to);
     }
     if (dstfor != EMLID) {
          pflvl=getdefp(dstfor)->pfnlvl;
          if (pflvl != DFTPFN) {
               edtpfn(pflvl);
          }
     }
     peruflg[usrnum]|=INEDIT;
}

GBOOL                              /*   returns TRUE to stay in module     */
cmtdone(                           /* done editing comments                */
SHORT edflgs)                      /*   edit result flags                  */
{
     setmeup();
     peruflg[usrnum]&=~INEDIT;
     if (!(edflgs&ED_QUITEX)) {
          sprintf(msgtxt+strlen(msgtxt),blwcmt,usaptr->userid);
          movmem(&msgtxt[efvda->cmtspc],msgtxt+strlen(msgtxt),
                 strlen(&msgtxt[efvda->cmtspc])+1);
     }
     (*efvda->whndun)((edflgs&ED_QUITEX) != 0);
     efvda->dftchr=getdft();
     outprf(usrnum);
     return(TRUE);
}

VOID
fwdit(                             /* initiate message forward process     */
VOID (*whndun)(SHORT rc))          /*   function to invoke when finished   */
{
     cncall();
     setgmecb(efwork,fwdcpycb);
     efvda->whndun=whndun;
     usrptr->substt=FWDING;
     docyc(FALSE);
}

VOID
cfwdr(VOID)                        /* cycled message forward process       */
{
     SHORT rc;

     switch (rc=fwdmsg(efwork,msg,msgtxt)) {
     case GMEAGAIN:
          if (efvda->cflags&WCPYING) {
               efvda->count=spin(efvda->count);
               outprf(usrnum);
          }
          docyc(FALSE);
          break;
     default:
          (*efvda->whndun)(rc);
          chkcyc();
          break;
     }
}

VOID
copyit(                            /* initiate message copy process        */
VOID (*whndun)(SHORT rc))          /*   function to invoke when finished   */
{
     cncall();
     setgmecb(efwork,fwdcpycb);
     efvda->whndun=whndun;
     usrptr->substt=COPYING;
     docyc(FALSE);
}

VOID
ccopyr(VOID)                       /* cycled message copy process          */
{
     SHORT rc;

     switch (rc=copymsg(efwork,msg,msgtxt)) {
     case GMEAGAIN:
          if (efvda->cflags&WCPYING) {
               efvda->count=spin(efvda->count);
               outprf(usrnum);
          }
          docyc(FALSE);
          break;
     default:
          (*efvda->whndun)(rc);
          chkcyc();
          break;
     }
}

VOID
fwdcpycb(                          /* show progress of forward/copy        */
INT evt,                           /*   current event                      */
INT rc)                            /*   result of last operation           */
{
     (VOID)rc;
     switch (evt) {
     case EVTCPYS:
          prfmsg(CPYATT);
          outprf(usrnum);
          efvda->cflags|=WCPYING;
          break;
     case EVTCPYD:
          prf("\b\n");
          outprf(usrnum);
          efvda->cflags&=~WCPYING;
          break;
     }
}

VOID
docyc(                             /* switch from input mode to cycle mode */
GBOOL lock)                        /*   lock out input while cycling?      */
{
     if (!(efvda->cflags&DIDCYC)) {
          efvda->cflags&=~CKDCYC;
          if (morcnc()) {
               stlcpy(efvda->inpsav,cncall(),DFTIMX+1);
               clrprf();
          }
          else {
               *efvda->inpsav='\0';
          }
     }
     btulok(usrnum,lock);
     efvda->cflags&=~DIDCYC;
     btuinj(usrnum,CYCLE);
}

VOID
cycall(VOID)                       /* cncall() equivalent for cycled proc  */
{
     if (efvda->cflags&DIDCYC) {
          *efvda->inpsav='\0';
     }
     else {
          cncall();
     }
}

VOID
chkcyc(VOID)                       /* return from cycling to input handler */
{
     if ((efvda->cflags&DIDCYC) && !(efvda->cflags&CKDCYC)) {
          efvda->cflags|=CKDCYC;
          efvda->dftchr=getdft();
          if (*efvda->inpsav == '\0') {
               efvda->cflags&=~DIDCYC;
               outprf(usrnum);
          }
          else {
               clrprf();
               btuinj(usrnum,CRSTG);
          }
          btulok(usrnum,FALSE);
     }
}

VOID
rstcnc(VOID)                       /* restore concat context after cycling */
{
     stlcpy(input,efvda->inpsav,DFTIMX+1);
     parsin();
     efvda->cflags&=~(DIDCYC+CKDCYC);
}

/* The following routine generates message summary lines like this:

Date: Monday, September 29, 1994  12:02am    *PRIORITY*        Electronic Mail
From: Marc Fountain                                           Msg#: 1234567890
  To: Chris Robert                                  *RETURN RECEIPT REQUESTED*
  Re: The best System I've ever used!                       File: MAJORBBS.EXE
      (Reply to #76543, Copy by Jack Alvrus, Fwd by Brian Step*)

     or this (Forum messages viewed from E-mail or Forums):

Date: Wednesday, September 31, 1994  12:02pm                Forum: TechSupport
From: Tim Stark                                               Msg#: 1234567890
  To: Bill Hyatt                                                      *EXEMPT*
  Re: the second law of thermodynamics...
                                                                 (123 replies)
*/

VOID
sumams(                            /* generate message summary lines       */
INT txtblk,                        /*   level 6 text block to use          */
struct message *dspmsg)            /*   for given message                  */
{
     ASSERT(dspmsg != NULL);
     if (dspmsg->forum == EMLID) {
          prfmsg(txtblk,
           datlin(dspmsg->crdate,dspmsg->crtime),
           (dspmsg->flags&PRIMSG) ? "*PRIORITY*" : "",
           "Electronic Mail",
           adrfld(dspmsg->from,vdatmp,50),
           spr("Msg#: %ld",dspmsg->msgid),
           adrfld(dspmsg->to,vdatmp+MAXADR,40),
           (dspmsg->flags&RECREQ) ? "*RETURN RECEIPT REQUESTED*" : "",
           dspmsg->topic,
           (dspmsg->flags&FILATT) ? spr("File: %s",dspmsg->attname) : "",
           hststr(dspmsg->history),
           nrstr(dspmsg->nrpl));
     }
     else {
          prfmsg(txtblk,
           datlin(dspmsg->crdate,dspmsg->crtime),
           "",
           fnmlin(dspmsg->forum),
           adrfld(dspmsg->from,vdatmp,50),
           spr("Msg#: %ld",dspmsg->msgid),
           adrfld(dspmsg->to,vdatmp+MAXADR,40),
           (dspmsg->flags&EXEMPT) ? "*EXEMPT*" : "",
           dspmsg->topic,
           (dspmsg->flags&FILATT) ? spr("File: %s",dspmsg->attname) : "",
           hststr(dspmsg->history),
           nrstr(dspmsg->nrpl));
     }
     if (!(*(dspmsg->history) == '\0' && dspmsg->nrpl == 0)) {
          prf("\n");
     }
}

static 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);
}

CHAR *
fnmlin(                            /* generate forum name string           */
USHORT forum)                      /*   forum ID                           */
{
     static CHAR s[FNMSIZ+sizeof("Forum: ")];

     strcpy(s,"Forum: ");
     stlcat(s,getfnm(forum),sizeof(s));
     return(s);
}

CHAR *
hststr(                            /* history string formatter             */
CHAR *history)                     /*   history string to format           */
{
     return(*history == '\0' ? "" : spr("(%s)",history));
}

CHAR *
adrfld(                            /* massage address for display          */
CHAR *adr,                         /*   address to display                 */
CHAR *buf,                         /*   additional buffer to use if nec.   */
SHORT maxlen)                      /*   max # chars alotted in slot        */
{
     INT len;

     ASSERT(adr != NULL);
     ASSERT(buf != NULL);
     if (strlen(adr) < maxlen) {
          return(adr);
     }
     strcpy(buf,adr);
     len=(((strlen(buf)-maxlen)/80)+1)*80+maxlen+1;
     padfld(buf,len);
     return(buf);
}

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


CHAR *
anpstr(                            /* ALWAYS/NEVER/PROMPT string           */
SHORT flags,                       /*   flag field to check                */
SHORT pmtflg,                      /*   flag set when "PROMPT"             */
SHORT alwflg)                      /*   flag set when "ALWAYS"             */
{
     if (flags&pmtflg) {
          if (flags&alwflg) {
               return("ALWAYS");
          }
          return("PROMPT");
     }
     return("NEVER");
}

CHAR *
ynstr(                             /* YES/NO string                        */
SHORT flags,                       /*   flag field to check                */
SHORT ynflg)                       /*   flag set when "YES"                */
{
     if (flags&ynflg) {
          return("YES");
     }
     return("NO");
}

CHAR *
rmodstr(                           /* read mode string (FULL/BROWSE)       */
SHORT flags,                       /*   flag field to check                */
SHORT modflg)                      /*   flag set when "FULL"               */
{
     if (flags&modflg) {
          return("FULL");
     }
     return("BROWSE");
}

CHAR *
cfortvar(VOID)                     /* current Forum text variable          */
{
     return((CHAR *)getdefp(ucurfor(usrnum))->name);
}

GBOOL
foppkey(                           /* Forum-Op (current Forum) pseudokey   */
INT unum,                          /*   user number to check on            */
const CHAR *lock)                  /*   lock name to check on              */
{
     struct qscfg *qsp;

     if (sameas(lock,"_FORUMOP")) {
          qsp=uqsptr(unum);
          if (*qsp->userid != '\0') {
               return(qforac(qsp,ucurfor(unum)) >= OPAXES);
          }
     }
     return(FALSE);
}

USHORT                             /*   always returns a valid forum ID    */
ucurfor(                           /* get current forum ID                 */
INT unum)                          /*   for this user                      */
{
     USHORT fid;
     struct qscfg *qsp;

     qsp=uqsptr(unum);
     fid=qsp->curfor;
     if (*qsp->userid == '\0' || !fidxst(fid)) {
          fid=dftfor;
     }
     return(fid);
}
