/***************************************************************************
 *                                                                         *
 *   WLMMODC.C                                                             *
 *                                                                         *
 *   Copyright (c) 1997 Galacticomm, Inc.         All Rights Reserved.     *
 *                                                                         *
 *   Worldlink Messaging Client Worldlink module.                          *
 *                                                                         *
 *                                            - J. Alvrus   1/22/97        *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "gme.h"
#include "majorbbs.h"
#include "galtext.h"
#include "worldlnk.h"
#include "wormsg.h"
#include "wlmutl.h"
#include "wlmstorc.h"
#include "wlmmsgu.h"
#include "wlminsu.h"
#include "wlmforu.h"
#include "wlmcfgu.h"
#include "wlmcfgc.h"
#include "wlmimpc.h"
#include "wlmexpc.h"
#include "wlmcsmc.h"
#ifndef GCDOS
#include "wlmahmc.h"
#endif // GCDOS
#include "wlmmodc.h"
#include "wlmcli.h"

#define FILREV "$Revision: 8 $"

VOID wlmConnect(VOID);
VOID wlmDisconnect(VOID);
VOID wlmCommand(SHORT cmdnum,UINT length,VOID *value);
VOID wlmSquelch(SHORT cmdnum,UINT length,VOID *value);

WORMODULE wlmod={                  /* WorldLink module                     */
     WLMAPID,                      /*   Application ID                     */
     WLMDESC,                      /*   module name (brief description)    */
     wlmConnect,                   /*   this system connection handler     */
     wlmDisconnect,                /*   this system disconnect handler     */
     NULL,                         /*   other system connection handler    */
     NULL,                         /*   other system disconnect handler    */
     wlmCommand,                   /*   command handler                    */
     wlmSquelch                    /*   squelch override                   */
};

VOID cmdReset(UINT length,VOID *value);
VOID cmdResetAck(UINT length,VOID *value);
VOID cmdError(UINT length,VOID *value);
VOID cmdCancel(UINT length,VOID *value);
VOID cmdCancelAck(UINT length,VOID *value);
VOID cmdAtt(UINT length,VOID *value);
VOID cmdAttRcv(UINT length,VOID *value);
VOID cmdMsgData(UINT length,VOID *value);
VOID cmdMsgDone(UINT length,VOID *value);
VOID cmdMsgRcv(UINT length,VOID *value);
VOID cmdEOT(UINT length,VOID *value);
VOID cmdSendMail(UINT length,VOID *value);
VOID cmdGetForLst(UINT length,VOID *value);
VOID cmdGetGenInf(UINT length,VOID *value);
VOID cmdGetForDet(UINT length,VOID *value);
VOID cmdCreateFor(UINT length,VOID *value);
VOID cmdGetForCfg(UINT length,VOID *value);
VOID cmdModForCfg(UINT length,VOID *value);
VOID cmdGetForAcc(UINT length,VOID *value);
VOID cmdSetForAcc(UINT length,VOID *value);
VOID cmdGetNonDft(UINT length,VOID *value);
VOID cmdDeleteFor(UINT length,VOID *value);
VOID cmdNewEmail(UINT length,VOID *value);
VOID cmdForChange(UINT length,VOID *value);

cmdFuncType cmdFuncGen[]={         /* side-independent command handlers    */
     cmdReset,                     /* RESET   - reset connection           */
     cmdResetAck,                  /* RSTRCV  - reset acknowledgement      */
     cmdError,                     /* ERROR   - signal error in xmit       */
     cmdCancel,                    /* CANCEL  - abort transmission         */
     cmdCancelAck,                 /* CANCRCV - abort acknowledgement      */
     cmdAtt,                       /* ATT     - sending attachment         */
     cmdAttRcv,                    /* ATTRCV  - attachment received        */
     cmdMsgData,                   /* MSGDATA - sending chunk of message   */
     cmdMsgDone,                   /* MSGDONE - signal end of message data */
     cmdMsgRcv,                    /* MSGRCV  - acknowledge receipt of msg */
     cmdEOT,                       /* EOT     - signal end of transmission */
     cmdSendMail,                  /* SNDMAIL - init/accept C->S mail xmit */
     cmdGetForLst,                 /* GETFLST - init/accept S->C forum xmit*/
     cmdGetGenInf,                 /* GETINFO - get server config info     */
     cmdGetForDet,                 /* GETFDET - get forum details          */
     cmdCreateFor,                 /* CRTFOR  - create forum               */
     cmdGetForCfg,                 /* GETFCFG - get forum configuration    */
     cmdModForCfg,                 /* MODFCFG - edit forum configuration   */
     cmdGetForAcc,                 /* GETFACC - get access of sys in forum */
     cmdSetForAcc,                 /* SETFACC - set access of sys in forum */
     cmdGetNonDft,                 /* NONDFT  - get sys w/non-default acc  */
     cmdDeleteFor                  /* DELFOR  - delete forum               */
};

cmdFuncType cmdFuncSide[]={        /* side-dependent command handlers      */
     cmdNewEmail,                  /* NEWMAIL - new mail on hub            */
     cmdForChange                  /* FORCHG  - forum info on hub changed  */
};

#define NGENCMD (sizeof(cmdFuncGen)/sizeof(cmdFuncGen[0]))
#define NSIDECMD (sizeof(cmdFuncSide)/sizeof(cmdFuncSide[0]))

struct wlinf {                     /* Worldlink module state structure     */
     LONG flags;                   /*   various boolean values             */
     INT sndstt;                   /*   C->S msg transfer state            */
     LONG sndtot;                  /*   total messages sent in a run       */
     LONG sndnat;                  /*   attachments sent in a run          */
     LONG sndasz;                  /*   size of attachment being sent      */
     ULONG sndbyt;                 /*   # of bytes sent in a run           */
     STORFILE *sndf;               /*   outgoing store file                */
     size_t sndlen;                /*   size of current outgoing message   */
     ULONG sndmsg;                 /*   index of current outgoing message  */
     LONG sndfnum;                 /*   file ID of current outgoing att    */
     INT rcvstt;                   /*   S->C msg transfer state            */
     LONG rcvtot;                  /*   total messages received in a run   */
     LONG rcveml;                  /*   email messages received in a run   */
     LONG rcvfor;                  /*   forum messages received in a run   */
     LONG rcvnat;                  /*   attachments received in a run      */
     ULONG rcvbyt;                 /*   # of bytes received in a run       */
     CHAR rcvatt[GCMAXPTH];        /*   received attachment path/file name */
     CHAR rcvfnm[MAXFNAM];         /*   name of forum being received       */
     INT rcvfidx;                  /*   index of forum currently receiving */
     INT rcvtid;                   /*   task ID of immediate importer      */
     GBOOL rcvnew;                 /*   just requested new forum from hub  */
     size_t rcvlen;                /*   length of data in rcvbuf           */
     INT mgrstt;                   /*   management state                   */
     INT mgrcmd;                   /*   management command in progress     */
     INT mgrreq;                   /*   management request ID              */
     mgrRespFunc mgrresp;          /*   got response to mgr op vector      */
     mgrAbortFunc mgrabort;        /*   management op aborted vector       */
     INT genstt;                   /*   general functions state            */
     INT gencmd;                   /*   general command in progress        */
} wlinf;                           /* an instance for our use              */

CHAR *rcvbuf;                      /*   buffer for receiving message       */

#define WLF_CONNECT 0x00000001     /* Worldlink module connected/active    */
#define WLF_RCVMAIL 0x00000002     /* currently receiving email (vs forums)*/
#define WLF_ATTREJ  0x00000004     /* incoming attachment rejected         */
#define WLF_SNDCIP  0x00000008     /* sender cancel in progress            */
#define WLF_SNDRIP  0x00000010     /* sender cancel in progress            */
#define WLF_RCVCIP  0x00000020     /* receiver cancel in progress          */
#define WLF_RCVRIP  0x00000040     /* receiver restart in progress         */
#define WLF_MGRCIP  0x00000080     /* management system cancel in progress */
#define WLF_GENCIP  0x00000100     /* message system cancel in progress    */
#define WLF_GENRIP  0x00000200     /* message system cancel in progress    */
#define WLF_RSTIPG  0x00000400     /* message system cancel in progress    */
#define WLF_NEWMAIL 0x00000800     /* new mail waiting on hub              */

                                   /* send states                          */
#define SS_READY    0              /*   not doing anything                 */
#define SS_START    1              /*   sent SNDMAIL, waiting for SNDMAIL  */
#define SS_SENTATT  2              /*   sent ATT, waiting for callback     */
#define SS_WAITRCV  3              /*   got callback, waiting for ATTRCV   */
#define SS_SENTMSG  4              /*   sent MSGDONE, waiting for MSGRCV   */

                                   /* receive states                       */
#define RS_READY    0              /*   not doing anything                 */
#define RS_WAITMSG  1              /*   waiting for next message           */
#define RS_ATTIPG   2              /*   in process of getting attachment   */
#define RS_GOTATT   3              /*   got an attachment, expect a msg    */
#define RS_GETMSG   4              /*   receiving a message                */

                                   /* management functions states          */
#define MS_READY    0              /*   not doing anything                 */
#define MS_GETDET   1              /*   getting forum details              */
#define MS_GETCFG   2              /*   getting forum configuration        */
#define MS_CRTFOR   3              /*   creating a forum                   */
#define MS_MODFOR   4              /*   modifying a forum                  */
#define MS_GETACC   5              /*   getting access                     */
#define MS_GETNDA   6              /*   getting non-default access list    */
#define MS_SETACC   7              /*   setting access                     */
#define MS_DELFOR   8              /*   deleting a forum                   */

                                   /* general activity states              */
#define GS_READY    0              /*   not doing anything                 */
#define GS_WAITLST  1              /*   waiting for forum list to start    */
#define GS_LSTIPG   2              /*   receiving forum list file          */

INT errwait;                       /* interval to wait after cancel        */
INT rcvtim;                        /* interval to check for msgs to get    */

GBOOL wlModActive=FALSE;           /* has Worldlink module been init?      */

GBOOL rcvKickIpg=FALSE;            /* already waiting to start receiving?  */
GBOOL lstKickIpg=FALSE;            /* already waiting to update list?      */

GBOOL emlPush;                     /* use server new-mail notification?    */
GBOOL audsnd;                      /* audit sending activity?              */
GBOOL audrcv;                      /* audit receiving activity?            */
GBOOL audmgr;                      /* audit management activity?           */
GBOOL audgen;                      /* audit general activity?              */
GBOOL audserr;                     /* audit send errors?                   */
GBOOL audrerr;                     /* audit receive errors?                */
GBOOL audmerr;                     /* audit management errors?             */
GBOOL audgerr;                     /* audit general errors?                */

VOID sndRestart(VOID);
VOID sndStopLow(VOID);
VOID sndNext(VOID);
VOID sndAttCbk(LONG filnum,CHAR *fileName,SHORT cmd,INT what);
VOID sndMessage(VOID);
VOID sndAudSeqErr(VOID);
VOID sndAudAbort(VOID);
VOID rcvKickOff(INT kickTime);
VOID rcvKickHdl(VOID);
VOID rcvStopLow(VOID);
GBOOL rcvNextFor(VOID);
VOID rcvTask(INT taskid);
VOID rcvFinMsg(VOID);
VOID rcvRestart(VOID);
VOID rcvAudSeqErr(VOID);
VOID rcvAudErr(const CHAR *details);
VOID rcvAudAbort(VOID);
VOID mgrGetInfo(VOID);
GBOOL mgrPassInfo(INT cmd,const CHAR *info,const CHAR *details,INT newstt,
                  INT reqid,mgrRespFunc respFunc,mgrAbortFunc abortFunc);
VOID mgrPassResp(INT reqstt,const CHAR *details,const CHAR *info);
VOID mgrCancelLow(VOID);
VOID mgrAudStart(const CHAR *detail);
VOID mgrAudDone(const CHAR *detail);
VOID mgrAudSeqErr(VOID);
VOID mgrAudErr(const CHAR *details);
VOID mgrAudAbort(INT cmd);
VOID lstKickUpd(VOID);
VOID lstTryUpdate(VOID);
GBOOL lstStart(VOID);
VOID lstRestart(VOID);
VOID genAudStart(const CHAR *detail);
VOID genAudDone(const CHAR *detail);
VOID genAudSeqErr(VOID);
VOID genAudAbort(INT cmd);
VOID startSys(VOID);
VOID lowResetSys(INT reason);
VOID cancelAll(VOID);
VOID cancelSnd(VOID);
VOID cancelRcv(VOID);
VOID cancelMgr(VOID);
VOID cancelGen(VOID);
VOID sendCancel(SHORT cmd);
VOID sendCmd(SHORT cmd,UINT length,VOID *value);
VOID clearInfo(VOID);

#ifdef DEBUG
#define CURCMD(cmd) (curcmd=(cmd))

static INT curcmd;

VOID RLOG(const CHAR *s);
VOID SLOG(const CHAR *s);
VOID MLOG(const CHAR *s);
VOID GLOG(const CHAR *s);
VOID CLOG(const CHAR *s);
const CHAR *CMDSTR(INT cmd);
const CHAR *SSTR(INT stt);
const CHAR *RSTR(INT stt);
const CHAR *MSTR(INT stt);
const CHAR *GSTR(INT stt);
const CHAR *FSTR(INT stat);
const CHAR *WSTR(INT what);
#else
#define CURCMD(c) ((VOID)0)
#define RLOG(s)   ((VOID)0)
#define SLOG(s)   ((VOID)0)
#define MLOG(s)   ((VOID)0)
#define GLOG(s)   ((VOID)0)
#define CLOG(s)   ((VOID)0)
#define CMDSTR(c) ((VOID)0)
#define SSTR(c)   ((VOID)0)
#define RSTR(c)   ((VOID)0)
#define MSTR(c)   ((VOID)0)
#define GSTR(c)   ((VOID)0)
#define FSTR(c)   ((VOID)0)
#define WSTR(c)   ((VOID)0)
#endif // DEBUG

GBOOL
initWLModule(VOID)                 /* initialize Worldlink module          */
{
     clearInfo();
     emlPush=ynopt(EMLPUSH);
     audsnd=ynopt(AUDSND);
     audrcv=ynopt(AUDRCV);
     audmgr=ynopt(AUDMGR);
     audgen=ynopt(AUDGEN);
     audserr=ynopt(AUDSERR);
     audrerr=ynopt(AUDRERR);
     audmerr=ynopt(AUDMERR);
     audgerr=ynopt(AUDGERR);
     rcvbuf=alcmem(MSGBUFSIZ);
     errwait=numopt(ERRWAIT,1,3600);
     rcvtim=numopt(RCVTIM,0,32767);
     dclvda(TXTLEN);
     worRegisterModule(&wlmod);
     wlModActive=TRUE;
     return(TRUE);
}

VOID
closeWLModule(VOID)                /* shut down Worldlink module           */
{
     if (wlModActive) {
          free(rcvbuf);
     }
     wlModActive=FALSE;
}

/* WorldLink module functions */

VOID
wlmConnect(VOID)                   /* WL module connect handler            */
{
     if (wlinf.flags&WLF_CONNECT) {
          lowResetSys(ERR_UNKNOWN);
     }
     wlinf.flags=WLF_CONNECT;
     startSys();
}

VOID
wlmDisconnect(VOID)                /* WL module disconnect handler         */
{
     lowResetSys(ERR_DISC);
     statUpdate();
}

VOID
wlmCommand(                        /* WL module command handler            */
SHORT cmdnum,                      /*   command to execute                 */
UINT length,                       /*   length of data                     */
VOID *value)                       /*   command data                       */
{
     INT i;

     CURCMD(cmdnum);
     LOG(spr("Command Rcvd:    cmd=%s",CMDSTR(cmdnum)));
     if (wlActive) {
          if (wlinf.flags&WLF_CONNECT) {
               /* The following line is a work-around for a bug in the     */
               /* 16-bit Borland compiler (v5.0) which treats a[i-c] as    */
               /* *((a+i)+(a-c)).  This is a big problem since i is large  */
               /* so evaluating (a+i) overflows a 16-bit integer.          */
               i=cmdnum-CMD_SIDBAS;
               if (cmdnum >= 0 && cmdnum < NGENCMD
                && cmdFuncGen[cmdnum] != NULL
                && (!(wlinf.flags&WLF_RSTIPG) || cmdnum == CMD_RSTRCV)) {
                    (*cmdFuncGen[cmdnum])(length,value);
               }
               else if (cmdnum >= CMD_SIDBAS && cmdnum < (CMD_SIDBAS+NSIDECMD)
                     && cmdFuncSide[i] != NULL && !(wlinf.flags&WLF_RSTIPG)) {
                    (*cmdFuncSide[i])(length,value);
               }
          }
          else {
               resetSys();
          }
          statUpdate();
     }
}

VOID
wlmSquelch(                        /* WL module squelch override handler   */
SHORT cmdnum,                      /*   command to execute                 */
UINT length,                       /*   length of data                     */
VOID *value)                       /*   command data                       */
{
     wlmCommand(cmdnum,length,value);
}

/* command handlers */

VOID
cmdReset(                          /* reset connection command             */
UINT length,                       /*   command data length                */
VOID *value)                       /*   command data                       */
{
     (VOID)length;
     (VOID)value;
     ASSERT(wlinf.flags&WLF_CONNECT);
     CLOG(spr("sndstt=%s, rcvstt=%s, genstt=%s",SSTR(wlinf.sndstt),
              RSTR(wlinf.rcvstt),GSTR(wlinf.genstt)));
     sendCmd(CMD_RSTRCV,0,NULL);
     lowResetSys(ERR_RESET);
     startSys();
}

VOID
cmdResetAck(                       /* reset connection acknowledged command*/
UINT length,                       /*   command data length                */
VOID *value)                       /*   command data                       */
{
     (VOID)length;
     (VOID)value;
     CLOG(spr("sndstt=%s, rcvstt=%s, genstt=%s",SSTR(wlinf.sndstt),
              RSTR(wlinf.rcvstt),GSTR(wlinf.genstt)));
     wlinf.flags&=~WLF_RSTIPG;
     startSys();
}

VOID
cmdError(                          /* error command                        */
UINT length,                       /*   command data length                */
VOID *value)                       /*   command data                       */
{
     INT cmd,err;

     (VOID)length;
     CLOG(spr("sndstt=%s, rcvstt=%s, genstt=%s",SSTR(wlinf.sndstt),
              RSTR(wlinf.rcvstt),GSTR(wlinf.genstt)));
     sscanf(value,"%d\t%d",&cmd,&err);
     switch (cmd) {
     case CMD_ATT:
     case CMD_MSGDATA:
     case CMD_MSGDONE:
     case CMD_EOT:
     case CMD_SNDMAIL:
          if (wlinf.sndstt != SS_READY && !(wlinf.flags&WLF_SNDCIP)) {
               if (cmd == CMD_MSGDONE && err == ERR_ACCESS) {
                    shocst("WL SEND-MESSAGE ERROR",
                           "You do not have access to e-mail");
                    storDelMsg(wlinf.sndf);
                    unlink(makeOutAtt(wlinf.sndmsg));
               }
               else if (audserr) {
                    shocst("WL SEND-MESSAGE ERROR","Hub reported error: %d",err);
               }
               sndRestart();
          }
          break;
     case CMD_ATTRCV:
     case CMD_MSGRCV:
     case CMD_GETEML:
     case CMD_GETFOR:
          if (wlinf.rcvstt != RS_READY && !(wlinf.flags&WLF_RCVCIP)) {
               if (audrerr) {
                    shocst("WL GET-MESSAGE ERROR","Hub reported error: %d",err);
               }
               rcvRestart();
          }
          break;
     case CMD_GETINFO:
     case CMD_GETFDET:
     case CMD_CRTFOR:
     case CMD_GETFCFG:
     case CMD_MODFCFG:
     case CMD_GETFACC:
     case CMD_SETFACC:
     case CMD_NONDFT:
     case CMD_DELFOR:
          if (wlinf.mgrstt != MS_READY && !(wlinf.flags&WLF_MGRCIP)) {
               if (audmerr) {
                    shocst("WL MSG MANAGER ERROR","Hub reported error: %d",err);
               }
               if (wlinf.mgrreq != NOREQ) {
                    (*wlinf.mgrabort)(wlinf.mgrreq,err);
               }
               cancelMgr();
          }
          break;
     case CMD_GETFLST:
          if (wlinf.genstt != GS_READY && !(wlinf.flags&WLF_GENCIP)) {
               if (audgerr) {
                    shocst("WL MESSAGE SYSTEM ERROR","Hub reported error: %d",err);
               }
               switch (cmd) {
               case CMD_GETFLST:
                    lstRestart();
                    break;
               }
          }
          break;
     }
}

VOID
cmdCancel(                         /* cancel command                       */
UINT length,                       /*   command data length                */
VOID *value)                       /*   command data                       */
{
     (VOID)length;
     (VOID)value;
     CLOG(NULL);
}

VOID
cmdCancelAck(                      /* cancelled command                    */
UINT length,                       /*   command data length                */
VOID *value)                       /*   command data                       */
{
     CLOG(spr("rcvstt=%s, sndstt=%s, len=%u, val=\"%.30s\"",
              RSTR(wlinf.rcvstt),SSTR(wlinf.sndstt),length,value));
     (VOID)length;
     switch (atoi(value)) {
     case CMD_SNDMAIL:
          wlinf.flags&=~WLF_SNDCIP;
          if (wlinf.flags&WLF_SNDRIP) {
               wlinf.flags&=~WLF_SNDRIP;
               rtkick(errwait,sndStart);
          }
          break;
     case CMD_GETEML:
     case CMD_GETFOR:
          wlinf.flags&=~WLF_RCVCIP;
          if (wlinf.flags&WLF_RCVRIP) {
               wlinf.flags&=~WLF_RCVRIP;
               rtkick(errwait,(voidfunc)rcvStart);
          }
          break;
     case CMD_GETINFO:
     case CMD_GETFDET:
     case CMD_CRTFOR:
     case CMD_GETFCFG:
     case CMD_MODFCFG:
     case CMD_GETFACC:
     case CMD_SETFACC:
     case CMD_NONDFT:
     case CMD_DELFOR:
          wlinf.flags&=~WLF_MGRCIP;
          break;
     case CMD_GETFLST:
          wlinf.flags&=~WLF_GENCIP;
          if (wlinf.flags&WLF_GENRIP) {
               wlinf.flags&=~WLF_GENRIP;
               rtkick(errwait,(voidfunc)lstStart);
          }
          break;
     }
}

VOID
cmdAtt(                            /* attachment command handler           */
UINT length,                       /*   command data length                */
VOID *value)                       /*   command data                       */
{
     WORFILEINFO *finf=(WORFILEINFO *)value;
     LONG maxAttSiz;

     RLOG(spr("fstat=%s",FSTR(finf->status)));
     (VOID)length;
     if (wlinf.flags&WLF_RCVCIP) {
          return;
     }
     ASSERT(length >= sizeof(WORFILEINFO));
     if (!((wlinf.rcvstt == RS_WAITMSG && finf->status == FST_START)
        || (wlinf.rcvstt == RS_ATTIPG && finf->status == FST_DONE))) {
          rcvAudSeqErr();
          rcvRestart();
     }
     else if (finf->status == FST_START) {
          if (wlinf.flags&WLF_RCVMAIL) {
               maxAttSiz=dftMaxEmlAtt;
          }
          else {
               maxAttSiz=getMaxForAtt(wlinf.rcvfnm);
          }
          if (maxAttSiz >= 0 && maxAttSiz < finf->totsiz/1024) {
               worFileAbortReceiver(ERR_SPACE);
               wlinf.flags|=WLF_ATTREJ;
               wlinf.rcvstt=RS_GOTATT;
               return;
          }
          wlinf.rcvnew=FALSE;
          wlinf.rcvstt=RS_ATTIPG;
          worFileDirectorySet(inDir);
          worFileNameSet(tmpanam(NULL));
     }
     else {
          wlinf.rcvstt=RS_GOTATT;
          ++wlinf.rcvnat;
          wlinf.rcvbyt+=finf->totsiz;
          makePath(wlinf.rcvatt,finf->dlpath,finf->filnam,GCMAXPTH);
          sendCmd(CMD_ATTRCV,0,NULL);
     }
}

VOID
cmdAttRcv(                         /* attachment receipt acknowledgement   */
UINT length,                       /*   command data length                */
VOID *value)                       /*   command data                       */
{
     (VOID)length;
     (VOID)value;
     SLOG(NULL);
     if (wlinf.flags&WLF_SNDCIP) {
          return;
     }
     if (wlinf.sndstt != SS_WAITRCV
      || wlinf.sndlen != storReadMsg(wlinf.sndf,wlMsgBuf,MSGBUFSIZ)) {
          sndAudSeqErr();
          sndRestart();
     }
     else {
          ++wlinf.sndnat;
          wlinf.sndbyt+=wlinf.sndasz;
          sndMessage();
     }
}

VOID
cmdMsgData(                        /* chunk of message received            */
UINT length,                       /*   command data length                */
VOID *value)                       /*   command data                       */
{
     RLOG(spr("len=%u",length));
     if (wlinf.flags&WLF_RCVCIP) {
          return;
     }
     if ((wlinf.rcvstt != RS_WAITMSG && wlinf.rcvstt != RS_GOTATT
       && wlinf.rcvstt != RS_GETMSG)
      || wlinf.rcvlen+length > MSGBUFSIZ) {
          rcvAudSeqErr();
          rcvRestart();
     }
     else {
          wlinf.rcvnew=FALSE;
          wlinf.rcvstt=RS_GETMSG;
          memcpy(&rcvbuf[wlinf.rcvlen],value,length);
          wlinf.rcvlen+=length;
     }
}

VOID
cmdMsgDone(                        /* end of message command               */
UINT length,                       /*   command data length                */
VOID *value)                       /*   command data                       */
{
     CHAR wlForumName[MAXFNAM];

     (VOID)length;
     (VOID)value;
     RLOG(NULL);
     if (wlinf.flags&WLF_RCVCIP) {
          return;
     }
     ++wlinf.rcvtot;
     if (wlinf.flags&WLF_RCVMAIL) {
          ++wlinf.rcveml;
     }
     else {
          ++wlinf.rcvfor;
     }
     wlinf.rcvbyt+=wlinf.rcvlen;
     if (wlinf.rcvstt != RS_GETMSG) {
          rcvAudSeqErr();
          rcvRestart();
          return;
     }
     wlmGetSectStr(wlHdrBuf,WLMAXHDR,HDRSECT,rcvbuf,wlinf.rcvlen);
     getWLHeader(wlForumName,MAXFNAM,WLH_FORUM,"",wlHdrBuf);
     if (((wlinf.flags&WLF_RCVMAIL) && *wlForumName != '\0')
      || !sameas(wlinf.rcvfnm,wlForumName)) {
          rcvAudErr("Received message in wrong forum");
          rcvRestart();
          return;
     }
     if (wlinf.flags&WLF_ATTREJ) {
          setmbk(wlmb);
          prfmsg(ATTREJ);
          stpans(prfbuf);
          rstmbk();
          wlmGetSectStr(vdatmp,TXTLEN,TXTSECT,rcvbuf,wlinf.rcvlen);
          if (isfmtted(vdatmp)) {
               insasc(TRUE,prfbuf,vdatmp,TXTLEN);
          }
          else {
               stlcat(vdatmp,prfbuf,TXTLEN);
          }
          wlmSetSectStr(rcvbuf,wlinf.rcvlen,MSGBUFSIZ,TXTSECT,vdatmp);
          clrprf();
     }
     if (wlImpBkgnd) {
          if (addMsg2In(rcvbuf,wlinf.rcvlen,
                        *wlinf.rcvatt == '\0' ? NULL : wlinf.rcvatt)) {
               LOG(spr("Added to store:  idx=%lu",storLastIdx()));
               rcvFinMsg();
          }
          else {
               if (audrerr) {
                    shocst("WL GET-MESSAGE ERROR",
                           "Unable to save message, may be out of disk space");
               }
               rcvRestart();
          }
     }
     else {
          if (impPrepMsg(rcvbuf,wlinf.rcvlen,wlinf.rcvatt)) {
               wlinf.rcvtid=initask(rcvTask);
          }
          else {
               rcvFinMsg();
          }
     }
}

VOID
cmdMsgRcv(                         /* message received acknowledgement     */
UINT length,                       /*   command data length                */
VOID *value)                       /*   command data                       */
{
     (VOID)length;
     (VOID)value;
     SLOG(NULL);
     if (wlinf.flags&WLF_SNDCIP) {
          return;
     }
     if (wlinf.sndstt != SS_SENTMSG) {
          sndAudSeqErr();
          sndRestart();
     }
     else {
          ++wlinf.sndtot;
          wlinf.sndbyt+=wlinf.sndlen;
          storDelMsg(wlinf.sndf);
          unlink(makeOutAtt(wlinf.sndmsg));
          sndNext();
     }
}

VOID
cmdEOT(                            /* end of transmission command          */
UINT length,                       /*   command data length                */
VOID *value)                       /*   command data                       */
{
     (VOID)length;
     (VOID)value;
     RLOG(NULL);
     if (wlinf.flags&WLF_RCVCIP) {
          return;
     }
     if (wlinf.rcvstt != RS_WAITMSG) {
          rcvAudSeqErr();
          rcvRestart();
          return;
     }
     if (wlImpBkgnd) {
          impStart();
     }
     wlinf.flags&=~WLF_RCVMAIL;
     if (!rcvNextFor()) {
          if (audrcv) {
               shocst("WL GET-MESSAGE DONE",
                      "Bytes: %lu, Msgs: %ld (%lde/%ldf), Files: %ld",
                      wlinf.rcvbyt,wlinf.rcvtot,wlinf.rcveml,
                      wlinf.rcvfor,wlinf.rcvnat);
          }
          cancelRcv();
          if (emlPush && (wlinf.flags&WLF_NEWMAIL)) {
               rcvStart();
          }
          else if (rcvtim != 0) {
               rcvKickOff(rcvtim);
          }
     }
}

VOID
cmdSendMail(                       /* begin C->S message transfer          */
UINT length,                       /*   command data length                */
VOID *value)                       /*   command data                       */
{
     (VOID)length;
     (VOID)value;
     SLOG(NULL);
     if (wlinf.flags&WLF_SNDCIP) {
          return;
     }
     if (wlinf.sndstt != SS_START) {
          sndAudSeqErr();
          sndRestart();
     }
     else {
          sndNext();
     }
}

VOID
cmdGetForLst(                      /* forum list command handler           */
UINT length,                       /*   command data length                */
VOID *value)                       /*   command data                       */
{
     WORFILEINFO *finf=(WORFILEINFO *)value;
     CHAR tmpBuf[GCMAXPTH];

     (VOID)length;
     GLOG(spr("fstat=%s",FSTR(finf->status)));
     if (wlinf.flags&WLF_GENCIP) {
          return;
     }
     ASSERT(length >= sizeof(WORFILEINFO));
     if (!((wlinf.genstt == GS_WAITLST && finf->status == FST_START)
        || (wlinf.genstt == GS_LSTIPG && finf->status == FST_DONE))) {
          genAudSeqErr();
          lstRestart();
     }
     else if (finf->status == FST_START) {
          wlinf.genstt=GS_LSTIPG;
          worFileDirectorySet(tmpDir);
          worFileNameSet(tmpanam(NULL));
     }
     else {
          genAudDone(spr("Done downloading forum list (%lu bytes)",
                         finf->totsiz));
          makePath(tmpBuf,finf->dlpath,finf->filnam,GCMAXPTH);
          unlink(wlForumList);
          if (rename(tmpBuf,wlForumList) == 0) {
               wlinf.genstt=GS_READY;
               infoChangeCS();
#ifndef GCDOS
               infoChangeAH();
#endif // GCDOS
          }
          else {
               unlink(tmpBuf);
               lstRestart();
          }
     }
}

VOID
cmdGetGenInf(                      /* get hub configuration info           */
UINT length,                       /*   command data length                */
VOID *value)                       /*   command data                       */
{
     (VOID)length;
     MLOG(NULL);
     setGlobalInfo(value);
}

VOID
cmdGetForDet(                      /* get details on a forum on hub        */
UINT length,                       /*   command data length                */
VOID *value)                       /*   command data                       */
{
     (VOID)length;
     MLOG(NULL);
     mgrPassResp(MS_GETDET,"Done retrieving forum details",value);
}

VOID
cmdCreateFor(                      /* create a forum on hub                */
UINT length,                       /*   command data length                */
VOID *value)                       /*   command data                       */
{
     (VOID)length;
     MLOG(NULL);
     mgrPassResp(MS_CRTFOR,"Done creating forum on hub",value);
}

VOID
cmdGetForCfg(                      /* get config of forum on hub           */
UINT length,                       /*   command data length                */
VOID *value)                       /*   command data                       */
{
     (VOID)length;
     MLOG(NULL);
     mgrPassResp(MS_GETCFG,"Done retrieving forum configuration",value);
}

VOID
cmdModForCfg(                      /* modify forum on hub                  */
UINT length,                       /*   command data length                */
VOID *value)                       /*   command data                       */
{
     (VOID)length;
     MLOG(NULL);
     mgrPassResp(MS_MODFOR,"Done modifying forum on hub",value);
}

VOID
cmdGetForAcc(                      /* get access of systems in forums      */
UINT length,                       /*   command data length                */
VOID *value)                       /*   command data                       */
{
     (VOID)length;
     MLOG(NULL);
     mgrPassResp(MS_GETACC,"Done getting access from hub",value);
}

VOID
cmdSetForAcc(                      /* set access of systems in forums      */
UINT length,                       /*   command data length                */
VOID *value)                       /*   command data                       */
{
     (VOID)length;
     MLOG(NULL);
     mgrPassResp(MS_SETACC,"Done setting access on hub",value);
}

VOID
cmdGetNonDft(                      /* systems w/non-default access in forum*/
UINT length,                       /*   command data length                */
VOID *value)                       /*   command data                       */
{
     (VOID)length;
     MLOG(NULL);
     mgrPassResp(MS_GETNDA,"Done getting access from hub",value);
}

VOID
cmdDeleteFor(                      /* forum deleted on hub                 */
UINT length,                       /*   command data length                */
VOID *value)                       /*   command data                       */
{
     (VOID)length;
     MLOG(NULL);
     mgrPassResp(MS_DELFOR,"Done deleting forum on hub",value);
}

VOID
cmdNewEmail(                       /* new mail for system rcvd by hub      */
UINT length,                       /*   command data length                */
VOID *value)                       /*   command data                       */
{
     (VOID)length;
     (VOID)value;
     GLOG("Got New-Mail notification");
     if (emlPush) {
          wlinf.flags|=WLF_NEWMAIL;
          rcvStart();
     }
}

VOID
cmdForChange(                      /* forum info on hub changed            */
UINT length,                       /*   command data length                */
VOID *value)                       /*   command data                       */
{
     (VOID)length;
     (VOID)value;
     GLOG("Got Forum-Change notification");
     lstTryUpdate();
}

/* send-messages functions */

VOID
sndStart(VOID)                     /* start sending outgoing mail (if any) */
{
     if ((wlinf.flags&WLF_CONNECT) && wlinf.sndstt == SS_READY
      && !(wlinf.flags&(WLF_SNDCIP|WLF_RSTIPG))) {
          ASSERT(wlinf.sndf == NULL);
          if (isfile(outStor)) {
               if ((wlinf.sndf=storOpen(outStor)) == NULL) {
                    if (audserr) {
                         shocst("WL SEND-MESSAGE ERROR",
                                "Unable to open outgoing message file");
                    }
                    return;
               }
               if (audsnd) {
                    shocst("WL SEND-MESSAGE START",
                           "Starting upload of messages to hub");
               }
               ASSERT(wlinf.sndtot == 0 && wlinf.sndnat == 0
                   && wlinf.sndbyt == 0);
               wlinf.sndstt=SS_START;
               sendCmd(CMD_SNDMAIL,0,NULL);
               statUpdate();
          }
     }
}

VOID
sndStop(VOID)                      /* cancel sending outgoing mail (if any)*/
{
     if (wlinf.sndstt != SS_READY) {
          sndStopLow();
          statUpdate();
     }
}

VOID
sndRestart(VOID)                   /* restart send process after a pause   */
{
     sndStopLow();
     if (worConnected()) {
          wlinf.flags|=WLF_SNDRIP;
     }
}

VOID
sndStopLow(VOID)                   /* low-level cancel sending             */
{
     sndAudAbort();
     cancelSnd();
     if (worConnected()) {
          sendCancel(CMD_SNDMAIL);
          wlinf.flags|=WLF_SNDCIP;
     }
}

VOID
sndNext(VOID)                      /* get next message to send             */
{
     INT ec;
     const CHAR *att;
     struct ffblk fb;

     if ((wlinf.sndlen=storNextMsg(wlinf.sndf,wlMsgBuf,MSGBUFSIZ)) == 0) {
          if ((ec=storLastErr()) != STER_EOF && audserr) {
               shocst("WL SEND-MESSAGE ERROR",
                      "Error reading outgoing mail file: %d",ec);
          }
          if (audsnd) {
               shocst("WL SEND-MESSAGE DONE",
                      "Bytes: %lu, Msgs: %ld, Files: %ld",
                      wlinf.sndbyt,wlinf.sndtot,wlinf.sndnat);
          }
          cancelSnd();
          unlink(outStor);
          sendCmd(CMD_EOT,0,NULL);
     }
     else {
          wlinf.sndmsg=storLastIdx();
          att=makeOutAtt(wlinf.sndmsg);
          if (fndfile(&fb,att,0)) {
               wlinf.sndstt=SS_SENTATT;
               wlinf.sndasz=fb.ff_fsize;
               LOG(spr("Sending Command: cmd=%s, sndstt=%s, name=%s",
                       CMDSTR(CMD_ATT),SSTR(wlinf.sndstt),att));
               if ((wlinf.sndfnum=worSendFile(WLMAPID,CMD_ATT,att,"",sndAttCbk))
                 < 0) {
                    if (audserr) {
                         shocst("WL SEND-MESSAGE ERROR",
                                "Error sending attachment: %d", wlinf.sndfnum);
                    }
                    sndRestart();
               }
          }
          else {
               sndMessage();
          }
     }
}

VOID
sndAttCbk(                         /* handle callbacks while sending att   */
LONG filnum,                       /*   file ID number                     */
CHAR *fileName,                    /*   name of file being sent            */
SHORT cmd,                         /*   command being used                 */
INT what)                          /*   status of file                     */
{
     (VOID)filnum;
     (VOID)fileName;
     (VOID)cmd;
     LOG(spr("Send-File Cbk:   cmd=%s, sndstt=%s, num=%ld, name=%s, what=%s",
             CMDSTR(cmd),SSTR(wlinf.sndstt),filnum,fileName,WSTR(what)));
     ASSERT(cmd == CMD_ATT);
     wlinf.sndfnum=-1L;
     if (wlinf.flags&WLF_CONNECT) {
          if (wlinf.sndstt == SS_SENTATT && what == SND_FINISH) {
               wlinf.sndstt=SS_WAITRCV;
               statUpdate();
               return;
          }
          if (audserr) {
               shocst("WL SEND-MESSAGE ERROR",
                      "Error sending attachment, stt=%d, what=%d",
                      wlinf.sndstt,what);
          }
          sndRestart();
     }
     statUpdate();
}

VOID
sndMessage(VOID)                   /* send a message to the hub            */
{
     size_t n,nsent;
     CHAR *optr;

     for (optr=wlMsgBuf,nsent=0 ; nsent < wlinf.sndlen ; optr+=n,nsent+=n) {
          n=min(wlinf.sndlen-nsent,CHUNKSZ);
          sendCmd(CMD_MSGDATA,n,optr);
     }
     sendCmd(CMD_MSGDONE,0,NULL);
     wlinf.sndstt=SS_SENTMSG;
}

VOID
sndAudSeqErr(VOID)                 /* audit command-out-of-sequence error  */
{
     if (audserr) {
          shocst("WL SEND-MESSAGE ERROR","Command out of sequence");
     }
}

VOID
sndAudAbort(VOID)                  /* audit send-message abort             */
{
     if (audserr) {
          shocst("WL SEND-MESSAGE ABORTED",
                 "Bytes: %lu, Msgs: %ld, Files: %ld",
                 wlinf.sndbyt,wlinf.sndtot,wlinf.sndnat);
     }
}

/* receive-messages functions */

VOID
rcvKickOff(                        /* rtkick get-message retry             */
INT kickTime)                      /*   amount of time to wait             */
{
     if (!rcvKickIpg) {
          rcvKickIpg=TRUE;
          rtkick(kickTime,rcvKickHdl);
     }
}

VOID
rcvKickHdl(VOID)                   /* get-messages rtkick handler          */
{
     rcvKickIpg=FALSE;
     rcvStart();
}

GBOOL
rcvStart(VOID)                     /* check for new mail for us            */
{
     if ((wlinf.flags&WLF_CONNECT) && !(wlinf.flags&(WLF_RCVCIP|WLF_RSTIPG))
      && wlinf.rcvstt == RS_READY) {
          if (audrcv) {
               shocst("WL GET-MESSAGE START",
                      "Starting download of messages from hub");
          }
          sendCmd(CMD_GETEML,0,NULL);
          ASSERT(wlinf.rcvtot == 0 && wlinf.rcveml == 0 && wlinf.rcvfor == 0
              && wlinf.rcvnat == 0 && wlinf.rcvbyt == 0);
          wlinf.flags|=WLF_RCVMAIL;
          wlinf.flags&=~(WLF_ATTREJ|WLF_NEWMAIL);
          wlinf.rcvnew=TRUE;
          wlinf.rcvstt=RS_WAITMSG;
          statUpdate();
          return(TRUE);
     }
     return(FALSE);
}

VOID
rcvStop(VOID)                      /* cancel receiving of mail (if any)    */
{
     if (wlinf.rcvstt != RS_READY) {
          rcvStopLow();
          statUpdate();
     }
}

VOID
rcvRestart(VOID)                   /* restart send process after a pause   */
{
     rcvStopLow();
     if (worConnected()) {
          wlinf.flags|=WLF_RCVRIP;
     }
}

VOID
rcvStopLow(VOID)                   /* low-level cancel receiving           */
{
     rcvAudAbort();
     cancelRcv();
     if (worConnected()) {
          sendCancel(wlinf.flags&WLF_RCVMAIL ? CMD_GETEML : CMD_GETFOR);
          wlinf.flags|=WLF_RCVCIP;
     }
}

GBOOL                              /*   returns TRUE if next forum found   */
rcvNextFor(VOID)                   /* find next forum to get messages from */
{
     INT i,j,n;
     const adr_t *echo;
     const struct fordef *fdef;
     CHAR *fornam;

     i=wlinf.rcvfidx+1;
     fornam=NULL;
     if (wlXrefActive) {
          if (i < xrfInUse) {
               fornam=xrfList[i]->name;
          }
     }
     else {
          for (n=numforums() ; i < n ; ++i) {
               fdef=fiddefp(i);
               echo=(const adr_t *)fdef->echoes;
               for (j=0 ; j < fdef->necho ; ++j) {
                    if (sameto(wfpfx,echo[j])) {
                         fornam=skppfx(echo[j]);
                         break;
                    }
               }
               if (j < fdef->necho) {
                    break;
               }
          }
     }
     if (fornam != NULL) {
          wlinf.rcvfidx=i;
          wlinf.rcvstt=RS_WAITMSG;
          wlinf.rcvnew=TRUE;
          sendCmd(CMD_GETFOR,STGLEN,stlcpy(wlinf.rcvfnm,fornam,MAXFNAM));
          return(TRUE);
     }
     return(FALSE);
}

VOID
rcvTask(                           /* receive/import task handler          */
INT taskid)                        /*   task ID being processed            */
{
     ASSERT(taskid == wlinf.rcvtid);
     if (!impPump()) {
          mfytask(taskid,NULL);
          wlinf.rcvtid=-1;
          rcvFinMsg();
          statUpdate();
     }
}

VOID
rcvFinMsg(VOID)                    /* finished receiving a message         */
{
     wlinf.rcvlen=0;
     *wlinf.rcvatt='\0';
     wlinf.flags&=~WLF_ATTREJ;
     wlinf.rcvstt=RS_WAITMSG;
     sendCmd(CMD_MSGRCV,0,NULL);
}

VOID
rcvAudSeqErr(VOID)                 /* audit command-out-of-sequence error  */
{
     rcvAudErr("Command out of sequence");
}

VOID
rcvAudErr(                         /* audit receiver errors                */
const CHAR *details)               /*   details of error                   */
{
     if (audrerr) {
          shocst("WL GET-MESSAGE ERROR",details);
     }
}

VOID
rcvAudAbort(VOID)                  /* audit get-message aborted            */
{
     if (audrerr) {
          shocst("WL GET-MESSAGE ABORTED",
                 "Bytes: %lu, Msgs: %ld (%lde/%ldf), Files: %ld",
                 wlinf.rcvbyt,wlinf.rcvtot,wlinf.rcveml,
                 wlinf.rcvfor,wlinf.rcvnat);
     }
}

/* forum management functions */

VOID
mgrGetInfo(VOID)                   /* get global info from hub             */
{
     sendCmd(CMD_GETINFO,0,NULL);
}

GBOOL                              /*   returns TRUE if request forwarded  */
mgrGetDetails(                     /* get forum details from hub           */
const CHAR *name,                  /*   WL forum name                      */
INT reqid,                         /*   request ID to respond to           */
mgrRespFunc respFunc,              /*   func to call with success response */
mgrAbortFunc abortFunc)            /*   func to call with error response   */
{
     return(mgrPassInfo(CMD_GETFDET,name,"Starting retrieval of forum details",
                        MS_GETDET,reqid,respFunc,abortFunc));
}

GBOOL                              /*   returns TRUE if request forwarded  */
mgrGetConfig(                      /* get forum configuration from hub     */
const CHAR *name,                  /*   WL forum name                      */
INT reqid,                         /*   request ID to respond to           */
mgrRespFunc respFunc,              /*   func to call with success response */
mgrAbortFunc abortFunc)            /*   func to call with error response   */
{
     return(mgrPassInfo(CMD_GETFCFG,name,"Starting retrieval of forum configuration",
                        MS_GETCFG,reqid,respFunc,abortFunc));
}

GBOOL                              /*   returns TRUE if request forwarded  */
mgrCreateFor(                      /* create a forum on the hub            */
const CHAR *cfg,                   /*   configuration info to create with  */
INT reqid,                         /*   request ID to respond to           */
mgrRespFunc respFunc,              /*   func to call with success response */
mgrAbortFunc abortFunc)            /*   func to call with error response   */
{
     return(mgrPassInfo(CMD_CRTFOR,cfg,"Starting to create a forum on the hub",
                        MS_CRTFOR,reqid,respFunc,abortFunc));
}

GBOOL                              /*   returns TRUE if request forwarded  */
mgrModifyFor(                      /* modify a forum on the hub            */
const CHAR *cfg,                   /*   new configuration info             */
INT reqid,                         /*   request ID to respond to           */
mgrRespFunc respFunc,              /*   func to call with success response */
mgrAbortFunc abortFunc)            /*   func to call with error response   */
{
     return(mgrPassInfo(CMD_MODFCFG,cfg,"Starting to modify a forum on the hub",
                        MS_MODFOR,reqid,respFunc,abortFunc));
}

GBOOL                              /*   returns TRUE if request forwarded  */
mgrDeleteFor(                      /* delete a forum on the hub            */
const CHAR *name,                  /*   WL forum name                      */
INT reqid,                         /*   request ID to respond to           */
mgrRespFunc respFunc,              /*   func to call with success response */
mgrAbortFunc abortFunc)            /*   func to call with error response   */
{
     return(mgrPassInfo(CMD_DELFOR,name,"Starting to delete a forum on the hub",
                        MS_DELFOR,reqid,respFunc,abortFunc));
}

GBOOL                              /*   returns TRUE if request forwarded  */
mgrGetAccess(                      /* get forum access from hub            */
const CHAR *list,                  /*   WL forum name + list of systems    */
INT reqid,                         /*   request ID to respond to           */
mgrRespFunc respFunc,              /*   func to call with success response */
mgrAbortFunc abortFunc)            /*   func to call with error response   */
{
     return(mgrPassInfo(CMD_GETFACC,list,"Starting retrieval of forum access",
                        MS_GETACC,reqid,respFunc,abortFunc));
}

GBOOL                              /*   returns TRUE if request forwarded  */
mgrGetNonDft(                      /* get non-default forum access from hub*/
const CHAR *name,                  /*   WL forum name                      */
INT reqid,                         /*   request ID to respond to           */
mgrRespFunc respFunc,              /*   func to call with success response */
mgrAbortFunc abortFunc)            /*   func to call with error response   */
{
     return(mgrPassInfo(CMD_NONDFT,name,"Starting retrieval of forum access",
                        MS_GETNDA,reqid,respFunc,abortFunc));
}

GBOOL                              /*   returns TRUE if request forwarded  */
mgrSetAccess(                      /* set forum access on hub              */
const CHAR *list,                  /*   WL forum + list of systems/access  */
INT reqid,                         /*   request ID to respond to           */
mgrRespFunc respFunc,              /*   func to call with success response */
mgrAbortFunc abortFunc)            /*   func to call with error response   */
{
     return(mgrPassInfo(CMD_SETFACC,list,"Starting to set forum access on hub",
                        MS_SETACC,reqid,respFunc,abortFunc));
}

GBOOL                              /*   returns TRUE if request forwarded  */
mgrPassInfo(                       /* pass info to hub from client         */
INT cmd,                           /*   command to send to hub             */
const CHAR *info,                  /*   info to pass to hub                */
const CHAR *details,               /*   auditing details                   */
INT newstt,                        /*   new state for management system    */
INT reqid,                         /*   request ID to respond to           */
mgrRespFunc respFunc,              /*   func to call with success response */
mgrAbortFunc abortFunc)            /*   func to call with error response   */
{
     ASSERT(info != NULL);
     ASSERT(reqid != NOREQ);
     if ((wlinf.flags&WLF_CONNECT) && !(wlinf.flags&(WLF_MGRCIP|WLF_RSTIPG))
      && wlinf.mgrstt == MS_READY) {
          mgrAudStart(details);
          wlinf.mgrstt=newstt;
          wlinf.mgrreq=reqid;
          wlinf.mgrresp=respFunc;
          wlinf.mgrabort=abortFunc;
          sendCmd(wlinf.mgrcmd=cmd,STGLEN,info);
          statUpdate();
          return(TRUE);
     }
     return(FALSE);
}

VOID
mgrPassResp(                       /* pass response to client from hub     */
INT reqstt,                        /*   required management system state   */
const CHAR *details,               /*   auditing details                   */
const CHAR *info)                  /*   response to pass to client         */
{
     if (wlinf.flags&WLF_MGRCIP) {
          return;
     }
     if (wlinf.mgrstt != reqstt) {
          mgrAudSeqErr();
     }
     else {
          mgrAudDone(details);
          (*wlinf.mgrresp)(wlinf.mgrreq,info);
          cancelMgr();
     }
}

VOID
mgrCancel(                         /* notify WL module of operation cancel */
INT reqid)                         /*   request ID being cancelled         */
{
     if (wlinf.mgrstt != MS_READY && reqid == wlinf.mgrreq) {
          mgrCancelLow();
     }
}

VOID
mgrStop(VOID)                      /* cancel any activity of management sys*/
{
     if (wlinf.mgrstt != MS_READY) {
          ASSERT(wlinf.mgrcmd != 0);
          mgrAudAbort(wlinf.mgrcmd);
          if (wlinf.mgrreq != NOREQ) {
               (*wlinf.mgrabort)(wlinf.mgrreq,ERR_CANCEL);
          }
          mgrCancelLow();
     }
}

VOID
mgrCancelLow(VOID)                 /* low-level cancel-management-command  */
{
     ASSERT(wlinf.mgrstt != MS_READY);
     ASSERT(wlinf.mgrcmd != 0);
     if (worConnected()) {
          wlinf.flags|=WLF_MGRCIP;
          sendCancel(wlinf.mgrcmd);
     }
     cancelMgr();
     statUpdate();
}

VOID
mgrAudStart(                       /* audit start of management operation  */
const CHAR *detail)                /*   details of operation               */
{
     if (audmgr) {
          shocst("WL MSG MANAGER OPERATION START",detail);
     }
}

VOID
mgrAudDone(                        /* audit completion of management op    */
const CHAR *detail)                /*   details of operation               */
{
     if (audmgr) {
          shocst("WL MSG MANAGER OPERATION DONE",detail);
     }
}

VOID
mgrAudSeqErr(VOID)                 /* audit command-out-of-sequence error  */
{
     mgrAudErr("Command out of sequence");
}

VOID
mgrAudErr(                         /* audit management system errors       */
const CHAR *details)               /*   details of error                   */
{
     if (audrerr) {
          shocst("WL MESSAGING MANAGER ERROR",details);
     }
}

VOID
mgrAudAbort(                       /* audit management operation aborted   */
INT cmd)                           /*   command in progress                */
{
     if (audmerr) {
          shocst("WL MSG MANAGER OPERATION ABORTED","Command #%d aborted",cmd);
     }
}

/* general processing functions */

VOID
lstKickUpd(VOID)                   /* rtkick a list update retry           */
{
     if (!lstKickIpg) {
          lstKickIpg=TRUE;
          rtkick(1,lstTryUpdate);
     }
}

VOID
lstTryUpdate(VOID)                 /* try to update list                   */
{
     lstKickIpg=FALSE;
     if (!lstStart() && (wlinf.flags&WLF_CONNECT)) {
          lstKickUpd();
     }
}

GBOOL                              /*   returns TRUE if started            */
lstStart(VOID)                     /* start download of forum list file    */
{
     if ((wlinf.flags&WLF_CONNECT) && wlinf.genstt == GS_READY
      && !(wlinf.flags&(WLF_GENCIP|WLF_RSTIPG))) {
          genAudStart("Starting download of forum list");
          sendCmd(wlinf.gencmd=CMD_GETFLST,0,NULL);
          wlinf.genstt=GS_WAITLST;
          return(TRUE);
     }
     return(FALSE);
}

VOID
lstRestart(VOID)                   /* restart download of forum list file  */
{
     genAudAbort(CMD_GETFLST);
     if (worConnected()) {
          sendCancel(CMD_GETFLST);
          wlinf.flags|=(WLF_GENCIP|WLF_GENRIP);
     }
}

VOID
genStop(VOID)                      /* cancel any activity of general system*/
{
     if (wlinf.genstt != GS_READY) {
          genAudAbort(wlinf.gencmd);
          if (worConnected()) {
               wlinf.flags|=WLF_GENCIP;
               switch (wlinf.gencmd) {
               case CMD_GETFLST:
                    sendCancel(CMD_GETFLST);
                    break;
               }
          }
          cancelGen();
          statUpdate();
     }
}

VOID
genAudStart(                       /* audit start of general operation     */
const CHAR *detail)                /*   details of operation               */
{
     if (audgen) {
          shocst("WL MSG SYSTEM OPERATION START",detail);
     }
}

VOID
genAudDone(                        /* audit completion of general operation*/
const CHAR *detail)                /*   details of operation               */
{
     if (audgen) {
          shocst("WL MSG SYSTEM OPERATION DONE",detail);
     }
}

VOID
genAudSeqErr(VOID)                 /* audit command-out-of-sequence error  */
{
     if (audgerr) {
          shocst("WL MESSAGE SYSTEM ERROR","Command out of sequence");
     }
}

VOID
genAudAbort(                       /* audit operation aborted              */
INT cmd)                           /*   command in progress                */
{
     if (audgerr) {
          shocst("WL MSG SYSTEM OPERATION ABORTED","Command #%d aborted",cmd);
     }
}

/* utility functions */

VOID
startSys(VOID)                     /* do all the connection-started stuff  */
{
     sndStart();
     rcvStart();
     mgrGetInfo();
     lstStart();
     statUpdate();
}

VOID
resetSys(VOID)                     /* reset mail system                    */
{
     lowResetSys(ERR_RESET);
     if (worConnected()) {
          wlinf.flags=(WLF_CONNECT|WLF_RSTIPG);
          sendCmd(CMD_RESET,0,NULL);
     }
     statUpdate();
}

VOID
lowResetSys(                       /* low-level reset system utility       */
INT reason)                        /*   reason for reset                   */
{
     if (wlinf.sndstt != SS_READY) {
          sndAudAbort();
     }
     if (wlinf.rcvstt != RS_READY) {
          rcvAudAbort();
     }
     if (wlinf.mgrstt != MS_READY) {
          mgrAudAbort(wlinf.mgrcmd);
          if (wlinf.mgrreq != NOREQ) {
               (*wlinf.mgrabort)(wlinf.mgrreq,reason);
          }
     }
     if (wlinf.genstt != GS_READY) {
          genAudAbort(wlinf.gencmd);
     }
     cancelAll();
     clearInfo();
}

VOID
cancelAll(VOID)                    /* cancel any outstanding operations    */
{
     cancelSnd();
     cancelRcv();
     cancelMgr();
     cancelGen();
}

VOID
cancelSnd(VOID)                    /* cancel outstanding send operation    */
{
     LOG(spr("Send Operation Cancel: sndstt=%s",SSTR(wlinf.sndstt)));
     if (wlinf.sndf != NULL) {
          storClose(wlinf.sndf);
          wlinf.sndf=NULL;
     }
     if (wlinf.sndfnum >= 0) {
          worFileAbortSender(wlinf.sndfnum);
          wlinf.sndfnum=-1L;
     }
     wlinf.sndlen=0;
     wlinf.sndtot=wlinf.sndnat=wlinf.sndbyt=0;
     wlinf.sndstt=SS_READY;
}

VOID
cancelRcv(VOID)                    /* cancel outstanding receive operation */
{
     LOG(spr("Receive Operation Cancel: rcvstt=%s",RSTR(wlinf.rcvstt)));
     if (*wlinf.rcvatt != '\0') {
          unlink(wlinf.rcvatt);
          *wlinf.rcvatt='\0';
     }
     if (wlinf.rcvtid >= 0) {
          ASSERT(!wlImpBkgnd);
          mfytask(wlinf.rcvtid,NULL);
          wlinf.rcvtid=-1;
          cancelImp();
     }
     wlinf.flags&=~(WLF_ATTREJ|WLF_RCVRIP);
     *wlinf.rcvfnm='\0';
     wlinf.rcvfidx=-1;
     wlinf.rcvlen=0;
     wlinf.rcvtot=wlinf.rcveml=wlinf.rcvfor=wlinf.rcvnat=wlinf.rcvbyt=0;
     wlinf.rcvstt=RS_READY;
}

VOID
cancelMgr(VOID)                    /* cancel outstanding management op     */
{
     wlinf.mgrcmd=0;
     wlinf.mgrstt=MS_READY;
     wlinf.mgrreq=NOREQ;
}

VOID
cancelGen(VOID)                    /* cancel outstanding general operation */
{
     wlinf.gencmd=0;
     wlinf.genstt=GS_READY;
}

VOID
sendCancel(                        /* send cancel command to server        */
SHORT cmd)                         /*   command causing error              */
{
     CHAR buf[sizeof("-32768")];

     sprintf(buf,"%hd",cmd);
     sendCmd(CMD_CANCEL,STGLEN,buf);
}

VOID
sendCmd(                           /* send a command to the server         */
SHORT cmd,                         /*   command to send                    */
UINT length,                       /*   length of command data             */
VOID *value)                       /*   command data                       */
{
     LOG(spr("Sending Command: cmd=%s, sndstt=%s, rcvstt=%s, len=%u",
             CMDSTR(cmd),SSTR(wlinf.sndstt),RSTR(wlinf.rcvstt),length));
     worSend(WLMAPID,cmd,length,value);
}

VOID
clearInfo(VOID)                    /* clear state info                     */
{
     memset(&wlinf,0,sizeof(struct wlinf));
     wlinf.sndfnum=-1L;
     wlinf.rcvfidx=-1;
     wlinf.rcvtid=-1;
     wlinf.mgrreq=NOREQ;
}

/* status reporting functions */

const CHAR *                       /*   pointer to internal buffer         */
statSend(VOID)                     /* get sender status                    */
{
     INT status;

     setmbk(wlmb);
     clrprf();
     if (wlinf.flags&WLF_RSTIPG) {
          status=STRESET;
     }
     else {
          switch (wlinf.sndstt) {
          case SS_READY:
               if (wlinf.flags&WLF_SNDCIP) {
                    status=STCANCEL;
               }
               else {
                    status=STIDLE;
               }
               break;
          case SS_START:
               status=STSNDST;
               break;
          case SS_SENTATT:
          case SS_WAITRCV:
               status=STSNDATT;
               break;
          case SS_SENTMSG:
               status=STSNDMSG;
               break;
          default:
               status=STERROR;
               break;
          }
     }
     prfmsg(MODSND);
     prf("\t");
     prfmsg(status);
     stpans(prfbuf);
     rstmbk();
     return(prfbuf);
}

const CHAR *                       /*   pointer to internal buffer         */
statRecv(VOID)                     /* get receiver status                  */
{
     INT status;

     setmbk(wlmb);
     clrprf();
     if (wlinf.flags&WLF_RSTIPG) {
          status=STRESET;
     }
     else {
          switch (wlinf.rcvstt) {
          case RS_READY:
               if (wlinf.flags&WLF_RCVCIP) {
                    status=STCANCEL;
               }
               else {
                    status=STIDLE;
               }
               break;
          case RS_WAITMSG:
               if (wlinf.rcvnew) {
                    status=(wlinf.flags&WLF_RCVMAIL) ? STRCVRQE : STRCVRQF;
                    break;
               }
          case RS_GOTATT:
               status=STRCVWT;
               break;
          case RS_ATTIPG:
               status=STRCVATT;
               break;
          case RS_GETMSG:
               if (wlinf.rcvtid >= 0) {
                    status=STRCVIMP;
               }
               else {
                    status=STRCVMSG;
               }
               break;
          default:
               status=STERROR;
               break;
          }
     }
     prfmsg(MODRCV);
     prf("\t");
     prfmsg(status,wlinf.rcvfnm);
     stpans(prfbuf);
     rstmbk();
     return(prfbuf);
}

const CHAR *                       /*   pointer to internal buffer         */
statMgr(VOID)                      /* get management system status         */
{
     INT status;

     setmbk(wlmb);
     clrprf();
     if (wlinf.flags&WLF_RSTIPG) {
          status=STRESET;
     }
     else {
          switch (wlinf.mgrstt) {
          case MS_READY:
               if (wlinf.flags&WLF_MGRCIP) {
                    status=STCANCEL;
               }
               else {
                    status=STIDLE;
               }
               break;
          case MS_GETDET:
               status=STMGRDET;
               break;
          case MS_GETCFG:
               status=STMGRCFG;
               break;
          case MS_CRTFOR:
               status=STMGRCRT;
               break;
          case MS_MODFOR:
               status=STMGRMOD;
               break;
          case MS_DELFOR:
               status=STMGRDEL;
               break;
          case MS_GETACC:
               status=STMGRGAC;
               break;
          case MS_GETNDA:
               status=STMGRNDA;
               break;
          case MS_SETACC:
               status=STMGRSAC;
               break;
          default:
               status=STERROR;
               break;
          }
     }
     prfmsg(MODMGR);
     prf("\t");
     prfmsg(status);
     stpans(prfbuf);
     rstmbk();
     return(prfbuf);
}

const CHAR *                       /*   pointer to internal buffer         */
statSys(VOID)                      /* get general system status            */
{
     INT status;

     setmbk(wlmb);
     clrprf();
     if (wlinf.flags&WLF_RSTIPG) {
          status=STRESET;
     }
     else {
          switch (wlinf.genstt) {
          case GS_READY:
               if (wlinf.flags&WLF_GENCIP) {
                    status=STCANCEL;
               }
               else {
                    status=STIDLE;
               }
               break;
          case GS_WAITLST:
               status=STGENSFL;
               break;
          case GS_LSTIPG:
               status=STGENRFL;
               break;
          default:
               status=STERROR;
               break;
          }
     }
     prfmsg(MODGEN);
     prf("\t");
     prfmsg(status);
     stpans(prfbuf);
     rstmbk();
     return(prfbuf);
}

/* debug logging functions */

#ifdef DEBUG
VOID
SLOG(const CHAR *s)
{
     if (s == NULL) {
          CLOG(spr("sndstt=%s",SSTR(wlinf.sndstt)));
     }
     else {
          CLOG(spr("sndstt=%s, %s",SSTR(wlinf.sndstt),s));
     }
}

VOID
RLOG(const CHAR *s)
{
     if (s == NULL) {
          CLOG(spr("rcvstt=%s",RSTR(wlinf.rcvstt)));
     }
     else {
          CLOG(spr("rcvstt=%s, %s",RSTR(wlinf.rcvstt),s));
     }
}

VOID
MLOG(const CHAR *s)
{
     if (s == NULL) {
          CLOG(spr("mgrstt=%s",MSTR(wlinf.mgrstt)));
     }
     else {
          CLOG(spr("mgrstt=%s, %s",MSTR(wlinf.mgrstt),s));
     }
}

VOID
GLOG(const CHAR *s)
{
     if (s == NULL) {
          CLOG(spr("genstt=%s",GSTR(wlinf.genstt)));
     }
     else {
          CLOG(spr("genstt=%s, %s",GSTR(wlinf.genstt),s));
     }
}

VOID
CLOG(const CHAR *s)
{
     if (s == NULL) {
          LOG(spr("Command Handler: cmd=%s",CMDSTR(curcmd)));
     }
     else {
          LOG(spr("Command Handler: cmd=%s, %s",CMDSTR(curcmd),s));
     }
}

const CHAR *
CMDSTR(INT cmd)
{
     switch (cmd) {
     case CMD_RESET:
          return("RESET");
     case CMD_RSTRCV:
          return("RSTRCV");
     case CMD_ERROR:
          return("ERROR");
     case CMD_CANCEL:
          return("CANCEL");
     case CMD_CANCRCV:
          return("CANCRCV");
     case CMD_ATT:
          return("ATT");
     case CMD_ATTRCV:
          return("ATTRCV");
     case CMD_MSGDATA:
          return("MSGDATA");
     case CMD_MSGDONE:
          return("MSGDONE");
     case CMD_MSGRCV:
          return("MSGRCV");
     case CMD_EOT:
          return("EOT");
     case CMD_SNDMAIL:
          return("SNDMAIL");
     case CMD_GETFLST:
          return("GETFLST");
     case CMD_GETINFO:
          return("GETINFO");
     case CMD_GETFDET:
          return("GETFDET");
     case CMD_CRTFOR:
          return("CRTFOR");
     case CMD_GETFCFG:
          return("GETFCFG");
     case CMD_MODFCFG:
          return("MODFCFG");
     case CMD_GETFACC:
          return("GETFACC");
     case CMD_SETFACC:
          return("SETFACC");
     case CMD_NONDFT:
          return("NONDFT");
     case CMD_DELFOR:
          return("DELFOR");
     case CMD_GETEML:
          return("GETEML/FORCHG");
     case CMD_GETFOR:
          return("GETFOR");
     default:
          return(spr("%d",cmd));
     }
}

const CHAR *
SSTR(INT stt)
{
     switch (stt) {
     case SS_START:
          return("NEXTMSG");
     case SS_SENTATT:
          return("SENTATT");
     case SS_WAITRCV:
          return("WAITRCV");
     case SS_SENTMSG:
          return("SENTMSG");
     default:
          return(spr("%d",stt));
     }
}

const CHAR *
RSTR(INT stt)
{
     switch (stt) {
     case RS_WAITMSG:
          return("WAITMSG");
     case RS_ATTIPG:
          return("ATTIPG");
     case RS_GOTATT:
          return("GOTATT");
     case RS_GETMSG:
          return("GETMSG");
     default:
          return(spr("%d",stt));
     }
}

const CHAR *
MSTR(INT stt)
{
     switch (stt) {
     case MS_GETDET:
          return("GETDET");
     default:
          return(spr("%d",stt));
     }
}

const CHAR *
GSTR(INT stt)
{
     switch (stt) {
     case GS_WAITLST:
          return("WAITLST");
     case GS_LSTIPG:
          return("LSTIPG");
     default:
          return(spr("%d",stt));
     }
}

const CHAR *
FSTR(INT stat)
{
     switch (stat) {
     case FST_START:
          return("START");
     case FST_CHUNK:
          return("CHUNK");
     case FST_DONE:
          return("DONE");
     default:
          return(spr("%d",stat));
     }
}

const CHAR *
WSTR(INT what)
{
     switch (what) {
     case SND_NOROOM:
          return("NOROOM");
     case SND_INVAPP:
          return("INVAPP");
     case SND_INVOAP:
          return("INVOAP");
     case SND_FINISH:
          return("FINISH");
     case SND_ABORTD:
          return("ABORTD");
     case SND_NOCONN:
          return("NOCONN");
     case SND_NOFILE:
          return("NOFILE");
     case SND_ERRFIL:
          return("ERRFIL");
     case SND_ERROPN:
          return("ERROPN");
     case SND_EROSPC:
          return("EROSPC");
     case SND_ERORCV:
          return("ERORCV");
     default:
          return(spr("%d",what));
     }
}
#endif DEBUG
