/***************************************************************************
 *                                                                         *
 *   CSFOR.C                                                               *
 *                                                                         *
 *   Copyright (c) 1988-1997 Galacticomm, Inc.    All Rights Reserved.     *
 *                                                                         *
 *   This is the Worldgroup Client/Server Forums (public messaging areas)  *
 *   handler.                                                              *
 *                                                                         *
 *                                                - J. Alvrus  11/11/94    *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "majorbbs.h"
#include "gcspsrv.h"
#include "filexfer.h"
#include "chandir.h"
#include "gme.h"
#include "galtext.h"
#include "galmsg.h"
#include "emlfor.h"
#include "csef.h"

#define FILREV "$Revision: 25 $"
                                   /* forums dynapak suffix-prefixes       */
#define FORINF   "forum "          /*   get basic forum info               */
#define FORDET   "fordet "         /*   get details on a forum             */
#define FORFSFX  "forfil"          /*   download forum info file suffix    */
#define FORFDPK  "sauf:"FORFSFX    /*   download forum info file dpk name  */
#define FORFSSFX "forfilsys"       /*   download sysop forum file suffix   */
#define FORFSDPK "saf:"FORFSSFX    /*   download sysop forum info dpk name */
#define THRINF   "thread "         /*   get info on a thread               */
#define FORHDR   "forhdr "         /*   get forum msg header               */
#define FORMSG   "formsg "         /*   get forum message                  */
#define THRHDR   "h "              /*   get thread msg header              */
#define THREXC   "e "              /*   get thread msg excerpt             */
#define THRMSG   "m "              /*   get thread message                 */
#define SCNHDR   "scnhdr "         /*   get scan msg header                */
#define SCNMSG   "scnmsg "         /*   get scan message                   */
#define PARHDR   "parhdr "         /*   get parent msg header              */
#define PARMSG   "parmsg "         /*   get parent message                 */
#define UNAHDR   "unahdr "         /*   get header of msg w/unapv att      */
#define UNAMSG   "unamsg "         /*   get message w/unapproved attachment*/
#define INISCN   "sau:iniscn"      /*   initialize scan dynapak name       */
#define MODMSG   "modmsg "         /*   modify forum message               */
#define FWDFOR   "fwdmsg "         /*   forward forum message              */
#define CPYFOR   "cpymsg "         /*   copy forum message                 */
#define INIFOR   "inifor"          /*   create a forum (read and write)    */
#define CRTFOR   "crtfor"          /*   create a forum (read and write)    */
#define MODFOR   "modfor "         /*   modify or delete a forum           */
#define MSGXMT   "msgxmt "         /*   exempt/unexempt a message          */
#define MSGAPV   "msgapv "         /*   approve/unapprove an attachment    */
#define USRACC   "usracc "         /*   get/set user access to a forum     */
#define CPYACC   "cpyacc"          /*   copy user access to all forums     */
#define CFGACC   "cfgacc "         /*   set default access to a forum      */
#define CRTGRP   "crtgrp"          /*   create a forum group               */
#define MODGRP   "modgrp "         /*   modify/delete a forum group        */
#define MODGRPF  "modgrpf "        /*   modify a group's list of forums    */
#define GRPFSFX  "grpfil"          /*   download group info file suffix    */
#define GRPFDPK  "sauf:"GRPFSFX    /*   download group info file dpk name  */
#define GRPFSSFX "grpfilsys"       /*   sysop download group file suffix   */
#define GRPFSDPK "saf:"GRPFSSFX    /*   sysop download group file dpk name */
#define GRPINF   "grpinf "         /*   read complete group info           */
#define GRPGRP   "grpgrp "         /*   read groups in a group             */
#define GRPFOR   "grpfor "         /*   read forums in a group             */

#define NFNOTDPK "newfornot "      /* new forum notify unsolicited dpk     */
#define MFNOTDPK "modfornot "      /* forum modified ntfy unsolicited dpk  */
#define DFNOTDPK "delfornot "      /* forum deleted notify unsolicited dpk */

#define FIECHO   0x0010            /* forum info flag: forum is echoed     */
#define CHGFNAM  "GALFGCHG.DTA"    /* forum/group change time file         */
#define DROPFPTH "GALFORCS"        /* drop file channel subdirectory name  */
#define FORFNAM  "FORINF.TXT"      /* forum file name                      */
#define GRPFNAM  "FORGRP.TXT"      /* group file name                      */
#define MAXNTFY  8                 /* max App-IDs to get forum chg notify  */
#define grprqi   rqiptr->u.g       /* group-specific per-request info      */

                                   /* global forum access flags            */
#define GFAOPMOD 0x0001            /*   forum-ops can modify whole forum   */
#define GFASYSOP 0x0002            /*   user is forum sysop                */
/*      EAINDATT 0x0020                 make indirect attachments to msgs  */

                                   /* group file processing states         */
#define GRPSFOR 0                  /*   outputting forums                  */
#define GRPSGRP 1                  /*   outputting groups                  */

                                   /* scan init error codes                */
#define SCINVR 0                   /*   can't scan at all                  */
#define SCINOW 1                   /*   can't scan now (no buffers)        */
#define SCINVF 2                   /*   no valid forums in scan            */

static GBOOL badaxes(INT usraxes,INT newaxes,INT oldaxes);
static VOID f_read(INT direction,struct saunam *dpknam);
static VOID f_write(struct saunam *dpknam,USHORT length,VOID *value);
static VOID fxdone(VOID);
static VOID fabort(VOID);

struct agent foragt={              /* agent information structure          */
     FORAPID,                      /*   appid                              */
     f_read,                       /*   read-dynapak function pointer      */
     f_write,                      /*   write-dynapak function pointer     */
     fxdone,                       /*   file xfer-done function pointer    */
     fabort                        /*   abort-request function pointer     */
};

struct globfacc {                  /* global forum access structure        */
     SHORT flags;                  /*   global access flags                */
     CHAR dftfor[FORNSZ-1];        /*   default forum name                 */
     SHORT maxscnf;                /*   max # of forums allowed in scan    */
     LONG txtlen;                  /*   max message text length            */
     SHORT ccmax;                  /*   max # cc:s per message             */
     SHORT maxgrpf;                /*   max # of forums per group          */
     CHAR fmgrapid[AIDSIZ-1];      /*   forum manager App-ID               */
};

struct flddef globfaccFDA[] = {
     {CVTFLD_SHORT,1       ,fldoff(globfacc,flags)   ,NULL},
     {CVTFLD_CHAR ,FORNSZ-1,fldoff(globfacc,dftfor)  ,NULL},
     {CVTFLD_SHORT,1       ,fldoff(globfacc,maxscnf) ,NULL},
     {CVTFLD_LONG ,1       ,fldoff(globfacc,txtlen)  ,NULL},
     {CVTFLD_SHORT,1       ,fldoff(globfacc,ccmax)   ,NULL},
     {CVTFLD_SHORT,1       ,fldoff(globfacc,maxgrpf) ,NULL},
     {CVTFLD_CHAR ,AIDSIZ-1,fldoff(globfacc,fmgrapid),NULL},
     {CVTFLD_END  ,0       ,0                        ,NULL}
};

struct basfinf {                   /* basic forum info structure           */
     USHORT forum;                 /*   forum ID                           */
     CHAR axes;                    /*   current user's access level        */
     CHAR topic[TPCSIZ];           /*   forum topic                        */
};

#define fdpksiz(infptr) (fldoff(basfinf,topic)+strlen((infptr)->topic))

struct flddef basfinfFDA[] = {
     {CVTFLD_SHORT,1       ,fldoff(basfinf,forum),NULL},
     {CVTFLD_RTEXT,0       ,fldoff(basfinf,axes) ,NULL},
     {CVTFLD_END  ,0       ,0                    ,NULL}
};

struct credfdpk {                  /* create/edit-a-forum dpk structure    */
     SHORT dfnpv;                  /*   default non-priv access setting    */
     SHORT dfprv;                  /*   default priviledged access setting */
     SHORT mxnpv;                  /*   maximum non-priv access setting    */
     SHORT msglif;                 /*   message lifetime (days)            */
     SHORT chgmsg;                 /*   charge per message posted          */
     SHORT chgrdm;                 /*   charge per message read            */
     SHORT chgatt;                 /*   charge per file attachment uploaded*/
     SHORT chgadl;                 /*   charge per file attachment download*/
     SHORT chgupk;                 /*   charge per-kbyte for upload        */
     SHORT chgdpk;                 /*   charge per-kbyte for download      */
     SHORT ccr;                    /*   credit consumption rate            */
     SHORT pfnlvl;                 /*   profanity suppression level        */
     LONG seqid;                   /*   number of forum in list of forums  */
     SHORT necho;                  /*   number of echo addresses           */
     CHAR info[1];                 /*   variable-length forum info:        */
                                   /*   (forum name, forum topic, forum op,*/
                                   /*   privleged access key, echo         */
                                   /*   addresses (if any), help message,  */
                                   /*   data file (if creating), attachment*/
                                   /*   path (if creating))                */
};

struct flddef credfdpkFDA[] = {
     {CVTFLD_SHORT,12,fldoff(credfdpk,dfnpv),NULL},
     {CVTFLD_LONG ,1 ,fldoff(credfdpk,seqid),NULL},
     {CVTFLD_SHORT,1 ,fldoff(credfdpk,necho),NULL},
     {CVTFLD_RTEXT,0 ,fldoff(credfdpk,info) ,NULL},
     {CVTFLD_END  ,0 ,0                     ,NULL}
};

struct flddef foraccFDA[]={
     {CVTFLD_SHORT,3     ,fldoff(foracc,dfnpv) ,NULL},
     {CVTFLD_CHAR ,KEYSIZ,fldoff(foracc,forlok),NULL},
     {CVTFLD_END  ,0     ,0                    ,NULL}
};

struct viewfdpk {                  /* view-a-forum dpk structure           */
     SHORT forum;                  /*   forum ID                           */
     LONG nthrs;                   /*   number of threads in forum         */
     LONG nmsgs;                   /*   number of messages in forum        */
     LONG nfiles;                  /*   number of files in forum           */
     LONG nw4app;                  /*   number of files waiting for apprvl */
     SHORT dfnpv;                  /*   default non-privileged access      */
     SHORT dfprv;                  /*   default privileged access setting  */
     SHORT mxnpv;                  /*   maximum non-privileged access      */
     SHORT msglif;                 /*   message lifetime (days)            */
     SHORT chgmsg;                 /*   charge per message posted          */
     SHORT chgrdm;                 /*   charge per message read            */
     SHORT chgatt;                 /*   charge per file attachment uploaded*/
     SHORT chgadl;                 /*   charge per file attachment download*/
     SHORT chgupk;                 /*   charge per-kbyte for upload        */
     SHORT chgdpk;                 /*   charge per-kbyte for download      */
     SHORT ccr;                    /*   credit consumption rate            */
     SHORT pfnlvl;                 /*   profanity suppression level        */
     DOUBLE crdatim;               /*   forum creation date/time           */
     SHORT necho;                  /*   number of echo addresses           */
     LONG seqid;                   /*   number of forum in list of forums  */
     CHAR info[1];                 /*   variable-length forum info:        */
                                   /*   (forum name, forum topic, forum op,*/
                                   /*   privileged access key, echo        */
                                   /*   addresses (if any), help message,  */
                                   /*   data file, attachment path)        */
};

struct flddef viewfdpkFDA[] = {
     {CVTFLD_SHORT ,1 ,fldoff(viewfdpk,forum)  ,NULL},
     {CVTFLD_LONG  ,4 ,fldoff(viewfdpk,nthrs)  ,NULL},
     {CVTFLD_SHORT ,12,fldoff(viewfdpk,dfnpv)  ,NULL},
     {CVTFLD_DOUBLE,1 ,fldoff(viewfdpk,crdatim),NULL},
     {CVTFLD_SHORT ,1 ,fldoff(viewfdpk,necho)  ,NULL},
     {CVTFLD_LONG  ,1 ,fldoff(viewfdpk,seqid)  ,NULL},
     {CVTFLD_RTEXT ,0 ,fldoff(viewfdpk,info)   ,NULL},
     {CVTFLD_END   ,0 ,0                       ,NULL}
};

struct thrinfo {                   /* info on a thread structure           */
     USHORT nmsgs;                 /*   number of messages in the thread   */
     CHAR topic[TPCSIZ];           /*   thread topic                       */
};

struct flddef thrinfoFDA[] = {
     {CVTFLD_SHORT,1,fldoff(thrinfo,nmsgs),NULL},
     {CVTFLD_RTEXT,0,fldoff(thrinfo,topic),NULL},
     {CVTFLD_END  ,0,0                    ,NULL}
};

struct flddef otscanFDA[]={
     {CVTFLD_RTEXT,MAXSKWD,fldoff(otscan,keywds) ,NULL},
     {CVTFLD_LONG ,1      ,fldoff(otscan,stmsgid),NULL},
     {CVTFLD_CHAR ,1      ,fldoff(otscan,flags)  ,NULL},
     {CVTFLD_SHORT,1      ,fldoff(otscan,nforums),NULL},
     {CVTFLD_SHORT,0      ,fldoff(otscan,forlst) ,NULL},
     {CVTFLD_END  ,0      ,0                     ,NULL}
};

struct cmgrpdpk {                  /* create/modify forum group dpk struct */
     USHORT parid;                 /*   group ID of this group's parent    */
     SHORT flags;                  /*   group flags                        */
     CHAR info[1];                 /*   string info, consisting of:        */
                                   /*        group name                    */
                                   /*        key required to use group     */
                                   /*        group topic                   */
};                                 /*   separated by FLDSEP                */

struct flddef cmgrpdpkFDA[]={
     {CVTFLD_SHORT,2,fldoff(cmgrpdpk,parid),NULL},
     {CVTFLD_RTEXT,0,fldoff(cmgrpdpk,info) ,NULL},
     {CVTFLD_END  ,0,0                     ,NULL}
};

#define MINGDPKSZ (fldoff(cmgrpdpk,info))
#define MAXGDPKSZ (fldoff(cmgrpdpk,info)+FORNSZ+KEYSIZ+TPCSIZ-1)

struct dpkgrpi {                   /* dpk form of forum group all info     */
     USHORT grpid;                 /*   group ID                           */
     USHORT parid;                 /*   group ID of this group's parent    */
     CHAR name[FORNSZ-1];          /*   group name                         */
     CHAR topic[TPCSIZ-1];         /*   group topic                        */
     CHAR key[KEYSIZ-1];           /*   key required to use this group     */
     SHORT flags;                  /*   group flags                        */
     SHORT nforums;                /*   number of forums in group          */
     USHORT forarr[1];             /*   list of forum IDs in group         */
};
#define dpkgrplen(n) (fldoff(dpkgrpi,forarr)+(n)*sizeof(USHORT))
                                   /* macro to compute group info dpk len  */

struct flddef dpkgrpiFDA[]={
     {CVTFLD_SHORT,2       ,fldoff(dpkgrpi,grpid),NULL},
     {CVTFLD_CHAR ,FORNSZ-1,fldoff(dpkgrpi,name) ,NULL},
     {CVTFLD_RTEXT,TPCSIZ-1,fldoff(dpkgrpi,topic),NULL},
     {CVTFLD_CHAR ,KEYSIZ-1,fldoff(dpkgrpi,key)  ,NULL},
     {CVTFLD_SHORT,0       ,fldoff(dpkgrpi,flags),NULL},
     {CVTFLD_END  ,0       ,0                    ,NULL}
};

CHAR ntfyapp[MAXNTFY][AIDSIZ];     /* array of App-IDs to get notification */
UCHAR *ntfyflg;                    /* per-user, per-app notify flags       */
INT numntfy=0;                     /* number of App-IDs in use             */

static CHAR *csfkey;               /* key required to use C/S Forums agent */
static CHAR *fmgrapid;             /* Forum Manager App-ID                 */

static VOID nwfhook(const struct fordsk *def,const CHAR *desc,const CHAR *echoes);
static VOID mdfhook(const struct fordef *def,const CHAR *desc,const CHAR *echoes);
static VOID dlfhook(const struct fordsk *def,const CHAR *desc,const CHAR *echoes);
static VOID ntfyfchg(const CHAR *dpksfx,const CHAR *fornam);
static GBOOL addntfy(INT unum,const CHAR *appid);
static VOID rmvntfy(INT unum,const CHAR *appid);
static INT findntfy(const CHAR *appid);
static VOID foracinf(struct saunam *dpknam);
static VOID forinf(INT direction,struct saunam *dpknam);
static VOID getbasinf(const struct fordef *fdef,struct basfinf *finf);
static VOID fordet(struct saunam *dpknam);
static VOID cpyfdet(struct viewfdpk *dpk,struct fordsk *def,CHAR *desc,
                    adr_t *echoes);
static VOID mkforf(struct saunam *dpknam,GBOOL sysflg);
static VOID cmkforf(VOID);
static GBOOL forfilipg(VOID);
static CHAR *forfpth(GBOOL inclfil);
static VOID thrinf(INT direction,struct saunam *dpknam);
static VOID crdtinf(VOID);
static VOID rdtinf(VOID);
static VOID rdformsg(INT direction,struct saunam *dpknam,GBOOL incltxt);
static VOID crdfmsg(VOID);
static VOID rdfmsg(VOID);
static VOID rdthrmsg(INT direction,struct saunam *dpknam,INT flags);
static VOID crdtmsg(VOID);
static VOID rdtmsg(VOID);
static CHAR *excerpt(CHAR *dst,CHAR *src,UINT maxlen);
static GBOOL isquoted(CHAR *s);
static VOID iniscan(struct otscan *newscn);
static INT remfor(USHORT *forlst,INT ninlst,INT f2ridx);
static VOID clsscan(VOID);
static VOID rdscnmsg(INT direction,struct saunam *dpknam,GBOOL incltxt);
static VOID crdsmsg(VOID);
static VOID rdsmsg(VOID);
static VOID rdparmsg(struct saunam *dpknam,GBOOL incltxt);
static VOID crdpmsg(VOID);
static VOID rdpmsg(VOID);
static VOID rdunamsg(INT direction,struct saunam *dpknam,GBOOL incltxt);
static VOID crdumsg(VOID);
static VOID rdumsg(VOID);
static VOID foratt(struct saunam *dpknam);
static GBOOL getfms(CHAR *sfx,USHORT *fid,LONG *mid);
static VOID initfor(struct saunam *dpknam);
static VOID crsvfbuf(VOID);
static VOID rsvfbuf(VOID);
static VOID cgetfdft(VOID);
static VOID getfdft(VOID);
static VOID fdsk2dpk(struct fordsk *dsk,struct credfdpk *dpk,GBOOL inclfil);
static VOID getuacc(struct saunam *dpknam);
static VOID csmodmsg(struct saunam *dpknam,CHAR *modtxt);
static VOID cmodmsg(VOID);
static VOID cdelmsg(VOID);
static VOID cfordel(VOID);
static VOID cformod(VOID);
static VOID fwdfor(struct saunam *dpknam,CHAR *fwdinf);
static VOID cpyfor(struct saunam *dpknam,CHAR *cpyinf);
static VOID delfmsg(struct saunam *dpknam);
static VOID cdelmsg(VOID);
static VOID cfordel(VOID);
static VOID cformod(VOID);
static VOID cscrtfor(struct credfdpk *newdpk);
static VOID ccreatef(VOID);
static VOID createf(VOID);
static VOID csdelfor(struct saunam *dpknam);
static VOID cfordel(VOID);
static VOID csmodfor(struct saunam *dpknam,struct credfdpk *moddpk);
static VOID cformod(VOID);
static GBOOL fdpk2dsk(struct credfdpk *dpk,struct fordsk *dsk,GBOOL inclfil);
static GBOOL fdpk2def(USHORT forum,struct credfdpk *dpk,struct fordef *def,
                      CHAR *info);
static GBOOL parsecho(INT necho,CHAR *list,CHAR *arr);
static VOID csxmtmsg(struct saunam *dpknam,GBOOL exempt);
static VOID rd4xmt(VOID);
static VOID csapvatt(struct saunam *dpknam,GBOOL approve);
static VOID crd4apv(VOID);
static VOID crd4xmt(VOID);
static VOID rd4apv(VOID);
static VOID setuacc(struct saunam *dpknam,INT access);
static VOID cpyuacc(struct saunam *dpknam,CHAR *srcusr);
static VOID cscfgac(struct saunam *dpknam,UINT length,
                    struct foracc *newdfts);
static VOID cscrtgrp(struct cmgrpdpk *dpk);
static VOID cgrpcrt(VOID);
static VOID csdelgrp(struct saunam *dpknam);
static VOID cgrpdel(VOID);
static VOID csmodgrp(struct saunam *dpknam,struct cmgrpdpk *dpk);
static VOID cgrpmodh(VOID);
static VOID csgrpfor(struct saunam *dpknam,INT nforums,USHORT *forlst);
static VOID cgrpmodf(VOID);
static GBOOL dpk2grp(struct cmgrpdpk *dpk,struct forgrp *grp);
static VOID mkgrpf(struct saunam *dpknam,GBOOL sysflg);
static VOID cmkgrpf(VOID);
static GBOOL grpfilipg(VOID);
static CHAR *grpfpth(GBOOL inclfil);
static VOID grpinf(struct saunam *dpknam);
static VOID grpgrp(INT direction,struct saunam *dpknam);
static VOID cgrpgrp(VOID);
static GBOOL getgrp(INT direction,USHORT grpid,const CHAR *grpnam,
                    struct forgrp *grpbuf);
static VOID grpfor(INT direction,struct saunam *dpknam);
static VOID cgrpfor(VOID);
static GBOOL getgrpf(INT direction,USHORT grpid,const CHAR *fornam,
                     struct fordef *forbuf);
static GBOOL valapid(const CHAR *appid);
static GBOOL isapidc(CHAR c);
static VOID reg_dpk(VOID);

VOID
inicsfor(VOID)                     /* initialize Client/Server Forums agent*/
{
     register_agent(&foragt);
     csfkey=stgopt(CSFKEY);
     fmgrapid=stgopt(FMGRAPID);
     hook_gme(GMEHOOK_NOT_NEWFOR,(voidfunc)nwfhook);
     hook_gme(GMEHOOK_NOT_UPDFOR,(voidfunc)mdfhook);
     hook_gme(GMEHOOK_NOT_DELFOR,(voidfunc)dlfhook);
     memset(ntfyapp,0,MAXNTFY*AIDSIZ);
     ntfyflg=alczer(MAXNTFY*(nterms/8+1));
     reg_dpk();
}

static VOID
f_read(                            /* read-dynapak handler                 */
INT direction,                     /*   read direction: 0=eq, 1=gt, -1=lt  */
struct saunam *dpknam)             /*   dynapak name to read               */
{
     CHAR *dpkstr;

     dpkstr=cnvs2d(dpknam);
     if (direction == 0 && sameas("sau:foracc",dpkstr)) {
          if (stdchk(csfkey)) {
               foracinf(dpknam);
          }
          else {
               setmem(rsptmp,sizeof(struct globfacc),0);
               rsp2read(dpknam,sizeof(struct globfacc),rsptmp,globfaccFDA);
          }
          return;
     }
     if (!stdchk(csfkey)) {
          rejectreq();
          return;
     }
     cssetup();
     if (direction == 0 && sameto("saf:",dpkstr)
      && sameto(FORATT,dpknam->suffix)) {
          foratt(dpknam);
     }
     else if (direction == 0 && sameas(FORFDPK,dpkstr)) {
          mkforf(dpknam,FALSE);
     }
     else if (direction == 0 && sameas(FORFSDPK,dpkstr)) {
          mkforf(dpknam,TRUE);
     }
     else if (direction == 0 && sameto("saf:"LEXDPK,dpkstr)) {
          getlex(dpknam);
     }
     else if (direction == 0 && sameas(GRPFDPK,dpkstr)) {
          mkgrpf(dpknam,FALSE);
     }
     else if (direction == 0 && sameas(GRPFSDPK,dpkstr)) {
          mkgrpf(dpknam,TRUE);
     }
     else if (sameto("sau:",dpkstr) && sameto(FORINF,dpknam->suffix)) {
          forinf(direction,dpknam);
     }
     else if (direction == 0 && sameto("sa:",dpkstr)
           && sameto(FORDET,dpknam->suffix)) {
          fordet(dpknam);
     }
     else if (direction >= 0 && sameto("sa:",dpkstr)
           && sameto(THRINF,dpknam->suffix)) {
          thrinf(direction,dpknam);
     }
     else if (sameto("sau:",dpkstr) && sameto(FORHDR,dpknam->suffix)) {
          rdformsg(direction,dpknam,FALSE);
     }
     else if (sameto("sau:",dpkstr) && sameto(FORMSG,dpknam->suffix)) {
          rdformsg(direction,dpknam,TRUE);
     }
     else if (sameto("sau:",dpkstr) && sameto(THRHDR,dpknam->suffix)) {
          rdthrmsg(direction,dpknam,0);
     }
     else if (sameto("sau:",dpkstr) && sameto(THREXC,dpknam->suffix)) {
          rdthrmsg(direction,dpknam,EXCERPT);
     }
     else if (sameto("sau:",dpkstr) && sameto(THRMSG,dpknam->suffix)) {
          rdthrmsg(direction,dpknam,INCLTXT);
     }
     else if (sameto("sau:",dpkstr) && sameto(SCNHDR,dpknam->suffix)) {
          rdscnmsg(direction,dpknam,FALSE);
     }
     else if (sameto("sau:",dpkstr) && sameto(SCNMSG,dpknam->suffix)) {
          rdscnmsg(direction,dpknam,TRUE);
     }
     else if (direction == 0 && sameto("sau:",dpkstr)
           && sameto(PARHDR,dpknam->suffix)) {
          rdparmsg(dpknam,FALSE);
     }
     else if (direction == 0 && sameto("sau:",dpkstr)
           && sameto(PARMSG,dpknam->suffix)) {
          rdparmsg(dpknam,TRUE);
     }
     else if (sameto("sau:",dpkstr) && sameto(UNAHDR,dpknam->suffix)) {
          rdunamsg(direction,dpknam,FALSE);
     }
     else if (sameto("sau:",dpkstr) && sameto(UNAMSG,dpknam->suffix)) {
          rdunamsg(direction,dpknam,TRUE);
     }
     else if (direction == 0 && sameto("sau:",dpkstr)
           && sameas(INIFOR,dpknam->suffix)) {
          initfor(dpknam);
     }
     else if (direction == 0 && samepato("sau:",dpkstr)
           && sameto(USRACC,dpknam->suffix)) {
          getuacc(dpknam);
     }
     else if (direction == 0 && sameto("sa:"GRPINF,dpkstr)) {
          grpinf(dpknam);
     }
     else if (direction != 0 && sameto("sau:"GRPGRP,dpkstr)) {
          grpgrp(direction,dpknam);
     }
     else if (sameto("sau:"GRPFOR,dpkstr)) {
          grpfor(direction,dpknam);
     }
     else {
          rejectreq();
     }
}

static VOID
f_write(                           /* write-dynapak handler                */
struct saunam *dpknam,             /*   dynapak name to write              */
USHORT length,                     /*   length of dynapak value            */
VOID *value)                       /*   dynapak value to write             */
{
     CHAR *dpkstr;

     dpkstr=cnvs2d(dpknam);
     if (!stdchk(csfkey)) {
          rejectreq();
          return;
     }
     cssetup();
     if (length > 0 && sameto("saf:",dpkstr)
      && sameto(UPLATT,dpknam->suffix)) {
          ASSERT(length == sizeof(struct filinf));
          uplatt(dpknam,(struct filinf *)value);
     }
     else if (sameto("sau:forntfy ",dpkstr)) {
          if ((dpkstr=strchr(dpknam->suffix,' ')) == NULL
           || !valapid(++dpkstr)) {
               rejectreq();
               return;
          }
          if (length == 0) {
               rmvntfy(usrnum,dpkstr);
          }
          else if (!addntfy(usrnum,dpkstr)) {
               rejectreq();
               return;
          }
          rsp2write(TRUE,0,NULL,NULL);
     }
     else if (sameto("sa:",dpkstr) && sameas(WRTNEW,dpknam->suffix)) {
          wrtnew(dpknam,(struct newdpk *)value);
     }
     else if (sameto("sa:",dpkstr) && sameas(WRTRPL,dpknam->suffix)) {
          wrtrpl(dpknam,(struct newdpk *)value);
     }
     else if (sameto("sa:",dpkstr) && sameto(CCLSFX,dpknam->suffix)) {
          gcclst(dpknam,unpad((CHAR *)value));
     }
     else if (length >= fldoff(otscan,forlst) && sameas(INISCN,dpkstr)) {
          iniscan((struct otscan *)value);
     }
     else if (sameas("sau:clsscn",dpkstr)) {
          clsscan();
     }
     else if (sameto("sa:",dpkstr) && sameto(MODMSG,dpknam->suffix)) {
          if (length == 0) {
               delfmsg(dpknam);
          }
          else {
               csmodmsg(dpknam,unpad((CHAR *)value));
          }
     }
     else if (length > 0 && sameto("sa:",dpkstr)
           && sameto(FWDFOR,dpknam->suffix)) {
          fwdfor(dpknam,unpad((CHAR *)value));
     }
     else if (length > 0 && sameto("sa:",dpkstr)
           && sameto(CPYFOR,dpknam->suffix)) {
          cpyfor(dpknam,unpad((CHAR *)value));
     }
     else if (length >= sizeof(struct credfdpk)-1 && sameto("sa:",dpkstr)
           && sameas(CRTFOR,dpknam->suffix)) {
          cscrtfor((struct credfdpk *)value);
     }
     else if (sameto("sa:",dpkstr) && sameto(MODFOR,dpknam->suffix)) {
          if (length == 0) {
               csdelfor(dpknam);
          }
          else if (length >= sizeof(struct credfdpk)-1) {
               csmodfor(dpknam,(struct credfdpk *)value);
          }
          else {
               rejectreq();
          }
     }
     else if (length == sizeof(GBOOL) && sameto("sa:",dpkstr)
           && sameto(MSGXMT,dpknam->suffix)) {
          csxmtmsg(dpknam,*(GBOOL *)value);
     }
     else if (length == sizeof(GBOOL) && sameto("sa:",dpkstr)
           && sameto(MSGAPV,dpknam->suffix)) {
          csapvatt(dpknam,*(GBOOL *)value);
     }
     else if (length == sizeof(SHORT) && samepato("sau:",dpkstr)
           && sameto(USRACC,dpknam->suffix)) {
          setuacc(dpknam,*(SHORT *)value);
     }
     else if (length > 0 && samepato("sau:",dpkstr)
           && sameto(CPYACC,dpknam->suffix)) {
          cpyuacc(dpknam,unpad((CHAR *)value));
     }
     else if (length >= sizeof(struct foracc)-KEYSIZ && sameto("sa:",dpkstr)
           && sameto(CFGACC,dpknam->suffix)) {
          cscfgac(dpknam,length,(struct foracc *)value);
     }
     else if (length >= MINGDPKSZ && sameas("sa:"CRTGRP,dpkstr)) {
          cscrtgrp((struct cmgrpdpk *)value);
     }
     else if (sameto("sa:"MODGRP,dpkstr)) {
          if (length == 0) {
               csdelgrp(dpknam);
          }
          else if (length >= MINGDPKSZ) {
               csmodgrp(dpknam,(struct cmgrpdpk *)value);
          }
          else {
               rejectreq();
          }
     }
     else if (length%sizeof(USHORT) == 0 && sameto("sa:"MODGRPF,dpkstr)) {
          csgrpfor(dpknam,length/sizeof(USHORT),(USHORT *)value);
     }
     else {
          rejectreq();
     }
}

static VOID
fxdone(VOID)                       /* file transfer-done handler           */
{
     cssetup();
     if (iswrite()) {
          if (sameto(UPLATT,rqiptr->dpknam.suffix)) {
               uladone();
          }
     }
     else {
          if (sameas(FORFSFX,rqiptr->dpknam.suffix)
           || sameas(FORFSSFX,rqiptr->dpknam.suffix)) {
               unlink(forfpth(TRUE));
          }
          else if (sameas(GRPFSFX,rqiptr->dpknam.suffix)
                || sameas(GRPFSSFX,rqiptr->dpknam.suffix)) {
               unlink(grpfpth(TRUE));
          }
          else if (sameto(FORATT,rqiptr->dpknam.suffix)) {
               dldone(rqiptr->u.tag);
          }
     }
}

static VOID
fabort(VOID)                       /* abort-request handler                */
{
     cssetup();
     if (sameas(FORFSFX,rqiptr->dpknam.suffix)
      || sameas(FORFSSFX,rqiptr->dpknam.suffix)) {
          if (rqiptr->ofp != NULL) {
               fclose(rqiptr->ofp);
          }
          unlink(forfpth(TRUE));
     }
     else if (sameas(GRPFSFX,rqiptr->dpknam.suffix)
           || sameas(GRPFSSFX,rqiptr->dpknam.suffix)) {
          if (rqiptr->ofp != NULL) {
               fclose(rqiptr->ofp);
          }
          unlink(grpfpth(TRUE));
     }
     else if (sameto(FORATT,rqiptr->dpknam.suffix)) {
          if (rqiptr->stt == 0) {
               clsgmerq(efwork);
          }
          else {
               dlabt(rqiptr->u.tag);
          }
     }
     else if (sameas(WRTNEW,rqiptr->dpknam.suffix)
           || sameas(WRTRPL,rqiptr->dpknam.suffix)) {
          wrtabt();
     }
     else if (sameto(UPLATT,rqiptr->dpknam.suffix)) {
          uplabt();
     }
     else {
          if (gmerqopn(efwork)) {
               clsgmerq(efwork);
          }
          if (rqiptr->flags&BUFINU) {
               unrarea(wrmpool,rqiptr->wrthdl);
          }
     }
}

VOID
fordisc(VOID)                      /* forum-specific disconnect handler    */
{
     INT app,lastnum;

     app=0;
     while (app < numntfy) {
          lastnum=numntfy;
          rmvntfy(usrnum,ntfyapp[app]);
          if (lastnum == numntfy) {
               ++app;
          }
     }
}

static VOID
nwfhook(                           /* forum has been created hook          */
const struct fordsk *def,          /*   new forum definition structure     */
const CHAR *desc,                  /*   descriptive text                   */
const CHAR *echoes)                /*   pointer to array of echo addresses */
{
     (VOID)desc;
     (VOID)echoes;
     ntfyfchg(NFNOTDPK,def->name);
}

static VOID
mdfhook(                           /* forum has been updated hook          */
const struct fordef *def,          /*   forum definition structure         */
const CHAR *desc,                  /*   descriptive text                   */
const CHAR *echoes)                /*   pointer to array of echo addresses */
{                                  /*(desc & echoes may be NULL if no chng)*/
     (VOID)desc;
     (VOID)echoes;
     ntfyfchg(MFNOTDPK,def->name);
}

static VOID
dlfhook(                           /* forum has been deleted hook          */
const struct fordsk *def,          /*   forum definition structure         */
const CHAR *desc,                  /*   descriptive text                   */
const CHAR *echoes)                /*   pointer to array of echo addresses */
{
     (VOID)desc;
     (VOID)echoes;
     ntfyfchg(DFNOTDPK,def->name);
}

static VOID
ntfyfchg(                          /* notify users that a forum has changed*/
const CHAR *dpksfx,                /*   dynapak suffix to use              */
const CHAR *fornam)                /*   name of forum that changed         */
{
     INT u,a,nperu,idx,bit;
     struct saunam dpknam;

     setmem(&dpknam,sizeof(struct saunam),0);
     stlcpy(dpknam.sysid,msysid,SIDSIZ);
     stlcpy(dpknam.appid,FORAPID,AIDSIZ);
     stlcpy(dpknam.suffix,(CHAR *)dpksfx,SFXSIZ);
     stlcat(dpknam.suffix,(CHAR *)fornam,SFXSIZ);
     nperu=nterms/8+1;
     for (u=0 ; u < nterms ; ++u) {
          if (usroff(u)->flags&ISGCSU) {
               bit=1<<(u&7);            /* u % 8 */
               for (a=0 ; a < numntfy ; ++a) {
                    idx=nperu*a+(u>>3); /* u / 8 */
                    if ((ntfyflg[idx]&bit) && qroom(u,NORMAL)) {
                         senddpk(u,ntfyapp[a],NORMAL,&dpknam,0,NULL,NULL);
                    }
               }
          }
     }
}

static GBOOL                       /*   returns TRUE if successful         */
addntfy(                           /* add a notification App-ID            */
INT unum,                          /*   User-ID to add to                  */
const CHAR *appid)                 /*   App-ID to add                      */
{
     INT app,bit,idx,nperu;

     if ((app=findntfy(appid)) == NOIDX) {
          if (numntfy >= MAXNTFY) {
               return(FALSE);
          }
          app=numntfy++;
          stlcpy(ntfyapp[app],(CHAR *)appid,AIDSIZ);
     }
     nperu=nterms/8+1;
     bit=1<<(unum&7);              /* unum % 8 */
     idx=nperu*app+(unum>>3);      /* unum / 8 */
     ntfyflg[idx]|=bit;
     return(TRUE);
}

static VOID
rmvntfy(                           /* remove a notification App-ID         */
INT unum,                          /*   User-ID to remove from             */
const CHAR *appid)                 /*   App-ID to remove                   */
{
     INT app,bit,idx,basidx,nperu;

     if ((app=findntfy(appid)) == NOIDX) {
          return;
     }
     nperu=nterms/8+1;
     basidx=nperu*app;
     bit=1<<(unum&7);              /* unum % 8 */
     idx=basidx+(unum>>3);         /* unum / 8 */
     ntfyflg[idx]&=~bit;
     for (idx=0 ; idx < nperu ; ++idx) {
          if (ntfyflg[basidx+idx] != 0) {
               return;
          }
     }
     --numntfy;
     movmem(ntfyapp[app+1],ntfyapp[app],(numntfy-app)*AIDSIZ);
     movmem(&ntfyflg[basidx+nperu],&ntfyflg[basidx],(numntfy-app)*nperu);
}

static INT                         /*   returns NOIDX if not found         */
findntfy(                          /* find index of a notification App-ID  */
const CHAR *appid)                 /*   App-ID to find                     */
{
     INT i;

     for (i=0 ; i < numntfy ; ++i) {
          if (sameas((CHAR *)appid,ntfyapp[i])) {
               return(i);
          }
     }
     return(NOIDX);
}

static VOID
foracinf(                          /* get generic Forum access/info        */
struct saunam *dpknam)             /*   dynapak name to read               */
{
     struct globfacc *facc;

     ASSERT(getfnm(dftfor) != NULL);
     facc=(struct globfacc *)rsptmp;
     facc->flags=fopmfd ? GFAOPMOD : 0;
     if (haskey(fprlock)) {
          facc->flags|=EAINDATT;
     }
     if (haskey(forsys)) {
          facc->flags|=GFASYSOP;
     }
     c2bcpy(facc->dftfor,getfnm(dftfor),FORNSZ-1);
     facc->maxscnf=MAXQSF;
     facc->txtlen=(LONG)(TXTLEN-1);
     if (alwcpy) {
          facc->ccmax=usrptr->flags&MASTER ? -1 : csmaxcc;
     }
     else {
          facc->ccmax=0;
     }
     facc->maxgrpf=gmeMaxGrpFor();
     c2bcpy(facc->fmgrapid,fmgrapid,AIDSIZ-1);
     rsp2read(dpknam,sizeof(struct globfacc),rsptmp,globfaccFDA);
}

static VOID
forinf(                            /* get basic info on a Forum            */
INT direction,                     /*   read direction: 0=eq, 1=gt, -1=lt  */
struct saunam *dpknam)             /*   dynapak name to read               */
{
     USHORT fid;
     const struct fordef *tmpdef;

     ASSERT(sameto(FORINF,dpknam->suffix));
     if (direction == 0) {
          fid=getfid(&dpknam->suffix[sizeof(FORINF)-1]);
          if (fid != EMLID && foracc(fid) >= RDAXES) {
               getbasinf(getdefp(fid),(struct basfinf *)rsptmp);
               rsp2read(NULL,fdpksiz((struct basfinf *)rsptmp),rsptmp,
                        basfinfFDA);
               return;
          }
     }
     else {
          if (direction > 0) {
               tmpdef=nxtdefp(&dpknam->suffix[sizeof(FORINF)-1]);
               while (tmpdef != NULL && !faccok(tmpdef->forum)) {
                    tmpdef=nxtdefp(tmpdef->name);
               }
          }
          else {
               tmpdef=prvdefp(&dpknam->suffix[sizeof(FORINF)-1]);
               while (tmpdef != NULL && !faccok(tmpdef->forum)) {
                    tmpdef=prvdefp(tmpdef->name);
               }
          }
          if (tmpdef != NULL) {
               getbasinf(tmpdef,(struct basfinf *)rsptmp);
               *namtmp=*dpknam;
               sprintf(namtmp->suffix,FORINF"%s",tmpdef->name);
               rsp2read(namtmp,fdpksiz((struct basfinf *)rsptmp),rsptmp,
                        basfinfFDA);
               return;
          }
     }
     rejectreq();
}

static VOID
getbasinf(                         /* get basic info on forum              */
const struct fordef *fdef,         /*   from full forum def structure      */
struct basfinf *finf)              /*   into basic info structure          */
{
     finf->forum=fdef->forum;
     finf->axes=(CHAR)foracc(fdef->forum);
     if (fdef->necho > 0) {
          finf->axes|=FIECHO;
     }
     stlcpy(finf->topic,fdef->topic,TPCSIZ);
}

static VOID
fordet(                            /* get details on a Forum               */
struct saunam *dpknam)             /*   dynapak name to read               */
{
     USHORT fid;
     CHAR *desc;
     adr_t *echoes;
     struct fordsk *tmpdef;

     ASSERT(dpknam != NULL);
     ASSERT(sameto(FORDET,dpknam->suffix));
     fid=getfid(&dpknam->suffix[sizeof(FORDET)-1]);
     if (fid != EMLID && foracc(fid) > NOAXES) {
          tmpdef=(struct fordsk *)vdatmp;
          echoes=(adr_t *)tmpdef->info;
          desc=(CHAR *)echoes[getdefp(fid)->necho];
          getallf(fid,tmpdef,desc,(CHAR *)echoes);
          cpyfdet((struct viewfdpk *)rsptmp,tmpdef,desc,echoes);
          rsp2read(dpknam,
                   fldoff(viewfdpk,info)+strlen(((struct viewfdpk *)rsptmp)->info),
                   rsptmp,viewfdpkFDA);
          return;
     }
     rejectreq();
}

static VOID
cpyfdet(                           /* copy forum details to dpk form       */
struct viewfdpk *dpk,              /*   dynapak structure buffer           */
struct fordsk *def,                /*   forum header                       */
CHAR *desc,                        /*   forum description/help message     */
adr_t *echoes)                     /*   array of echo addresses            */
{
     INT i;
     CHAR *cp;

     dpk->forum=def->forum;
     dpk->nthrs=(LONG)def->nthrs;
     dpk->nmsgs=(LONG)def->nmsgs;
     dpk->nfiles=(LONG)def->nfiles;
     dpk->nw4app=(LONG)def->nw4app;
     dpk->dfnpv=def->dfnpv;
     dpk->dfprv=def->dfprv;
     dpk->mxnpv=def->mxnpv;
     dpk->msglif=def->msglif;
     dpk->chgmsg=def->chgmsg;
     dpk->chgrdm=def->chgrdm;
     dpk->chgatt=def->chgatt;
     dpk->chgadl=def->chgadl;
     dpk->chgupk=def->chgupk;
     dpk->chgdpk=def->chgdpk;
     dpk->ccr=def->ccr;
     dpk->pfnlvl=def->pfnlvl;
     dpk->crdatim=d2vdat(def->crdate,def->crtime);
     dpk->necho=def->necho;
     dpk->seqid=def->seqid;
     stlcpy(dpk->info,def->name,FORNSZ);
     cp=dpk->info+strlen(dpk->info);
     *cp++=FLDSEP;
     stlcpy(cp,def->topic,TPCSIZ);
     cp+=strlen(cp);
     *cp++=FLDSEP;
     stlcpy(cp,def->forop,UIDSIZ);
     cp+=strlen(cp);
     *cp++=FLDSEP;
     stlcpy(cp,def->forlok,KEYSIZ);
     cp+=strlen(cp);
     *cp++=FLDSEP;
     for (i=0 ; i < def->necho ; ++i) {
          if (i != 0) {
               *cp++=';';
          }
          stlcpy(cp,echoes[i],MAXADR);
          cp+=strlen(cp);
     }
     *cp++=FLDSEP;
     lf2cr(desc);
     if (desc[0] == '\r') {
          ++desc;
     }
     stpans(desc);
     i=strlen(def->attpath)+strlen(def->datfil)+2;
     i=(MAXDPKV-sizeof(struct viewfdpk))-(INT)(cp-dpk->info)-i;
     if (strlen(desc) > i) {
          desc[i]='\0';
     }
     strcpy(cp,desc);
     cp+=strlen(cp);
     *cp++=FLDSEP;
     strcpy(cp,def->attpath);
     cp+=strlen(cp);
     *cp++=FLDSEP;
     strcpy(cp,def->datfil);
}

static VOID
mkforf(                            /* make forum information file          */
struct saunam *dpknam,             /*   dynapak name in use                */
GBOOL sysflg)                      /*   make sysop info file               */
{
     if (forfilipg()) {
          rejectreq();
          return;
     }
     movmem(dpknam,&rqiptr->dpknam,sizeof(struct saunam));
     chdmak();
     MKDIR(forfpth(FALSE));
     if ((rqiptr->ofp=fopen(forfpth(TRUE),FOPWA)) == NULL) {
          rejectreq();
          return;
     }
     ASSERT(!(rqiptr->flags&FORSYS));
     if (sysflg) {
          if (!haskey(forsys)) {
               rejectreq();
               return;
          }
          rqiptr->flags|=FORSYS;
     }
     ASSERT(*rqiptr->u.fornam == '\0');
     cycleme(cmkforf);
     cmkforf();
}

static VOID
cmkforf(VOID)                      /* cycled make forum info file process  */
{
     ULONG numtck,begtck;
     const struct fordef *defptr;
     struct basfinf *infptr;

     cssetup();
     numtck=iotck();
     begtck=hrtval();
     infptr=(struct basfinf *)vdatmp;
     do {
          if ((defptr=nxtdefp(rqiptr->u.fornam)) == NULL) {
               fclose(rqiptr->ofp);
               rqiptr->ofp=NULL;
               cycleme(NULL);
               rsp2read(NULL,STGLEN,forfpth(TRUE),NULL);
               return;
          }
          else {
               stlcpy(rqiptr->u.fornam,defptr->name,FORNSZ);
               if ((rqiptr->flags&FORSYS) || faccok(defptr->forum)) {
                    getbasinf(defptr,infptr);
                    fprintf(rqiptr->ofp,"%s %hd %hd %s\n",defptr->name,
                            infptr->forum,(SHORT)infptr->axes,infptr->topic);
               }
          }
     } while (hrtval()-begtck < numtck);
}

static GBOOL
forfilipg(VOID)                    /* is another forum info dnld in prog   */
{
     INT i,rqid;
     struct rqinfo *tmprqi;

     for (i=0 ; i < MAXREQS ; ++i) {
          rqid=srvrqid(usrnum,i);
          if (rqid != greqid && ismyreq(rqid,FORAPID)) {
               tmprqi=(struct rqinfo *)mrqoff(rqid);
               if (sameas(tmprqi->dpknam.suffix,FORFSFX)
                || sameas(tmprqi->dpknam.suffix,FORFSSFX)) {
                    return(TRUE);
               }
          }
     }
     return(FALSE);
}

static CHAR *
forfpth(                           /* generate group file path & file name */
GBOOL inclfil)                     /*   TRUE=w/ file name, FALSE=path only */
{
     if (inclfil) {
          return(uchdpath(usrnum,DROPFPTH"\\"FORFNAM));
     }
     return(uchdpath(usrnum,DROPFPTH));
}

static VOID
thrinf(                            /* get info on a thread                 */
INT direction,                     /*   read direction: 0=eq, 1=gt, -1=lt  */
struct saunam *dpknam)             /*   dynapak name to read               */
{
     USHORT forum;
     ULONG thrid;
     CHAR *cp;

     forum=getfid(itemidxd(dpknam->suffix,1," "));
     if (forum == EMLID || foracc(forum) < RDAXES) {
          rejectreq();
          return;
     }
     thrid=0L;
     cp=itemidxd(dpknam->suffix,2," ");
     if (strlen(cp) > 10 || (strlen(cp) > 0 && !lsfxval(&thrid,cp))) {
          rejectreq();
          return;
     }
     if (cp[0] == '0' && thrid == 0xFFFFFFFFL) {
          thrid=0L;
     }
     rqiptr->dpknam=*dpknam;
     inigmerq(efwork);
     rqiptr->stt=(signed char)direction;
     inormrd(efwork,usaptr->userid,forum,FIRSTM);
     thrctx(efwork,thrid);
     rdtinf();
}

static VOID
crdtinf(VOID)                      /* cycled read message for thread info  */
{
     cssetup();
     cycleme(NULL);
     rdtinf();
}

static VOID
rdtinf(VOID)                       /* read message for thread info         */
{
     USHORT nmsgs;

     msg=(struct message *)vdatmp;
     switch (thrinfo(efwork,(INT)rqiptr->stt,(VOID *)&nmsgs,msg,
               (CHAR *)rsptmp)) {
     case GMEAGAIN:
          cycleme(crdtinf);
          return;
     case GMEOK:
          stlcpy(((struct thrinfo *)rsptmp)->topic,msg->topic,TPCSIZ);
          ((struct thrinfo *)rsptmp)->nmsgs=nmsgs;
          *namtmp=rqiptr->dpknam;
          if (rqiptr->stt != 0) {
               sprintf(namtmp->suffix,"%s%s %010lu",THRINF,getfnm(msg->forum),
                 (ULONG)msg->thrid);
          }
          rsp2read(namtmp,
                   fldoff(thrinfo,topic)+strlen(((struct thrinfo *)rsptmp)->topic),
                   rsptmp,thrinfoFDA);
          break;
     default:
          rejectreq();
          break;
     }
     clsgmerq(efwork);
}

static VOID
rdformsg(                          /* fire up forum message read process   */
INT direction,                     /*   read direction: 0=eq, 1=gt, -1=lt  */
struct saunam *dpknam,             /*   dynapak name to read               */
GBOOL incltxt)                     /*   TRUE=whole message, FALSE=header   */
{
     USHORT forum;
     LONG msgid;

     ASSERT(dpknam != NULL);
     if (!getfms(dpknam->suffix,&forum,&msgid)) {
          rejectreq();
          return;
     }
     rqiptr->dpknam=*dpknam;
     rqiptr->stt=(signed char)direction;
     if (incltxt) {
          rqiptr->flags|=INCLTXT;
     }
     inigmerq(efwork);
     inormrd(efwork,usaptr->userid,forum,msgid);
     rdfmsg();
}

static VOID
crdfmsg(VOID)                      /* cycled read forum message            */
{
     cssetup();
     cycleme(NULL);
     rdfmsg();
}

static VOID
rdfmsg(VOID)                       /* read forum message                   */
{

     vdamsg();
     switch (readir(rqiptr->stt,efwork,msg,msgtxt)) {
     case GMEAGAIN:
          cycleme(crdfmsg);
          return;
     case GMEOK:
          if ((rqiptr->flags&INCLTXT) && forreal()
           && markreadf(efwork,msg,msgtxt) != GMEOK) {
               rejectreq();
               break;
          }
          *namtmp=rqiptr->dpknam;
          if (rqiptr->stt != 0) {
               sprintf(&namtmp->suffix[sizeof(FORMSG)-1],"%s %010ld",
                       getfnm(msg->forum),msg->msgid);
          }
          msg2dpk(msg,msgtxt,(struct msgdpk *)rsptmp,rqiptr->flags&INCLTXT);
          ((struct msgdpk *)rsptmp)->axes=(CHAR)foracc(msg->forum);
          rsp2read(namtmp,
                   fldoff(msgdpk,info)+strlen(((struct msgdpk *)rsptmp)->info),
                   rsptmp,msgdpkFDA);
          break;
     default:
          rejectreq();
          break;
     }
     clsgmerq(efwork);
}

static VOID
rdthrmsg(                          /* fire up msg-in-thread read process   */
INT direction,                     /*   read direction: 0=eq, 1=gt, -1=lt  */
struct saunam *dpknam,             /*   dynapak name to read               */
INT flags)                         /*   dynapak type flags                 */
{                                  /*   0=header, INCLTXT=whole, EXCERPT   */
     USHORT forum;
     ULONG thrid;
     LONG msgid;
     CHAR *cp;

     if (itemcntd(dpknam->suffix," ") < 3) {
          rejectreq();
          return;
     }
     forum=getfid(itemidxd(dpknam->suffix,1," "));
     if (forum == EMLID || foracc(forum) < RDAXES) {
          rejectreq();
          return;
     }
     thrid=0L;
     cp=itemidxd(dpknam->suffix,2," ");
     if (strlen(cp) > 10 || !lsfxval(&thrid,cp)) {
          rejectreq();
          return;
     }
     if (cp[0] == '0' && thrid == 0xFFFFFFFFL) {
          thrid=0L;
     }
     msgid=0L;
     cp=itemidxd(dpknam->suffix,3," ");
     if (strlen(cp) > 10
      || (strlen(cp) > 0 && !lsfxval((ULONG *)&msgid,cp))) {
          rejectreq();
          return;
     }
     if (msgid < 0L) {
          msgid=0L;
     }
     rqiptr->dpknam=*dpknam;
     rqiptr->stt=(signed char)direction;
     rqiptr->flags=flags;
     inigmerq(efwork);
     inictx(efwork,usaptr->userid,FSQTHR,forum,msgid,thrid);
     rdtmsg();
}

static VOID
crdtmsg(VOID)                      /* cycled read message in a thread      */
{
     cssetup();
     cycleme(NULL);
     rdtmsg();
}

static VOID
rdtmsg(VOID)                       /* read message in a thread             */
{
     CHAR *cp;

     vdamsg();
     switch (readir(rqiptr->stt,efwork,msg,msgtxt)) {
     case GMEAGAIN:
          cycleme(crdtmsg);
          return;
     case GMEOK:
          if ((rqiptr->flags&INCLTXT) && forreal()
            && markreadf(efwork,msg,msgtxt) != GMEOK) {
               rejectreq();
               break;
          }
          *namtmp=rqiptr->dpknam;
          if (rqiptr->stt != 0) {
               sprintf(&namtmp->suffix[sizeof(THRMSG)-1],"%s %010lu %010ld",
                       getfnm(msg->forum),(ULONG)msg->thrid,msg->msgid);
          }
          if (rqiptr->flags&EXCERPT) {
               if (isfmtted(msgtxt)) {
                    if (stripfmt(msgtxt,rsptmp,TXTLEN)) {
                         stlcpy(msgtxt,rsptmp,TXTLEN);
                    }
               }
          }
          msg2dpk(msg,msgtxt,(struct msgdpk *)rsptmp,rqiptr->flags&INCLTXT);
          ((struct msgdpk *)rsptmp)->axes=(CHAR)foracc(msg->forum);
          if (rqiptr->flags&EXCERPT) {
               cp=strchr(((struct msgdpk *)rsptmp)->info,FLDSEP);
               ASSERT(cp != NULL);
               cp=strchr(++cp,FLDSEP);
               ASSERT(cp != NULL);
               excerpt(++cp,lf2cr(msgtxt),TPCSIZ+HSTSIZ-1);
          }
          rsp2read(namtmp,
                   fldoff(msgdpk,info)+strlen(((struct msgdpk *)rsptmp)->info),
                   rsptmp,msgdpkFDA);
          break;
     default:
          rejectreq();
          break;
     }
     clsgmerq(efwork);
}

static CHAR *                      /*   returns copy of dst                */
excerpt(                           /* generate an "excerpt" of a message   */
CHAR *dst,                         /*   buffer to fill in                  */
CHAR *src,                         /*   source buffer (message text)       */
UINT maxlen)                   /*   maximum size of filled-in dst      */
{                                  /*   NOTE: assumes EOLs are \r          */
     INT i;
     GBOOL lastsp;
     CHAR *cp;

     do {
          while (*src <= ' ' && *src != '\0') {
               ++src;
          }
          if (isquoted(src)) {
               if ((cp=strchr(src,'\r')) == NULL) {
                    break;
               }
               src=cp+1;
          }
          else {
               break;
          }
     } while (*src != '\0');
     for (cp=dst,i=0,lastsp=FALSE ; i < maxlen && *src != '\0' ; ++i,++src) {
          if (*src <= ' ') {
               if (!lastsp) {
                    *cp++=' ';
               }
               lastsp=TRUE;
          }
          else {
               *cp++=*src;
               lastsp=FALSE;
          }
     }
     *cp='\0';
     return(dst);
}

static GBOOL
isquoted(                          /* does line start with quote symbol?   */
CHAR *s)                           /*   pointer to start of line           */
{
     if (isalnum(*s)) {
          ++s;
          if (isalnum(*s)) {
               ++s;
          }
     }
     return(strchr("<>{}[]|:",*s) != NULL);
}

static VOID
iniscan(                           /* init one-time scan for this channel  */
struct otscan *newscn)             /*   new scan buffer to use             */
{
     INT i,j;
     USHORT tmpfid;
     const CHAR *cp;
     CHAR ifnm[FORNSZ];

     if (numscans == 0) {
          *(SHORT *)rsptmp=SCINVR;
          rsp2write(FALSE,sizeof(SHORT),rsptmp,shortFDA);
          return;
     }
     if (newscn->nforums < 0 || newscn->nforums > MAXQSF) {
          rejectreq();
          return;
     }
     if (usrscn[usrnum] == NULL) {
          usrscn[usrnum]=rsvscan(usrnum);
          if (usrscn[usrnum] == NULL) {
               *(SHORT *)rsptmp=SCINOW;
               rsp2write(FALSE,sizeof(SHORT),rsptmp,shortFDA);
               return;
          }
     }
     for (i=0 ; i < newscn->nforums ; ) {
          if (!fidxst(newscn->forlst[i]) || !faccok(newscn->forlst[i])) {
               newscn->nforums=remfor((VOID *)newscn->forlst,
                          newscn->nforums,i);
          }
          else {
               ++i;
          }
     }
     for (i=0 ; i < newscn->nforums ; i++) {
          cp=getfnm(newscn->forlst[i]);
          ASSERT(cp != NULL);
          stlcpy(ifnm,cp,FORNSZ);
          for (j=i+1 ; j < newscn->nforums ; j++) {
               cp=getfnm(newscn->forlst[j]);
               ASSERT(cp != NULL);
               if (stricmp(cp,ifnm) < 0) {
                    tmpfid=newscn->forlst[i];
                    newscn->forlst[i]=newscn->forlst[j];
                    newscn->forlst[j]=tmpfid;
                    stlcpy(ifnm,cp,FORNSZ);
               }
          }
     }
     if (!(newscn->flags&SCALL) && newscn->nforums == 0) {
          *(SHORT *)rsptmp=SCINVF;
          rsp2write(FALSE,sizeof(SHORT),rsptmp,shortFDA);
          return;
     }
     movmem(newscn,usrscn[usrnum],sizeof(struct otscan)
         +sizeof(USHORT)*(newscn->nforums-1));
     b2ccvt(usrscn[usrnum]->keywds,MAXSKWD);
     rsp2write(TRUE,0,NULL,NULL);
}

static INT                         /*   returns new # forums in list       */
remfor(                            /* remove forum ID from list            */
USHORT *forlst,                    /*   list of forum IDs                  */
INT ninlst,                        /*   number of forums currently in list */
INT f2ridx)                        /*   index of forum to remove           */
{
     ASSERT(forlst != NULL);
     ASSERT(ninlst != 0);
     ASSERT(0 <= f2ridx && f2ridx < ninlst);
     if (f2ridx < ninlst-1) {
          movmem(&forlst[f2ridx+1],&forlst[f2ridx],
                 sizeof(USHORT)*(ninlst-f2ridx-1));
     }
     return(ninlst-1);
}

static VOID
clsscan(VOID)                      /* shut down one-time scan for channel  */
{
     if (usrscn[usrnum] == NULL) {
          rejectreq();
          return;
     }
     unrscan(usrnum);
     usrscn[usrnum]=NULL;
     rsp2write(TRUE,0,NULL,NULL);
}

static VOID
rdscnmsg(                          /* fire up scan msg read process        */
INT direction,                     /*   read direction: 0=eq, 1=gt, -1=lt  */
struct saunam *dpknam,             /*   dynapak name to read               */
GBOOL incltxt)                     /*   TRUE=whole message, FALSE=header   */
{
     INT i;
     USHORT forum;
     LONG msgid,tmpmid;
     struct otscan *tmpscn;
     CHAR *cp;

     if (usrscn[usrnum] == NULL) {
          rejectreq();
          return;
     }
     tmpscn=usrscn[usrnum];
     msgid=FIRSTM;
     cp=itemidxd(dpknam->suffix,1," ");
     if (*cp == '\0') {
          forum=fstscnf(usaptr->userid,tmpscn);
          if (forum == EMLID) {
               rejectreq();
               return;
          }
     }
     else {
          if (!getfms(dpknam->suffix,&forum,&msgid)) {
               rejectreq();
               return;
          }
          for (i=0 ; i < tmpscn->nforums && forum != tmpscn->forlst[i] ; ++i) {
          }
          if ((i >= tmpscn->nforums && !(tmpscn->flags&SCALL))
           || (i < tmpscn->nforums && (tmpscn->flags&SCALL))) {
               rejectreq();
               return;
          }
     }
     tmpmid=fstscnm(usaptr->userid,tmpscn,forum);
     if (msgid < tmpmid) {
          msgid=tmpmid;
     }
     rqiptr->dpknam=*dpknam;
     rqiptr->stt=(signed char)direction;
     if (incltxt) {
          rqiptr->flags|=INCLTXT;
     }
     inigmerq(efwork);
     setscan(efwork,tmpscn);
     inictx(efwork,usaptr->userid,FSQSCN,forum,msgid,0L);
     rdsmsg();
}

static VOID
crdsmsg(VOID)                      /* cycled read message in a scan        */
{
     cssetup();
     cycleme(NULL);
     rdsmsg();
}

static VOID
rdsmsg(VOID)                       /* read message in a scan               */
{
     vdamsg();
     switch (readir(rqiptr->stt,efwork,msg,msgtxt)) {
     case GMEAGAIN:
          cycleme(crdsmsg);
          return;
     case GMEOK:
          if ((rqiptr->flags&INCLTXT) && forreal()
           && markreadf(efwork,msg,msgtxt) != GMEOK) {
               rejectreq();
               break;
          }
          *namtmp=rqiptr->dpknam;
          if (rqiptr->stt != 0) {
               sprintf(&namtmp->suffix[sizeof(SCNMSG)-1],"%s %010ld",
                       getfnm(msg->forum),msg->msgid);
          }
          msg2dpk(msg,msgtxt,(struct msgdpk *)rsptmp,rqiptr->flags&INCLTXT);
          ((struct msgdpk *)rsptmp)->axes=(CHAR)foracc(msg->forum);
          rsp2read(namtmp,
                   fldoff(msgdpk,info)+strlen(((struct msgdpk *)rsptmp)->info),
                   rsptmp,msgdpkFDA);
          break;
     default:
          rejectreq();
          break;
     }
     clsgmerq(efwork);
}

static VOID
rdparmsg(                          /* read parent of a forum message       */
struct saunam *dpknam,             /*   dpk name to read                   */
GBOOL incltxt)                     /*   TRUE=whole message, FALSE=header   */
{
     USHORT forum;
     LONG msgid;

     ASSERT(sameto(PARMSG,dpknam->suffix));
     if (!getfms(dpknam->suffix,&forum,&msgid)) {
          rejectreq();
          return;
     }
     rqiptr->dpknam=*dpknam;
     if (incltxt) {
          rqiptr->flags|=INCLTXT;
     }
     rqiptr->dpknam=*dpknam;
     inigmerq(efwork);
     inormrd(efwork,usaptr->userid,forum,msgid);
     rdpmsg();
}

static VOID
crdpmsg(VOID)                      /* cycled read-parent-message           */
{
     cssetup();
     rdpmsg();
}

static VOID
rdpmsg(VOID)                       /* read parent of a message             */
{
     vdamsg();
     switch (*(INT *)rsptmp=readparf(efwork,msg,msgtxt)) {
     case GMEAGAIN:
          cycleme(crdpmsg);
          return;
     case GMEOK:
          if ((rqiptr->flags&INCLTXT) && forreal()
           && markreadf(efwork,msg,msgtxt) != GMEOK) {
               rejectreq();
               break;
          }
          msg2dpk(msg,msgtxt,(struct msgdpk *)rsptmp,rqiptr->flags&INCLTXT);
          ((struct msgdpk *)rsptmp)->axes=(CHAR)foracc(msg->forum);
          rsp2read(&rqiptr->dpknam,
                   fldoff(msgdpk,info)+strlen(((struct msgdpk *)rsptmp)->info),
                   rsptmp,msgdpkFDA);
          break;
     default:
          rejectreq();
          break;
     }
     clsgmerq(efwork);
}

static VOID
rdunamsg(                          /* fire up forum message read process   */
INT direction,                     /*   read direction: 0=eq, 1=gt, -1=lt  */
struct saunam *dpknam,             /*   dynapak name to read               */
GBOOL incltxt)                     /*   TRUE=whole message, FALSE=header   */
{
     USHORT forum;
     LONG msgid;

     if (!getfms(dpknam->suffix,&forum,&msgid) || foracc(forum) < OPAXES) {
          rejectreq();
          return;
     }
     rqiptr->dpknam=*dpknam;
     rqiptr->stt=(signed char)direction;
     if (incltxt) {
          rqiptr->flags|=INCLTXT;
     }
     inigmerq(efwork);
     inormrd(efwork,usaptr->userid,forum,msgid);
     rdumsg();
}

static VOID
crdumsg(VOID)                      /* cycled read message w/unapv att      */
{
     cssetup();
     cycleme(NULL);
     rdumsg();
}

static VOID
rdumsg(VOID)                       /* read message w/unapproved attachment */
{
     vdamsg();
     switch (readir(rqiptr->stt,efwork,msg,msgtxt)) {
     case GMEOK:
          if ((msg->flags&FILATT) && !(msg->flags&FILAPV)) {
               if ((rqiptr->flags&INCLTXT) && forreal()
                && markreadf(efwork,msg,msgtxt) != GMEOK) {
                    rejectreq();
                    break;
               }
               *namtmp=rqiptr->dpknam;
               if (rqiptr->stt != 0) {
                    sprintf(&namtmp->suffix[sizeof(UNAMSG)-1],"%s %010ld",
                            getfnm(msg->forum),msg->msgid);
               }
               msg2dpk(msg,msgtxt,(struct msgdpk *)rsptmp,
                       rqiptr->flags&INCLTXT);
               ((struct msgdpk *)rsptmp)->axes=(CHAR)foracc(msg->forum);
               rsp2read(namtmp,
                        fldoff(msgdpk,info)+strlen(((struct msgdpk *)rsptmp)->info),
                        rsptmp,msgdpkFDA);
               break;
          }
          if (rqiptr->stt == 0) {
               rejectreq();
               break;
          }
     case GMEAGAIN:
          cycleme(crdumsg);
          return;
     default:
          rejectreq();
          break;
     }
     clsgmerq(efwork);
}

static VOID
foratt(                            /* get attachment to Forum message      */
struct saunam *dpknam)             /*   dynapak name in use                */
{
     USHORT forum;
     LONG msgid;

     if (!getfms(dpknam->suffix,&forum,&msgid)) {
          rejectreq();
          return;
     }
     if (foracc(forum) < DLAXES) {
          rejectreq();
          return;
     }
     rqiptr->stt=0;
     rqiptr->dpknam=*dpknam;
     inigmerq(efwork);
     inormrd(efwork,usaptr->userid,forum,msgid);
     rd4att();
}

static GBOOL                       /*   returns TRUE if got OK             */
getfms(                            /* get forum ID & msg ID from dpk suffix*/
CHAR *sfx,                         /*   dynapak suffix                     */
USHORT *fid,                       /*   buffer for forum ID                */
LONG *mid)                         /*   buffer for message ID              */
{                                  /* (forum must be present, msg # optnl) */
     CHAR *cp;

     if (itemcntd(sfx," ") < 2) {
          return(FALSE);
     }
     *mid=0L;
     cp=itemidxd(sfx,2," ");
     if (strlen(cp) > 10
      || (strlen(cp) > 0 && !lsfxval((ULONG *)mid,cp))) {
          return(FALSE);
     }
     if (*mid < 0L) {
          *mid=0L;
     }
     *fid=getfid(itemidxd(sfx,1," "));
     return(*fid != EMLID && foracc(*fid) >= RDAXES);
}

static VOID
initfor(                           /* initialize forum def with defaults   */
struct saunam *dpknam)             /*   dpk name in use                    */
{
     inigmerq(efwork);
     rqiptr->dpknam=*dpknam;
     rsvfbuf();
}

static VOID
crsvfbuf(VOID)                     /* cycled reserve buffer for forum init */
{
     cssetup();
     cycleme(NULL);
     rsvfbuf();
}

static VOID
rsvfbuf(VOID)                      /* reserve pool buffer for forum init   */
{
     if ((rqiptr->wrthdl=rsvarea(wrmpool)) == NOHDL) {
          cycleme(crsvfbuf);
     }
     else {
          rqiptr->flags|=BUFINU;
          getfdft();
     }
}

static VOID
cgetfdft(VOID)                     /* cycled get forum defaults process    */
{
     cssetup();
     cycleme(NULL);
     getfdft();
}

static VOID
getfdft(VOID)                      /* get forum defaults                   */
{
     struct fordsk *tmpdef;

     tmpdef=areaptr(wrmpool,rqiptr->wrthdl);
     switch (inifdef(efwork,tmpdef)) {
     case GMEAGAIN:
          cycleme(cgetfdft);
          return;
     case GMEOK:
          fdsk2dpk(tmpdef,(struct credfdpk *)rsptmp,TRUE);
          rsp2read(&rqiptr->dpknam,
                   fldoff(credfdpk,info)+strlen(((struct credfdpk *)rsptmp)->info),
                   rsptmp,credfdpkFDA);
          break;
     default:
          rejectreq();
          break;
     }
     unrarea(wrmpool,rqiptr->wrthdl);
}

static VOID
fdsk2dpk(                          /* copy forum on-disk struct to dpk     */
struct fordsk *dsk,                /*   on-disk structure                  */
struct credfdpk *dpk,              /*   in-dynapak structure               */
GBOOL inclfil)                     /*   include file name/path info        */
{
     INT i;
     CHAR *cp;
     adr_t *tmpecho;

     dpk->dfnpv=dsk->dfnpv;
     dpk->dfprv=dsk->dfprv;
     dpk->mxnpv=dsk->mxnpv;
     dpk->msglif=dsk->msglif;
     dpk->chgmsg=dsk->chgmsg;
     dpk->chgrdm=dsk->chgrdm;
     dpk->chgatt=dsk->chgatt;
     dpk->chgadl=dsk->chgadl;
     dpk->chgupk=dsk->chgupk;
     dpk->chgdpk=dsk->chgdpk;
     dpk->ccr=dsk->ccr;
     dpk->pfnlvl=(INT)dsk->pfnlvl;
     dpk->seqid=(LONG)dsk->seqid;
     dpk->necho=dsk->necho;
     cp=dpk->info;
     stlcpy(cp,dsk->name,FORNSZ);
     cp+=strlen(cp);
     *cp++=FLDSEP;
     stlcpy(cp,dsk->topic,TPCSIZ);
     cp+=strlen(cp);
     *cp++=FLDSEP;
     stlcpy(cp,dsk->forop,UIDSIZ);
     cp+=strlen(cp);
     *cp++=FLDSEP;
     stlcpy(cp,dsk->forlok,KEYSIZ);
     cp+=strlen(cp);
     *cp++=FLDSEP;
     tmpecho=(adr_t *)dsk->info;
     for (i=0 ; i < dsk->necho ; ++i) {
          if (i > 0) {
               *cp++=';';
          }
          stlcpy(cp,tmpecho[i],MAXADR);
          cp+=strlen(cp);
     }
     *cp++=FLDSEP;
     stlcpy(cp,&dsk->info[dsk->necho*MAXADR],MAXFDV-dsk->necho*MAXADR);
     if (inclfil) {
          cp+=strlen(cp);
          *cp++=FLDSEP;
          stlcpy(cp,dsk->attpath,GCMAXPTH);
          cp+=strlen(cp);
          *cp++=FLDSEP;
          stlcpy(cp,dsk->datfil,GCMAXPTH);
     }
}

static VOID
getuacc(                           /* set access for a user                */
struct saunam *dpknam)             /*   dynapak name in use                */
{
     USHORT forum;

     ASSERT(sameto(USRACC,dpknam->suffix));
     forum=getfid(&dpknam->suffix[sizeof(USRACC)-1]);
     if (forum == EMLID) {
          rejectreq();
          return;
     }
     if (foracc(forum) < OPAXES) {
          rejectreq();
          return;
     }
     if (!uidxst(dpknam->usrid)) {
          rejectreq();
          return;
     }
     *(SHORT *)rsptmp=gforac(dpknam->usrid,forum);
     rsp2read(dpknam,sizeof(SHORT),rsptmp,shortFDA);
}

static VOID
csmodmsg(                          /* modify a message                     */
struct saunam *dpknam,             /*   dynapak name in use                */
CHAR *modtxt)                      /*   new topic and text                 */
{
     USHORT forum;
     LONG msgid;
     CHAR *cp;

     ASSERT(sameto(MODMSG,dpknam->suffix));
     *((CHAR *)vdatmp)='\0';
     if (sscanf(&dpknam->suffix[sizeof(MODMSG)-1],"%s %lu",
                (CHAR *)vdatmp,&msgid) < 2) {
          rejectreq();
          return;
     }
     forum=getfid((CHAR *)vdatmp);
     if (forum == EMLID) {
          rejectreq();
          return;
     }
     if (foracc(forum) < RDAXES) {
          rejectreq();
          return;
     }
     if ((rqiptr->wrthdl=rsvarea(wrmpool)) == NOHDL) {
          (*(SHORT *)rsptmp)=WRTBUSY;
          rsp2write(FALSE,sizeof(SHORT),rsptmp,shortFDA);
          return;
     }
     rqiptr->flags|=BUFINU;
     cswrtup();
     cp=strchr(modtxt,FLDSEP);
     if (cp == NULL) {
          rejectreq();
          return;
     }
     *cp='\0';
     stlcpy(msg->topic,modtxt,TPCSIZ);
     if (isfmtted(cp+1)) {
          ++cp;
     }
     else {
          *cp='\r';
     }
     stlcpy(msgtxt,cp,TXTLEN);
     inigmerq(efwork);
     inormrd(efwork,usaptr->userid,forum,msgid);
     cycleme(cmodmsg);
     cmodmsg();
}

static VOID
cmodmsg(VOID)                      /* cycled modify-message process        */
{
     cssetup();
     cswrtup();
     if ((*(SHORT *)rsptmp=modmsg(efwork,msg,msgtxt)) != GMEAGAIN) {
          clsgmerq(efwork);
          rsp2write(*(SHORT *)rsptmp == GMEOK,sizeof(SHORT),rsptmp,shortFDA);
     }
}

static VOID
fwdfor(                            /* forward a forum message              */
struct saunam *dpknam,             /*   dynapak name in use                */
CHAR *fwdinf)                      /*   address to fwd to/comments         */
{
     USHORT forum;
     LONG msgid;

     ASSERT(sameto(FWDFOR,dpknam->suffix));
     *((CHAR *)vdatmp)='\0';
     if (sscanf(&dpknam->suffix[sizeof(FWDFOR)-1],"%s %lu",
                (CHAR *)vdatmp,&msgid) < 2) {
          rejectreq();
          return;
     }
     forum=getfid((CHAR *)vdatmp);
     if (forum == EMLID) {
          rejectreq();
          return;
     }
     if (foracc(forum) < RDAXES) {
          rejectreq();
          return;
     }
     inigmerq(efwork);
     inormrd(efwork,usaptr->userid,forum,msgid);
     csfwd(dpknam,fwdinf);
}

static VOID
cpyfor(                            /* copy a forum message                 */
struct saunam *dpknam,             /*   dynapak name in use                */
CHAR *cpyinf)                      /*   address to copy to/comments        */
{
     USHORT forum;
     LONG msgid;

     ASSERT(sameto(CPYFOR,dpknam->suffix));
     *((CHAR *)vdatmp)='\0';
     if (sscanf(&dpknam->suffix[sizeof(CPYFOR)-1],"%s %lu",
                (CHAR *)vdatmp,&msgid) < 2) {
          rejectreq();
          return;
     }
     forum=getfid((CHAR *)vdatmp);
     if (forum == EMLID || !faccok(forum)) {
          rejectreq();
          return;
     }
     inigmerq(efwork);
     inormrd(efwork,usaptr->userid,forum,msgid);
     cscopy(dpknam,cpyinf);
}

static VOID
delfmsg(                           /* delete a forum message               */
struct saunam *dpknam)             /*   dynapak name in use                */
{
     USHORT forum;
     LONG msgid;

     ASSERT(sameto(MODMSG,dpknam->suffix));
     *((CHAR *)vdatmp)='\0';
     if (sscanf(&dpknam->suffix[sizeof(MODMSG)-1],"%s %lu",
                (CHAR *)vdatmp,&msgid) < 2) {
          rejectreq();
          return;
     }
     forum=getfid((CHAR *)vdatmp);
     if (forum == EMLID || !faccok(forum)) {
          rejectreq();
          return;
     }
     inigmerq(efwork);
     inormrd(efwork,usaptr->userid,forum,msgid);
     cycleme(cdelmsg);
     cdelmsg();
}

static VOID
cdelmsg(VOID)                      /* cycled delete-message process        */
{
     cssetup();
     if ((*(SHORT *)rsptmp=delmsg(efwork)) != GMEAGAIN) {
          clsgmerq(efwork);
          rsp2write(*(SHORT *)rsptmp == GMEOK,sizeof(SHORT),rsptmp,shortFDA);
     }
}

static VOID
cscrtfor(                          /* create a forum                       */
struct credfdpk *newdpk)           /*   new forum dynapak structure        */
{
     struct fordsk *tmpdef;

     if ((rqiptr->wrthdl=rsvarea(wrmpool)) == NOHDL) {
          *(SHORT *)rsptmp=WRTBUSY;
          rsp2write(FALSE,sizeof(SHORT),rsptmp,shortFDA);
          return;
     }
     rqiptr->flags|=BUFINU;
     tmpdef=areaptr(wrmpool,rqiptr->wrthdl);
     if (!fdpk2dsk(newdpk,tmpdef,TRUE)) {
          rejectreq();
          return;
     }
     inigmerq(efwork);
     createf();
}

static VOID
ccreatef(VOID)                     /* cycled create a forum process        */
{
     cssetup();
     cycleme(NULL);
     createf();
}

static VOID
createf(VOID)                      /* create a new forum                   */
{
     CHAR *tmpdesc,*tmpecho;
     struct fordsk *tmpdef;

     tmpdef=areaptr(wrmpool,rqiptr->wrthdl);
     tmpecho=tmpdef->info;
     tmpdesc=&tmpdef->info[tmpdef->necho*MAXADR];
     switch (*(SHORT *)rsptmp=creatfor(efwork,tmpdef,tmpdesc,tmpecho)) {
     case GMEAGAIN:
          cycleme(ccreatef);
          break;
     default:
          unrarea(wrmpool,rqiptr->wrthdl);
          rsp2write(*(SHORT *)rsptmp == GMEOK,sizeof(SHORT),rsptmp,shortFDA);
          break;
     }
}

static VOID
csdelfor(                          /* delete a forum                       */
struct saunam *dpknam)             /*   dynapak name in use                */
{
     USHORT forum;

     ASSERT(sameto(MODFOR,dpknam->suffix));
     forum=getfid(&dpknam->suffix[sizeof(MODFOR)-1]);
     if (forum == EMLID) {
          rejectreq();
          return;
     }
     inigmerq(efwork);
     rqiptr->wrthdl=(int)forum;
     cycleme(cfordel);
     cfordel();
}

static VOID
cfordel(VOID)                      /* cycled delete-forum process          */
{
     SHORT rc;

     cssetup();
     if ((rc=delfor(efwork,(unsigned)rqiptr->wrthdl)) != GMEAGAIN) {
          rsp2write(rc == GMEOK,sizeof(SHORT),&rc,shortFDA);
     }
}

static VOID
csmodfor(                          /* modify a forum                       */
struct saunam *dpknam,             /*   dynapak name in use                */
struct credfdpk *moddpk)           /*   modify forum dynapak structure     */
{
     USHORT forum;
     CHAR *tmpinf;
     struct fordef *tmpdef;

     ASSERT(sameto(MODFOR,dpknam->suffix));
     forum=getfid(&dpknam->suffix[sizeof(MODFOR)-1]);
     if (forum == EMLID) {
          rejectreq();
          return;
     }
     if ((rqiptr->wrthdl=rsvarea(wrmpool)) == NOHDL) {
          *(SHORT *)rsptmp=WRTBUSY;
          rsp2write(FALSE,sizeof(SHORT),rsptmp,shortFDA);
          return;
     }
     rqiptr->flags|=BUFINU;
     tmpdef=areaptr(wrmpool,rqiptr->wrthdl);
     tmpinf=(CHAR *)tmpdef+sizeof(struct fordef);
     if (!fdpk2def(forum,moddpk,tmpdef,tmpinf)) {
          rejectreq();
          return;
     }
     tmpdef->forum=forum;
     inigmerq(efwork);
     cycleme(cformod);
     cformod();
}

static VOID
cformod(VOID)                      /* cycled modify forum process          */
{
     SHORT rc;
     char *tmpinf;
     struct fordef *tmpdef;

     cssetup();
     tmpdef=areaptr(wrmpool,rqiptr->wrthdl);
     tmpinf=(char *)tmpdef+sizeof(struct fordef);
     if ((rc=modfor(efwork,tmpdef,&tmpinf[tmpdef->necho*MAXADR],tmpinf))
      != GMEAGAIN) {
          unrarea(wrmpool,rqiptr->wrthdl);
          rsp2write(rc == GMEOK,sizeof(SHORT),&rc,shortFDA);
     }
}

static GBOOL                       /*   returns FALSE if bad dynapak format*/
fdpk2dsk(                          /* copy forum dpk to on-disk struct     */
struct credfdpk *dpk,              /*   in-dynapak structure               */
struct fordsk *dsk,                /*   on-disk structure                  */
GBOOL inclfil)                     /*   include file name/path info        */
{
     CHAR *scp,*ecp;

     if ((dpk->dfprv&1) || dpk->dfprv > OPAXES) {
          return(FALSE);
     }
     dsk->dfprv=dpk->dfprv;
     if ((dpk->mxnpv&1) || dpk->mxnpv > OPAXES) {
          return(FALSE);
     }
     dsk->mxnpv=dpk->mxnpv;
     if ((dpk->dfnpv&1) || dpk->dfnpv > OPAXES) {
          return(FALSE);
     }
     dsk->dfnpv=dpk->dfnpv;
     if (dpk->msglif < -1) {
          return(FALSE);
     }
     dsk->msglif=dpk->msglif;
     dsk->chgmsg=dpk->chgmsg;
     dsk->chgrdm=dpk->chgrdm;
     dsk->chgatt=dpk->chgatt;
     dsk->chgadl=dpk->chgadl;
     dsk->chgupk=dpk->chgupk;
     dsk->chgdpk=dpk->chgdpk;
     dsk->ccr=dpk->ccr;
     if (dpk->pfnlvl < 0 || dpk->pfnlvl > DFTPFN) {
          return(FALSE);
     }
     dsk->pfnlvl=(signed char)dpk->pfnlvl;
     dsk->seqid=(UINT)dpk->seqid;
     dsk->necho=dpk->necho;
     if (dsk->necho < 0 || dsk->necho > MAXECHO) {
          return(FALSE);
     }
     scp=unpad(dpk->info);
     ecp=strchr(scp,FLDSEP);
     if (ecp == NULL) {
          return(FALSE);
     }
     *ecp='\0';
     stlcpy(dsk->name,scp,FORNSZ);
     if (strlen(dsk->name) == 0 || !valfornm(dsk->name)) {
          return(FALSE);
     }
     scp=ecp+1;
     ecp=strchr(scp,FLDSEP);
     if (ecp == NULL) {
          return(FALSE);
     }
     *ecp='\0';
     stlcpy(dsk->topic,scp,TPCSIZ);
     scp=ecp+1;
     ecp=strchr(scp,FLDSEP);
     if (ecp == NULL) {
          return(FALSE);
     }
     *ecp='\0';
     stlcpy(dsk->forop,scp,UIDSIZ);
     if (strlen(dsk->forop) == 0) {
          return(FALSE);
     }
     scp=ecp+1;
     ecp=strchr(scp,FLDSEP);
     if (ecp == NULL) {
          return(FALSE);
     }
     *ecp='\0';
     strupr(stlcpy(dsk->forlok,scp,KEYSIZ));
     if (strlen(dsk->forlok) > 0 && !keynam(dsk->forlok)) {
          return(FALSE);
     }
     scp=ecp+1;
     ecp=strchr(scp,FLDSEP);
     if (ecp == NULL) {
          return(FALSE);
     }
     *ecp='\0';
     if (dsk->necho > 0) {
          if (!parsecho(dsk->necho,scp,dsk->info)) {
               return(FALSE);
          }
     }
     scp=ecp+1;
     if (inclfil) {
          ecp=strchr(scp,FLDSEP);
          if (ecp == NULL) {
               return(FALSE);
          }
          *ecp='\0';
     }
     stlcpy(&dsk->info[dsk->necho*MAXADR],scp,MAXFDV-dsk->necho*MAXADR);
     if (inclfil) {
          scp=ecp+1;
          ecp=strchr(scp,FLDSEP);
          if (ecp == NULL) {
               return(FALSE);
          }
          *ecp='\0';
          stlcpy(dsk->attpath,scp,GCMAXPTH);
          scp=ecp+1;
          stlcpy(dsk->datfil,scp,GCMAXPTH);
          if (strlen(dsk->datfil) == 0 || strlen(dsk->datfil) >= MAXDFAP
           || !valfdfnam(dsk->datfil)) {
               return(FALSE);
          }
          fnmcse(dsk->attpath);
          fnmcse(fixfdfnam(dsk->datfil));
     }
     return(TRUE);
}

static GBOOL                       /*   returns FALSE if bad dynapak format*/
fdpk2def(                          /* copy forum dpk to in-memory struct   */
USHORT forum,                      /*   forum being edited                 */
struct credfdpk *dpk,              /*   in-dynapak structure               */
struct fordef *def,                /*   on-disk structure                  */
CHAR *info)                        /*   buffer for echoes and description  */
{
     CHAR *scp,*ecp;
     UINT usraxes;
     const struct fordef *curdef;

     curdef=getdefp(forum);
     usraxes=foracc(forum);
     if (badaxes(usraxes,dpk->dfnpv,curdef->dfnpv)) {
          return(FALSE);
     }
     def->dfnpv=dpk->dfnpv;
     if (badaxes(usraxes,dpk->dfprv,curdef->dfprv)) {
          return(FALSE);
     }
     def->dfprv=dpk->dfprv;
     if (badaxes(usraxes,dpk->mxnpv,curdef->mxnpv)) {
          return(FALSE);
     }
     def->mxnpv=dpk->mxnpv;
     if (dpk->msglif < -1) {
          return(FALSE);
     }
     def->msglif=dpk->msglif;
     def->chgmsg=dpk->chgmsg;
     def->chgrdm=dpk->chgrdm;
     def->chgatt=dpk->chgatt;
     def->chgadl=dpk->chgadl;
     def->chgupk=dpk->chgupk;
     def->chgdpk=dpk->chgdpk;
     def->ccr=dpk->ccr;
     if (dpk->pfnlvl < 0 || dpk->pfnlvl > DFTPFN) {
          return(FALSE);
     }
     def->pfnlvl=(signed char)dpk->pfnlvl;
     def->seqid=(unsigned)dpk->seqid;
     def->necho=dpk->necho;
     if (def->necho < 0 || def->necho > MAXECHO) {
          return(FALSE);
     }
     scp=unpad(dpk->info);
     ecp=strchr(scp,FLDSEP);
     if (ecp == NULL) {
          return(FALSE);
     }
     *ecp='\0';
     stlcpy(def->name,scp,FORNSZ);
     if (strlen(def->name) == 0 || !valfornm(def->name)) {
          return(FALSE);
     }
     scp=ecp+1;
     ecp=strchr(scp,FLDSEP);
     if (ecp == NULL) {
          return(FALSE);
     }
     *ecp='\0';
     stlcpy(def->topic,scp,TPCSIZ);
     scp=ecp+1;
     ecp=strchr(scp,FLDSEP);
     if (ecp == NULL) {
          return(FALSE);
     }
     *ecp='\0';
     stlcpy(def->forop,scp,UIDSIZ);
     scp=ecp+1;
     ecp=strchr(scp,FLDSEP);
     if (ecp == NULL) {
          return(FALSE);
     }
     *ecp='\0';
     strupr(stlcpy(def->forlok,scp,KEYSIZ));
     if (strlen(def->forlok) > 0 && !keynam(def->forlok)) {
          return(FALSE);
     }
     scp=ecp+1;
     ecp=strchr(scp,FLDSEP);
     if (ecp == NULL) {
          return(FALSE);
     }
     *ecp='\0';
     if (def->necho > 0) {
          if (!parsecho(def->necho,scp,info)) {
               return(FALSE);
          }
     }
     scp=ecp+1;
     stlcpy(&info[def->necho*MAXADR],scp,MAXFDV-def->necho*MAXADR);
     return(TRUE);
}

static GBOOL
badaxes(                           /* is default access level invalid?     */
INT usraxes,                       /*   current user's access              */
INT newaxes,                       /*   access level being set             */
INT oldaxes)                       /*   current access level               */
{
     return((newaxes&1)
         || (newaxes > (usraxes >= SYAXES ? OPAXES : COAXES)
          && newaxes != oldaxes));
}

static GBOOL                       /*   returns FALSE if wrong # of addrs  */
parsecho(                          /* parse ';'-delimited list of echoes   */
INT necho,                         /*   number of echoes expected          */
CHAR *list,                        /*   ';'-delimited list                 */
CHAR *arr)                         /*   echo address array                 */
{
     INT i;
     adr_t *tmpecho;

     tmpecho=(adr_t *)arr;
     for (i=0 ; i < necho ; ++i) {
          if (list == NULL) {
               break;
          }
          list=parscc(tmpecho[i],list);
     }
     return(i == necho && list == NULL);
}

static VOID
csxmtmsg(                          /* exempt/unexempt a message            */
struct saunam *dpknam,             /*   dynapak name in use                */
GBOOL exempt)                      /*   turn exempt on=TRUE, off=FALSE     */
{
     USHORT forum;
     LONG msgid;

     *((CHAR *)vdatmp)='\0';
     ASSERT(sameto(MSGXMT,dpknam->suffix));
     if (sscanf(&dpknam->suffix[sizeof(MSGXMT)-1],"%s %lu",
                (CHAR *)vdatmp,&msgid) < 2) {
          rejectreq();
          return;
     }
     forum=getfid((CHAR *)vdatmp);
     if (forum == EMLID || !faccok(forum)) {
          rejectreq();
          return;
     }
     rqiptr->stt=!(!exempt);
     inigmerq(efwork);
     inormrd(efwork,usaptr->userid,forum,msgid);
     rd4xmt();
}

VOID
crd4xmt(VOID)                      /* cycled read message to exempt        */
{
     cssetup();
     cycleme(NULL);
     rd4xmt();
}

VOID
rd4xmt(VOID)                       /* read message to exempt               */
{
     vdamsg();
     switch (*(SHORT *)rsptmp=readmsgf(efwork,msg,msgtxt)) {
     case GMEAGAIN:
          cycleme(crd4xmt);
          return;
     case GMEOK:
          *(SHORT *)rsptmp=exmtmsg(efwork,msg,msgtxt,(GBOOL)rqiptr->stt);
          ASSERT(*(SHORT *)rsptmp != GMEAGAIN);
          break;
     }
     clsgmerq(efwork);
     rsp2write(*(SHORT *)rsptmp == GMEOK,sizeof(SHORT),rsptmp,shortFDA);
}

static VOID
csapvatt(                          /* approve/unapprove attachment         */
struct saunam *dpknam,             /*   dynapak name in use                */
GBOOL approve)                     /*   approve att on=TRUE, off=FALSE     */
{
     USHORT forum;
     LONG msgid;

     *((CHAR *)vdatmp)='\0';
     ASSERT(sameto(MSGAPV,dpknam->suffix));
     if (sscanf(&dpknam->suffix[sizeof(MSGAPV)-1],"%s %lu",
                (CHAR *)vdatmp,&msgid) < 2) {
          rejectreq();
          return;
     }
     forum=getfid((CHAR *)vdatmp);
     if (forum == EMLID || !faccok(forum)) {
          rejectreq();
          return;
     }
     rqiptr->stt=!(!approve);
     inigmerq(efwork);
     inormrd(efwork,usaptr->userid,forum,msgid);
     rd4apv();
}

VOID
crd4apv(VOID)                      /* cycled read message to approve       */
{
     cssetup();
     cycleme(NULL);
     rd4apv();
}

VOID
rd4apv(VOID)                       /* read message to approve attachment   */
{
     vdamsg();
     switch (*(SHORT *)rsptmp=readmsgf(efwork,msg,msgtxt)) {
     case GMEAGAIN:
          cycleme(crd4apv);
          return;
     case GMEOK:
          *(SHORT *)rsptmp=aprvmsg(efwork,msg,msgtxt,(GBOOL)rqiptr->stt);
          ASSERT(*(SHORT *)rsptmp != GMEAGAIN);
          break;
     }
     clsgmerq(efwork);
     rsp2write(*(SHORT *)rsptmp == GMEOK,sizeof(SHORT),rsptmp,shortFDA);
}

static VOID
setuacc(                           /* set access for a user                */
struct saunam *dpknam,             /*   dynapak name in use                */
INT access)                        /*   access level to set to             */
{
     USHORT forum;

     ASSERT(sameto(USRACC,dpknam->suffix));
     forum=getfid(&dpknam->suffix[sizeof(USRACC)-1]);
     if (forum == EMLID || !faccok(forum) || !uidxst(dpknam->usrid)
      || access == SYAXES) {
          rejectreq();
          return;
     }
     *(SHORT *)rsptmp=setaxes(forum,dpknam->usrid,access);
     rsp2write(*(SHORT *)rsptmp == GMEOK,sizeof(SHORT),rsptmp,shortFDA);
}

static VOID
cpyuacc(                           /* copy forum access levels             */
struct saunam *dpknam,             /*   dynapak name in use                */
CHAR *srcusr)                      /*   source User-ID                     */
{
     if (!uidxst(dpknam->usrid) || !uidxst(srcusr)) {
          rejectreq();
          return;
     }
     *(SHORT *)rsptmp=cpyaxes(dpknam->usrid,srcusr);
     rsp2write(*(SHORT *)rsptmp > GMEAGAIN,sizeof(SHORT),rsptmp,shortFDA);
}

static VOID
cscfgac(                           /* set default access for a forum       */
struct saunam *dpknam,             /*   dynapak name in use                */
UINT length,                       /*   dynapak length                     */
struct foracc *newdfts)            /*   new default access structure       */
{
     INT access;
     USHORT forum;
     struct foracc tmpacc;

     ASSERT(sameto(CFGACC,dpknam->suffix));
     forum=getfid(&dpknam->suffix[sizeof(CFGACC)-1]);
     if (forum == EMLID || (access=foracc(forum)) < RDAXES) {
          rejectreq();
          return;
     }
     setmem(&tmpacc,sizeof(struct foracc),0);
     movmem(newdfts,&tmpacc,length);
     b2ccvt(tmpacc.forlok,KEYSIZ);
     if (tmpacc.dfnpv < NOAXES
      || tmpacc.dfnpv > (access < SYAXES ? COAXES : OPAXES)
      || (tmpacc.dfnpv&1)
      || tmpacc.dfprv < NOAXES
      || tmpacc.dfprv > (access < SYAXES ? COAXES : OPAXES)
      || (tmpacc.dfprv&1)
      || tmpacc.mxnpv < NOAXES
      || tmpacc.mxnpv > (access < SYAXES ? COAXES : OPAXES)
      || (tmpacc.mxnpv&1)
      || (strlen(tmpacc.forlok) > 0 && !keynam(tmpacc.forlok))) {
          rejectreq();
          return;
     }
     *(SHORT *)rsptmp=cfgfacc(forum,&tmpacc);
     rsp2write(*(SHORT *)rsptmp == GMEOK,sizeof(SHORT),rsptmp,shortFDA);
}

static VOID
cscrtgrp(                          /* begin create a forum group process   */
struct cmgrpdpk *dpk)              /*   new group info structure           */
{
     if ((rqiptr->wrthdl=rsvarea(wrmpool)) == NOHDL) {
          *(SHORT *)rsptmp=WRTBUSY;
          rsp2write(FALSE,sizeof(SHORT),rsptmp,shortFDA);
          return;
     }
     rqiptr->flags|=BUFINU;
     if (!dpk2grp(dpk,areaptr(wrmpool,rqiptr->wrthdl))) {
          rejectreq();
          return;
     }
     inigmerq(efwork);
     cycleme(cgrpcrt);
     cgrpcrt();
}

static VOID
cgrpcrt(VOID)                      /* cycled create group process          */
{
     SHORT rc;
     struct forgrp *grpptr;

     cssetup();
     grpptr=areaptr(wrmpool,rqiptr->wrthdl);
     switch (rc=gmeCreateGrp(efwork,grpptr)) {
     case GMEAGAIN:
          return;
     case GMEOK:
          rsp2write(TRUE,sizeof(USHORT),&grpptr->grpid,shortFDA);
          break;
     default:
          rsp2write(FALSE,sizeof(SHORT),&rc,shortFDA);
          break;
     }
     unrarea(wrmpool,rqiptr->wrthdl);
}

static VOID
csdelgrp(                          /* delete a forum group                 */
struct saunam *dpknam)             /*   dynapak name in use                */
{
     USHORT grpid;

     ASSERT(sameto(MODGRP,dpknam->suffix));
     if ((grpid=(USHORT)atol(&dpknam->suffix[sizeof(MODGRP)-1])) == ROOTGRP) {
          rejectreq();
          return;
     }
     rqiptr->wrthdl=(INT)grpid;
     inigmerq(efwork);
     cycleme(cgrpdel);
     cgrpdel();
}

static VOID
cgrpdel(VOID)                      /* cycled delete group process          */
{
     SHORT rc;

     cssetup();
     if ((rc=gmeDeleteGrp(efwork,(USHORT)rqiptr->wrthdl)) != GMEAGAIN) {
          rsp2write(rc == GMEOK,sizeof(SHORT),&rc,shortFDA);
     }
}

static VOID
csmodgrp(                          /* modify a forum group (header)        */
struct saunam *dpknam,             /*   dynapak name in use                */
struct cmgrpdpk *dpk)              /*   group header dynapak               */
{
     USHORT grpid;
     struct forgrp *grpptr;

     ASSERT(sameto(MODGRP,dpknam->suffix));
     if ((grpid=(USHORT)atol(&dpknam->suffix[sizeof(MODGRP)-1])) == ROOTGRP) {
          rejectreq();
          return;
     }
     if ((rqiptr->wrthdl=rsvarea(wrmpool)) == NOHDL) {
          *(SHORT *)rsptmp=WRTBUSY;
          rsp2write(FALSE,sizeof(SHORT),rsptmp,shortFDA);
          return;
     }
     rqiptr->flags|=BUFINU;
     if (!dpk2grp(dpk,grpptr=areaptr(wrmpool,rqiptr->wrthdl))) {
          rejectreq();
          return;
     }
     grpptr->grpid=grpid;
     inigmerq(efwork);
     cycleme(cgrpmodh);
     cgrpmodh();
}

static VOID
cgrpmodh(VOID)                     /* cycled modify group header process   */
{
     SHORT rc;

     cssetup();
     if ((rc=gmeModGrpHdr(efwork,areaptr(wrmpool,rqiptr->wrthdl)))
      != GMEAGAIN) {
          unrarea(wrmpool,rqiptr->wrthdl);
          rsp2write(rc == GMEOK,sizeof(SHORT),&rc,shortFDA);
     }
}

static VOID
csgrpfor(                          /* modify the forums in a group         */
struct saunam *dpknam,             /*   dynapak name in use                */
INT nforums,                       /*   number of forums in new list       */
USHORT *forlst)                    /*   new list of forum IDs              */
{
     USHORT grpid;

     if (nforums > gmeMaxGrpFor()) {
          rejectreq();
          return;
     }
     ASSERT(sameto(MODGRPF,dpknam->suffix));
     grpid=(USHORT)atol(&dpknam->suffix[sizeof(MODGRPF)-1]);
     if (nforums != 0) {
          if ((rqiptr->wrthdl=rsvarea(wrmpool)) == NOHDL) {
               *(SHORT *)rsptmp=WRTBUSY;
               rsp2write(FALSE,sizeof(SHORT),rsptmp,shortFDA);
               return;
          }
          rqiptr->flags|=BUFINU;
          movmem(forlst,areaptr(wrmpool,rqiptr->wrthdl),nforums*sizeof(USHORT));
     }
     rqiptr->ifp=(FILE *)grpid;
     rqiptr->ofp=(FILE *)nforums;
     inigmerq(efwork);
     cycleme(cgrpmodf);
     cgrpmodf();
}

static VOID
cgrpmodf(VOID)                     /* cycled modify group forums process   */
{
     SHORT rc;
     USHORT *forlst;

     cssetup();
     forlst=NULL;
     if ((INT)rqiptr->ofp != 0) {
          forlst=areaptr(wrmpool,rqiptr->wrthdl);
     }
     if ((rc=gmeModGrpFor(efwork,(USHORT)rqiptr->ifp,(INT)rqiptr->ofp,forlst))
      != GMEAGAIN) {
          if (rqiptr->flags&BUFINU) {
               unrarea(wrmpool,rqiptr->wrthdl);
          }
          rsp2write(rc == GMEOK,sizeof(SHORT),&rc,shortFDA);
     }
}

static GBOOL                       /*   returns FALSE if bad format        */
dpk2grp(                           /* copy group info                      */
struct cmgrpdpk *dpk,              /*   from dynapak format                */
struct forgrp *grp)                /*   to GME format                      */
{
     CHAR *scp,*ecp;

     setmem(grp,sizeof(struct forgrp),0);
     grp->parid=dpk->parid;
     grp->flags=dpk->flags;
     scp=dpk->info;
     if ((ecp=strchr(scp,FLDSEP)) == NULL) {
          return(FALSE);
     }
     *ecp++='\0';
     if (!valfornm(stlcpy(grp->name,scp,FORNSZ))) {
          return(FALSE);
     }
     scp=ecp;
     if ((ecp=strchr(scp,FLDSEP)) == NULL) {
          return(FALSE);
     }
     *ecp++='\0';
     strupr(stlcpy(grp->key,scp,KEYSIZ));
     if (*grp->key != '\0' && !keynam(grp->key)) {
          return(FALSE);
     }
     if (!valtpc(stlcpy(grp->topic,ecp,TPCSIZ))) {
          return(FALSE);
     }
     return(TRUE);
}

static VOID
mkgrpf(                            /* form group info drop file            */
struct saunam *dpknam,             /*   dynapak name in use                */
GBOOL sysflg)                      /*   make sysop group file (all groups) */
{
     if (grpfilipg()) {
          rejectreq();
          return;
     }
     movmem(dpknam,&rqiptr->dpknam,sizeof(struct saunam));
     chdmak();
     mkdir(grpfpth(FALSE));
     if ((rqiptr->ofp=fopen(grpfpth(TRUE),FOPWA)) == NULL) {
          rejectreq();
          return;
     }
     ASSERT(!(rqiptr->flags&GRPSYS));
     if (sysflg) {
          if (!haskey(forsys)) {
               rejectreq();
               return;
          }
          rqiptr->flags|=GRPSYS;
     }
     ASSERT(rqiptr->stt == GRPSFOR);
     ASSERT(grprqi.grpid == ROOTGRP);
     ASSERT(*grprqi.name == '\0');
     ASSERT(*grprqi.grpstr == '\0');
     ASSERT(grprqi.sp == 0);
     cycleme(cmkgrpf);
     cmkgrpf();
}

static VOID
cmkgrpf(VOID)                      /* cycled form group info drop file     */
{
     ULONG numtck,begtck;
     CHAR *cp;
     const struct fordef *defptr;
     const struct forgrp *grpptr;

     cssetup();
     numtck=iotck();
     begtck=hrtval();
     do {
          switch (rqiptr->stt) {
          case GRPSFOR:
               if ((defptr=gmeNextGrpFDefP(grprqi.grpid,grprqi.name)) == NULL) {
                    rqiptr->stt=GRPSGRP;
                    *grprqi.name='\0';
               }
               else {
                    stlcpy(grprqi.name,defptr->name,FORNSZ);
                    if ((rqiptr->flags&GRPSYS) || faccok(defptr->forum)) {
                         fprintf(rqiptr->ofp,"%s%s\n",grprqi.grpstr,
                                 defptr->name);
                    }
               }
               break;
          case GRPSGRP:
               if ((grpptr=gmeNextGrpP(grprqi.grpid,grprqi.name)) == NULL) {
                    if (grprqi.grpid == ROOTGRP) {
                         ASSERT(grprqi.sp == 0);
                         fclose(rqiptr->ofp);
                         rqiptr->ofp=NULL;
                         cycleme(NULL);
                         rsp2read(NULL,STGLEN,grpfpth(TRUE),NULL);
                         return;
                    }
                    else {
                         grprqi.grpid=grprqi.grpstk[--grprqi.sp];
                         grprqi.grpstr[strlen(grprqi.grpstr)-1]='\0';
                         if ((cp=strrchr(grprqi.grpstr,FORIDC)) == NULL) {
                              stlcpy(grprqi.name,grprqi.grpstr,FORNSZ);
                              *grprqi.grpstr='\0';
                         }
                         else {
                              stlcpy(grprqi.name,++cp,FORNSZ);
                              *cp='\0';
                         }
                    }
               }
               else {
                    if (grprqi.sp < GRPSTKSZ) {
                         stlcpy(grprqi.name,grpptr->name,FORNSZ);
                         if ((rqiptr->flags&GRPSYS) || haskey(grpptr->key)) {
                              fprintf(rqiptr->ofp,"%s%s %d %s\n",grprqi.grpstr,
                                      grpptr->name,grpptr->grpid,grpptr->topic);
                              grprqi.grpstk[grprqi.sp++]=grprqi.grpid;
                              grprqi.grpid=grpptr->grpid;
                              stlcat(grprqi.grpstr,grpptr->name,GRPSTKSZ*FORNSZ);
                              chrcat(grprqi.grpstr,FORIDC);
                              *grprqi.name='\0';
                              rqiptr->stt=GRPSFOR;
                         }
                    }
                    else {
                         shocst("FORUM GROUPS NESTED TOO DEEPLY",grprqi.grpstr);
                         fclose(rqiptr->ofp);
                         unlink(grpfpth(TRUE));
                         rejectreq();
                         return;
                    }
               }
               break;
          default:
               ASSERT(FALSE);
          }
     } while (hrtval()-begtck < numtck);
}

static GBOOL
grpfilipg(VOID)                    /* is another group info dnld in prog   */
{
     INT i,rqid;
     struct rqinfo *tmprqi;

     for (i=0 ; i < MAXREQS ; ++i) {
          rqid=srvrqid(usrnum,i);
          if (rqid != greqid && ismyreq(rqid,FORAPID)) {
               tmprqi=(struct rqinfo *)mrqoff(rqid);
               if (sameas(tmprqi->dpknam.suffix,GRPFSFX)
                || sameas(tmprqi->dpknam.suffix,GRPFSSFX)) {
                    return(TRUE);
               }
          }
     }
     return(FALSE);
}

static CHAR *
grpfpth(                           /* generate group file path & file name */
GBOOL inclfil)                     /*   TRUE=w/ file name, FALSE=path only */
{
     if (inclfil) {
          return(uchdpath(usrnum,DROPFPTH"\\"GRPFNAM));
     }
     return(uchdpath(usrnum,DROPFPTH));
}

static VOID
grpinf(                            /* read info on a group                 */
struct saunam *dpknam)             /*   dynapak name in use                */
{
     USHORT grpid;
     struct forgrp *grpptr;
     struct dpkgrpi *dpkptr;

     if (!haskey(forsys)) {
          rejectreq();
          return;
     }
     ASSERT(sameto(GRPINF,dpknam->suffix));
     grpid=(USHORT)atol(&dpknam->suffix[sizeof(GRPINF)-1]);
     grpptr=(struct forgrp *)vdatmp;
     if (gmeGetGrpB(grpid,grpptr) == NULL) {
          rejectreq();
     }
     else {
          dpkptr=(struct dpkgrpi *)rsptmp;
          dpkptr->grpid=grpptr->grpid;
          dpkptr->parid=grpptr->parid;
          c2bcpy(dpkptr->name,grpptr->name,FORNSZ-1);
          c2bcpy(dpkptr->key,grpptr->key,KEYSIZ-1);
          c2bcpy(dpkptr->topic,grpptr->topic,TPCSIZ-1);
          dpkptr->flags=grpptr->flags;
          dpkptr->nforums=grpptr->nforums;
          movmem(grpptr->forarr,dpkptr->forarr,grpptr->nforums*sizeof(USHORT));
          rsp2read(NULL,dpkgrplen(dpkptr->nforums),rsptmp,dpkgrpiFDA);
     }
}

static VOID
grpgrp(                            /* read groups in a group               */
INT direction,                     /*   read direction                     */
struct saunam *dpknam)             /*   dynapak name in use                */
{
     ASSERT(sameto(GRPGRP,dpknam->suffix));
     rqiptr->stt=(SCHAR)direction;
     *vdatmp='\0';
     if (sscanf(&dpknam->suffix[sizeof(MSGAPV)-1],"%U %s",
                &rqiptr->wrthdl,(CHAR *)vdatmp) < 1) {
          rejectreq();
          return;
     }
     stlcpy(grprqi.name,vdatmp,FORNSZ);
     movmem(dpknam,&rqiptr->dpknam,sizeof(struct saunam));
     cycleme(cgrpgrp);
     cgrpgrp();
}

static VOID
cgrpgrp(VOID)                      /* cycled get groups in a group         */
{
     INT i;
     struct forgrp *grpptr;

     grpptr=(struct forgrp *)vdatmp;
     for (i=0 ; i < MAXBTV ; ++i) {
          if (!getgrp((INT)rqiptr->stt,(USHORT)rqiptr->wrthdl,
                      grprqi.name,grpptr)) {
               rejectreq();
               return;
          }
          stlcpy(grprqi.name,grpptr->name,FORNSZ);
          if (haskey(grpptr->key)) {
               ASSERT((USHORT)rqiptr->wrthdl == grpptr->parid);
               sprintf(rsptmp,"%d %s",grpptr->grpid,grpptr->topic);
               sprintf(rqiptr->dpknam.suffix,GRPGRP"%05u %s",
                       grpptr->parid,grpptr->name);
               rsp2read(&rqiptr->dpknam,STGLEN,rsptmp,rtextFDA);
               return;
          }
     }
}

static GBOOL                       /*   returns FALSE if not found         */
getgrp(                            /* directional get group in group       */
INT direction,                     /*   read direction                     */
USHORT grpid,                      /*   parent group ID                    */
const CHAR *grpnam,                /*   base group name                    */
struct forgrp *grpbuf)             /*   buffer to read into                */
{
     if (direction > 0) {
          return(gmeNextGrpB(grpid,grpnam,grpbuf) != NULL);
     }
     ASSERT(direction < 0);
     return(gmePrevGrpB(grpid,grpnam,grpbuf) != NULL);
}

static VOID
grpfor(                            /* read forums in a group               */
INT direction,                     /*   read direction                     */
struct saunam *dpknam)             /*   dynapak name in use                */
{
     ASSERT(sameto(GRPFOR,dpknam->suffix));
     rqiptr->stt=(SCHAR)direction;
     *vdatmp='\0';
     if (sscanf(&dpknam->suffix[sizeof(MSGAPV)-1],"%U %s",
                &rqiptr->wrthdl,(CHAR *)vdatmp) < 1) {
          rejectreq();
          return;
     }
     stlcpy(grprqi.name,vdatmp,FORNSZ);
     movmem(dpknam,&rqiptr->dpknam,sizeof(struct saunam));
     cycleme(cgrpfor);
     cgrpfor();
}

static VOID
cgrpfor(VOID)                      /* cycled get forums in a group         */
{
     INT i;
     struct fordef *defptr;
     struct saunam *dpknam;

     defptr=(struct fordef *)vdatmp;
     for (i=0 ; i < MAXBTV ; ++i) {
          if (!getgrpf((INT)rqiptr->stt,(USHORT)rqiptr->wrthdl,
                       grprqi.name,defptr)) {
               rejectreq();
               return;
          }
          stlcpy(grprqi.name,defptr->name,FORNSZ);
          if (faccok(defptr->forum)) {
               getbasinf(defptr,(struct basfinf *)rsptmp);
               dpknam=NULL;
               if (rqiptr->stt != 0) {
                    sprintf(rqiptr->dpknam.suffix,GRPFOR"%05u %s",
                            rqiptr->wrthdl,defptr->name);
                    dpknam=&rqiptr->dpknam;
               }
               rsp2read(dpknam,fdpksiz((struct basfinf *)rsptmp),rsptmp,
                        basfinfFDA);
               return;
          }
          else if (rqiptr->stt == 0) {
               rejectreq();
               return;
          }
     }
}

static GBOOL                       /*   returns FALSE if not found         */
getgrpf(                           /* directional get forums in a group    */
INT direction,                     /*   read direction                     */
USHORT grpid,                      /*   parent group ID                    */
const CHAR *fornam,                /*   base forum name                    */
struct fordef *forbuf)             /*   buffer to read into                */
{
     if (direction > 0) {
          return(gmeNextGrpFDefB(grpid,fornam,forbuf) != NULL);
     }
     if (direction < 0) {
          return(gmePrevGrpFDefB(grpid,fornam,forbuf) != NULL);
     }
     return(gmeGetGrpFDefB(grpid,fornam,forbuf) != NULL);
}

static GBOOL
valapid(                           /* is this a valid App-ID               */
const CHAR *appid)                 /*   string to check                    */
{
     INT i;
     const CHAR *cp;

     for (i=1,cp=appid; *cp != '\0' ; ++i,++cp) {
          if (i >= AIDSIZ || !isapidc(*cp)) {
               return(FALSE);
          }
     }
     return(TRUE);
}

static GBOOL
isapidc(                           /* is this a valid App-ID character?    */
CHAR c)                            /*   character to check                 */
{
     return(c > ' ' && strchr("\"*+,./:;<=>?[\\]|",c) == NULL);
}

static VOID
reg_dpk(VOID)                      /* register write dynapak FDAs          */
{
     register_dpkfda(FORAPID,"saf:"UPLATT,filinfFDA);
     register_dpkfda(FORAPID,"sa:"WRTNEW,newdpkFDA);
     register_dpkfda(FORAPID,"sa:"WRTRPL,newdpkFDA);
     register_dpkfda(FORAPID,INISCN,otscanFDA);
     register_dpkfda(FORAPID,"sa:"CRTFOR,credfdpkFDA);
     register_dpkfda(FORAPID,"sa:"MODFOR,credfdpkFDA);
     register_dpkfda(FORAPID,"sa:"CRTGRP,cmgrpdpkFDA);
     register_dpkfda(FORAPID,"sa:"MODGRP,cmgrpdpkFDA);
     register_dpkfda(FORAPID,"sa:"MODGRPF,shortsFDA);
     register_dpkfda(FORAPID,"sa:"MSGXMT,shortFDA);
     register_dpkfda(FORAPID,"sa:"MSGAPV,shortFDA);
     register_dpkfda(FORAPID,"sau:"USRACC,shortFDA);
     register_dpkfda(FORAPID,"sa:"CFGACC,foraccFDA);
}
