/***************************************************************************
 *                                                                         *
 *   WLMAHMC.CPP                                                           *
 *                                                                         *
 *   Copyright (c) 1997 Galacticomm, Inc.         All Rights Reserved.     *
 *                                                                         *
 *   Worldlink Messaging Client management module (Active HTML)            *
 *                                                                         *
 *                                            - J. Alvrus   8/1/97         *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "majorbbs.h"
#include "gme.h"
#include "galacth.h"
#include "dnf.h"
#include "tvb.h"
#include "worldlnk.h"
#include "smbapi.h"
#include "smbwrap.h"
#include "cyctimer.h"
#include "wormsg.h"
#include "wlmutl.h"
#include "wlmcfgu.h"
#include "wlmcfgc.h"
#include "wlmexpc.h"
#include "wlmmodc.h"
#include "wlmcsmc.h"
#include "wlmahmc.h"

#define FILREV "$Revision: 8 $"

/* Form parameter identifiers */

#define FP_DEFAULT  "Default"      // use default setting submit button type
#define FP_ATTSZLIM "Attachment-Size-Limit-On" // size limit on flag
#define FP_SYSNAME  "System"       // add-system-access system name
#define FP_SPECACC  "Access-Specified" // forum has non-default default access
#define FP_ACCVIEW  "Access-View"  // forum/system access: view
#define FP_ACCREAD  "Access-Read"  // forum/system access: read
#define FP_ACCWRT   "Access-Write" // forum/system access: write
#define FP_ACCMGR   "Access-Manage" // forum/system access: manage

/* Miscellaneous global functions */

size_t
encodedSize(                       // size of entity-encoded string (incl '\0')
const CHAR *src);                  //   unencoded string

CHAR *                             //   returns pointer to destination
encodeEntities(                    // encode string using HTML entities
CHAR *buf,                         //   buffer to receive encoded string
const CHAR *src,                   //   unencoded string
size_t bufSiz);                    //   size of destination buffer

INT
getAccFlags(                       // extract access flags from a form response
acthSession *ses);                 //   session handling form response

/* All DNF steps and maps */

enum {ERRTEXT};
dnfStep remErrStep[]={
     dnfStep(DNFWATCH,ERRTEXT,"WL_ERROR_MSG"),
     dnfStep(DNFMAPEND)
};
dnfMap remErrMap("galacth/wormsg/wlmrerr.htm",
                 "Remote Error Message",remErrStep);

enum {FORCFGPIC,FORUSEPIC,FORLINK,FORNAME};
dnfStep forListStep[]={            // forum list DNF map
     dnfStep(DNFTABLE),
          dnfStep(DNFCOLUMN,FORCFGPIC,"WL_FORUM_LIST_CFG"),
          dnfStep(DNFCOLUMN,FORUSEPIC,"WL_FORUM_LIST_USE"),
          dnfStep(DNFCOLUMN,FORLINK,"WL_FORUM_LIST_LINK"),
          dnfStep(DNFCOLUMN,FORNAME,"WL_FORUM_LIST_NAME"),
     dnfStep(DNFTABLEEND),
     dnfStep(DNFMAPEND)
};
dnfMap forListMap("galacth/wormsg/wlmflst.htm",
                  "List of WorldLink Forums",forListStep);

dnfStep forViewStep[]={
     dnfStep(DNFMAPEND)
};
dnfMap forViewMap("galacth/wormsg/wlmview.htm",
                  "View WorldLink Forum",forViewStep);

dnfStep forViewMgrStep[]={
     dnfStep(DNFMAPEND)
};
dnfMap forViewMgrMap("galacth/wormsg/wlmviewm.htm",
                     "View WorldLink Forum (Manager)",forViewMgrStep);

dnfStep getCfgStep[]={
     dnfStep(DNFMAPEND)
};
dnfMap getCfgMap("galacth/wormsg/wlmeditf.htm",
                 "Forum Configuration Form",getCfgStep);

dnfStep cfgOKStep[]={
     dnfStep(DNFMAPEND)
};
dnfMap cfgOKMap("galacth/wormsg/wlmmodok.htm",
                "Configuration Error Message",cfgOKStep);

dnfStep cfgErrStep[]={
     dnfStep(DNFWATCH,ERRTEXT,"WL_ERROR_MSG"),
     dnfStep(DNFMAPEND)
};
dnfMap cfgErrMap("galacth/wormsg/wlmcerr.htm",
                 "Configuration Error Message",cfgErrStep);

enum {FORDESC};
dnfStep remDetStep[]={
     dnfStep(DNFWATCH,FORDESC,"WL_FORUM_DESC"),
     dnfStep(DNFMAPEND)
};
dnfMap remDetMap("galacth/wormsg/wlmrdet.htm",
                  "View Remote Forum Details",remDetStep);

dnfStep remGetCrtStep[]={
     dnfStep(DNFMAPEND)
};
dnfMap remGetCrtMap("galacth/wormsg/wlmrcrt.htm",
                    "Create Remote Forum Form",remGetCrtStep);

dnfStep remSetCrtStep[]={
     dnfStep(DNFMAPEND)
};
dnfMap remSetCrtMap("galacth/wormsg/wlmrcrok.htm",
                    "Create Remote Forum Response",remSetCrtStep);

dnfStep remGetModStep[]={
     dnfStep(DNFWATCH,FORDESC,"WL_FORUM_DESC"),
     dnfStep(DNFMAPEND)
};
dnfMap remGetModMap("galacth/wormsg/wlmrmod.htm",
                    "Modify Remote Forum Form",remGetModStep);

dnfStep remSetModStep[]={
     dnfStep(DNFMAPEND)
};
dnfMap remSetModMap("galacth/wormsg/wlmrmdok.htm",
                    "Modify Remote Forum Response",remSetModStep);

enum {ACCLINK,ACCNAME};
dnfStep remGetAccStep[]={          // remote forum access list DNF map
     dnfStep(DNFTABLE),
          dnfStep(DNFCOLUMN,ACCLINK,"WL_ACCESS_LIST_LINK"),
          dnfStep(DNFCOLUMN,ACCNAME,"WL_ACCESS_LIST_NAME"),
     dnfStep(DNFTABLEEND),
     dnfStep(DNFMAPEND)
};
dnfMap remGetAccMap("galacth/wormsg/wlmracc.htm",
                    "Remote Forum Access List",remGetAccStep);

dnfStep remGetAddAccStep[]={
     dnfStep(DNFMAPEND)
};
dnfMap remGetAddAccMap("galacth/wormsg/wlmracad.htm",
                       "Add Remote Forum Access Form",remGetAddAccStep);

dnfStep remSetAddAccStep[]={
     dnfStep(DNFMAPEND)
};
dnfMap remSetAddAccMap("galacth/wormsg/wlmraaok.htm",
                       "Add Remote Forum Access Response",remSetAddAccStep);

dnfStep remGetSysAccStep[]={
     dnfStep(DNFMAPEND)
};
dnfMap remGetSysAccMap("galacth/wormsg/wlmracmd.htm",
                       "Set Remote Forum Access Form",remGetSysAccStep);

dnfStep remSetSysAccStep[]={
     dnfStep(DNFMAPEND)
};
dnfMap remSetSysAccMap("galacth/wormsg/wlmramok.htm",
                       "Set Remote Forum Access Response",remSetSysAccStep);

dnfStep remDelConfStep[]={
     dnfStep(DNFMAPEND)
};
dnfMap remDelConfMap("galacth/wormsg/wlmrcond.htm",
                     "Confirm Remote Forum Delete",remDelConfStep);

dnfStep remDeleteStep[]={
     dnfStep(DNFMAPEND)
};
dnfMap remDeleteMap("galacth/wormsg/wlmrdlok.htm",
                    "Remote Forum Deleted",remDeleteStep);

/* WL Mail-specific text variable objects */

class wlmTimeStampTxv : public tvbDefinition { // time stamp text variable
public:
     wlmTimeStampTxv() :
          tvbDefinition("WL_TIME_STAMP")
     {
          registerDef();
     }

     virtual const CHAR *          //   returns text var value
     resolve(                      // resolve a text var name into its value
     const CHAR *nam);             //   name of text var being resolved
} txvTimeStamp;                    // the one instance of this variable

class wlmTextVar : public tvbDefinition { // generic text variable handler
public:
     wlmTextVar(                   // constructor for dynamic variables
     const CHAR *name) :           //   variable name
          tvbDefinition(name)
     {
          clr();
          registerDef();
     }

     wlmTextVar(                   // constructor for static variables
     const CHAR *name,             //   variable name
     const CHAR *value) :          //   pointer to buffer containing value
          tvbDefinition(name)      //   (contents must be persistent)
     {
          set(value);
          registerDef();
     }

     VOID
     set(                          // set value to display
     const CHAR *newValue)         //   to this string
     {
          m_pVal=newValue;
     }

     const CHAR *
     get() const                   // get current value
     {
          return(m_pVal);
     }

     VOID
     clr()                         // clear current value
     {
          m_pVal="";
     }

     const CHAR *                  //   returns text var value
     resolve(                      // resolve a text var name into its value
     const CHAR *nam);             //   name of text var being resolved

private:                           // private member variables
     const CHAR *m_pVal;           //   pointer to text to return
};

class wlmCheckTxv : public wlmTextVar { // txt var for "CHECKED" items in forms
public:
     wlmCheckTxv(
     const CHAR *name) :
          wlmTextVar(name)
     {}

     VOID
     setChecked(                   // set checked status
     bool isChecked);              //   is the option checked?
};

/* Forum array management objects */

class forArrayMgr;                 // forward declaration
class forArrayUser;                // forward declaration

class forArrayElem {               // forum array element
     friend class forArrayMgr;     //   give manager access

public:
     forArrayElem(                 // constructor
     const CHAR *wlForName,        //   WL forum name
     USHORT gmeForumID,            //   ID of associated GME forum
     INT access)                   //   WL access flags
     {
          stlcpy(m_forName,wlForName,MAXFNAM);
          m_gmeID=gmeForumID;
          m_acc=(SHORT)access;
     }

private:                           // forbidden fruit

     forArrayElem();               // no default constructor

     forArrayElem(                 // no copy constructor
     forArrayElem &);

     VOID
     operator=(                    // no assignment operator
     forArrayElem &);

public:                            // public member functions

     const CHAR *
     name() const                  // get WL forum name
     {
          return(m_forName);
     }

     USHORT
     gmeID() const                 // get GME forum ID
     {
          return(m_gmeID);
     }

     SHORT
     acc() const                   // get access flags
     {
          return(m_acc);
     }

     VOID
     setID(                        // set GME forum ID
     USHORT gmeID)                 //   new GME forum ID
     {
          m_gmeID=gmeID;
     }

private:                           // private data members
     USHORT m_gmeID;               //   GME forum ID
     SHORT m_acc;                  //   WL access flags
     CHAR m_forName[MAXFNAM];      //   WL forum name
};

class forArrayMgr {                // forum array management class
     friend class forArrayUser;    //   give user access

public:
     forArrayMgr();                // constructor
     ~forArrayMgr();               // destructor

public:                            // public member functions

     VOID
     notifyChange()                // notify us of forum info change
     {
          m_changed=true;
     }

private:                           // private member functions

     bool
     ready();                      // is forum array ready for use?
                                   //   (call repeatedly until returns true)

     VOID
     prcFile();                    // process forum list file while rebuilding

     bool                          //   returns true if did update
     prcEchoes();                  // process forum echoes while rebuilding

     VOID
     addElem(                      // add an element to new array
     const CHAR *wlForName,        //   WL forum name
     USHORT gmeForumID,            //   ID of associated GME forum
     INT access);                  //   WL access flags

     bool                          //   returns true if did update
     doUpdate(                     // swap in the newly-formed array
     bool incref);                 //   increment ref count if successful?

     INT
     getCount()                    // get number of elements in array
     {
          ASSERT(m_smb != NULL);
          return(m_smb->GetCount());
     }

     forArrayElem *                //   returns pointer to element
     getFirst()                    // get first element in array
     {
          ASSERT(m_smb != NULL);
          return((forArrayElem *)m_smb->GetLow(0));
     }

     forArrayElem *                //   returns pointer to element
     getNext()                     // get next element in array
     {
          ASSERT(m_smb != NULL);
          return((forArrayElem *)m_smb->GetNext());
     }

     forArrayElem *                //   returns pointer to element
     getEqual(                     // find a specific element in array
     const CHAR *forName)          //   with this forum name
     {
          ASSERT(m_smb != NULL);
          return((forArrayElem *)m_smb->GetEqual(forName,0));
     }

     forArrayElem *                //   returns pointer to element
     getGreater(                   // find element greater than
     const CHAR *forName)          //   this forum name
     {
          ASSERT(m_smb != NULL);
          return((forArrayElem *)m_smb->GetGreater(forName,0));
     }

     forArrayElem *                //   returns pointer to first element
     getCurrent()                  // get the current element in the array
     {
          ASSERT(m_smb != NULL);
          return((forArrayElem *)m_smb->CurrentData());
     }

     SMBPTR
     getPos()                      // get current record position
     {
          ASSERT(m_smb != NULL);
          return(m_smb->CurrentNumber());
     }

     VOID
     setPos(                       // set current record position
     SMBPTR pos)                   //   to this position
     {
          ASSERT(m_smb != NULL);
          m_smb->GetBynum(pos,0);
     }

     VOID
     delUser()                     // decrement reference count
     {                             //   (updates if waiting)
          --m_refs;
          doUpdate(false);
     }

private:                           // private data members
     SMBKEYTABLE m_keys[2];        //   key table for SMB
     CSmb *m_smb;                  //   primary tree handler
     CSmb *m_newsmb;               //   tree handler used for re-forming
     FILE *m_fp;                   //   stream for reading forum list
     bool m_changed;               //   forum info has changed
     bool m_rebuilding;            //   rebuild in progress
     bool m_updateReady;           //   ready to be updated
     INT m_fidx;                   //   index of current forum during rebuild
     INT m_refs;                   //   reference count
} TheArray;                        // the one-and-only instance of this class

class forArrayUser {               // forum array user class
public:
     forArrayUser() :              // constructor
          m_pos(SMBNULL),
          m_gotready(false)
     {}
     ~forArrayUser()               // destructor
     {
          if (m_gotready) {
               TheArray.delUser();
          }
     }

public:                            // public member functions

     bool
     ready()                       // is forum array ready for use?
     {                             //   (call repeatedly until returns true)
          return(m_gotready=TheArray.ready());
     }

     INT
     getCount()                    // get number of elements in array
     {
          return(TheArray.getCount());
     }

     forArrayElem *                //   returns pointer to first element
     getFirst();                   // get first element in array

     forArrayElem *                //   returns pointer to first element
     getNext();                    // get next element in array

     forArrayElem *                //   returns pointer to first element
     getEqual(                     // find a specific element in array
     const CHAR *forName);         //   with this forum name

     forArrayElem *                //   returns pointer to element
     getGreater(                   // find element greater than
     const CHAR *forName);         //   this forum name

     forArrayElem *                //   returns pointer to first element
     getCurrent();                 // get the current element in the array

private:                           // private data members
     SMBPTR m_pos;                 //   current position
     bool m_gotready;              //   did ready() return true?
};

/* WL Mail system agent */

class wlmAgent : public acthAgent { // WL Mail system agent
public:
     wlmAgent(                     // constructor
     const CHAR *apnam,            //   description (eg "File Libraries")
     const CHAR *upfx) :           //   URL prefix (eg "library")
          acthAgent(apnam,upfx)
     {
          registerAgent(acthVersion);
     }

     acthSynthesis *               //   session info
     newSynthesis(                 // instantiate Synthesis class
     acthSession *ses);            //   for passing to acthSynthesis's ctor
};

/* Base synthesis class for WL Mail requests */

class wlmSynthesis : public acthSynthesis { // virtual base class
public:
     wlmSynthesis(
     acthSession *ses);

     ~wlmSynthesis();

     virtual ACTHCODE              //   returns enumerated status value
     proceed()=0;                  // agent's request handler

protected:

     bool
     userOK(                       // does this user have access?
     ACTHCODE &rc);                //   error code if not

     bool
     startProc();                  // start proceed() processing?

     bool
     contProc(                     // continue proceed() processing?
     ACTHCODE rc);                 //   current proceed() return code

     bool
     haveRoom();                   // do we have room for output?

private:
     wlmSynthesis();               // no default constructor

     wlmSynthesis(                 // no copy constructor
     wlmSynthesis& hs);

     VOID
     operator=(                    // no assignment operator
     wlmSynthesis& hs);

protected:                         // standard data members
     dnfHandler *m_dnf;            //   DynaFile handler
     cycleTimer m_timer;           //   cycle time slicer
};

/* Base synthesis declarations for requests handled by hub */

                                   // extended error codes
#define ERR_BUSY    20000          //   management module busy
#define ERR_NOCONN  20001          //   not connected to hub

extern "C" {

VOID
hubResp(                           // got response to mgr op function type
INT reqid,                         //   request ID getting response
const CHAR *value);                //   info returned by hub

VOID
hubAbort(                          // management op aborted function type
INT reqid,                         //   request ID getting response
INT reason);                       //   reason for abort

typedef
GBOOL                              //   returns TRUE if request forwarded
(*hubReqFunc)(                     // function used to send requests to hub
const CHAR *value,                 //   contents of request
INT reqid,                         //   request ID to respond to
mgrRespFunc respFunc,              //   func to call with success response
mgrAbortFunc abortFunc);           //   func to call with error response

}; // extern "C"

class synHubReq;                   // forward declaration

struct hubInfo {                   // globals for tracking hub requests
     friend synHubReq;
     friend VOID hubResp(INT reqid,const CHAR *value);
     friend VOID hubAbort(INT reqid,INT reason);

public:
     hubInfo() :
          pSyn(NULL),
          reqID(0),
          gotResp(false)
     {
          buf=new CHAR[WBUFSZ];
     }

     ~hubInfo()
     {
          delete buf;
     }

private:                           // private data members
     CHAR *buf;                    //   buffer for holding response
     synHubReq *pSyn;              //   synthesis object handling current req
     INT reqID;                    //   ID for current request
     INT error;                    //   code describing error (if any)
     bool gotResp;                 //   have we gotten a response?
} TheHub;                          // the one and only instance of this class

class synHubReq : public wlmSynthesis { // generic hub-related request handler
public:
     synHubReq(
     acthSession *ses) :
          wlmSynthesis(ses),
          m_state(PREPREQ)
     {}

     ~synHubReq();

protected:

     virtual bool                  //   returns true if should be called again
     prepReq()=0;                  // prepare request for hub
                                   // (not called after send() called)

     virtual bool                  //   return true if should be called again
     hdlResp()=0;                  // handle response from hub
                                   // (called after response received from hub)
                                   // (not called if error response received)

     VOID
     setError(                     // report error in response
     INT err);                     //   error code
                                   // (can be called from prepReq or hdlResp)

     VOID
     send(                         // send a request to the hub
     const CHAR *value,            //   what to send
     hubReqFunc func);             //   function to use

     const CHAR *
     resp()                        // get response value
     {
          ASSERT(TheHub.gotResp);
          return(TheHub.buf);
     }

private:                           // private member functions

     ACTHCODE                      //   returns enumerated status value
     proceed();                    // agent's request handler

     INT                           //   returns new ID
     newID();                      // generate a new request ID

     bool
     gotResp()                     // have we gotten a response yet?
     {
          return(TheHub.gotResp);
     }

     bool
     gotError()                    // was the response an error?
     {
          return(TheHub.error != ERR_NONE);
     }

     const CHAR *                  //   error message to output
     errText();                    // form error message

     INT
     error()                       // get error code
     {
          return(TheHub.error);
     }

private:                           // private data members
     enum {PREPREQ,WAITHUB,HDLRESP,SENDERR} m_state; // processing state
};

class synHubReqResp : public synHubReq { // hub request response base class
public:
     synHubReqResp(
     acthSession *ses,
     dnfMap *map) :
          synHubReq(ses),
          m_respdnf(NULL),
          m_respmap(map),
          m_startresp(true)
     {}

     ~synHubReqResp();

protected:                         // implementation-specific member functions

     virtual bool                  //   returns true if should be called again
     prepReq()=0;                  // prepare request for hub

     virtual bool                  //   returns false when done
     hdlResp()=0;                  // handle response from hub

private:                           // private member functions

     synHubReqResp(                // no standard synthesis constructor
     acthSession *ses);

protected:                         // protected data members
     dnfHandler *m_respdnf;        //   DynaFile handler for OK response
     dnfMap *m_respmap;            //   DynaFile map for OK response
     bool m_startresp;             //   are we just starting response?
};

class synHubManage : public synHubReqResp { // hub manage-forum base class
public:
     synHubManage(
     acthSession *ses,
     dnfMap *map) :
          synHubReqResp(ses,map)
     {}

protected:

     virtual bool                  //   returns true if should be called again
     prepReq()=0;                  // prepare request for hub

     const CHAR *                  //   returns buffer (caller must free)
     getCfg(                       // get config info from form parameters
     const CHAR *forName);         //   name of forum to use

private:                           // private member functions

     synHubManage(                 // no standard synthesis constructor
     acthSession *ses);

     bool                          //   returns false when done
     hdlResp();                    // handle response from hub
};

class synHubSetAcc : public synHubReqResp { // hub add/set forum access
public:                            // public member functions
     synHubSetAcc(
     acthSession *ses,
     dnfMap *map) :
          synHubReqResp(ses,map)
     {}

protected:

     virtual bool                  //   returns false when done
     prepReq()=0;                  // prepare request for hub

private:                           // private member functions

     synHubSetAcc(                 // no standard synthesis constructor
     acthSession *ses);

     bool                          //   returns false when done
     hdlResp();                    // handle response from hub
};

/* Request-specific synthesis classes */

class synBadRequest : public acthSynthesis { // bad request handler
public:
     synBadRequest(
     acthSession *ses,
     ACTHCODE rc) :
          acthSynthesis(ses),
          m_rc(rc)
     {}

     ACTHCODE                      //   returns enumerated status value
     proceed();                    // agent's request handler

private:                           // private member functions

     synBadRequest(                // no standard synthesis constructor
     acthSession *ses);

private:                           // private data members
     ACTHCODE m_rc;                //   what's wrong with request
};

class synSendFile : public wlmSynthesis { // generic send-file handler
public:
     synSendFile(
     acthSession *ses,
     const CHAR *fileName) :
          wlmSynthesis(ses),
          m_map(NULL),
          m_step(NULL)
     {
          ::stlcpy(m_fileName,fileName,GCMAXPTH);
     }

     ~synSendFile();

     ACTHCODE                      //   returns enumerated status value
     proceed();                    // agent's request handler

private:                           // private member functions

     synSendFile(                  // no standard synthesis constructor
     acthSession *ses);

private:                           // private data members
     dnfMap *m_map;                //   DynaFile map
     dnfStep *m_step;              //   DynaFile step array
     CHAR m_fileName[GCMAXPTH];    //   name of file to send
};

class synForList : public wlmSynthesis { // forum list handler
public:
     synForList(
     acthSession *ses) :
          wlmSynthesis(ses),
          m_starting(true)
     {
          *m_curForName='\0';
     }

     ACTHCODE                      //   returns enumerated status value
     proceed();                    // agent's request handler

private:                           // private data members
     forArrayUser m_farr;          //   forum array handler
     bool m_starting;              //   preparing forum array
     CHAR m_curForName[MAXFNAM];   //   current WL forum name
};

class synViewFor : public wlmSynthesis { // view forum handler
public:
     synViewFor(
     acthSession *ses) :
          wlmSynthesis(ses),
          m_accstr(NULL),
          m_starting(true)
     {
          *m_forList='\0';
     }

     ~synViewFor();

     ACTHCODE                      //   returns enumerated status value
     proceed();                    // agent's request handler

private:                           // private data members
     forArrayUser m_farr;          //   forum array handler
     CHAR *m_accstr;               //   access description string
     bool m_starting;              //   preparing forum array
     CHAR m_forList[100];          //   list of local forums using WL forum
                                   //   (size is arbitrary)
};

class synGetConfig : public wlmSynthesis { // get forum configuration handler
public:
     synGetConfig(
     acthSession *ses) :
          wlmSynthesis(ses)
     {}

     ACTHCODE                      //   returns enumerated status value
     proceed();                    // agent's request handler

private:                           // private data members
     bool m_limatt;                //   is attachment size limited?
     bool m_autapv;                //   auto-approve attachments
     bool m_canin;                 //   allow incoming cancellations?
     bool m_canout;                //   allow outgoing cancellations?
     bool m_modin;                 //   allow incoming modifications?
     bool m_modout;                //   allow outgoing modifications?
     CHAR m_maxatt[sizeof("-1234567890")]; // maximum attachment size
};

class synSetConfig : public wlmSynthesis { // set forum configuration handler
public:
     synSetConfig(
     acthSession *ses) :
          wlmSynthesis(ses),
          m_errmsg(0)
     {}

     ACTHCODE                      //   returns enumerated status value
     proceed();                    // agent's request handler

private:                           // private member functions

     VOID
     setConfigBool(                // set a boolean value in config buffer
     const CHAR *itemName,         //   name of item to set
     CHAR *buf,                    //   buffer to set in
     size_t bufSiz);               //   size of buffer

private:                           // private data members
     INT m_errmsg;                 //   MCV number of error message
};

class synRDetails : public synHubReq { // get details from hub
public:                            // public member functions
     synRDetails(
     acthSession *ses) :
          synHubReq(ses),
          m_accstr(NULL),
          m_topic(NULL),
          m_desc(NULL),
          m_dptr(NULL),
          m_lineStart(true),
          m_state(START)
     {}

     ~synRDetails();

     bool                          //   returns false when done
     prepReq();                    // prepare request for hub

     bool                          //   returns false when done
     hdlResp();                    // handle response from hub

private:                           // private data members
     CHAR *m_accstr;               //   access description string
     CHAR *m_topic;                //   buffer to hold topic
     CHAR *m_desc;                 //   buffer to hold description
     CHAR *m_dptr;                 //   output position in description
     bool m_lineStart;             //   is this the start of a desc line?
     enum {START,SENDDNF,OUTDESC} m_state; // response processing state
};

class synRGetCreate : public wlmSynthesis { // get form to create remote forum
public:                            // public member functions
     synRGetCreate(
     acthSession *ses) :
          wlmSynthesis(ses),
          m_accstr(NULL)
     {}

     ~synRGetCreate();

     ACTHCODE                      //   returns enumerated status value
     proceed();                    // agent's request handler

private:                           // private data members
     CHAR *m_accstr;               //   access description string
};

class synRSetCreate : public synHubManage { // get form to create remote forum
public:
     synRSetCreate(
     acthSession *ses) :
          synHubManage(ses,&remSetCrtMap)
     {}

     bool                          //   returns false when done
     prepReq();                    // prepare request for hub
};

class synRGetConfig : public synHubReq { // get forum config from hub
public:                            // public member functions
     synRGetConfig(
     acthSession *ses) :
          synHubReq(ses),
          m_accstr(NULL),
          m_topic(NULL),
          m_desc(NULL),
          m_dptr(NULL),
          m_accflg(0),
          m_state(START)
     {}

     ~synRGetConfig();

     bool                          //   returns false when done
     prepReq();                    // prepare request for hub

     bool                          //   returns false when done
     hdlResp();                    // handle response from hub

private:                           // private data members
     CHAR *m_accstr;               //   access description string
     CHAR *m_topic;                //   buffer to hold topic
     CHAR *m_desc;                 //   buffer to hold description
     CHAR *m_dptr;                 //   output position in description
     INT m_accflg;                 //   access flags
     enum {START,SENDDNF,OUTDESC} m_state; // response processing state
};

class synRSetConfig : public synHubManage { // set forum config on hub
public:                            // public member functions
     synRSetConfig(
     acthSession *ses) :
          synHubManage(ses,&remSetModMap)
     {}

     bool                          //   returns false when done
     prepReq();                    // prepare request for hub
};

class synRGetAccess : public synHubReq { // get forum access list from hub
public:                            // public member functions
     synRGetAccess(
     acthSession *ses) :
          synHubReq(ses),
          m_accstr(NULL),
          m_resp(NULL),
          m_rptr(NULL),
          m_starting(true)
     {
          *m_curSysName='\0';
     }

     ~synRGetAccess();

     bool                          //   returns false when done
     prepReq();                    // prepare request for hub

     bool                          //   returns false when done
     hdlResp();                    // handle response from hub

private:                           // private data members
     CHAR *m_accstr;               //   access description string
     CHAR *m_resp;                 //   buffer to hold response
     CHAR *m_rptr;                 //   current position in response
     bool m_starting;              //   are we just starting?
     CHAR m_curSysName[WBOASIZ];   //   buffer for current system name
};

class synRGetAddAcc : public wlmSynthesis { // get add system access form
public:                            // public member functions
     synRGetAddAcc(
     acthSession *ses) :
          wlmSynthesis(ses)
     {}

     ACTHCODE                      //   returns enumerated status value
     proceed();                    // agent's request handler
};

class synRSetAddAcc : public synHubSetAcc { // add new system w/special access
public:
     synRSetAddAcc(
     acthSession *ses) :
          synHubSetAcc(ses,&remSetAddAccMap)
     {}

     bool                          //   returns false when done
     prepReq();                    // prepare request for hub
};

class synRGetSysAcc : public synHubReq { // get set system access form
public:                            // public member functions
     synRGetSysAcc(
     acthSession *ses) :
          synHubReq(ses),
          m_accflg(0),
          m_starting(true)
     {}

     bool                          //   returns false when done
     prepReq();                    // prepare request for hub

     bool                          //   returns false when done
     hdlResp();                    // handle response from hub

private:                           // private data members
     INT m_accflg;                 //   access flags
     bool m_starting;              //   are we just starting?
};

class synRSetSysAcc : public synHubSetAcc { // set system access
public:
     synRSetSysAcc(
     acthSession *ses) :
          synHubSetAcc(ses,&remSetSysAccMap)
     {}

     bool                          //   returns false when done
     prepReq();                    // prepare request for hub
};

class synRDelConf : public wlmSynthesis { // get add system access form
public:                            // public member functions
     synRDelConf(
     acthSession *ses) :
          wlmSynthesis(ses)
     {}

     ACTHCODE                      //   returns enumerated status value
     proceed();                    // agent's request handler
};

class synRDelete : public synHubManage { // set forum config on hub
public:                            // public member functions
     synRDelete(
     acthSession *ses) :
          synHubManage(ses,&remDeleteMap)
     {}

     bool                          //   returns false when done
     prepReq();                    // prepare request for hub
};

/* Global variables */

wlmAgent *TheMan;                  // Secret Agent Man

GBOOL wlAHMgrActive=FALSE;         // has management module been init?

CHAR *ahMgrKey;                    // key to use ActiveH management module
USHORT listSlice;                  // time slice for generating forum list

class forListPix {                 // forum list pictures object
public:
     forListPix();
     ~forListPix();

     const CHAR *
     configPic(                    // get picture for configured status
     const CHAR *forName);         //   of this WL forum

     const CHAR *
     usePic(                       // get picture for in-use status
     const forArrayElem *pel);     //   forum array element describing forum

private:                           // private data members
     CHAR *m_empty;                //   empty picture file
     CHAR *m_config;               //   configured picture file
     CHAR *m_used;                 //   in use picture file
     CHAR *m_invalid;              //   invalid picture file
} *TheListPix;

wlmTextVar txvCurFor("WL_CUR_FORUM");
wlmTextVar txvViewAcc("WL_VIEW_ACCESS");
wlmTextVar txvViewLocal("WL_VIEW_LOCAL");
wlmTextVar txvHubTopic("WL_FORUM_TOPIC");
wlmTextVar txvMaxAtt("WL_CFG_MAXATT");
wlmTextVar txvUserBranch("WL_USER_BRANCH");
wlmTextVar txvGlobalAcc("WL_GLOB_ACCESS");
wlmTextVar txvCurSys("WL_CUR_SYSTEM");

wlmCheckTxv chkAutoApprove("WL_CFG_AUTAPV");
wlmCheckTxv chkLimitAttSize("WL_CFG_ATTLIM");
wlmCheckTxv chkCancelIn("WL_CFG_CANIN");
wlmCheckTxv chkCancelOut("WL_CFG_CANOUT");
wlmCheckTxv chkModifyIn("WL_CFG_MODIN");
wlmCheckTxv chkModifyOut("WL_CFG_MODOUT");
wlmCheckTxv chkUseGlobAcc("WL_RCFG_GLOBACC");
wlmCheckTxv chkUseSpecAcc("WL_RCFG_SPECACC");
wlmCheckTxv chkCfgAccView("WL_RCFG_ACCVIEW");
wlmCheckTxv chkCfgAccRead("WL_RCFG_ACCREAD");
wlmCheckTxv chkCfgAccWrite("WL_RCFG_ACCWRT");
wlmCheckTxv chkCfgAccManage("WL_RCFG_ACCMGR");
wlmCheckTxv chkSysAccView("WL_RSYS_ACCVIEW");
wlmCheckTxv chkSysAccRead("WL_RSYS_ACCREAD");
wlmCheckTxv chkSysAccWrite("WL_RSYS_ACCWRT");
wlmCheckTxv chkSysAccManage("WL_RSYS_ACCMGR");

class accString {                  // access string object
public:
     accString();                  // constructor
     ~accString();                 // destructor

public:                            // public member functions

     const CHAR *                  //   returns pointer to static buffer
     get(                          // get string describing access
     INT acc);                     //   access to describe

private:                           // private member functions

     VOID
     cat(                          // concatenate onto return buffer
     const CHAR *s);               //   string to concatenate

private:                           // private data members
     CHAR *m_vstr;                 //   "View" access string
     CHAR *m_rstr;                 //   "Read" access string
     CHAR *m_wstr;                 //   "Write" access string
     CHAR *m_mstr;                 //   "Manage" access string
     CHAR *m_ustr;                 //   unknown access string
     CHAR *m_retbuf;               //   return buffer
} *TheAccStr;                      // global instance for general use

CHAR *noForums;                    // not used by local forum string

/* Interface functions */

GBOOL
initAHMgr(VOID)                    // initialize ActiveH management module
{
     if (ynopt(AHMGRON)) {
          ahMgrKey=stgopt(AHMGRKY);
          listSlice=(USHORT)((numopt(LSTSLC,1,1000)*65536L)/1000L);
          TheMan=new wlmAgent("WorldLink Forum Manager","WL-forum-manager");
          TheListPix=new forListPix;
          TheAccStr=new accString;
          noForums=stgopt(NOFORS);
          wlAHMgrActive=TRUE;
     }
     return(TRUE);
}

VOID
closeAHMgr(VOID)                   // shut down ActiveH management module
{
     if (wlAHMgrActive) {
          free(ahMgrKey);
          free(noForums);
          delete TheMan;
     }
     wlAHMgrActive=FALSE;
}

VOID
infoChangeAH(VOID)                 // forum list or global info changed
{
     TheArray.notifyChange();
}

/* Agent functions */

acthSynthesis *                    //   new session-specific structure
wlmAgent::newSynthesis(            // instantiate Synthesis class
acthSession *ses)                  //   passed to acthSynthesis's constructor
{
     CHAR tmpPath[GCMAXPTH];

     switch (ses->urlargc()) {
     case 0:
          return(new synSendFile(ses,"galacth/wormsg/wlmopen.htm"));
     case 1:
          if (::sameas("list",ses->urlargv(0))) {
               return(new synForList(ses));
          }
          if (::isfile(::makePath(tmpPath,"files",ses->urlargv(0),GCMAXPTH))) {
               return(new synSendFile(ses,tmpPath));
          }
          break;
     case 2:
          if (::sameas("view",ses->urlargv(0))) {
               return(new synViewFor(ses));
          }
          if (::sameas("get-config",ses->urlargv(0))) {
               return(new synGetConfig(ses));
          }
          if (::sameas("set-config",ses->urlargv(0))) {
               return(new synSetConfig(ses));
          }
          if (::sameas("remote",ses->urlargv(0))) {
               if (::sameas("get-create",ses->urlargv(1))) {
                    return(new synRGetCreate(ses));
               }
               if (::sameas("set-create",ses->urlargv(1))) {
                    return(new synRSetCreate(ses));
               }
          }
          break;
     case 3:
          if (::sameas("remote",ses->urlargv(0))) {
               if (::sameas("details",ses->urlargv(1))) {
                    return(new synRDetails(ses));
               }
               if (::sameas("get-config",ses->urlargv(1))) {
                    return(new synRGetConfig(ses));
               }
               if (::sameas("set-config",ses->urlargv(1))) {
                    return(new synRSetConfig(ses));
               }
               if (::sameas("get-access",ses->urlargv(1))) {
                    return(new synRGetAccess(ses));
               }
               if (::sameas("get-add-system",ses->urlargv(1))) {
                    return(new synRGetAddAcc(ses));
               }
               if (::sameas("set-add-system",ses->urlargv(1))) {
                    return(new synRSetAddAcc(ses));
               }
               if (::sameas("confirm-delete",ses->urlargv(1))) {
                    return(new synRDelConf(ses));
               }
               if (::sameas("delete",ses->urlargv(1))) {
                    return(new synRDelete(ses));
               }
          }
          break;
     case 4:
          if (::sameas("remote",ses->urlargv(0))) {
               if (::sameas("get-access",ses->urlargv(1))) {
                    return(new synRGetSysAcc(ses));
               }
               if (::sameas("set-access",ses->urlargv(1))) {
                    return(new synRSetSysAcc(ses));
               }
          }
          break;
     }
     return(new synBadRequest(ses,ACTHNOTFND));
}

/* Base synthesis functions */

wlmSynthesis::wlmSynthesis(
acthSession *ses) :
     acthSynthesis(ses),
     m_dnf(NULL),
     m_timer(::listSlice)
{
}

bool
wlmSynthesis::userOK(              // does this user have access?
ACTHCODE &rc)                      //   error code if not
{
     if (!::wlAHMgrActive) {
          rc=ACTHNOTFND;
          return(false);
     }
     acthUserID *usr=ses->getUser();
     if (usr == NULL) {
          rc=ACTHNOANON;
          return(false);
     }
     if (!usr->hasKey(::ahMgrKey)) {
          rc=ACTHFORBID;
          return(false);
     }
     return(true);
}

bool
wlmSynthesis::startProc()          // start proceed() processing?
{
     return(haveRoom() && m_timer.start());
}

bool
wlmSynthesis::contProc(            // continue proceed() processing?
ACTHCODE rc)                       //   current proceed() return code
{
     return(rc == ACTHMORE && haveRoom() && m_timer.haveTime());
}

bool
wlmSynthesis::haveRoom()           // do we have room for output?
{
     // NOTE:  512 is hard-coded bout stream buffer size
     return(::btuoba(::usrnum) > 512);
}

wlmSynthesis::~wlmSynthesis()
{
     if (m_dnf != NULL) {
          delete m_dnf;
          m_dnf=NULL;
     }
}

/* General request functions */

ACTHCODE
synBadRequest::proceed()
{
     return(m_rc);
}

synSendFile::~synSendFile()
{
     if (m_map != NULL) {
          delete m_map;
          m_map=NULL;
     }
     if (m_step != NULL) {
          delete m_step;
          m_step=NULL;
     }
}

ACTHCODE
synSendFile::proceed()
{
     ACTHCODE rc=ACTHMORE;

     if (firsttime()) {
          if (!userOK(rc)) {
               return(rc);
          }
          m_step=new dnfStep(DNFMAPEND);
          m_map=new dnfMap(m_fileName,"Generic send-file",m_step);
          m_dnf=new dnfHandler(*m_map,bout);
     }
     else {
          switch (m_dnf->process()) {
          case DNFEND:
               rc=ACTHDONE;
               break;
          }
     }
     return(rc);
}

/* Forum list functions */

ACTHCODE
synForList::proceed()
{
     ACTHCODE rc=ACTHMORE;

     if (firsttime() && !userOK(rc)) {
          return(rc);
     }
     if (startProc()) {
          do {
               if (m_starting) {
                    if (m_farr.ready()) {
                         m_starting=false;
                         ASSERT(m_dnf == NULL);
                         m_dnf=new dnfHandler(forListMap,bout);
                    }
               }
               else {
                    switch (m_dnf->process()) {
                    case DNFROWBEGIN:
                         {
                              forArrayElem *pel=m_farr.getGreater(m_curForName);
                              if (pel == NULL) {
                                   m_dnf->tableDone();
                              }
                              else {
                                   stlcpy(m_curForName,pel->name(),MAXFNAM);
                              }
                         }
                         break;
                    case FORCFGPIC:
                         bout << TheListPix->configPic(m_curForName);
                         break;
                    case FORUSEPIC:
                         bout << TheListPix->usePic(m_farr.getCurrent());
                         break;
                    case FORLINK:
                    case FORNAME:
                         bout << m_curForName;
                         break;
                    case DNFEND:
                         rc=ACTHDONE;
                         break;
                    }
               }
          } while (contProc(rc));
     }
     return(rc);
}

/* View forum functions */

synViewFor::~synViewFor()
{
     if (m_accstr != NULL) {
          free(m_accstr);
          m_accstr=NULL;
     }
}

ACTHCODE
synViewFor::proceed()
{
     ACTHCODE rc=ACTHMORE;

     if (firsttime() && !userOK(rc)) {
          return(rc);
     }
     if (m_starting) {
          if (m_farr.ready()) {
               m_starting=false;
               ASSERT(m_dnf == NULL);
               forArrayElem *pel=m_farr.getEqual(ses->urlargv(1));
               if (pel == NULL) {
                    return(ACTHNOTFND);
               }
               m_accstr=::alcdup(TheAccStr->get(pel->acc()));
               if (pel->acc()&FAF_MANAGE) {
                    m_dnf=new dnfHandler(forViewMgrMap,bout);
               }
               else {
                    m_dnf=new dnfHandler(forViewMap,bout);
               }
               *m_forList='\0';
               do {
                    if (fidxst(pel->gmeID())) {
                         const CHAR *nam=::getfnm(pel->gmeID());
                         if (::strlen(m_forList)+CSTRLEN(", ")+::strlen(nam)
                          >= sizeof(m_forList)) {
                              ::stlcat(m_forList,"...",sizeof(m_forList));
                              break;
                         }
                         if (*m_forList != '\0') {
                              ::strcat(m_forList,", ");
                         }
                         ::strcat(m_forList,nam);
                    }
               } while ((pel=m_farr.getNext()) != NULL
                     && sameas(ses->urlargv(1),pel->name()));
               if (*m_forList == '\0') {
                    ::stlcpy(m_forList,::noForums,sizeof(m_forList));
               }
          }
     }
     else {
          ::txvCurFor.set(ses->urlargv(1));
          ::txvViewAcc.set(m_accstr);
          ::txvViewLocal.set(m_forList);
          switch (m_dnf->process()) {
          case DNFEND:
               rc=ACTHDONE;
               break;
          }
          ::txvCurFor.clr();
          ::txvViewAcc.clr();
          ::txvViewLocal.clr();
     }
     return(rc);
}

/* Get forum configuration functions */

ACTHCODE
synGetConfig::proceed()
{
     ACTHCODE rc=ACTHMORE;

     if (firsttime()) {
          if (!userOK(rc)) {
               return(rc);
          }
          LONG maxatt=::getMaxForAtt(ses->urlargv(1));
          m_limatt=(maxatt >= 0);
          if (m_limatt) {
               stlcpy(m_maxatt,l2as(maxatt),sizeof(m_maxatt));
          }
          else {
               *m_maxatt='\0';
          }
          m_autapv=::getAutoApv(ses->urlargv(1));
          m_canin=::getNetCanIn(ses->urlargv(1));
          m_canout=::getNetCanOut(ses->urlargv(1));
          m_modin=::getNetModIn(ses->urlargv(1));
          m_modout=::getNetModOut(ses->urlargv(1));
          m_dnf=new dnfHandler(getCfgMap,bout);
     }
     else {
          ::txvCurFor.set(ses->urlargv(1));
          ::txvMaxAtt.set(m_maxatt);
          ::chkAutoApprove.setChecked(m_autapv);
          ::chkLimitAttSize.setChecked(m_limatt);
          ::chkCancelIn.setChecked(m_canin);
          ::chkCancelOut.setChecked(m_canout);
          ::chkModifyIn.setChecked(m_modin);
          ::chkModifyOut.setChecked(m_modout);
          switch (m_dnf->process()) {
          case DNFEND:
               rc=ACTHDONE;
               break;
          }
          ::txvCurFor.clr();
          ::txvMaxAtt.clr();
          ::chkAutoApprove.clr();
          ::chkLimitAttSize.clr();
          ::chkCancelIn.clr();
          ::chkCancelOut.clr();
          ::chkModifyIn.clr();
          ::chkModifyOut.clr();
     }
     return(rc);
}

/* Set forum configuration functions */

ACTHCODE
synSetConfig::proceed()
{
     ACTHCODE rc=ACTHMORE;

     if (firsttime()) {
          if (!userOK(rc)) {
               return(rc);
          }
          if (ses->param(FP_DEFAULT)) {
               clearCfg(ses->urlargv(1));
               rc=ACTHDONE;
          }
          else {
               CHAR numBuf[sizeof("-1234567890")];
               CHAR *cfg=new CHAR[WLMAXFCFG];
               *cfg='\0';
               if (ses->param(FP_ATTSZLIM)) {
                    ses->param(FC_MAXATT,numBuf,sizeof(numBuf));
                    if (!::alldgs(numBuf)
                     || ::atol(numBuf) < 0 || ::atol(numBuf) > MAXMAXATT) {
                         m_errmsg=CFGBADSZ;
                         m_dnf=new dnfHandler(cfgErrMap,bout);
                    }
               }
               else {
                    ::strcpy(numBuf,"-1");
               }
               if (m_errmsg == 0) {
                    ::setConfigStr(NULL,FC_MAXATT,numBuf,cfg,WLMAXFCFG);
                    setConfigBool(FC_APVATT,cfg,WLMAXFCFG);
                    setConfigBool(FC_NETCANI,cfg,WLMAXFCFG);
                    setConfigBool(FC_NETCANO,cfg,WLMAXFCFG);
                    setConfigBool(FC_NETMODI,cfg,WLMAXFCFG);
                    setConfigBool(FC_NETMODO,cfg,WLMAXFCFG);
                    if (!editCfg(cfg,ses->urlargv(1))) {
                         m_errmsg=CFGROOM;
                    }
               }
               if (m_errmsg == 0) {
                    rc=ACTHDONE;
               }
               else {
                    m_dnf=new dnfHandler(cfgErrMap,bout);
               }
               delete[] cfg;
          }
          if (rc == ACTHDONE) {
               m_dnf=new dnfHandler(cfgOKMap,bout);
               rc=ACTHMORE;
          }
     }
     else {
          ::txvCurFor.set(ses->urlargv(1));
          switch (m_dnf->process()) {
          case ERRTEXT:
               if (m_errmsg != 0) {
                    ::setmbk(::wlmb);
                    ::clrprf();
                    ::prfmsg(m_errmsg);
                    ::rstmbk();
                    ::stpans(::prfbuf);
                    bout << ::prfbuf;
               }
               break;
          case DNFEND:
               rc=ACTHDONE;
               break;
          }
          ::txvCurFor.clr();
     }
     return(rc);
}

VOID
synSetConfig::setConfigBool(       // set a boolean value in config buffer
const CHAR *itemName,              //   name of item to set
CHAR *buf,                         //   buffer to set in
size_t bufSiz)                     //   size of buffer
{
     ::setConfigStr(NULL,itemName,ses->param(itemName) ? "1" : "0",buf,bufSiz);
}

/* Hub details functions */

synRDetails::~synRDetails()
{
     if (m_accstr != NULL) {
          ::free(m_accstr);
          m_accstr=NULL;
     }
     if (m_topic != NULL) {
          delete[] m_topic;
          m_topic=NULL;
     }
     if (m_desc != NULL) {
          delete[] m_desc;
          m_desc=NULL;
     }
}

bool
synRDetails::prepReq()
{
     send(ses->urlargv(2),mgrGetDetails);
     return(false);
}

bool
synRDetails::hdlResp()
{
     bool rc=true;

     switch (m_state) {
     case START:
          {
               CHAR numBuf[sizeof("-1234567890")];
               INT acc=::atoi(::getConfigStr(NULL,WFK_ACCESS,"",numBuf,
                                             sizeof(numBuf),resp()));
               m_accstr=::alcdup(TheAccStr->get(acc));
               CHAR tmptpc[TPCSIZ];
               ::getConfigStr(NULL,WFK_TOPIC,"",tmptpc,TPCSIZ,resp());
               size_t len=::encodedSize(tmptpc);
               m_topic=new CHAR[len];
               ::encodeEntities(m_topic,tmptpc,len);
               CHAR *tmpbuf=new CHAR[MAXFDESC];
               ::getConfigStr(WFS_DESC,NULL,"",tmpbuf,MAXFDESC,resp());
               ::strrpl(::unpad(::strmove(tmpbuf,::skptwht(tmpbuf))),
                        '\n','\r');
               len=::encodedSize(tmpbuf);
               m_desc=new CHAR[len];
               ::encodeEntities(m_desc,tmpbuf,len);
               delete[] tmpbuf;
               m_dnf=new dnfHandler(remDetMap,bout);
               m_state=SENDDNF;
          }
          break;
     case SENDDNF:
          ::txvCurFor.set(ses->urlargv(2));
          ::txvViewAcc.set(m_accstr);
          ::txvHubTopic.set(m_topic);
          switch (m_dnf->process()) {
          case FORDESC:
               bout << "<P>";
               m_dptr=m_desc;
               m_state=OUTDESC;
               break;
          case DNFEND:
               rc=false;
               break;
          }
          ::txvCurFor.clr();
          ::txvViewAcc.clr();
          ::txvHubTopic.clr();
          break;
     case OUTDESC:
          if (NULSTR(m_dptr)) {
               bout << "</P>" << crlf;
               m_state=SENDDNF;
          }
          else if (*m_dptr == '\r') {
               if (*(m_dptr+1) == '\r') {
                    bout << "</P>" << crlf;
                    while (*(m_dptr+1) == '\r') {
                         ++m_dptr;
                    }
                    if (NULSTR(m_dptr)) {
                         break;
                    }
                    bout << "<P>";
                    m_lineStart=true;
               }
               else if (*(m_dptr+1) == ' ') {
                    bout << "<BR>" << crlf;
                    m_lineStart=true;
               }
               else {
                    bout << " ";
               }
               ++m_dptr;
          }
          else if (m_lineStart) {
               m_lineStart=false;
               while (*m_dptr == ' ') {
                    bout << "&nbsp;";
                    ++m_dptr;
               }
          }
          else {
               CHAR *cp=::strchr(m_dptr,'\r');
               size_t len=cp == NULL ? ::strlen(m_dptr) : (size_t)(cp-m_dptr);
               bout.flush();
               m_dptr+=ses->sndrsp(m_dptr,len);
          }
          break;
     }
     return(rc);
}

/* Hub get create form functions */

synRGetCreate::~synRGetCreate()
{
     if (m_accstr != NULL) {
          ::free(m_accstr);
          m_accstr=NULL;
     }
}

ACTHCODE
synRGetCreate::proceed()
{
     ACTHCODE rc=ACTHMORE;

     if (firsttime()) {
          if (!userOK(rc)) {
               return(rc);
          }
          CHAR numBuf[sizeof("-1234567890")];
          INT acc=::atoi(::getConfigStr(NULL,WFK_ACCESS,"",numBuf,
                                        sizeof(numBuf),::getGlobalInfo()));
          m_accstr=::alcdup(TheAccStr->get(acc));
          m_dnf=new dnfHandler(remGetCrtMap,bout);
     }
     else {
          CHAR tmpName[MAXFNAM];
          ::getConfigStr(NULL,WIK_UBRANCH,"",tmpName,MAXFNAM,::getGlobalInfo());
          ::txvUserBranch.set(tmpName);
          ::txvGlobalAcc.set(m_accstr);
          switch (m_dnf->process()) {
          case DNFEND:
               rc=ACTHDONE;
               break;
          }
          ::txvUserBranch.clr();
          ::txvGlobalAcc.clr();
     }
     return(rc);
}

/* Hub create forum functions */

bool
synRSetCreate::prepReq()
{
     CHAR forName[MAXFNAM];
     ses->param(WFK_NAME,forName,MAXFNAM);
     const CHAR *buf=getCfg(forName);
     send(buf,mgrCreateFor);
     delete[] buf;
     return(false);
}

/* Hub get config to edit functions */

synRGetConfig::~synRGetConfig()
{
     if (m_accstr != NULL) {
          ::free(m_accstr);
          m_accstr=NULL;
     }
     if (m_topic != NULL) {
          delete[] m_topic;
          m_topic=NULL;
     }
     if (m_desc != NULL) {
          delete[] m_desc;
          m_desc=NULL;
     }
}

bool
synRGetConfig::prepReq()
{
     send(ses->urlargv(2),mgrGetConfig);
     return(false);
}

bool
synRGetConfig::hdlResp()
{
     bool rc=true;

     switch (m_state) {
     case START:
          {
               CHAR numBuf[sizeof("-1234567890")];
               INT acc=::atoi(::getConfigStr(NULL,WFK_ACCESS,"",numBuf,
                                             sizeof(numBuf),::getGlobalInfo()));
               m_accstr=::alcdup(TheAccStr->get(acc));
               if (*::getConfigStr(NULL,WFK_ACCESS,"",
                                   numBuf,sizeof(numBuf),resp()) == '\0') {
                    m_accflg=FAF_DEFAULT;
               }
               else {
                    m_accflg=::atoi(numBuf);
               }
               CHAR tmpTopic[TPCSIZ];
               ::getConfigStr(NULL,WFK_TOPIC,"",tmpTopic,TPCSIZ,resp());
               size_t len=::encodedSize(tmpTopic);
               m_topic=new CHAR[len];
               ::encodeEntities(m_topic,tmpTopic,len);
               CHAR *tmpbuf=new CHAR[MAXFDESC];
               ::getConfigStr(WFS_DESC,NULL,"",tmpbuf,MAXFDESC,resp());
               ::strrpl(::unpad(::strmove(tmpbuf,::skptwht(tmpbuf))),
                        '\n','\r');
               len=::encodedSize(tmpbuf);
               m_desc=new CHAR[len];
               ::encodeEntities(m_desc,tmpbuf,len);
               delete[] tmpbuf;
               m_dnf=new dnfHandler(remGetModMap,bout);
               m_state=SENDDNF;
          }
          break;
     case SENDDNF:
          ::txvCurFor.set(ses->urlargv(2));
          ::txvHubTopic.set(m_topic);
          ::txvGlobalAcc.set(m_accstr);
          ::chkUseGlobAcc.setChecked(m_accflg&FAF_DEFAULT);
          ::chkUseSpecAcc.setChecked(!(m_accflg&FAF_DEFAULT));
          ::chkCfgAccView.setChecked(m_accflg&FAF_VIEW);
          ::chkCfgAccRead.setChecked(m_accflg&FAF_READ);
          ::chkCfgAccWrite.setChecked(m_accflg&FAF_WRITE);
          ::chkCfgAccManage.setChecked(m_accflg&FAF_MANAGE);
          switch (m_dnf->process()) {
          case FORDESC:
               m_dptr=m_desc;
               m_state=OUTDESC;
               break;
          case DNFEND:
               rc=false;
               break;
          }
          ::txvCurFor.clr();
          ::txvHubTopic.clr();
          ::txvGlobalAcc.clr();
          ::chkUseGlobAcc.clr();
          ::chkUseSpecAcc.clr();
          ::chkCfgAccView.clr();
          ::chkCfgAccRead.clr();
          ::chkCfgAccWrite.clr();
          ::chkCfgAccManage.clr();
          break;
     case OUTDESC:
          if (NULSTR(m_dptr)) {
               m_state=SENDDNF;
          }
          else {
               if (*m_dptr == '\r') {
                    bout << crlf;
                    ++m_dptr;
               }
               CHAR *cp=::strchr(m_dptr,'\r');
               size_t len=cp == NULL ? ::strlen(m_dptr) : (size_t)(cp-m_dptr);
               bout.flush();
               m_dptr+=ses->sndrsp(m_dptr,len);
          }
          break;
     }
     return(rc);
}

/* Hub modify forum functions */

bool
synRSetConfig::prepReq()
{
     const CHAR *buf=getCfg(ses->urlargv(2));
     send(buf,mgrModifyFor);
     delete[] buf;
     return(false);
}

/* Hub get non-default access list functions */

synRGetAccess::~synRGetAccess()
{
     if (m_accstr != NULL) {
          ::free(m_accstr);
          m_accstr=NULL;
     }
     if (m_resp != NULL) {
          ::free(m_resp);
          m_resp=NULL;
     }
}

bool
synRGetAccess::prepReq()
{
     send(ses->urlargv(2),mgrGetNonDft);
     return(false);
}

bool
synRGetAccess::hdlResp()
{
     bool rc=true;

     if (m_starting) {
          m_resp=::alcdup(resp());
          INT acc=0;
          if ((m_rptr=::nextLine(m_resp,"\t")) != NULL) {
               acc=::atoi(m_rptr);
          }
          m_accstr=::alcdup(TheAccStr->get(acc));
          m_rptr=::nextLine(m_resp,"\n");
          m_dnf=new dnfHandler(remGetAccMap,bout);
          m_starting=false;
     }
     else {
          ::txvCurFor.set(ses->urlargv(2));
          ::txvViewAcc.set(m_accstr);
          switch (m_dnf->process()) {
          case DNFROWBEGIN:
               {
                    INT dummy;
                    if ((m_rptr=parseAccLine(m_rptr,m_curSysName,&dummy))
                     == NULL) {
                         m_dnf->tableDone();
                    }
               }
               break;
          case ACCLINK:
          case ACCNAME:
               bout << m_curSysName;
               break;
          case DNFEND:
               rc=false;
               break;
          }
          ::txvCurFor.clr();
          ::txvViewAcc.clr();
     }
     return(rc);
}

/* Hub get add-system access form */

ACTHCODE
synRGetAddAcc::proceed()
{
     ACTHCODE rc=ACTHMORE;

     if (firsttime()) {
          if (!userOK(rc)) {
               return(rc);
          }
          m_dnf=new dnfHandler(remGetAddAccMap,bout);
     }
     else {
          ::txvCurFor.set(ses->urlargv(2));
          switch (m_dnf->process()) {
          case DNFEND:
               rc=ACTHDONE;
               break;
          }
          ::txvCurFor.clr();
     }
     return(rc);
}

/* Hub add system access functions */

bool
synRSetAddAcc::prepReq()
{
     CHAR buf[(MAXFNAM-1)+CSTRLEN("\n")
             +(WBOASIZ-1)+CSTRLEN("\t")+sizeof("-32768")];
     stlcpy(buf,ses->urlargv(2),sizeof(buf));
     stlcat(buf,"\n",sizeof(buf));
     size_t len=strlen(buf);
     ses->param(FP_SYSNAME,buf+len,sizeof(buf)-len);
     stlcat(buf,"\t",sizeof(buf));
     INT acc=::getAccFlags(ses);
     stlcat(buf,::l2as((LONG)acc),sizeof(buf));
     send(buf,mgrSetAccess);
     return(false);
}

/* Hub get set system access form */

bool
synRGetSysAcc::prepReq()
{
     CHAR buf[(MAXFNAM-1)+CSTRLEN("\n")+WBOASIZ];
     stlcpy(buf,ses->urlargv(2),sizeof(buf));
     stlcat(buf,"\n",sizeof(buf));
     stlcat(buf,ses->urlargv(3),sizeof(buf));
     send(buf,mgrGetAccess);
     return(false);
}

bool
synRGetSysAcc::hdlResp()
{
     bool rc=true;

     if (m_starting) {
          m_starting=false;
          CHAR *cp=::nextLine(resp(),"\n");
          if (cp != NULL) {
               cp=::nextLine(cp,"\t");
               if (cp != NULL) {
                    m_accflg=::atoi(cp);
               }
          }
          m_dnf=new dnfHandler(remGetSysAccMap,bout);
     }
     else {
          ::txvCurFor.set(ses->urlargv(2));
          ::txvCurSys.set(ses->urlargv(3));
          ::chkSysAccView.setChecked(m_accflg&FAF_VIEW);
          ::chkSysAccRead.setChecked(m_accflg&FAF_READ);
          ::chkSysAccWrite.setChecked(m_accflg&FAF_WRITE);
          ::chkSysAccManage.setChecked(m_accflg&FAF_MANAGE);
          switch (m_dnf->process()) {
          case DNFEND:
               rc=false;
               break;
          }
          ::txvCurFor.clr();
          ::txvCurSys.clr();
          ::chkSysAccView.clr();
          ::chkSysAccRead.clr();
          ::chkSysAccWrite.clr();
          ::chkSysAccManage.clr();
     }
     return(rc);
}

/* Hub set system access functions */

bool
synRSetSysAcc::prepReq()
{
     CHAR buf[(MAXFNAM-1)+CSTRLEN("\n")
             +(WBOASIZ-1)+CSTRLEN("\t")+sizeof("-32768")];
     stlcpy(buf,ses->urlargv(2),sizeof(buf));
     stlcat(buf,"\n",sizeof(buf));
     stlcat(buf,ses->urlargv(3),sizeof(buf));
     stlcat(buf,"\t",sizeof(buf));
     INT acc=(ses->param("Default") ? FAF_DEFAULT : ::getAccFlags(ses));
     stlcat(buf,::l2as((LONG)acc),sizeof(buf));
     send(buf,mgrSetAccess);
     return(false);
}

/* Hub confirm forum deletion functions */

ACTHCODE
synRDelConf::proceed()
{
     ACTHCODE rc=ACTHMORE;

     if (firsttime()) {
          if (!userOK(rc)) {
               return(rc);
          }
          m_dnf=new dnfHandler(remDelConfMap,bout);
     }
     else {
          ::txvCurFor.set(ses->urlargv(2));
          switch (m_dnf->process()) {
          case DNFEND:
               rc=ACTHDONE;
               break;
          }
          ::txvCurFor.clr();
     }
     return(rc);
}

/* Hub delete forum functions */

bool
synRDelete::prepReq()
{
     send(ses->urlargv(2),mgrDeleteFor);
     return(false);
}

/* Hub request handler functions */

synHubReq::~synHubReq()
{
     if (TheHub.pSyn == this) {
          TheHub.pSyn=NULL;
          if (!TheHub.gotResp) {
               ::mgrCancel(TheHub.reqID);
          }
     }
}

ACTHCODE
synHubReq::proceed()
{
     ACTHCODE rc=ACTHMORE;

     if (firsttime() && !userOK(rc)) {
          return(rc);
     }
     switch (m_state) {
     case PREPREQ:
          if (!prepReq() && m_state == PREPREQ) {
               rc=ACTHDONE;
          }
          break;
     case WAITHUB:
          if (gotError()) {
               m_dnf=new dnfHandler(remErrMap,bout);
               m_state=SENDERR;
          }
          else if (gotResp()) {
               m_state=HDLRESP;
          }
          break;
     case HDLRESP:
          if (startProc()) {
               do {
                    if (!hdlResp() && m_state == HDLRESP) {
                         rc=ACTHDONE;
                    }
               } while (m_state == HDLRESP && contProc(rc));
          }
          break;
     case SENDERR:
          if (startProc()) {
               do {
                    switch (m_dnf->process()) {
                    case ERRTEXT:
                         bout << errText();
                         break;
                    case DNFEND:
                         rc=ACTHDONE;
                         break;
                    }
               } while (contProc(rc));
          }
          break;
     }
     return(rc);
}

VOID
synHubReq::setError(               // report error request
INT err)                           //   error code
{
     ASSERT(err != ERR_NONE);
     TheHub.gotResp=true;
     TheHub.error=err;
     m_state=WAITHUB;
}

VOID
synHubReq::send(                   // send a request to the hub
const CHAR *value,                 //   what to send
hubReqFunc func)                   //   function to use
{
     if (::worConnected()) {
          if (TheHub.pSyn == NULL
           && (*func)(value,newID(),::hubResp,::hubAbort)) {
               TheHub.gotResp=false;
               TheHub.error=ERR_NONE;
               m_state=WAITHUB;
          }
          else {
               setError(ERR_BUSY);
          }
     }
     else {
          setError(ERR_NOCONN);
     }
     TheHub.pSyn=this;
}

INT
synHubReq::newID()                 // generate a new request ID
{
     if (++TheHub.reqID == NOREQ) {
          ++TheHub.reqID;
     }
     return(TheHub.reqID);
}

const CHAR *                       //   error message to output
synHubReq::errText()               // form error message
{
     ::setmbk(::wlmb);
     ::clrprf();
     switch (error()) {
     case ERR_BUSY:
          ::prfmsg(MEBUSY);
          break;
     case ERR_NOCONN:
          ::prfmsg(MENOC);
          break;
     case ERR_DISC:
          ::prfmsg(MEDISC);
          break;
     case ERR_RESET:
          ::prfmsg(MERST);
          break;
     case ERR_CANCEL:
          ::prfmsg(MECAN);
          break;
     case ERR_SEQ:
          ::prfmsg(MESEQ);
          break;
     case ERR_SPACE:
          ::prfmsg(MESPC);
          break;
     case ERR_NOTFND:
          ::prfmsg(MENFND);
          break;
     case ERR_ACCESS:
          ::prfmsg(MEACC);
          break;
     case ERR_MAXCRT:
          ::prfmsg(MEMAX);
          break;
     case ERR_EXISTS:
          ::prfmsg(MEXST);
          break;
     case ERR_BADNAME:
          ::prfmsg(MEFNAM);
          break;
     case ERR_NOSYS:
          ::prfmsg(AENSYS);
          break;
     case GMEERR:
     case GMEDUP:
     case GME2MFR:
     case GMENFID:
     case GMEMEM:
     case GME2MFL:
          ::prfmsg(MENOMO);
          break;
     default:
          ::prfmsg(MEUNK,error());
          break;
     }
     ::rstmbk();
     ::stpans(::prfbuf);
     return(::prfbuf);
}

VOID
hubResp(                           // got response to mgr op function
INT reqid,                         //   request ID getting response
const CHAR *value)                 //   info returned by hub
{
     if (TheHub.pSyn != NULL && TheHub.reqID == reqid) {
          stlcpy(TheHub.buf,value,WBUFSZ);
          TheHub.gotResp=true;
     }
}

VOID
hubAbort(                          // management op aborted function
INT reqid,                         //   request ID getting response
INT reason)                        //   reason for abort
{
     if (TheHub.pSyn != NULL && TheHub.reqID == reqid) {
          TheHub.error=reason;
          TheHub.gotResp=true;
     }
}

synHubReqResp ::~synHubReqResp()
{
     if (m_respdnf != NULL) {
          delete m_respdnf;
          m_respdnf=NULL;
     }
}

const CHAR *                       //   returns buffer (caller must free)
synHubManage::getCfg(              // get config info from form parameters
const CHAR *forName)               //   name of forum to use
{
     CHAR tmpBuf[TPCSIZ];
     CHAR *buf=new CHAR[WBUFSZ];
     *buf='\0';
     ::setConfigStr(NULL,WFK_NAME,forName,buf,WBUFSZ);
     ses->param(WFK_TOPIC,tmpBuf,TPCSIZ);
     ::setConfigStr(NULL,WFK_TOPIC,tmpBuf,buf,WBUFSZ);
     INT acc;
     ses->param(FP_SPECACC,tmpBuf,sizeof(tmpBuf));
     if (::atoi(tmpBuf)) {
          acc=::getAccFlags(ses);
     }
     else {
          acc=FAF_DEFAULT;
     }
     ::setConfigStr(NULL,WFK_ACCESS,::l2as((LONG)acc),buf,WBUFSZ);
     CHAR *desc=new CHAR[MAXFDESC];
     ses->param(WFS_DESC,desc,MAXFDESC);
     ::unpad(::strmove(desc,::skptwht(desc)));
     CHAR *cp=desc;
     while ((cp=::strchr(cp,'\n')) != NULL) {
          ::strmove(cp,cp+1);
     }
     ::setConfigStr(WFS_DESC,NULL,desc,buf,WBUFSZ);
     delete[] desc;
     return(buf);
}

bool
synHubManage::hdlResp()
{
     bool rc=true;

     if (m_startresp) {
          m_startresp=false;
          m_respdnf=new dnfHandler(*m_respmap,bout);
     }
     else {
          if (ses->urlargc() > 2) {
               ::txvCurFor.set(ses->urlargv(2));
          }
          switch (m_respdnf->process()) {
          case DNFEND:
               rc=false;
               break;
          }
          ::txvCurFor.clr();
     }
     return(rc);
}

bool
synHubSetAcc::hdlResp()
{
     bool rc=true;

     if (m_startresp) {
          m_startresp=false;
          const CHAR *cp=::nextLine(resp(),"\n");
          if (cp != NULL) {
               cp=::nextLine(cp,"\t");
               if (cp != NULL) {
                    INT err=::atoi(cp);
                    if (err != ERR_NONE) {
                         setError(err);
                         return(false);
                    }
               }
          }
          m_respdnf=new dnfHandler(*m_respmap,bout);
     }
     else {
          ::txvCurFor.set(ses->urlargv(2));
          if (ses->urlargc() > 3) {
               ::txvCurSys.set(ses->urlargv(3));
          }
          switch (m_respdnf->process()) {
          case DNFEND:
               rc=false;
               break;
          }
          ::txvCurFor.clr();
          ::txvCurSys.clr();
     }
     return(rc);
}

/* Text variable handler functions */

const CHAR *                       //   returns text var value
wlmTimeStampTxv::resolve(          // resolve a text var name into its value
const CHAR *nam)                   //   name of text var being resolved
{
     static CHAR buf[sizeof("19800101120102")];

     (VOID)nam;
     USHORT d=today();
     USHORT t=now();
     sprintf(buf,"%04.4d%02.2d%02.2d%02.2d%02.2d%02.2d",
             ddyear(d),ddmon(d),ddday(d),dthour(t),dtmin(t),dtsec(t));
     return(buf);
}

const CHAR *                       //   returns text var value
wlmTextVar::resolve(               // resolve a text var name into its value
const CHAR *nam)                   //   name of text var being resolved
{
     (VOID)nam;
     return(m_pVal);
}

VOID
wlmCheckTxv::setChecked(           // set checked status
bool isChecked)                    //   is the option checked?
{
     if (isChecked) {
          set("CHECKED");
     }
     else {
          clr();
     }
}

/* Forum array management functions */

forArrayMgr::forArrayMgr() :       // constructor
     m_smb(NULL),
     m_newsmb(NULL),
     m_fp(NULL),
     m_changed(true),
     m_rebuilding(false),
     m_updateReady(false),
     m_fidx(0),
     m_refs(0)
{
     m_keys[0].Offset=offsetof(forArrayElem,m_forName);
     m_keys[0].Compare=::smbCompareString;
     m_keys[1].Offset=0;
     m_keys[1].Compare=NULL;
}

forArrayMgr::~forArrayMgr()        // destructor
{
     if (m_smb != NULL) {
          delete m_smb;
          m_smb=NULL;
     }
     if (m_newsmb != NULL) {
          delete m_newsmb;
          m_newsmb=NULL;
     }
     if (m_fp != NULL) {
          ::fclose(m_fp);
          m_fp=NULL;
     }
}

bool
forArrayMgr::ready()               // check/reform array
{
     if (m_changed) {
          m_rebuilding=true;
     }
     if (m_rebuilding) {
          if (m_newsmb == NULL) {
               ASSERT(!m_updateReady);
               m_changed=false;
               m_newsmb=new CSmb(sizeof(forArrayElem),m_keys,0);
               m_fp=::fopen(wlForumList,FOPRA);
               m_fidx=0;
          }
          else if (m_updateReady) {
               return(doUpdate(true));
          }
          else if (m_fp != NULL) {
               prcFile();
          }
          else {
               return(prcEchoes());
          }
          return(false);
     }
     ++m_refs;
     return(true);
}

VOID
forArrayMgr::prcFile()             // process forum list file while rebuilding
{
     CHAR forName[MAXFNAM+CSTRLEN(" 15\n")];
     for (INT nadd=0 ; nadd < faddpc ; ++nadd) {
          if (::fgets(forName,sizeof(forName),m_fp) == NULL) {
               ::fclose(m_fp);
               m_fp=NULL;
               break;
          }
          else {
               CHAR *cp=::strchr(forName,' ');
               if (cp != NULL) {
                    *cp++='\0';
               }
               ::unpad(forName);
               addElem(::skptwht(forName),EMLID,::atoi(cp));
          }
     }
}

bool
forArrayMgr::prcEchoes()           // process forum echoes while rebuilding
{
     INT nchk,nadd,nfor=numforums();
     for (nchk=nadd=0 ; nchk < fchkpc && nadd < faddpc ; ++nchk) {
          if (m_fidx < nfor) {
               const struct fordef *fdef=::fiddefp(m_fidx++);
               const adr_t *echo=(const adr_t *)fdef->echoes;
               for (INT i=0 ; i < fdef->necho ; ++i) {
                    if (::sameto(wfpfx,echo[i])) {
                         const CHAR *cp=::skppfx(echo[i]);
                         forArrayElem *pel
                              =(forArrayElem *)m_newsmb->GetEqual(cp,0);
                         if (pel != NULL && pel->gmeID() == EMLID) {
                              pel->setID(fdef->forum);
                         }
                         else {
                              addElem(cp,fdef->forum,0);
                              ++nadd;
                         }
                    }
               }
          }
          else {
               m_updateReady=true;
               return(doUpdate(true));
          }
     }
     return(false);
}

VOID
forArrayMgr::addElem(              // add an element to new array
const CHAR *wlForName,             //   WL forum name
USHORT gmeForumID,                 //   ID of associated GME forum
INT access)                        //   WL access flags
{
     forArrayElem elem(wlForName,gmeForumID,access);
     m_newsmb->Insert(&elem);
}

bool                               //   returns true if did update
forArrayMgr::doUpdate(             // swap in the newly-formed array
bool incref)                       //   increment ref count if successful?
{
     if (m_refs == 0 && m_updateReady) {
          ASSERT(m_newsmb != NULL);
          ASSERT(m_rebuilding);
          if (m_smb != NULL) {
               delete m_smb;
          }
          m_smb=m_newsmb;
          m_newsmb=NULL;
          m_updateReady=false;
          m_rebuilding=false;
          if (incref) {
               ++m_refs;
          }
          return(true);
     }
     return(false);
}

forArrayElem *                     //   returns pointer to first element
forArrayUser::getFirst()           // get first element in array
{
     ASSERT(m_gotready);
     forArrayElem *p=TheArray.getFirst();
     if (p != NULL) {
          m_pos=TheArray.getPos();
     }
     return(p);
}

forArrayElem *                     //   returns pointer to first element
forArrayUser::getNext()            // get next element in array
{
     ASSERT(m_gotready);
     if (m_pos == SMBNULL) {
          return(NULL);
     }
     TheArray.setPos(m_pos);
     forArrayElem *p=TheArray.getNext();
     if (p != NULL) {
          m_pos=TheArray.getPos();
     }
     return(p);
}

forArrayElem *                     //   returns pointer to first element
forArrayUser::getEqual(            // find a specific element in array
const CHAR *forName)               //   with this forum name
{
     ASSERT(m_gotready);
     forArrayElem *p=TheArray.getEqual(forName);
     if (p != NULL) {
          m_pos=TheArray.getPos();
     }
     return(p);
}

forArrayElem *                     //   returns pointer to element
forArrayUser::getGreater(          // find element greater than
const CHAR *forName)               //   this forum name
{
     ASSERT(m_gotready);
     forArrayElem *p=TheArray.getGreater(forName);
     if (p != NULL) {
          m_pos=TheArray.getPos();
     }
     return(p);
}

forArrayElem *                     //   returns pointer to first element
forArrayUser::getCurrent()         // get the current element in the array
{
     ASSERT(m_gotready);
     if (m_pos == SMBNULL) {
          return(NULL);
     }
     TheArray.setPos(m_pos);
     return(TheArray.getCurrent());
}

/* Miscellaneous object functions */

forListPix::forListPix()
{
     ASSERT(wlmb != NULL);
     ::setmbk(::wlmb);
     m_empty=::stgopt(LSTMT);
     m_config=::stgopt(LSTCFG);
     m_used=::stgopt(LSTUSED);
     m_invalid=::stgopt(LSTINV);
     ::rstmbk();
}

forListPix::~forListPix()
{
     ::free(m_empty);
     ::free(m_config);
     ::free(m_used);
     ::free(m_invalid);
}

const CHAR *
forListPix::configPic(             // get picture for configured status
const CHAR *forName)               //   of this WL forum
{
     if (::isCfg(forName)) {
          return(m_config);
     }
     return(m_empty);
}

const CHAR *
forListPix::usePic(                // get picture for in-use status
const forArrayElem *pel)           //   forum array element describing forum
{
     if (pel->gmeID() != EMLID) {
          if (pel->acc() == 0) {
               return(m_invalid);
          }
          return(m_used);
     }
     return(m_empty);
}

accString::accString()             // access string class constructor
{
     ASSERT(::wlmb != NULL);
     ::setmbk(::wlmb);
     m_vstr=::stgopt(ACCVIEW);
     m_rstr=::stgopt(ACCREAD);
     m_wstr=::stgopt(ACCWRT);
     m_mstr=::stgopt(ACCMGR);
     m_ustr=::stgopt(ACCUNK);
     ::rstmbk();
     size_t len=::strlen(m_vstr)+CSTRLEN(", ")+::strlen(m_rstr)+CSTRLEN(", ")
               +::strlen(m_wstr)+CSTRLEN(", ")+::strlen(m_mstr)+1;
     len=max(len,strlen(m_ustr)+1);
     m_retbuf=(CHAR *)::alcmem(len);
}

accString::~accString()            // access string class destructor
{
     ::free(m_vstr);
     ::free(m_rstr);
     ::free(m_wstr);
     ::free(m_mstr);
     ::free(m_ustr);
     ::free(m_retbuf);
}

const CHAR *                       //   returns pointer to static buffer
accString::get(                    // get string describing access
INT acc)                           //   access to describe
{
     *m_retbuf='\0';
     if (acc&FAF_VIEW) {
          cat(m_vstr);
     }
     if (acc&FAF_READ) {
          cat(m_rstr);
     }
     if (acc&FAF_WRITE) {
          cat(m_wstr);
     }
     if (acc&FAF_MANAGE) {
          cat(m_mstr);
     }
     if (*m_retbuf == '\0') {
          ::strcpy(m_retbuf,m_ustr);
     }
     return(m_retbuf);
}

VOID
accString::cat(                    // concatenate onto return buffer
const CHAR *s)                     //   string to concatenate
{
     if (*m_retbuf != '\0') {
          ::strcat(m_retbuf,", ");
     }
     ::strcat(m_retbuf,s);
}

/* Miscellaneous global functions */

size_t
encodedSize(                       // size of entity-encoded string (incl '\0')
const CHAR *src)                   //   unencoded string
{
     CHAR c;
     size_t len=0;
     while ((c=*src++) != '\0') {
          switch (c) {
          case '<':
               len+=CSTRLEN("&lt;");
               break;
          case '>':
               len+=CSTRLEN("&gt;");
               break;
          case '"':
               len+=CSTRLEN("&quot;");
               break;
          case '&':
               len+=CSTRLEN("&amp;");
               break;
          default:
               ++len;
               break;
          }
     }
     return(++len);
}

CHAR *                             //   returns pointer to destination
encodeEntities(                    // encode string using HTML entities
CHAR *buf,                         //   buffer to receive encoded string
const CHAR *src,                   //   unencoded string
size_t bufSiz)                     //   size of destination buffer
{
     CHAR c,*pOut=buf;
     while ((c=*src++) != '\0') {
          const CHAR *pAdd=NULL;
          switch (c) {
          case '<':
               pAdd="&lt;";
               break;
          case '>':
               pAdd="&gt;";
               break;
          case '"':
               pAdd="&quot;";
               break;
          case '&':
               pAdd="&amp;";
               break;
          }
          size_t addLen=pAdd == NULL ? 1 : strlen(pAdd);
          if ((size_t)(pOut-buf)+addLen >= bufSiz) {
               break;
          }
          if (pAdd != NULL) {
               pOut=stpcpy(pOut,pAdd);
          }
          else {
               *pOut++=c;
          }
     }
     *pOut='\0';
     return(buf);
}

INT
getAccFlags(                       // extract access flags from a form response
acthSession *ses)                  //   session handling form response
{
     INT acc=0;
     CHAR tmpBuf[sizeof("-1234567890")];
     ses->param(FP_ACCVIEW,tmpBuf,sizeof(tmpBuf));
     if (::atoi(tmpBuf)) {
          acc|=FAF_VIEW;
     }
     ses->param(FP_ACCREAD,tmpBuf,sizeof(tmpBuf));
     if (::atoi(tmpBuf)) {
          acc|=FAF_READ;
     }
     ses->param(FP_ACCWRT,tmpBuf,sizeof(tmpBuf));
     if (::atoi(tmpBuf)) {
          acc|=FAF_WRITE;
     }
     ses->param(FP_ACCMGR,tmpBuf,sizeof(tmpBuf));
     if (::atoi(tmpBuf)) {
          acc|=FAF_MANAGE;
     }
     return(acc);
}
