/***************************************************************************
 *                                                                         *
 *   MAJORBBS.C                                                            *
 *                                                                         *
 *   Copyright (c) 1987-1997 Galacticomm, Inc.    All Rights Reserved.     *
 *                                                                         *
 *   This is the Worldgroup mainline.                                      *
 *                                                                         *
 *                                        - T. Stryker 6/24/86             *
 *                    v6.0 enhancements   - C. Robert & S. Brinker 10/1/91 *
 *                                                                         *
 ***************************************************************************/

#ifdef GCWINNT
#include <windows.h>
#endif
#include "gcomm.h"
#include "majorbbs.h"
#include "wgsmajor.h"
#include "ipx.h"
#ifndef GCWINNT
#include "saputl.h"
#endif // GCWINNT
#include "gcspsrv.h"
#include "filexfer.h"
#include "fsdbbs.h"
#include "remote.h"
#include "protstuf.h"
#include "phasedbg.h"
#include "excphand.h"
#ifdef GCWINNT
#include "majorsvc.h"
#include "majorwin.h"
#endif // GCWINNT

#define FILREV "$Revision: 117 $"

INT lastusrnu=-1;             /* usrnum of last exception                  */

CHAR version[VERSIZ];         /* main version code, in form X.YY           */
CHAR subvers[SBVSIZ];         /* sub-version info, ext ed rls letters      */

struct module module00={      /* module interface block                    */
     "Menuing System",        /*    description for main menu              */
     mainlon,                 /*    user logon supplemental routine        */
     mainu,                   /*    input routine if selected              */
     musthn,                  /*    status-input routine if selected       */
     NULL,                    /*    "injoth" routine for this module       */
     NULL,                    /*    user logoff supplemental routine       */
     loscar,                  /*    hangup (lost carrier) routine          */
     midnit,                  /*    midnight cleanup routine               */
     mjrdla,                  /*    delete-account routine                 */
     mjrfin                   /*    finish-up (sys shutdown) routine       */
};

UINT syslod=1;                /* system loading (1 percent units)          */

struct module **module;       /* in-memory array of online modules         */
INT nmods=0;                  /* current number of online modules in memory*/

#define   HOOKINC   25        /* increment size for hook vector tables     */

INT conhdl,                   /* handle to array of connect functions      */
    dschdl,                   /* handle to array of disconnect functions   */
    mcuhdl,                   /* handle to array of auto-cleanup functions */
    hCreateAcctVecs,          /* handle to array of create-acct functions  */
    dlahdl,                   /* handle to array of delete-acct functions  */
    hStartupVecs,             /* handle to array of startup functions      */
    finhdl,                   /* handle to array of shutdown functions     */
    ffinhdl,                  /* handle to array of final shutdown funcs   */
    s2aahdl,                  /* handle to array of C/S->A/A functions     */
    s2cshdl,                  /* handle to array of A/A->C/S functions     */
    annohdl;                  /* handle to array of announcement functions */
static
INT nmehdl;                   /* hdl to arr of lon/lof handler dependencies*/

CHAR *annomem;                /* per-request logon announcement memory     */

struct mdstats *mdstats;      /* in-memory array of module statistics      */

struct textvar *txtvars;      /* in-memory array of text variables         */
INT ntvars=0;                 /* current number of text variables in memory*/

INT numxrf;                   /* size of possible user-id listing for usrs */
LONG *xrfpos;                 /* uid xref positions/user in WGSXRF2.DAT    */
DFAFILE *xrfbb;               /* Btrieve file pointer for WGSXRF2.DAT      */
struct uidxrf uidxrf;         /* general user-id cross reference structure */

DFAFILE *genbb;               /* generic user data file btrieve file ptr   */

#define xrfidx(n)   (usrnum*numxrf+n)

INT nterms=1,               /* this many simultaneous users are config'd */
    hichp1=1;                 /* highest channel number in use, plus 1     */

INT usrnum,                   /* global user-number (channel) in effect    */
    othusn,                   /* general purpose other-user channel number */
    uisusn;                   /* uinsys() other-user channel number        */
INT nchans;                   /* number of GSBL channels defined, nterms+1?*/

#ifndef GCWINNT
INT sapsup=0;                 /* Server Advertising Protocol supported?    */
#endif // GCWINNT

static VOID *user;            /* user ptr for use with ptrblok()           */
struct user *usrptr,          /* global pointer to user data in effect     */
            *othusp;          /* gen purp other-user user structure ptr    */

DFAFILE *mstbb;               /* module statistics btrieve file pointer    */

CHAR input[INPSIZ],           /* raw user input data buffer                */
     *margv[INPSIZ/2],        /* array of ptrs to word starts, a la argv[] */
     *margn[INPSIZ/2];        /* array of ptrs to word ends, for rstrin()  */

INT nglobs,                   /* # of global command handlers in operation */
    (*globs[GLBMAX])(VOID);   /* array of ptrs to glbl cmd hdlr functions  */

                              /* INTERCEPT VECTORS                         */
                              /* Use as you would interrupt vectors:  save */
                              /* what's here, replace with your handler    */
                              /* routine, then when that routine executes, */
                              /* call the vector that was saved.           */

VOID (*hdlcon)(VOID);         /* handle connection with user               */
VOID (*hdlc25)(VOID);         /* handle incoming X.25 call string (margv)  */
VOID (*hdlnrg)(VOID);         /* handle non-RING strings during AWAITC     */
VOID (*hdlrng)(VOID);         /* handle non-X25 RING string (AWAITC,HARING)*/
INT (*hdlcnc)(VOID);          /* handle non-RING whatever during HARING    */
VOID (*hdlrst)(VOID);         /* handle channel reset                      */
VOID (*loncdi)(VOID);         /* handle logon audit trail msg on GCDI chan */
VOID (*hdlrlg)(VOID);         /* handle relogging on a new user            */
VOID (*hdlzap)(VOID);         /* handle zapping an idle channel            */
INT (*hdlsmp)(VOID);          /* handle sample vs. paying channel determin.*/
GBOOL (*alwsup)(VOID);        /* allow a new user to be added? seat-count  */
GBOOL (*chkauid)(CHAR *uid);  /* allow proposed User-ID?                   */
INT (*hdlbump)(GBOOL logon);  /* handle kicking off user who just logged on*/

GBOOL (*iserrf)(INT chan);    /* is this channel "error free"?             */

VOID dftusrson(VOID);
VOID  (*hdlusrson)(VOID)=dftusrson;

#define RTIMAX  10            /* max number of real-time interrupt routines*/
INT nrtirs;                   /* # of real-time irpt routines in operation */
VOID (*rtirs[RTIMAX])(VOID);  /* array of ptrs to real-time irpt routines  */

INT margc;                    /* number of words in margv[], a la argc     */
INT inplen,                   /* overall raw input string length           */
    status,                   /* raw status from btusts, where appropriate */
    pfnlvl,                   /* profanity level of current input (0-3)    */
    shortm;                   /* display short (0) or long (1) menus on x? */

GBOOL valexi=FALSE;           /* is eximod currently valid?                */
jmp_buf eximod;               /* exit-module longjmp save block            */

HMCVFILE mjrmb;               /* executive named-message file block hdl    */
INT mbdone;                   /* main-loop exit flag                       */
INT rsmode,                   /* reset-mode code for channels              */
    *rsmodes;                 /* reset-mode array by channel number        */
INT kilipg=0;                 /* kill-system command in progress           */
INT kilctr;                   /* number of minutes to shutdown             */
INT kilsrc;                   /* kill-command source (-1=console, -2=MCU,  */
                              /* -3=timed event, >=0=chan #)               */
INT emubel;                   /* emulation bell period                     */
INT maxscns;                  /* maximum number of screens                 */
INT initdn;                   /* tells mjrfin all done initializing        */
INT mcuctr=-1;                /* auto-cleanup minute counter               */
INT lasthr;                   /* last hour reading sensed by ckmcu()       */
INT gsblup=0;                 /* one btuitz() and zero btuend() calls?     */

INT multsk;                   /* is a multitasker being used?              */
INT mulmth;                   /* if multitasking what method is used       */

INT errcod;                   /* MS-DOS exit codes (for batch files)       */
                              /*    errorlevel  0 - auto-cleanup (mcukil)  */
                              /*    errorlevel  1 - operator "kill-system" */
                              /*    errorlevel  8 - remote Sysop kill sys  */
                              /*    errorlevel 11 - mail shutdown #1       */
                              /*    errorlevel 12 - mail shutdown #2       */
                              /*    errorlevel 13 - mail shutdown #3       */
                              /*    errorlevel 14 - mail shutdown #4       */
                              /*    errorlevel 49 - out of memory          */
                              /*    errorlevel 70 - 1 catastro, clean kill */
                              /*    errorlevel 80 - 2 cata's, need reboot  */
                              /*    errorlevel 90 - cata w/no shutdown vec */
                              /*    errorlevel 99 - nonrecoverable GP      */

INT *shterc;                  /* auto-shutdown errorlevels as a function   */
                              /*    of the hour of the day                 */

INT *channel=NULL;            /* array of channel codes (as displayed)     */
INT *grpnum;                  /* array of group numbers, by channel number */
INT *zaptbl;                  /* array of connect timeout values           */

#define   DFTOFF    0x0400    /* default offset between channels for GSBL  */

UINT maxspd=2400;             /* overall maximum bps of all channels       */
UINT maxpol=2400;             /* overall maximum polling rate for channels */
LONG mxbaudl[NUMGRPS]={       /* max baud rates, by chan group as longs */
     2400L,2400L,2400L,2400L,2400L,2400L,2400L,2400L,2400L,2400L,2400L,2400L,
     2400L,2400L,2400L,2400L,2400L};
CHAR echtyp[NUMGRPS];         /* array of echo options:                    */
#define ECHNON 0              /*    no echo at all of user keystrokes      */
#define ECHPLX 1              /*    echo comes from user's PAD             */
#define ECHBBS 2              /*    echo comes from the svr thru X.25 net  */
CHAR *startv[NUMGRPS];        /* array of pointers to startup AT-commands  */
CHAR *mdmatz[NUMGRPS];        /* array of pointers to modem reset strings  */
CHAR *mdmbsy[NUMGRPS];        /* array of pointers to modem busy-out stgs  */
CHAR *mdmnan[NUMGRPS];        /* array of pointers to modem no-answer stgs */
CHAR chanty[NUMGRPS];         /* array of btusdf() codes for LAN chan grps */
CHAR grtype[NUMGRPS];         /* array of Group Type codes                 */
CHAR *grtsub[NUMGRPS];        /* array of GCDI channel type names          */
INT usofgr[NUMGRPS];          /* 1st usrnum of each channel group (-1=NONE)*/
#ifndef GCWINNT
signed char sapstt[NUMGRPS];  /* S.A.P. status 1=avail -1=busy 0=not used  */
#endif // GCWINNT

                              /* line charges for X.25 lines:              */
LONG ksec[NUMGRPS];           /* credits consumed per minute               */
LONG kpak[NUMGRPS];           /* credits consumed per kilo-packet          */
LONG kchar[NUMGRPS];          /* credits consumed per kilo-character       */

static CHAR *vdahdl=NULL;     /* volatile data area table (vdasiz per user)*/
CHAR *vdarea;                 /* pointer to first vda element (bkwd. comp.)*/
CHAR *vdaptr;                 /* user's pointer to volatile data area      */
CHAR *vdatmp;                 /* general-purpose temporary vda-size area   */
INT vdasiz=0;                 /* max. size requirement of volatile data    */
CHAR *region=NULL;            /* GSBL channel data region                  */

USHORT tcklst;                /* last value of ticker                      */
ULONG timcntr,                /* temporary time (btuhrt) storage           */
      prodtmr;                /* total productive time counter             */
ULONG lngtck;                 /* 4-gig second counter                      */

extern INT stthue;            /* system status display attribute code      */
extern INT lblhue;            /* soft-key labels display attribute code    */
extern INT dsphue;            /* data display box attribute code           */
extern INT manhue;            /* monitor-all normal display attribute code */
extern INT mashue;            /* monitor-all "special" disp attribute code */
extern INT emthue;            /* emulation text display attribute code     */
extern INT emshue;            /* emulation status display attribute code   */

extern INT rtkoff;            /* 1=turn off all RTKICK calls 0=on          */

extern INT atoggl;          /* ANSI-display toggle (defaults to ON)      */

#ifdef WEBCAST
extern ULONG btubcs(VOID);    /* broadcaster limitation value              */
#else // WEBCAST
extern ULONG btuseats(VOID);  /* account limitation value                  */
#endif // WEBCAST

INT ripdfd,                   /* any RIP language defined on this server?  */
    ripidx,                   /* index into languages[] of 1st /RIP lang   */
    nrpidx;                   /* index into languages[] of 1st non-RIP lang*/

#define LOGONPOL (65535L/10)  /* max time in btuhrt ticks for a logon cycle*/

/*--- OPTIONS FROM BBSMAJOR.MSG ---*/

INT outbsz,                   /* output buffer size per channel            */
    sampln,                   /* number of "free sample" channels          */
    exicnc,                   /* concatenation implies module exit?        */
    langop,                   /* Language query at log-on? 1=auto 2=ask    */
    ansiop,                   /* ANSI option: on=N; off=F; ask=K; auto=O   */
    rsetop,                   /* reset option: busy=B; no-answer=N         */
    pfceil,                   /* profanity-detection ceiling               */
    visbel,                   /* bell setting when emulation is visible    */
    invbel,                   /* bell setting when emulation is hidden     */
    sopaud,                   /* route Audit Trail stuff to online Sysop   */
    auxist,                   /* enable secondary crt?                     */
    outata,                   /* output ATA to modems when answering?      */
    vispsw,                   /* make passwords visible in detail displays?*/
    modzap,                   /* the modem zap interval                    */
    idlzap,                   /* the idle user zap interval                */
    zapser,                   /* whether or not to idle-zap serial ports   */
    zapdlan,                  /* idle-zap direct-circuit LAN channels?     */
    zapvlan,                  /* idle-zap virtual-circuit LAN channels?    */
    zapcsu,                   /* idle-zap client/server users?             */
    mcumin,                   /* max grace period minutes on auto-cleanup  */
    mcuwrn,                   /* number of warning messages before hangup  */
    lonaud,                   /* make audit trail entry for each logon?    */
    lofaud,                   /* make audit trail entry for each logoff?   */
    mmuaud,                   /* make audit trail entry for main menu sels?*/
    mmucrr,                   /* Main menu credit consumption rate per min */
    csvcrr,                   /* C/S credit consumption rate per minute    */
    svrate,                   /* System-variable save rate (seconds/save)  */
    mcuhr,                    /* Hour of day for midnight clean-up         */
    uspmod,                   /* Special UART polling mode?                */
    clkupd;                   /* DOS clock synchronization enabled?        */
CHAR *bbsttl,                 /* Title of your system                      */
     *company,                /* Your company name                         */
     *addres1,                /* Company address line 1 (street)           */
     *addres2,                /* Company address line 2 (city,state,zip)   */
     *dataph,                 /* The first phone line connected to server  */
     *liveph,                 /* The first phone line reserved 4 live users*/
     *chghour,                /* Connect time charge per hour              */
     *chgmin,                 /* Minimum charge                            */
     *chgtime,                /* Minimum connect time                      */
     **scnpaus,               /* screen pause prompt text for btuxnf()     */
     *syskey,                 /* key for "sysop" powers: profanity and idle*/
     *glbkey,                 /* key for "sysop" power: /l                 */
     *glbkeyi,                /* key for "sysop" power: /invis             */
     *sampky,                 /* key required to log on reserved channels  */
     *mmuatr,                 /* default attribute for main menu selections*/
     *ansdim;                 /* dimmed attribute for main menu selections */

VOID (*hangups)(VOID)=NULL;   /* always call 1st on hangup intercept vector*/

VOID (*tjoinrou)(USHORT forum)=NULL,    /* teleconference JOINT routine    */
     (*tlcInvisRou)(INT unum)=NULL, // tele invisible chg notify routine
     (*ntfysopr)()=NULL,      /* notify remote sysop routine               */
     (*emlsdrou)(VOID)=NULL;  /* Send Email to Sysop/ New User routine     */

INT (*bgnedt)(INT,CHAR *,INT,CHAR *,SHORT (*)(SHORT),INT)=NULL,
                              /* editor begin-editing routine              */
    (*inedit)(INT usn,SHORT (*exipnt)(SHORT))=NULL;/* user-in editor rou.  */

VOID (*edtimr)(GBOOL (*imradr)())=NULL,/* editor import hooking routine    */
     (*edtpfn)()=NULL;        /* editor set profanity level per user       */

INT nreccl,                   /* number of recent calls to record          */
    sysrec;                   /* include MASTERs in /RECENT list?          */
INT *logons;                  /* array of logon times (nterms long)        */

struct recalls *recents;      /* array of most recent callers              */

CHAR eurmsk=0x7F;             /* 0x7F if U.S.A. only, 0xFF if European     */

#define MAXSPXT 10            /* Max time allowed for SPX termination      */
CHAR *chantn[]={"IPXD","IPXV","SPX"};    /* LAN channel type names         */
UINT answait=2*16;                         /* ANSI/ASCII detection timeout */
CHAR *auansi="    [6n\b\b\b\b\r";        /* ANSI/ASCII detection string  */

            /* define socket table for socket numbers reserved for Gcomm   */
INT soctbl[]={0,0x80BB,0x80BC,0x80BD,0x80BE,0x80BF,0x80C0,0x80C1,0x80C2,
               0x86D0,0x86D1,0x86D2,0x86D3,0x86D4,0x86D5,0x86D6,0x86D7};

INT inpolr=-1;                /* usrnum of user in polrou right now (or -1)*/
VOID (*syscyc)(VOID)=prctask; /* system-cycle vector                       */
VOID (*chncyc)(VOID)=dwopr;   /* channel-cycle vector                      */
INT lstunm=-1;                /* last user-number returned by btuscn()     */
INT newunm;                   /* new user-number just returned by btuscn() */

INT actdet;                   /* activity detected in this channel cycle   */
INT sltrak=0;                 /* source line tracking                      */
CHAR *sftrak="";              /* source file tracking                      */

INT (*chkuid)(CHAR *userid)=dftuid;/* approving User-ID                    */
INT (*chkpsw)(CHAR *psword)=dftpsw;/* approving password                   */
INT (*cschkuid)(const CHAR *userid,const CHAR *password);
                                   /* C/S approve User-ID function         */
GBOOL (*cschkpsw)(const CHAR *password); /* C/S approve password           */
INT (*chkacc)(VOID)=dftacc;        /* check account info before logon      */
GBOOL (*vallon)(VOID)=dftlon;      /* vector for final logon validation    */
GBOOL (*reqpsw)(VOID);             /* password required?                   */
VOID (*greet)(VOID);               /* welcome online                       */
VOID (*uidpmt)(VOID);              /* send userid prompt                   */
VOID (*setpfn)(CHAR *inp)=dftpfn;  /* profanity checker routine            */

VOID (*oldgpr)(CHAR *dstnam);      /* for saving hdlgpr                    */
#ifndef GCWINNT
VOID (*olddblflt)(VOID);           /* for saving (*gpdblflt)()             */
#endif // GCWINNT
static
VOID (*awclan)(VOID)=NULL;         /* Gcomm-specific AWAITC LAN hook       */

static
GBOOL bdelay=TRUE;                 /* internal to byendl() and byenow()    */

GBOOL (*oesrou)(INT unum);         /* vector for BYEBYE disconnection      */

#ifdef STARTER
struct secgen {                    /* wgsgen2.dat load/save structure      */
     CHAR userid[UIDSIZ];          /*   User-ID for load/save              */
     CHAR modnam[MNMSIZ];          /*   Module name (Starter Security)     */
     LONG calls;                   /*   number of calls received so far    */
} secgen;                          /* occurance of structure               */

SHORT mcalls=MAXCALLS;             /* save the maximum calls allowed here  */

static VOID chksec(VOID);
#endif // STARTER

static VOID WINAPI mainloop(INT argc,CHAR *argv[]);
#ifdef DEBUG
#ifdef GCDOS
static VOID memgpr(CHAR *dstnam);
#endif // GCDOS
#endif // DEBUG DEFINE
static VOID init(VOID);
static VOID inimemdbg(VOID);
static VOID finmemdbg(VOID);
static void inipfn(VOID);
#ifdef GCDOS
static VOID gpalrt(VOID);
#endif // GCDOS
static VOID depadb(VOID);
static VOID finbye(VOID);
static INT  ctrlch(VOID);
static VOID incx25(VOID);
static VOID nonrng(VOID);
static VOID incrng(VOID);
static INT  mdmcnc(VOID);
#ifndef GCWINNT
static VOID saphdl(INT group,INT opt);
#endif // GCWINNT
static VOID svacant(VOID);
static VOID gtsens(VOID);
static VOID ansperm(VOID);
static GBOOL ansisns(UINT snccon,CHAR *incbuf,INT nbytes);
static VOID figlang(VOID);
static VOID askansi(VOID);
static VOID dftgreet(VOID);
static VOID dftuidpmt(VOID);
static VOID sonline(VOID);
static VOID hdlogo(VOID);
static VOID passclr(VOID);
static GBOOL dftreqpsw(VOID);
static VOID finpsw(VOID);
static CHAR *dspIPAddr(INT usrnum,CHAR *ipBuf);
static VOID ssupipg(VOID);
static VOID susing(VOID);
static VOID inirel(VOID);
static VOID dftzap(VOID);
static VOID inimzt(VOID);
static VOID inirch(VOID);
static VOID inimod(VOID);
static VOID dopoll(VOID);
static VOID updclk(VOID);
static VOID kalert(INT msgnum,INT nmins);
static VOID depadb(VOID);
static VOID dftrst(VOID);
static CHAR secchi(INT chan,INT c);
static VOID cdistb(VOID);
static VOID hdlcri(VOID);
static GBOOL sgnupk(VOID);
static GBOOL dfterf(INT chan);
static INT  cyclon(INT firststt);
static GBOOL isneeded(INT modnum);
static VOID go2mnu(INT substt);
#ifndef GCWINNT
static VOID mjrdblflt(VOID);
#endif // GCWINNT
static VOID relog(VOID);
static VOID gsbldn(VOID);
static VOID failsf(VOID);
static INT  newxrf(CHAR *uid);
static VOID xrfutl(CHAR *userid,INT add);
static GBOOL prtpsk(INT unum,const CHAR *lock);
static GBOOL grppsk(INT unum,const CHAR *lock);
static GBOOL lngpsk(INT unum,const CHAR *lock);
static GBOOL propsk(INT unum,const CHAR *lock);
static GBOOL gcspsk(INT unum,const CHAR *lock);
static INT bbsrti(VOID);
static GBOOL dftoes(INT unum);

#ifdef GCWINNT
static VOID postGlobalExcpHandler(VOID);
static HANDLE hReportTh=NULL;      // service status report thread handle
static DWORD mainThId;             // main thread id
static GBOOL fRunAsService=FALSE;  // WGS runs as a service
#endif // GCWINNT

#ifdef GCWINNT

INT WINAPI
WinMain(
HINSTANCE hInstance,               // handle of current instance
HINSTANCE hPrevInstance,           // handle of previous instance
LPSTR pCmdLine,                    // pointer to command line
INT nCmdShow)                      // show state of window
{
     CHAR *pTmp;
     CHAR tmpPath[2048];
     SERVICE_TABLE_ENTRY dispatchTable[]={
          { SMAINNAME, (LPSERVICE_MAIN_FUNCTION)mainloop },
          { NULL, NULL }
     };

     (VOID)hPrevInstance;
     (VOID)nCmdShow;
     GetModuleFileName(hInstance,tmpPath,sizeof(tmpPath));
     if ((pTmp=strrchr(tmpPath,'\\')) != NULL
      || (pTmp=strrchr(tmpPath,'/')) != NULL) {
          *pTmp='\0';
          chdir(tmpPath);
     }
     if (isWinNT()) {
          elogSetServName(SMAINNAME);
          elogSetServDispName(SMAINDISPNAME);
          if (pCmdLine[0] != '\0') {
               mainloop(1,&pCmdLine);
          }
          else {
               fRunAsService=TRUE;
               if (!StartServiceCtrlDispatcher(dispatchTable)) {
                    elogAPIError("StartServiceCtrlDispatcher failed.");
               }
          }
     }
     else {
          mainloop(1,&pCmdLine);
     }
     return(0);
}
#else
VOID
main(
INT argc,
CHAR *argv[])
{
     mainloop(argc,argv);
}
#endif // GCWINNT

static VOID WINAPI
mainloop(                          /* Worldgroup main program loop         */
INT argc,
CHAR *argv[])
{
     INT orgsts;
     VOID aud1st(VOID);
     VOID logmods(VOID);
     extern VOID roger(VOID);
     extern DFAFILE* audbb;
#ifdef GCWINNT
     const CHAR *pInsertStg[1];
     MSG  msg;
     WGSREGINFO regInfo;
     HANDLE hin;
#endif // GCWINNT

//TRY
// due to a bug in BC5 we can not use normal try/except for wgserver.exe
// because if it is running as a service "except" block is never executed
// to avoid the problem we set global exception filter.
// if Borland fixes that bug code should be changed back to try/except
     (VOID)argc;
     (VOID)argv;
#ifdef GCWINNT
     mainThId=GetCurrentThreadId();
     SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)excpFilter);
     setPostExcpProc((POSTEXCPPROC)postGlobalExcpHandler);
     if (isRunAsService()) {
          if (!serviceInit(&hReportTh)) {
               errcod=SMAIN_FAILEDTOSTART;
               serviceShutdown(hReportTh);
               return;
          }
     }
     openVideoOutput();
     sendStatusToMainWin("Initializing ...");
     SetProcessShutdownParameters(0x3FF,0);
     PeekMessage(&msg,(HWND)-1,0,0,0); // force message queue to be created
     dfaStop();          // that somehow helps to avoid lockup in dfaOpen()
     gcdelay(1000);      // call when server goes up and down
     hin=GetStdHandle(STD_INPUT_HANDLE);
     FlushConsoleInputBuffer(hin);
#endif // GCWINNT
     if ((errcod=setjmp(disaster)) != 0) {
#ifdef GCWINNT
          if (isRunAsService()) {
               terminateReportTh(hReportTh);
          }
#endif // GCWINNT
          (*(VOIDFUNC *)module00.finrou)();
     }
     wginit();
     sprintf(version,"%d.%02d%s",BBSVER/100,BBSVER%100,BBSIRV);
     pascrit();
     errcod=1;
     init();
     logmods();
     usrnum=-1;
     audFinDisp();
     roger();
#ifdef GCWINNT
     if (isRunAsService()) {
          terminateReportTh(hReportTh);
          reportStatusToSCM(SERVICE_RUNNING,NO_ERROR,0);
          pInsertStg[0]=SMAINDISPNAME;
          elogStatus(ELOG_SERVICE_STARTED,pInsertStg,1);
          fInitComplete=TRUE;
     }
     sendStatusToMainWin("");
     regInfo.version=BBSVER;
     regInfo.licenseCount=btusrs;
     regInfo.regNumber=bturno;
     regInfo.regName=company;
     sendRegInfoToMainWin(&regInfo);
     sendWGSReadyToMainWin();
#endif // GCWINNT
     BEG_PHASE("Startup vectors",0);
     dovecs(hStartupVecs);
     END_PHASE("Startup vectors",0);
     tcklst=btuTicker();
     while (!mbdone) {
#ifdef GCWINNT
          while (PeekMessage(&msg,(HWND)-1,0,0,PM_REMOVE) ) {
               if (msg.message == WM_CLOSE) {
                    kilsrc=-1;
                    kilctr=0;           // kill system NOW
                    fupkil();
                    break;
               }
          }
#endif // GCWINNT
          BEG_PHASE("Scanning channels",0);
          newunm=btuscn();
          END_PHASE("Scanning channels",newunm);
          if (newunm <= lstunm) {
               BEG_PHASE("System cycle",0);
#ifdef GCWINNT
               gcdelay(10);
#endif // GCWINNT
               (*syscyc)();
               END_PHASE("System cycle",0);
          }
          usrnum=lstunm=newunm;
          if (usrnum >= 0) {
               status=btusts(usrnum);
               if (usrnum == nterms) {
#ifndef GCWINNT
                    sapsts(status);
#endif // GCWINNT
               }
               else {
                    curusr(usrnum);
                    BEG_PHASE("Channel service",0);
                    if (status != CRSTG
                     && status != CYCLE
                     && status != FSESTS
                     && status != POLSTS) {
                         (*shomal_hook)();
                    }
                    if ((usrptr->flags&BYEBYE) && status == OUTMT) {
                         BEG_PHASE("Test output empty",0);
                         if ((*oesrou)(usrnum)) {
                              END_PHASE("Test output empty",1);
                              finbye();
                         }
                         else {
                              END_PHASE("Test output empty",0);
                              btuinj(usrnum,OUTMT);
                         }
                    }
                    else {
                         actdet=status < 250;
                         orgsts=status;
                         clrmlt();
                         switch (usrptr->usrcls) {
                         case VACANT:
                              svacant();
                              break;
                         case ONLINE:
                              sonline();
                              break;
                         case SUPIPG:
                              ssupipg();
                              break;
                         case SUPLON:
                         case SUPLOF:
                         default:
                              susing();
                         }
                         if (actdet) {
                              usrptr->flags|=ACTIVE;
                              if (orgsts != OUTMT) {
                                   usrptr->nazapc=0;
                              }
                         }
                    }
                    END_PHASE("Channel service",0);
               }
          }
          BEG_PHASE("Channel cycle",0);
          (*chncyc)();
          END_PHASE("Channel cycle",0);
          while (tcklst != btuTicker()) {
               tcklst++;
               lngtck++;
               prcrtk();
          }
#ifndef GCWINNT
          sapprc();
#endif // GCWINNT
#ifdef GCDOS
          if (gpcntr > 0) {
               gpalrt();
          }
#endif // GCDOS
     }
     usrnum=kilsrc;
     if (mbdone == 2) {
          catastro("GALGSBL License Violation Detected!");
     }
     shocst("SERVER SHUTDOWN","");
     (*(VOIDFUNC *)module00.finrou)();
//EXCEPT
// due to a bug in BC5 we can not use normal try/except for wgserver.exe
// because if it is running as a service "except" block is never executed
// to avoid the problem we set global exception filter.
// if Borland fixes that bug code should be changed back to try/except
//#ifdef GCWINNT
//     closeVideoOutput();
//     errcod=99;                    // GP
//     dfaStop();                    // DOS programms may expect BTV to be active
//     if (isRunAsService()) {
//          serviceShutdown(hReportTh);
//     }
//     exit(errcod);
//#endif // GCWINNT
}

static VOID
init(VOID)                         /* initialize the hardware and software */
{
     extern GBOOL isgooduid(CHAR *uid); // from baduid.c
     extern VOID inimdls(VOID);
     /* the following two functions are from GCSASYS.C */
     extern INT csCheckUser(const CHAR *userid,const CHAR *password);
     extern GBOOL csdftpsw(const CHAR *password);
     CHAR *cp;
     LONG memreq;
     SHORT iobase;
     INT i,group,chan,msg,numchn,rc;
     CHAR grt;
     INT irpsrc;
     INT itzerr;
     INT alttmr;
     VOID initrng3();
     VOID insdbz(VOID);
     UINT thisbaud;
     INT chnoff;
#ifdef GCWINNT
     VOID init_ntserial(VOID);
#endif // GCWINNT
#ifdef STARTER
     UINT hdwcnt;
#endif // STARTER

     ctrlbrk(ctrlch);
     explodem=0;
     hdlcon=gtsens;
     hdlc25=incx25;
     hdlnrg=nonrng;
     hdlrng=incrng;
     hdlcnc=mdmcnc;
     hdlrst=dftrst;
     loncdi=cdistb;
     hdlrlg=relog;
     hdlzap=dftzap;
     hdlsmp=sampok;
     alwsup=sgnupk;
     chkauid=isgooduid;
     hdlbump=csbump;
     cschkuid=csCheckUser;
     cschkpsw=csdftpsw;
     iserrf=dfterf;
     oesrou=dftoes;
     reqpsw=dftreqpsw;
     greet=dftgreet;
     uidpmt=dftuidpmt;
     inimemdbg();
     srand(now());
#ifdef GCDOS
     setbparm();
     fclose(stdaux);
     fclose(stdprn);
     fclose(stderr);
#endif // GCDOS
     inilingo();
     inimsg(8192);
     mjrmb=opnmsg("wgsmajor.mcv");
     monorcol();
     gpboot=ynopt(GPBOOT);
     shortm=sameas(lastwd(getmsg(SHORTM)),"LONG");
#ifdef GCDOS
     grtype[0]=GTMODEM;
#else
     grtype[0]=GTOTHER;
#endif // GCDOS
     chanty[0]=0;
     setmem(grtsub,sizeof(CHAR *)*NUMGRPS,0);
     for (group=1,msg=0 ; group < NUMGRPS ; group++,msg+=GROUP2-GROUP1) {
          i=tokopt(msg+GROUP1,"<NONE>","MODEM","(lock)",
                              "SERIAL","X.25","LAN",NULL)-1;
          if (i == -1) {
               grtype[group]=GTOTHER;
               grtsub[group]=alcdup(lastwd(getmsg(msg+GROUP1)));
          }
          else {
               grtype[group]=i;
               if (tokopt(msg+HTYPE1,"SINGLE","MULTI",NULL) == 0) {
                    grtsub[group]=alcdup(lastwd(getmsg(msg+HTYPE1)));
               }
          }
          chanty[group]=0;
          if (grtype[group] == GTMODEM && ynopt(msg+LOCK1)) {
               grtype[group]=GTMLOCK;
          }
          if (grtype[group] != GTNONE) {
               nterms+=numopt(msg+NUMBR1,1,256);
          }
#ifndef GCWINNT
          if (grtype[group] == GTLAN) {
               i=tokopt(msg+LANTYP1,"IPXD","IPXV","SPX",NULL);
               chanty[group]=SDFIPXD-1+i;
               if (chanty[group] == SDFIPXV || chanty[group] == SDFSPX) {
                    if (*rawmsg(msg+SAPNAM1) != '\0' &&(btulan&BTLIPXV) != 0) {
                         sapsup=1;
                    }
               }
          }
#endif // GCWINNT
     }
     if (nterms > 256) {
          catastro("MORE THAN 256 CHANNELS!");
     }
     switch (tokopt(OUTBSZ,"4096","8192","16384",NULL)) {
     case 1:
     case 2:
          outbsz=8192;
          break;
     case 3:
          outbsz=16384;
          break;
     default:
          catastro("INVALID VALUE FOR OUTBSZ");
     }
     sampln=numopt(SAMPLN,0,256);
     sampky=stgopt(SAMPKY);
     exicnc=ynopt(EXICNC);
     mmucrr=numopt(MMUCRR,0,32767);
     csvcrr=numopt(CSVCRR,0,32767);
     shterc=(INT *)alcmem(25*sizeof(INT));
     shterc++;
     setmem(shterc,24*sizeof(INT),-1);
     shterc[mcuhr=numopt(MCUHR,-1,23)]=0;
     for (i=1 ; i <= 4 ; i++) {
          shterc[numopt(MSHHR1-1+i,-1,23)]=10+i;
     }
     mcumin=numopt(MCUMIN,0,59);
     mcuwrn=numopt(MCUWRN,0,59);
     maxcat=numopt(MAXCAT,1,32767);
     pfceil=numopt(PFCEIL,0,3);
     visbel=numopt(VISBEL,0,2000);
     invbel=numopt(INVBEL,0,2000);
     sopaud=ynopt(SOPAUD);
#if defined(GCDOS) || defined(GCWINNT)
     auxist=ynopt(AUXIST);
     outata=ynopt(OUTATA);
     zapdlan=ynopt(ZAPDLAN);
     zapvlan=ynopt(ZAPVLAN);
     clkupd=ynopt(UPDCLK);
#endif
     gcspnl(numopt(NAKLIM,0,32767));
     vispsw=ynopt(VISPSW);
     gphdlr=ynopt(GPHDLR);
     gpslmt=(UINT)lngopt(GPSLMT,1L,50000L);
     maxscns=numopt(MAXSCNS,10,50);
     modzap=numopt(MODZAP,0,32767);
     idlzap=numopt(IDLZAP,0,32767);
     zapser=ynopt(ZAPSER);
     zapcsu=ynopt(ZAPCSU);
     svrate=numopt(SVRATE,0,32767);
     lonaud=ynopt(LONAUD);
     lofaud=ynopt(LOFAUD);
     mmuaud=ynopt(MMUAUD);
     bbsttl=stgopt(BBSTTL);
     syskey=stgopt(SYSKEY);
     glbkey=stgopt(GLBKEY);
     glbkeyi=stgopt(GLBKEYI);
     mmuatr=stgopt(MMUATR);
     ansdim=stgopt(ANSDIM);
     company=stgopt(COMPANY);
     addres1=stgopt(ADDRES1);
     addres2=stgopt(ADDRES2);
     dataph=stgopt(DATAPH);
     liveph=stgopt(LIVEPH);
     chghour=stgopt(CHGHOUR);
     chgmin=stgopt(CHGMIN);
     chgtime=stgopt(CHGTIME);
     uspmod=ynopt(USPMOD);
     nreccl=numopt(NRECCL,2,75);
     sysrec=ynopt(SYSREC);
     inipool(getmsg(ASFILE));
     logons=(INT *)alczer(sizeof(INT)*nterms);
     recents=(struct recalls *)alczer(sizeof(struct recalls)*nreccl);
     for (i=1 ; i < NUMGRPS ; i++) {
          mdmatz[i]="ATZ";
#ifdef GCWINNT
          mdmbsy[i]="ATM0H1&D0";
#else
          mdmbsy[i]="ATM0H1";
#endif
          mdmnan[i]="ATS0=0";
     }
     scnpaus=(CHAR **)alcmem(nlingo*sizeof(CHAR *));
     for (clingo=0 ; clingo < nlingo ; clingo++) {
          stpans(cp=rawmsg(SCNPAUS));
          scnpaus[clingo]=alcdup(cp);
     }
     clingo=0;
     langop=tokopt(LANGOP,"AUTO","ASK",NULL);
     iniaus();
     cp=lastwd(rawmsg(ANSIOP));
     ansiop=*(cp+strlen(cp)-1);
     if (ansiop == 'O') {
          regautsns(ansisns);
     }
     rsetop=chropt(RSETOP);
     nchans=nterms+((btulan&BTLIPXV) ? 1 : 0);
     if ((memreq=btulsz(nchans,INPSIZ,outbsz)) < 0L) {
          catastro("BTULSZ ERROR");
     }
     if ((region=getml(memreq)) == NULL) {
          memcata();
     }
     insdbz();
     multsk=ynopt(MULTSK);
#ifdef GCDOS                       // NT does not need double catastro logic
     if (!multsk && undwin()) {
          catastro("HARDWARE SETUP NOT CONFIGURED FOR WINDOWS!");
     }
     olddblflt=gpdblflt;
     gpdblflt=mjrdblflt;
#endif // GCDOS
     itzerr=multsk ? btuitm(region) : btuitz(region);
     if (itzerr == MEMERR) {
          memcata();
     }
     else if (itzerr != 0) {
          catastro("BTUITZ ERROR #%d",itzerr);
     }
     gsblup=1;
#ifdef WEBCAST
     sprintf(subvers,"-%lu",btubcs());
#else
     if (btux25) {
          strcat(subvers,"/X25");
     }
#ifdef STARTER
     if (btusrs > MAXSUSR) {
          catastro("GALGSBL License Violation Detected!");
     }
#endif // STARTER
     strcat(subvers,spr("-%d",btusrs));
     if (btuseats() > 0L) {
          strcat(subvers,spr("[%lu]",btuseats()));
     }
#endif // WEBCAST
     if (ynopt(ENAEUR)) {
          eurmsk=0xFF;
          for (i=128 ; i < 256 ; i++) {
               btuxlt(i,i);
          }
     }
     if (multsk) {
          switch (mulmth=tokopt(MLTMTH,"TIMER","INTER",NULL)) {
          case 1:
               if (btuirp(alttmr=chropt(ALTTMR)-'0') != 0) {
                    catastro("ALTERNATE TIMER (COM%d) NOT PRESENT",alttmr);
               }
               break;
          case 2:
               if (btuirp(-1) != 0) {
                    catastro("INTERRUPT HANDLER NOT INITIALIZED");
               }
               if (btuhit(irpsrc=numopt(IRPSRC1,2,7)) != 0) {
                    catastro("INVALID PRIMARY INTERRUPT %d",irpsrc);
               }
               if ((irpsrc=numopt(IRPSRC2,0,7)) > 1) {
                    if (btuhit(irpsrc) != 0) {
                         catastro("INVALID SECONDARY INTERRUPT %d",irpsrc);
                    }
               }
               break;
          default:
               catastro("INVALID MULTITASKING METHOD SELECTED");
          }
     }
#ifdef GCDOS
     else if (ynopt(RNG3HD)) {
          initrng3();
     }
#endif // GCDOS
     inimnu();
     ripdfd=iniarip();
     xrfbb=dfaOpen("wgsxrf2.dat",sizeof(struct uidxrf),NULL);
     if ((numxrf=numopt(NUMXRF,0,MAXXRF)) != 0) {
          xrfpos=(LONG *)alczer(sizeof(LONG)*numxrf*nterms);
     }
     else {
          xrfpos=(LONG *)alczer(sizeof(LONG)*2);
     }
     mstbb=dfaOpen("wgsstat2.dat",sizeof(struct mdstats),NULL);
     genbb=dfaOpen("wgsgen2.dat",GENSIZ,NULL);
     conhdl=newarr(HOOKINC,sizeof(VOID (*)()));
     dschdl=newarr(HOOKINC,sizeof(VOID (*)()));
     mcuhdl=newarr(HOOKINC,sizeof(VOID (*)()));
     hCreateAcctVecs=newarr(HOOKINC,sizeof(VOID (*)()));
     dlahdl=newarr(HOOKINC,sizeof(VOID (*)()));
     hStartupVecs=newarr(HOOKINC,sizeof(VOID (*)()));
     finhdl=newarr(HOOKINC,sizeof(VOID (*)()));
     ffinhdl=newarr(HOOKINC,sizeof(VOID (*)()));
     s2aahdl=newarr(HOOKINC,sizeof(VOID (*)()));
     s2cshdl=newarr(HOOKINC,sizeof(VOID (*)()));
     annohdl=newarr(HOOKINC,sizeof(VOID (*)()));
     nmehdl=newarr(HOOKINC,sizeof(CHAR *));
     if (tfsopn("wgserv.cfg") == 0) {
          catastro("CAN'T FIND wgserv.cfg!");
     }
     while (tfsrdl() != TFSDUN) {
          if (tfstate == TFSLIN && tfspfx("NEEDED4 ")) {
               cp=alcdup(tfspst);
               add2arr(nmehdl,&cp);
          }
     }
     user=(struct user *)alcblok(nterms,sizeof(struct user));
     rsmodes=(INT *)alcmem(nterms*sizeof(INT));
     grpnum=(INT *)alcmem(nterms*sizeof(INT));
     zaptbl=(INT *)alczer(nterms*sizeof(INT));
     setmem(channel=(INT *)alcmem((nterms+3)*sizeof(INT)),
            (nterms+3)*sizeof(INT),-1);
     *channel++=-3;     /* note: channel[-3] == -3 */
     *channel++=-2;     /*       channel[-2] == -2 */
     *channel++=-1;     /*       channel[-1] == -1 */
#ifndef GCWINNT
     sapecb=16;
     sapisiz=outbsz>>1;
     saposiz=1024;
     if (sapsup && !sapitz(nterms,GALSVR)) {
          sapsup=0;
     }
#endif // GCWINNT
     inimlt(outbsz);
     initmsg(outbsz);
     inisho();
     iniacc();
#ifdef STARTER
     dfaSetBlk(accbb);
     if (dfaAcqEQ(&acctmp,SSYSUID,0)) {
          if (sv2.totcalls == 0L && acctmp.csicnt == 0) {
               strcpy(secgen.userid,SSYSUID);
               strcpy(secgen.modnam,GENMODL);
               dfaSetBlk(genbb);
               if (dfaAcqEQ(&secgen,&secgen,0)) {
                    if (secgen.calls != 0L) {
                         catastro("CALL COUNT LIMIT EXCEEDED!");
                    }
               }
               else {
                    secgen.calls=0L;
                    dfaInsertV(&secgen,sizeof(struct secgen));
               }
               dfaRstBlk();
          }
          else if ((UINT)sv2.totcalls == acctmp.csicnt) {
               strcpy(secgen.userid,SSYSUID);
               strcpy(secgen.modnam,GENMODL);
               dfaSetBlk(genbb);
               if (!dfaAcqEQ(&secgen,&secgen,0)
                 || secgen.calls != sv2.totcalls) {
                    catastro("CALL COUNT LIMIT EXCEEDED!");
               }
               dfaRstBlk();
          }
          else {
               catastro("CALL COUNT LIMIT EXCEEDED!");
          }
     }
     else {
          catastro("CALL COUNT LIMIT EXCEEDED!");
     }
     dfaRstBlk();
     if (sv2.totcalls > MAXCALLS) {
          catastro("CALL COUNT LIMIT EXCEEDED!");
     }
#endif // STARTER
     setmbk(mjrmb);
     usrnum=0;
     if ((thisbaud=(UINT)atol(lastwd(rawmsg(POLRATE)))) != 0) {
          maxpol=thisbaud;
     }
     for (group=1,msg=0 ; group < NUMGRPS ; group++,msg+=GROUP2-GROUP1) {
          if ((grt=grtype[group]) != GTNONE) {
               usofgr[group]=usrnum;
               numchn=numopt(msg+NUMBR1,1,256);
               chan=hexopt(msg+START1,0x00,0xFF);
#ifndef GCWINNT
               sapstt[group]=0;
#endif // GCWINNT
               switch (grt) {
               case GTX25:
#ifdef GCWINNT
                    catastro("ERROR IN HARDWARE CONFIGURATION OPTION \"GROUP%d\":\nTYPE NOT OPERATIVE FOR OS",group);
#else // GCWINNT
                    echtyp[group]=tokopt(msg+ECHO1,"PLEX","SERVER",NULL);
                    strcpy(prfbuf,rawmsg(msg+X3PAR1));
                    strcat(prfbuf," ");
                    strcat(prfbuf,rawmsg(msg+X3MOR1));
                    strcpy(startv[group]=alcmem(strlen(prfbuf)+1),prfbuf);
                    if (fmtx3(startv[group]) == 0) {
                         catastro("ERROR IN HARDWARE CONFIGURATION OPTION \"X3PAR%d\" OR \"X3MOR%d\":\nBAD X.3 PARAMETER(S)",group,group);
                    }
                    ksec[group]=lngopt(msg+KSEC1,-1000000L,1000000L);
                    kpak[group]=lngopt(msg+KPAK1,-1000000L,1000000L);
                    kchar[group]=lngopt(msg+KCHAR1,-1000000L,1000000L);
                    rc=btusdf(usrnum,numchn,SDFX25,numopt(msg+CARD1,0,7),
                                                   numopt(msg+LINE1,0,1),
                                                   numopt(msg+LCN1,0,255));
                    if (rc == X25ERR) {
                         catastro("ERROR IN HARDWARE CONFIGURATION OPTION \"GROUP%d\":\nX.25 TYPE NOT OPERATIVE",group);
                    }
#endif // GCWINNT
                    break;
               case GTLAN:
#ifdef GCWINNT
                    catastro("ERROR IN HARDWARE CONFIGURATION OPTION \"GROUP%d\":\nTYPE NOT OPERATIVE FOR OS",group);
#else // GCWINNT
                    if (chanty[group] != 0) {
                         rc=btusdf(usrnum,numchn,chanty[group],soctbl[group],
                                          numopt(msg+NUMECB1,0,32767));
                         if (rc == LANERR) {
                              catastro("ERROR IN HARDWARE CONFIGURATION OPTION \"LANTYP%d\":\nLAN TYPE NOT OPERATIVE",group);
                         }
                         if (rc == MEMERR) {
                              memcata();
                         }
                         if (chanty[group] != SDFIPXD && sapsup
                          && *rawmsg(msg+SAPNAM1) != '\0') {
                              sapstt[group]=-1;
                         }
                    }
                    echtyp[group]=1;
#endif // GCWINNT
                    break;
               case GTOTHER:
                    if ((rc=btusdf(usrnum,numchn,SDFGCDI)) != 0) {
                         catastro("ERROR %d CONFIGURING CHANNEL GROUP %d",
                                  rc,group);
                    }
                    echtyp[group]=1;
                    break;
               default:
                    iobase=hexopt(msg+ADDR1,0x0000,0xFFFF);
                    echtyp[group]=ynopt(msg+DUPLX1);
                    chnoff=hexopt(msg+CHOFF1,0x0001,0x1000);
                    strcpy(prfbuf,rawmsg(msg+INIT1));
                    strcat(prfbuf,"\r");
                    startv[group]=(CHAR *)alcmem(strlen(prfbuf)+1);
                    strcpy(startv[group],prfbuf);
                    mxbaudl[group]=lngopt(msg+BAUD1,300L,230400L);
                    if (grtsub[group] != NULL) {
                         if ((rc=btusdf(usrnum,numchn,SDFGCDI)) != 0) {
                              catastro("ERROR %d CONFIGURING CHANNEL GROUP %d",
                                       rc,group);
                         }
                    }
                    else {
                         if (mxbaudl[group] > 57600L) {
                              catastro
                              ("BAUD RATE OVER 57,600 FOR CHANNEL GROUP %d",
                               group);
                         }
                         thisbaud=(UINT)mxbaudl[group];
                         if (thisbaud > maxspd) {
                              maxspd=thisbaud;
                         }
                         if (numchn == 1 || chnoff == DFTOFF) {
                              btudef(usrnum,iobase,numchn);
                         }
                         else {
                              for (i=0 ; i < numchn ; i++) {
                                   btudef(i+usrnum,iobase,1);
                                   iobase+=chnoff;
                              }
                         }
                    }
                    for (i=0 ; i < numchn ; i++) {
                         if (btuffo(i+usrnum,0x07) == 0) {
                              maxpol=max(maxpol,thisbaud/4);
                         }
                         else {
                              maxpol=max(maxpol,thisbaud);
                         }
                    }
               }
               for (i=0 ; i < numchn ; i++) {
                    if (chan > 0xFF) {
                         catastro("ERROR IN HARDWARE CONFIGURATION, CHANNEL GROUP %d:\nCHANNEL NUMBER EXCEEDS FF",group);
                    }
                    if (usridx(chan) >= 0) {
                         catastro("ERROR IN HARDWARE CONFIGURATION, CHANNEL GROUP %d:\nCHANNEL NUMBER %02x OVERLAPS",group,chan);
                    }
                    hichp1=max(hichp1,chan+1);
                    channel[usrnum]=chan;
                    grpnum[usrnum]=group;
                    usrnum++;
                    chan++;
               }
          }
          else {
               usofgr[group]=-1;
          }
     }
     usofgr[0]=usrnum;
     mxbaudl[0]=(LONG)maxpol;
     echtyp[0]=1;
#ifndef GCWINNT
     sapstt[0]=0;
#endif // GCWINNT
     startv[0]="Local channel ready...";
     channel[usrnum]=0;
     grpnum[usrnum]=0;
#ifdef GCDOS
     btudef(usrnum,hexopt(LOCALP,0,0xFFFF),1);
#else
     grtsub[0]="NHDWE";
     locchn(usrnum);
#endif // GCDOS
     inisho2();
     usrnum=-1;
     shocst(spr("SERVER UP: V%s%s",version,subvers),"(Ready to service users)");
     inimdls();
     inipfn();
     inisup();
     inimod();
#ifdef GCWINNT
     init_ntserial();
#endif // GCWINNT
     doaditbx();
     inikys();
     register_pseudok("_PORT#",prtpsk);
     register_pseudok("_GROUP#",grppsk);
     register_pseudok("_LANG=",lngpsk);
     register_pseudok("_PROT=",propsk);
     register_pseudok("_ISGCSU",gcspsk);
     iniftf1();
     inifsd();
     doaditbx();
     alcvda();
     iniftf2();
     doaditbx();
     initdn=1;
     lasthr=dthour(now());
     rtkick(50,ckmcu);
     setmbk(mjrmb);
     maxpol=(multsk && sameas("AUTO",lastwd(rawmsg(POLRATE)))) ? 2400 : maxpol;
     maxpol=(multsk && mulmth == COMIRP ? maxspd : maxpol);
     btumxs(maxpol);
     globalcmd(glorys);
     doaditbx();
     if (awclan == NULL) {
          for (group=1 ; group < NUMGRPS ; group++) {
               if (grtype[group] == GTLAN) {
                    catastro("LAN channel(s) defined, "
                             "but LAN support is not installed!");
               }
          }
     }
#ifdef STARTER
     hdwcnt=0;
#endif // STARTER
     for (usrnum=0 ; usrnum < nterms ; usrnum++) {
          rsmodes[usrnum]=NORMRS;
          rstchn();
#ifdef STARTER
          if (!(usroff(usrnum)->flags&NOHDWE)) {
               hdwcnt++;
          }
#endif // STARTER
     }
#ifdef STARTER
     if (hdwcnt > MAXSUSR) {
          catastro("GALGSBL License Violation Detected!");
     }
#endif // STARTER
     if (idlzap) {
          inirel();
          inirch();
     }
     if (modzap) {
          inimzt();
     }
     rtkick(ZAPINT,zapdec);
     if (clkupd) {
          rtkick(120,updclk);
     }
#ifdef STARTER
     rtkick(60,chksec);
#endif // STARTER
}

#ifdef STARTER
static VOID
chksec(VOID)                       /* check to see if over call limit      */
{
     if (sv2.totcalls > MAXCALLS) {
          catastro("CALL COUNT LIMIT EXCEEDED!");
     }
     rtkick(random(60)+60,chksec);
}
#endif // STARTER

#ifdef DEBUG
#ifdef GCDOS
static VOID
memgpr(                            /* memory debugging report for GP.OUT   */
CHAR *dstnam)
{
     FILE *fp;

     if ((fp=fopen(dstnam,FOPAA)) != NULL) {
          memdbgrpt(fp);
          fprintf(fp,"\n");
          fclose(fp);
     }
     (*oldgpr)(dstnam);
}
#endif // GCDOS
#endif // DEBUG DEFINE

static VOID
inimemdbg(VOID)                    /* init baseline memory debugging       */
{
#ifdef DEBUG
#ifdef GCDOS
          FILE *fp;

          oldgpr=hdlgpr;
          hdlgpr=memgpr;
          if (memdbg) {
               memdbg=0;
               if (tfsopn("galdebug.msg") > 0) {
                    while (tfsrdl() != TFSDUN) {
                         switch (tfstate) {
                         case TFSLIN:
                              if (tfspfx("DBGMEM {")
                               && strstr(tfspst,"YES") != NULL) {
                                   memdbg=1;
                              }
                              break;
                         }
                    }
               }
          }
          if (memdbg && (fp=fopen("galmemdb.flg",FOPRA)) != NULL) {
               fscanf(fp,"%lu",&nmskip);
               fclose(fp);
               unlink("galmemdb.flg");
          }
#endif // GCDOS
#endif // DEBUG
}

static VOID
finmemdbg(VOID)                    /* finalize baseline memory debugging   */
{
#ifdef DEBUG
#ifdef GCDOS
          FILE *fp;

          if (memdbg
           && (fp=fopen("galmemdb.out",FOPAA)) != NULL) {
               fprintf(fp,"Memory allocation report %s %s, System up %lu seconds\n",
                       ncdate(today()),nctime(now()),lngtck);
               memdbgrpt(fp);
               fprintf(fp,"\n");
               fclose(fp);
          }
          if (memdbg
           && nmbust > 0L
           && nmspec > 0L
           && (fp=fopen("galmemdb.flg",FOPWA)) != NULL) {
               fprintf(fp,"%lu initial memory allocations will skip high "
                          "scrutiny next run.\n",nmskip+nmspec);
               fclose(fp);
          }
          else {
               unlink("galmemdb.flg");
          }
#endif // GCDOS
#endif // DEBUG
}

static VOID
inipfn(VOID)                       /* initialize profanity list            */
{
     switch (readpfn(PFNLISTFILE,NULL)) {
     case RDPFN_OK:
          break;
     case RDPFN_FOPEN:
          shocst("PROFANITY LIST FILE NOT FOUND","unable to open %s for input",PFNLISTFILE);
          break;
     case RDPFN_EOF,RDPFN_INVALID:
          shocst("INVALID PROFANITY LIST FILE","%s is not a valid profanity list data file",PFNLISTFILE);
          break;
     default:
          shocst("ERROR READING PROFANITY LIST","error reading %s",PFNLISTFILE);
          break;
     }
}

#ifdef GCDOS
static VOID
gpalrt(VOID)                       /* GP alert notice in audit trail       */
{
     char gpmsg[80];

     curusr(lastusrnu);
     sprintf(gpmsg,"%04X:%04X/#%02X:%s/%d",FP_SEG(lastexcro),
          FP_OFF(lastexcro),channel[lastusrnu],usaptr->userid,
          usrptr->state);
     shocst("ERROR: GENERAL PROTECTION FAULT","%s",gpmsg);
     btuinj(usrnum,RING);
     gpcntr=0;
}
#endif // GCDOS

static INT
ctrlch(VOID)                       /* ignore ctrl-c's from keyboard & ret  */
{
     return(1);
}

static VOID
incx25(VOID)                       /* handle incoming X.25 call            */
{
     btucmd(usrnum,"A");
}

static VOID
nonrng(VOID)                       /* handle non-RING rec'd during AWAITC  */
{
}

static VOID
incrng(VOID)                       /* handle incoming "RING" string        */
{
}

static INT
mdmcnc(VOID)                       /* handle non-"RING" during HARING      */
{                                       /* 1=connect, 0=reset, -1=ignore   */
     if (margc == 0) {
          return(-1);
     }
     if (bdspec()) {
          return(1);
     }
     if (margc == 2 && sameas(margv[0],"NO") && sameas(margv[1],"CARRIER")) {
          return(0);
     }
     return(-1);
}

#ifndef GCWINNT
static VOID
saphdl(                    /* handle S.A.P. (if any) for this channel group */
INT group,                                         /* channel group, 0 to 8 */
INT opt)        /* -1=shut down, 1=bring up, 0=up if any channels available */
{
     INT altusn;
     struct user *altusp;
     CHAR svrnam[SNMLEN+1];

     if (sapstt[group] == 0) {
          return;
     }
     if (opt == 0 && (altusn=usofgr[group]) >= 0) {
          opt=-1;
          for (altusp=usroff(altusn) ;
               altusn < nterms && grpnum[altusn] == group ;
               altusp=usroff(++altusn)) {
               if (altusp->usrcls == VACANT && altusp->state == AWAITC) {
                    opt=1;
                    break;
               }
          }
     }
     if (opt != 0 && sapstt[group] == -opt) {
          setmbk(mjrmb);
          sprintf(svrnam,"%.*s(%3.3s)",SNMLEN-5,
                  rawmsg(SAPNAM1+(group-1)*(GROUP2-GROUP1)),
                  chantn[chanty[group]-SDFIPXD]);
          rstmbk();
          if ((sapstt[group]=opt) > 0) {
               sapadv(svrnam,GALSVR,soctbl[group]);
          }
          else {
               sapdwn(svrnam);
          }
     }
}
#endif // GCWINNT

INT
fmtx3(                          /* Format X.3 PAD-config string into prfbuf */
CHAR *prmstg)                  /* (returns length of X.29 string in prfbuf) */
{                          /* warning: copies to input[] buffer for parsing */
     INT i;
     INT parm,val;
     CHAR *cp;

     strcpy(input,prmstg);
     parsin();
     cp=prfbuf;
     *cp++=2;
     for (i=0 ; i < margc ; i++) {
          if (sscanf(margv[i],"%d:%d",&parm,&val) != 2) {
               return(0);
          }
          *cp++=parm;
          *cp++=val;
     }
     return(margc*2+1);
}

VOID
globalcmd(                         /* install global command handler       */
INT (*rouptr)(VOID))
{
     if (nglobs >= GLBMAX) {
          catastro("TOO MANY GLOBAL COMMAND HANDLERS");
     }
     globs[nglobs++]=rouptr;
}

VOID
rtihdlr(                      /* install 18 Hz real-time irpt-invoked rou  */
VOID (*rouptr)(VOID))
{
     if (nrtirs >= RTIMAX) {
          catastro("TOO MANY REAL-TIME IRPT ROUTINES");
     }
     rtirs[nrtirs]=rouptr;
     nrtirs++;
     bturti(18,bbsrti);
}

static INT
bbsrti(VOID)                  /* master 18 Hz real-time irpt routine       */
{
     INT i;

     for (i=0 ; i < nrtirs ; i++) {
          (*rtirs[i])();
     }
     return(0);
}

static GBOOL                  /*   returns whether hardware output empty   */
dftoes(                       /* default (*oesrou)() vector                */
INT unum)                     /*   usrnum to test                          */
{
     (VOID)unum;
     return(TRUE);
}

VOID
xltctls(                      /* translate up-arrow seqs to CTRL chars     */
CHAR *txtbuf)
{
     CHAR *cp,c;

     for (cp=txtbuf ; *cp != '\0' ; cp++) {
          if (*cp == '^') {
               switch (c=*(cp+1)) {
               case '\0':
                    break;
               case '^':
                    movmem(cp+1,cp,strlen(cp));
                    break;
               default:
                    *cp=(c&~0x40);
                    movmem(cp+2,cp+1,strlen(cp+1));
                    break;
               }
          }
     }
}

INT
usridx(                            /* find "usrnum", given visible chan #  */
INT chan)
{
     INT idx;

     for (idx=0 ; idx < nterms ; idx++) {
          if (channel[idx] == chan) {
               return(idx);
          }
     }
     return(-1);
}

VOID
begin_polling(                     /* begin polling user (c/o polrou())    */
INT unum,
VOID (*rouptr)(VOID))
{
     if (usroff(unum)->polrou == NULL && unum != inpolr) {
          btuinj(unum,POLSTS);
     }
     usroff(unum)->polrou=rouptr;
}

VOID
stop_polling(                      /* stop polling user                    */
INT unum)
{
     usroff(unum)->polrou=NULL;
}

VOID
hook_connect(                      /* hook client/server user logon        */
VOID (*conrou)(VOID))
{
     add2arr(conhdl,&conrou);
}

VOID
hook_disconnect(                   /* hook client/server user disconnect   */
VOID (*dscrou)(VOID))
{
     add2arr(dschdl,&dscrou);
}

VOID
hook_cleanup(                      /* hook system auto-cleanup             */
VOID (*mcurou)(VOID))
{
     add2arr(mcuhdl,&mcurou);
}

VOID
hook_createacct(                   /* hook account creation                */
CREATEACCOUNT *crarou)
{
     add2arr(hCreateAcctVecs,&crarou);
}

VOID
hook_delacct(                      /* hook account deletion                */
VOID (*dlarou)(CHAR *userid))
{
     add2arr(dlahdl,&dlarou);
}

VOID
hook_startup(                      /* hook system startup (post-init)      */
VOIDFUNC *begrou)
{
     add2arr(hStartupVecs,&begrou);
}

VOID
hook_shutdown(                     /* hook system shutdown                 */
VOID (*finrou)(VOID))
{
     add2arr(finhdl,&finrou);
}

VOID
hook_finalshutdown(                /* hook final system shutdown           */
VOID (*ffinrou)(VOID))
{
     add2arr(ffinhdl,&ffinrou);
}

VOID
hook_swt2aa(                       /* hook C/S user switching C/S->A/A mode*/
VOID (*s2aarou)(VOID))
{
     add2arr(s2aahdl,&s2aarou);
}

VOID
hook_swt2cs(                       /* hook C/S user switching A/A->C/S mode*/
VOID (*s2csrou)(VOID))
{
     add2arr(s2cshdl,&s2csrou);
}

VOID
hook_announce(                     /* hook announcement routine            */
VOID (*annorou)(VOID))
{
     add2arr(annohdl,&annorou);
}

VOID
dovecs(                            /* call all hooked functions            */
INT vechdl)                        /*   contained in this dynarr handle    */
{
     INT i,nvecs;
     VOIDFUNC *vec;

     nvecs=ninarr(vechdl);
     for (i=0 ; i < nvecs ; i++) {
          vec=*(VOIDFUNC **)arrelem(vechdl,i);
          BEG_PHASE("Vector",vec);
          (*vec)();
     }
}

INT
register_textvar(                  /* register a text variable for use     */
CHAR *name,                             /* name of text variable           */
CHAR *(*varrou)(VOID))                  /* pointer to text var function    */
{
     if (ntvars == 0) {
          txtvars=(struct textvar *)alcmem(sizeof(struct textvar));
     }
     else {
          txtvars=(struct textvar *)alcrsz(txtvars,
                                           sizeof(struct textvar)*ntvars,
                                           sizeof(struct textvar)*(ntvars+1));
     }
     stzcpy(txtvars[ntvars].name,name,TVRSIZ);
     txtvars[ntvars].varrou=varrou;
     return(ntvars++);
}

INT
findtvar(                          /* find text varibale & return number   */
CHAR *name)                             /* name of text variable to find   */
{
     INT i;

     for (i=0 ; i < ntvars ; i++) {
          if (sameas(name,txtvars[i].name)) {
               return(i);
          }
     }
     return(-1);
}

INT
register_module(                   /* register a module for online use     */
struct module *mod)                     /* pointer to a module block       */
{
     if (strlen(mod->descrp) < 3) {
          catastro("MODULE NAME \"%s\" TOO SHORT!",mod->descrp);
     }
     if (strlen(mod->descrp) > MNMSIZ-1) {
          catastro("MODULE NAME \"%s\" TOO LONG!",mod->descrp);
     }
     if (mod->stsrou == NULL) {
          mod->stsrou=dfsthn;
     }
     if (nmods == 0) {
          module=(struct module **)alcmem(sizeof(struct module *));
          mdstats=(struct mdstats *)alcmem(sizeof(struct mdstats));
     }
     else {
          module=(struct module **)alcrsz(module,sizeof(struct module *)*nmods,
                                         sizeof(struct module *)*(nmods+1));
          mdstats=(struct mdstats *)alcrsz(mdstats,sizeof(struct mdstats)*nmods,
                                           sizeof(struct mdstats)*(nmods+1));
     }
     module[nmods]=mod;
     dfaSetBlk(mstbb);
     if (dfaQueryEQ(mod->descrp,0)) {
          dfaAbsRec(&mdstats[nmods],0);
     }
     else {
          setmem(&mdstats[nmods],sizeof(struct mdstats),0);
          strcpy(mdstats[nmods].mdname,mod->descrp);
     }
     dfaRstBlk();
     return(nmods++);
}

INT
findmod(                           /* find module and return module number */
CHAR *name)                             /* name of module to find          */
{
     INT i;

     for (i=1 ; i < nmods ; i++) {
          if (sameas(name,module[i]->descrp)) {
               return(i);
          }
     }
     return(-1);
}

VOID
dclvda(                            /* module declaration for vda size      */
INT size)
{
     if (size > vdasiz) {
          vdasiz=size;
     }
}

VOID
alcvda(VOID)                       /* allocate volatile data area          */
{
     if (vdasiz != 0) {
          vdahdl=alcblok(nterms,vdasiz);
          vdarea=vdaoff(0);
          vdatmp=alcmem(vdasiz);
     }
}

CHAR *
vdaoff(                            /* vdaptr calculation routine           */
INT unum)
{
     ASSERTM(vdahdl != NULL,"vdaoff() called too early!");
     return((CHAR *)ptrblok(vdahdl,(USHORT)unum));
}

static VOID
svacant(VOID)                      /* vacant channel status handler        */
{
     setmbk(mjrmb);
     switch (usrptr->state) {
     case JSTRST:                    /* channel just reset, status received */
          btulok(usrnum,0);
          if (status == CMN2OK) {
               usrptr->flags|=IS2698;
               btuoes(usrnum,1);
               if (grtype[grpnum[usrnum]] == GTSERIAL) {
                    usrptr->flags|=ISRIAL;
               }
               switch (rsmodes[usrnum]) {
               case NORMRS:
                    if (usrptr->flags&ISRIAL) {
                         prf("%s",startv[grpnum[usrnum]]);
                         usrptr->state=ESTVEC;
                    }
                    else {
                         prf("%s\r",mdmatz[grpnum[usrnum]]);
                         usrptr->state=EMTATZ;
                    }
                    break;
               case BUSYRS:
                    if (usrptr->flags&ISRIAL) {
                         kiletc("Disabled serial channel");
                         prf("");
                    }
                    else {
                         prf("%s\r",mdmbsy[grpnum[usrnum]]);
                         usrptr->state=EKLVEC;
                    }
                    break;
               case NANSRS:
                    if (usrptr->flags&ISRIAL) {
                         kiletc("Disabled serial channel");
                         prf("");
                    }
                    else {
                         prf("%s\r",mdmnan[grpnum[usrnum]]);
                         usrptr->state=EKLVEC;
                    }
                    break;
               }
               powprf();
          }
          else if (status == CMDOK) {
               switch (rsmodes[usrnum]) {
               case NORMRS:
                    usrptr->state=AWAITC;
                    break;
               case BUSYRS:
                    btucmd(usrnum,"D");
                    usrptr->state=EKLVEC;
                    break;
               case NANSRS:
                    kiletc(usrnum != nterms-1 ? "Modem set to NO-ANSWER" : "Local session disabled");
                    break;
               }
          }
          else {
               rstchn();
          }
          break;
     case EMTATZ:                                      /* ATZ sent to modem */
          if (status == OUTMT) {
               btucmd(usrnum,"P");
          }
          else if (status == CRSTG) {
               if (sameas(getin(),"OK") || sameas(margv[0],"0")) {
                    shochl("ATZ OK, sending init string",'.',
                           baudat(usrptr->baud,0)&0xF7);
                    btuclc(usrnum);
                    btucmd(usrnum,"ttttt");
                    usrptr->state=ATZDUN;
               }
          }
          else if (status != LOST2C) {
               rstchn();
               shochl("ERROR: no response to ATZ!",'#',0x9c);
          }
          break;
     case EKLVEC:  /* chan "kill vector" (busy-out/no answer) has been sent */
          if (status == OUTMT) {
               btuoes(usrnum,0);
               btucmd(usrnum,"P");
          }
          else if (status == CRSTG) {
               if (sameas(getin(),"OK")) {
                    kiletc(rsmodes[usrnum] == BUSYRS ?
                         "Modem set to BUSY-OUT" : "Modem set to NO-ANSWER");
               }
          }
          else if (status == CMDOK) {
               if (usrptr->flags&NOHDWE) {
                    kiletc(usrnum == nterms-1 ? "Local session disabled"
                                       : "Non-hardware channel disabled");
               }
               else {
                    kiletc("Modem set to BUSY-OUT");
               }
          }
          else if (status == CMN2OK) {
               kiletc("Disabled serial channel");
          }
          else {
               rstchn();
          }
          break;
     case W4KILL:         /* this channel ready (limbo) for system shutdown */
          if ((usrptr->flags&ISX25) && status == CRSTG) {
               rstchn();
          }
          break;
     case ATZDUN:           /* waiting for 1/10-sec delay after OK from ATZ */
          if (status == CMN2OK) {
               prf("%s",startv[grpnum[usrnum]]);
               powprf();
               usrptr->state=ESTVEC;
          }
          else if (status != LOST2C) {
               rstchn();
               shochl("ERROR: bad status after ATZ!",'#',0x9c);
          }
          break;
     case ESTVEC:                 /* channel "startup vector" has been sent */
          if (status == OUTMT) {
               btuoes(usrnum,0);
               if (usrptr->flags&ISRIAL) {
                    usrptr->state=AWAITC;
                    shochl("Serial port ready to use...",'.',
                           baudat(usrptr->baud,0)&0xF7);
               }
               else {
                    btucmd(usrnum,"P");
                    usrptr->state=W4ISOK;
               }
          }
          else if (status != LOST2C && status != CMN2OK) {
               rstchn();
               shochl("ERROR: bad/no init response",'#',0x09c);
          }
          break;
     case W4ISOK:
          if (status == CRSTG) {
               if (sameas(getin(),"OK")) {
                    btuclc(usrnum);
                    usrptr->state=AWAITC;
                    shochl("Modem awaiting incoming call",'.',
                           baudat(usrptr->baud,0)&0xF7);
               }
          }
          else if (status != LOST2C) {
               rstchn();
               shochl("ERROR: bad status during init",'#',0x09c);
          }
          break;
     case W4SPXT:             /* wait for previous SPX session to terminate */
          switch (status) {
          case CYCLE:
               if (btuTicker()-usrptr->tspxt <= MAXSPXT) {
                    btuinj(usrnum,CYCLE);      /* give up on SPX terminate? */
                    break;
               }
          default:
          case SPXTDN:
               rstchn();
               break;
          }
          break;
     case AWAITC:                                 /* Await an incoming call */
          switch (status) {
          case RING:
#ifdef GCWINNT
               if (usrptr->flags&NOHDWE) {
                    rstchn();
                    break;
               }
#endif
               if (usrptr->flags&ISX25 || grtype[grpnum[usrnum]] == GTLAN) {
                    zaptbl[usrnum]=0;      /* non-hardware X.25 or LAN chan */
                    (*hdlcon)();
               }
               else if ((usrptr->flags&(ISRIAL+NOHDWE+IS1201)) != 0) {
                    zaptbl[usrnum]=ZAPMAX;       /* hw/non-hw XECOM channel */
                    shochl("ANSWERING CALL...",'',baudat(usrptr->baud,0));
                    btucmd(usrnum,"Ap^H");
                    usrptr->state=XERING;
               }
               else {
                    rstchn();
               }
               break;
          case CRSTG:
#ifdef GCWINNT
               if (usrptr->flags&NOHDWE) {
                    rstchn();
                    break;
               }
#endif
               zaptbl[usrnum]=ZAPMAX;
               if (sameas(getin(),"RING") && (usrptr->flags&ISX25)) {
                    btuinj(usrnum,CYCLE);             /* X.25 channel w/hdw */
                    usrptr->state=WAIT29;
                    (*hdlc25)();
               }
               else if (usrptr->lcstat == LSDCOMM && !(usrptr->flags&ISRIAL)) {
                    zaptbl[usrnum]=0;                 /* IPX Direct channel */
                    (*hdlcon)();
               }
               else {
                    if (sameas(margv[0],"RING")) {   /* HAYES channel w/hdw */
                         shochl("ANSWERING CALL...",'',baudat(usrptr->baud,0));
                         usrptr->state=HARING;
                         if (outata && !(usrptr->flags&ISRIAL)) {
                              btucmd(usrnum,"^E=A");
                         }
                         (*hdlrng)();
                    }
                    else if (usrptr->flags&ISRIAL) { /* UART channel w/hdw */
                         zaptbl[usrnum]=0;
                         (*hdlcon)();
                    }
                    else {
                         (*hdlnrg)();
                    }
               }
               break;
          case INBLK:
          case SPXINC:
               if (awclan != NULL) {
                    (*awclan)();
               }
               break;
          case LOST2C:
#ifdef UNIX
            rstchn();           /* In case user reset before the */
            break;              /* first CRSTG was received      */
#endif // UNIX
          case CMN2OK:
          case CYCLE:
          case SPXWDG:
               break;
          default:
               shochl("Status invoked reset",'#',0x9c);
               rstchn();
               break;
          }
          break;
     case WCDICN:
          switch (status) {
          case CDICONN:
               zaptbl[usrnum]=0;
               (*hdlcon)();
               break;
          case CMN2OK:
               break;
          default:
               shochl("Status invoked reset",'#',0x9c);
               rstchn();
               break;
          }
          break;
     case WAIT29:                       /* keep trying to send X.29 message */
          switch (status) {
          case CM25OK:
          case RCVX29:
               break;
          case CYCLE:
          case CLOX29:
               if (startv[grpnum[usrnum]] == '\0') {
                    zaptbl[usrnum]=0;
                    (*hdlcon)();
                    break;
               }
               switch (btux29(usrnum,fmtx3(startv[grpnum[usrnum]]),prfbuf)) {
               case 0:
                    zaptbl[usrnum]=0;
                    (*hdlcon)();
                    break;
               case X25CLO:
                    btuinj(usrnum,CLOX29);
                    break;
               default:
                    btuinj(usrnum,ERRX29);
                    break;
               }
               break;
          default:
               rstchn();
               break;
          }
          break;
     case XERING:              /* has answered XECOM call detected carrier? */
          if (status == CMDOK) {
               if (!(usrptr->flags&NOHDWE)) {
                    rcdbaud(1200L);
               }
               zaptbl[usrnum]=0;
               (*hdlcon)();
          }
          else if (status == INAPP) {
               rcdbaud(300L);
               zaptbl[usrnum]=0;
               (*hdlcon)();
          }
          else if (status != CRSTG) {
               rstchn();
          }
          break;
     case HARING:              /* has answered HAYES call detected carrier? */
          if (status == CRSTG) {
               if (sameas(getin(),"RING")) {
                    (*hdlrng)();
               }
               else {
                    switch ((*hdlcnc)()) {
                    case 1:
                         btucmd(usrnum,"p");
                         usrptr->state=HPAUSE;
                         break;
                    case 0:
                         rstchn();
                         break;
                    case -1:
                         break;
                    }
               }
          }
          else if (status != CMN2OK) {
               rstchn();
          }
          break;
     case HPAUSE:         /* pause after detecting carrier on HAYES channel */
          switch (status) {
          case CMN2OK:
               zaptbl[usrnum]=0;
               (*hdlcon)();
               break;
          case CRSTG:
               getin();
               break;
          case 251:      // data buffer overflow
               break;
          default:
               rstchn();
          }
          break;
     case AUTSNS:                        /* waiting out auto-sensing period */
          switch (status) {
          case CYCLE:
               if (prcaus()) {
                    figlang();
               }
               else {
                    btuinj(usrnum,CYCLE);
               }
               break;
          case CMDOK:
          case CMN2OK:
          case CM25OK:
          case RCVX29:
          case OUTMT:
          case IPXRER:
          case IPXUNK:
          case INBLK:
               break;
          case SPXTRM:
          case SPXWDG:
               othtrm();
          default:
               rstchn();
          }
          break;
     case CRESET:
          rstchn();
          break;
     }
}

VOID
rcdbaud(                           /* record the channel baud rate         */
LONG baud)
{
     usrptr->baud=baud;
}

VOID
powprf(VOID)                       /* "power" outprf() - cut through input  */
{
     btuxct(usrnum,strlen(prfbuf),prfbuf);
     clrprf();
     btucli(usrnum);
}

INT
bdspec(VOID)                       /* goto baud rate given in input, if any */
{
     if (sameas(margv[0],"CONNECT") || sameas(margv[0],"CARRIER")) {
          if (margc == 1) {
               rcdbaud(300L);
          }
          else if (sameas(margv[1],"14.4")) {        /* For Racal-Datacom */
               rcdbaud(14400L);                      /* High Speed Modems */
          }                                          /* Which Report As   */
          else if (sameas(margv[1],"12.0")) {        /* CONNECT 14.4 and  */
               rcdbaud(12000L);                      /* CONNECT 12.0      */
          }
          else if (sameas(margv[1],"FAST")) {        /* for Telebit */
               rcdbaud(mxbaudl[grpnum[usrnum]]);     /* Trailblazer */
          }
          else {
               rcdbaud(atol(margv[1]));
          }
          if (grtype[grpnum[usrnum]] == GTMODEM ||
              grtype[grpnum[usrnum]] == GTSERIAL) {
               btubbr(usrnum,usrptr->baud);
          }
          return(1);
     }
     return(0);
}

VOID
updcal(VOID)                       /* update baud rate based statistics    */
{
     INT i;
     UINT brates[]={300,1200,2400,9600,14400,19200u,38400u};
#ifdef STARTER
     struct usracc accsec;
     INT savusrn;
#endif // STARTER

     if (!(usrptr->flags&NOHDWE)) {
          sv2.totcalls++;
#ifdef STARTER
          strcpy(secgen.userid,SSYSUID);
          strcpy(secgen.modnam,GENMODL);
          dfaSetBlk(genbb);
          if (dfaAcqEQ(&secgen,&secgen,0)) {
               if (++secgen.calls != sv2.totcalls) {
                    catastro("CALL COUNT LIMIT EXCEEDED!");
               }
               dfaUpdateV(&secgen,sizeof(struct secgen));
               dfaRstBlk();
               savusrn=usrnum;     // If we are "Sysop" onbbs() would fail!
               usrnum=-1;          // To trick onbbs() into returning TRUE.
               if (onbbs(SSYSUID,1)) {
                    usrnum=savusrn;
                    if (++(uacoff(uisusn)->csicnt) != sv2.totcalls) {
                         catastro("CALL COUNT LIMIT EXCEEDED!");
                    }
                    else {
                         dfaSetBlk(accbb);
                         dfaGetEQ(&accsec,uacoff(uisusn)->userid,0);
                         dfaUpdate(uacoff(uisusn));
                         dfaRstBlk();
                    }
               }
               else {
                    usrnum=savusrn;
                    dfaSetBlk(accbb);
                    if (dfaAcqEQ(&accsec,SSYSUID,0)) {
                         if (++accsec.csicnt != sv2.totcalls) {
                              catastro("CALL COUNT LIMIT EXCEEDED!");
                         }
                         else {
                              dfaUpdate(&accsec);
                              dfaRstBlk();
                         }
                    }
                    else {
                         catastro("CALL COUNT LIMIT EXCEEDED!");
                    }
               }
          }
          else {
               catastro("CALL COUNT LIMIT EXCEEDED!");
          }
          if (sv2.totcalls > MAXCALLS) {
               catastro("CALL COUNT LIMIT EXCEEDED!");
          }
#endif // STARTER
          if (chanty[grpnum[usrnum]] != SDFIPXD
              && chanty[grpnum[usrnum]] != SDFIPXV
              && chanty[grpnum[usrnum]] != SDFSPX
              && !(usrptr->flags&(ISGCDI+ISX25))) {
               for (i=0 ; i < 7 ; i++) {
                    if (usrptr->baud == brates[i]) {
                         sv.calls[i]++;
                         return;
                    }
               }
          }
          sv.calls[7]++;
     }
}

VOID
kiletc(                            /* kill a channel, and possibly the sys */
CHAR *legend)
{
     INT unum;
     struct user *uptr;

     if (catact == 0) {
          shochl(legend,240,baudat(usrptr->baud,0)&0xF7);
     }
     usrptr->state=W4KILL;
     if (errcod != 1 || kilipg) {
          for (unum=0 ; unum < nterms ; unum++) {
               uptr=usroff(unum);
               if (uptr->usrcls != VACANT || uptr->state != W4KILL) {
                    return;
               }
          }
          failsf();
     }
}

static VOID
failsf(VOID)                       /* failsafe cleanup and shutdown routine*/
{
     INT savusn;

#ifdef GCWINNT
     if (isRunAsService() && !isNTShutdown()) {  // make sure SCM do not get mad
          reportStatusToSCM(SERVICE_STOP_PENDING,NO_ERROR,30000);
     }
#endif
     if (errcod == 0) {
          mcuctr=-1;
          savusn=usrnum;
          (*(VOIDFUNC *)module00.mcurou)();
          curusr(savusn);
          kilsrc=-2;
     }
     else if (errcod >= 11 && errcod <= 14) {
          kilsrc=-3;
     }
     kilipg=1;
     rtkoff=1;                          /* prevent all future rtkick's     */
     mbdone=max(1,mbdone);
}

#ifdef WEBCAST
INT
actliu(VOID)                       /* lines in use by actual users         */
{
     INT i,n,uc;

     for (n=i=0 ; i < nterms ; ++i) {
          uc=usroff(i)->usrcls;
          if (uc == ACTUSR || uc == SUPLON || uc == SUPLOF || uc == SUPIPG) {
               ++n;
          }
     }
     return(n);
}
#endif // WEBCAST

static VOID
gtsens(VOID)                       /* begin automatic sensing              */
{
     static CHAR *strtst[]={"Local channel has activity",
                            "Modem has carrier",
                            "Locked modem has carrier",
                            "Serial channel has activity",
                            "Incoming X.25 contact",
                            "Incoming LAN contact",
                            "%s contact"};
     static CHAR stgchr[]="";
     INT grt;

     setmbk(mjrmb);
#ifdef WEBCAST
     if (actliu() > btubcs()) {
          byenow(WCSFULL);
          return;
     }
#endif // WEBCAST
     grt=(usrnum != nterms-1) ? grtype[grpnum[usrnum]] : GTNONE;
     shochl(spr(strtst[grt],grtsub[grpnum[usrnum]]),
            stgchr[grt],baudat(usrptr->baud,0));
     ansperm();
     if (numaus > 0) {
          prf(getasc(AUTOSENS));
          powprf();
          bgnaus(TRUE);
          btuinj(usrnum,CYCLE);
          usrptr->state=AUTSNS;
     }
     else {
          bgnaus(FALSE);
          figlang();
     }
#ifndef GCWINNT
     saphdl(grpnum[usrnum],0);
#endif // GCWINNT
}

static VOID
ansperm(VOID)                      /* auto-set ANSI if configured to do so */
{
     switch (ansiop) {
     case 'N':  /* ON */
          usaptr->ansifl=ANSON;
          break;
     case 'F':  /* OFF */
          usaptr->ansifl=0;
          break;
     }
     stansi();
}

static GBOOL
ansisns(                           /* ANSI/ASCII autosense routine         */
UINT snccon,                       /* time since connect, 1/16 second units*/
CHAR *incbuf,                      /* bytes coming in (not NUL terminated) */
INT nbytes)                        /* number of bytes coming in            */
{
     if (usrptr->flags&NOHDWE) {
          usaptr->ansifl=atoggl ? ANSON : 0;
          return(TRUE);
     }
     if (snccon == 0) {
          btuxct(usrnum,strlen(auansi),auansi);
          return(FALSE);
     }
     while (nbytes-- > 0) {
          if (((*incbuf++)&0x7F) == 'R') {
               usaptr->ansifl=ANSON;
               return(TRUE);
          }
     }
     if (snccon >= answait) {
          usaptr->ansifl=0;
          return(TRUE);
     }
     return(FALSE);
}

static VOID                        /* auto sensing is done...              */
figlang(VOID)                      /* figure out what language we're using */
{
     usrptr->usrcls=ONLINE;
     usrptr->state=0;
     usaptr->scnbrk=24;
     rstrxf();
     stansi();
     echon();
     if (cntcand() <= 1 || langop == 1) {
          clingo=usrptr->lingo=fstcand;
          askansi();
     }
     else {
          prfmsg(LONLANGS);
          lnglist(0);
          lngfoot(0);
          outprf(usrnum);
          usrptr->substt=-2;
     }
}

static VOID
askansi(VOID)                      /* do we need to ask ANSI versus ASCII? */
{
     if (ansiop == 'K') {
          if (samend(languages[usrptr->lingo]->name,"/ANSI")) {
               prfmsg(QANSI);
               outprf(usrnum);
               usrptr->substt=-1;
          }
          else {
               usaptr->ansifl=ANSON;
               stansi();
               (*greet)();
          }
     }
     else {
          (*greet)();
     }
}

static VOID
dftgreet(VOID)                     /* about time to welcome the guy online */
{
     INT ctyp;

     btuxnf(usrnum,0,19);
     if (usrptr->flags&ISX25) {
          prfmsg(HELLO25);
     }
     else if ((ctyp=chanty[grpnum[usrnum]]) != 0) {
          prfmsg(HELLOGN,spr("via LAN (%s)",chantn[ctyp-SDFIPXD]));
     }
     else {
          prfmsg(HELLO);
     }
     if (strlen(sv.lonmsg) != 0) {
          prfmsg(LONHDR);
          prf("\r%s\r",sv.lonmsg);
          prfmsg(LONTRL);
     }
     outprf(usrnum);
     usrptr->countr=0;
     (*uidpmt)();
}

static
VOID
dftuidpmt(VOID)                    /* send the user-id prompt (or file)    */
{
     CHAR *cp;

     clrprf();
     prfmsg((*hdlsmp)() ? ENTUSID : LOGUID);
     if (strlen(prfbuf) < 40) {
          stpans(prfbuf);
          depad(cp=skpwht(prfbuf));
          if (*cp == '$' && opnans(cp+1)) {
               usrptr->substt=0;
               clrprf();
               return;
          }
          clrprf();
          prfmsg((*hdlsmp)() ? ENTUSID : LOGUID);
     }
     outprf(usrnum);
     usrptr->substt=1;
}

static
VOID
sonline(VOID)                      /* "online" channel status handler      */
{
     if (usrptr->flags&ISGCSU) {
          (*(VOIDFUNC *)(module[usrptr->state]->stsrou))();
     }
     else {
          setmbk(mjrmb);
          switch (status) {
          case POLSTS:
               dopoll();
               break;
          case CMDOK:
          case CMN2OK:
          case CM25OK:
          case RCVX29:
               break;
          case ABOREQ:
               if (usrptr->substt == 0) {
                    clfile();
                    usrptr->substt=1;
               }
               else {
                    btuclo(usrnum);
                    if (usrptr->substt == -2) {
                         lngfoot(0);
                         outprf(usrnum);
                    }
               }
               break;
          case OUTMT:
               if (usrptr->flags&INJOIP) {
                    btuoes(usrnum,0);
                    if (usrptr->substt == 2) {    /* got injo b4 done logging on*/
                         prfmsg(ENTPSW);
                         outprf(usrnum);
                    }
               }
               break;
          case CRSTG:
               hdlogo();
               break;
          case OBFCLR:
               if (btuoba(usrnum) >= outbsz-2) {
                    usrptr->flags|=ABOIP;
                    clrinp();
                    hdlogo();
                    usrptr->flags&=~ABOIP;
               }
               break;
          case IPXRER:
          case IPXUNK:
               break;
          case CYCLE:
               if (usrptr->substt == 0 && !rdfile()) {
                    usrptr->substt=1;
               }
               else if (usrptr->substt == 3) {
                    if (onbbs(usaptr->userid,1)) {
                         btuinj(usrnum,CYCLE);
                    }
                    else {
                         usrptr->substt=2;
                         strcpy(input,usaptr->userid);
                         parsin();
                         loadup();
                         finpsw();
                    }
               }
               break;
          case SPXTRM:
          case SPXWDG:
               othtrm();
          default:
               if (usrptr->substt == 0) {
                    clfile();
               }
               rstchn();
          }
     }
}

static
VOID
hdlogo(VOID)                       /* handle logon: lang, ansi, userid, psw*/
{
     INT ctyp,ilingo;
     CHAR ipBuffer[80];

     switch (usrptr->substt) {
     case -2:                      /* choosing language fm list of possible*/
          switch (*getin()) {
          case '?':
          case '\0':
               prfmsg(LONLANGS);
               lnglist(0);
               lngfoot(0);
               outprf(usrnum);
               break;
          default:
               rstrin();
               if (gcsplas(input)) {
                    break;
               }
               if (alldgs(input)) {
                    ilingo=lngposn(atoi(input));
               }
               else {
                    ilingo=lngfnd(input);
               }
               if (ilingo == -1) {
                    lngfoot(0);
                    outprf(usrnum);
               }
               else {
                    clingo=usrptr->lingo=ilingo;
                    askansi();
               }
               break;
          }
          break;
     case -1:                      /* choosing between ANSI/ASCII          */
          getin();
          bgncnc();
          if (gcsplas(nxtcmd)) {
               break;
          }
          switch (cncyesno()) {
          case 'Y':
               usaptr->ansifl|=ANSON;
               break;
          case 'N':
               usaptr->ansifl&=~ANSON;
               break;
          default:
               prfmsg(QANSI);
               outprf(usrnum);
               return;
          }
          stansi();
          (*greet)();
          break;
     case 0:                       /* user-id or new entered during file   */
     case 1:                       /* user has entered User-ID (or new)    */
          if (usrptr->substt == 0) {
               getin();
               clfile();
               usrptr->substt=1;
               if (*input == '\0') {
                    break;
               }
          }
          else if ((usrptr->flags&ABOIP) || *getin() == '\0') {
               (*uidpmt)();
               break;
          }
          rstrin();
          if (gcsplas(input)) {
               break;
          }
          if ((*hdlsmp)() && (sameas(margv[0],"new")
                          || sameas(margv[0],"\"new\""))) {
#ifdef STARTER
               dfaSetBlk(accbb);
               if (dfaCountRec() >= MAXSUSER) {
                    shocst("MAXIMUM USER COUNT REACHED","Delete a user and"
                           " run a cleanup before new users can sign-on");
                    prf("\rWorldgroup Starter cannot support additional"
                        " users at this time.");
                    byenow(PAMSG);
                    dfaRstBlk();
                    return;
               }
               dfaRstBlk();
#endif // STARTER
               shochl("New user signing up...",'?',baudat(usrptr->baud,0));
               usrptr->usrcls=SUPIPG;
               usrptr->substt=0;
               updcal();
               signup();
               break;
          }
          switch ((*chkuid)(margv[0])) {
          case LONALR:
               prfmsg((*hdlsmp)() ? ALRDON : LOGDON);
               outprf(usrnum);
               break;
          case LONDBT:
               byenow(sampln == 0 ? RMEMONL : MEMONL);
               break;
          case LONSUS:
               byenow(ACCSUS);
               break;
          case LONOCK:
               prfmsg(KNOCKPSW); /* falls through... */
          case LONOK:
               usrptr->substt=2;
               usrptr->countr=0;
               if ((*reqpsw)()) {
                    prfmsg(ENTPSW);
                    outprf(usrnum);
                    echsec(secchr,PSWSIZ-1);
               }
               else {
                    clrprf();
                    passclr();
               }
               break;
          case LONUNK:
               if (++(usrptr->countr) < 3) {
                    if (!(*hdlsmp)()) {
                         prfmsg(sampln == 0 || !(*alwsup)() ? NSUCHU : LOGNOG);
                    }
                    else {
                         prfmsg(UIDNOG);
                    }
                    outprf(usrnum);
               }
               else {
                    byenow(STRUKO);
               }
               break;
          }
          break;
     case 2:                       /* user has entered his/her password    */
          if (!(usrptr->flags&ABOIP)) {
               inplen=btuinp(usrnum,input);
          }
          if ((*chkpsw)(input)) {
               btuclo(usrnum);
               strcpy(input,"<password>");
               (*shomal_hook)();
               echon();
               passclr();
          }
          else {
               strcpy(input,"<invalid password>");
               (*shomal_hook)();
               if (++(usrptr->countr) < 3) {
                    prfmsg(PSWNOG);
                    outprf(usrnum);
               }
               else {
                    shochl("Invalid password attempt",'!',baudat(usrptr->baud,0));
                    byenow(STRUKO);
                    shocst("INVALID PASSWORD ATTEMPT","%s attempt on \"%s\"",
                           usrptr->flags&ISGCDI ? dspIPAddr(usrnum,ipBuffer)
                         : usrptr->flags&ISX25 ? "X.25"
                         : ((ctyp=chanty[grpnum[usrnum]]) != 0)
                         ? chantn[ctyp-SDFIPXD]
                         : spr("%ldbps",usrptr->baud),
                           usaptr->userid);
               }
          }
          break;
     }
}

static VOID
passclr(VOID)                      /* password cleared, continue           */
{
     if (onbbs(usaptr->userid,1)) {
          btuinj(uisusn,RING);
          usrptr->substt=3;
          btuinj(usrnum,CYCLE);
     }
     else {
          finpsw();
     }
}

static GBOOL
dftreqpsw(VOID)                    /* default handler for reqpsw           */
{
     return(TRUE);
}

static VOID
finpsw(VOID)                       /* finish logon verification            */
{
     INT ilingo;

     if (nlingo > 1
           && getgen(&genbuf,usaptr->userid)
           && (ilingo=lngfnd(genbuf.lngnam)) != -1
           && poslng[usrnum*nlingo+ilingo] >=
              poslng[usrnum*nlingo+clingo]
           && (langop == 1 || cntcand() <= 1)) {
          prfmsg(LNGSWT,languages[ilingo]->name);
          usrptr->lingo=clingo=ilingo;
          rstrxf();
     }
     if ((*chkacc)()) {
          if ((*vallon)()) {
               prfmsg(HITHAR);
               if (usrptr->flags&INVISB) {
                    prfmsg(URINVIS);
               }
               outprf(usrnum);
               audlon();
               lonstf();
          }
          else {
               byenow(PAMSG);
          }
     }
}

#ifdef GCDOS
static CHAR *                      /*   returns ip info or "" if not TCP/IP*/
dspIPAddr(                         /* return IP info or "" if not TCP/IP   */
INT usrnum,                        /*   usrnum to check                    */
CHAR *ipBuffer)                    /*   buffer to store data               */
{
     CHAR ipBuf[32];
     INT portNumber;
     HMODULE hMod;
     typedef GBOOL (*PFUNC)(INT,CHAR *,INT *);
     static GBOOL init=FALSE;
     static PFUNC pfunc;

     *ipBuffer='\0';
     if (!init) {
          init=TRUE;
          if (DosGetModHandle("GALTCPIP",&hMod) == 0) {
               if (DosGetProcAddr(hMod,
                                  "_getTCPConnectInfo",
                                  (PPFN)&pfunc) != 0) {
                    pfunc=NULL;
               }
          }
          else {
               pfunc=NULL;
          }
     }
     if (pfunc != NULL) {
          if (pfunc(usrnum,ipBuf,&portNumber) == TRUE) {
               sprintf(ipBuffer,"%s:%d",ipBuf,portNumber);
          }
     }
     return(ipBuffer);
}
#else
static CHAR *                      /*   returns ip info or "" if not TCP/IP*/
dspIPAddr(                         /* return IP info or "" if not TCP/IP   */
INT usrnum,                        /*   usrnum to check                    */
CHAR *ipBuffer)                    /*   buffer to store data               */
{
     CHAR ipBuf[32];
     INT portNumber;
     HMODULE hMod;
     typedef GBOOL (*PFUNC)(INT,CHAR *,INT *);
     static GBOOL init=FALSE;
     static PFUNC pfunc;

     *ipBuffer='\0';
     if (!init) {
          init=TRUE;
          if ((hMod=GetModuleHandle("GALTCPIP.DLL")) != NULL) {
               pfunc=(PFUNC)GetProcAddress(hMod,"_getTCPConnectInfo");
          }
          else {
               pfunc=NULL;
          }
     }
     if (pfunc != NULL) {
          if (pfunc(usrnum,ipBuf,&portNumber) == TRUE) {
               sprintf(ipBuffer,"%s:%d",ipBuf,portNumber);
          }
     }
     return(ipBuffer);
}
#endif // GCDOS

INT                                /*   see LONXXX return codes, MAJORBBS.H*/
dftuid(                            /* default (*chkuid)() (check User-ID)  */
CHAR *userid)                      /*   proposed User-ID                   */
{
     GBOOL alron;
     extern GBOOL alwknock; // in GCSASYS.C

     alron=FALSE;
     if (onbbs(userid,1)) {
          if (alwknock) {
               alron=TRUE;
          }
          else {
               return(LONALR);
          }
     }
     strcpy(input,userid);
     parsin();
     if (loadup()) {
          if (!(*hdlsmp)() && !haskey(sampky) && !(usaptr->flags&HASMST)) {
               shochl("Deadbeat user (hanging up)",9,baudat(usrptr->baud,0));
               return(LONDBT);
          }
          else if (usaptr->flags&SUSPEN) {
               shochl("Suspended user (hanging up)",9,baudat(usrptr->baud,0));
               return(LONSUS);
          }
          else {
               shochl("Logging on...",15,baudat(usrptr->baud,0));
               usrptr->usetmr=0;
               return(alron ? LONOCK : LONOK);
          }
     }
     return(LONUNK);
}

INT                                /*   returns 1=ok, 0=bad password       */
dftpsw(                            /* check password                       */
CHAR *psword)                      /*   proposed password                  */
{
     if (sameas(psword,usaptr->psword)) {
          shochl(usaptr->userid,'',baudat(usrptr->baud,0));
          updcal();
          return(1);
     }
     else {
          return(0);
     }
}

INT                                /*   returns 1=ok, 0=user bumped off    */
dftacc(VOID)                       /* check accounting information         */
{                                  /* default (*chkacc)() vector           */
     INT rc;

     setmbk(mjrmb);
     if (imbump(1)) {
          rc=0;
     }
     else {
          usrflags();
          rc=1;
     }
     rstmbk();
     return(rc);
}

VOID
usrflags(VOID)                     /* set some usrptr->flags & ->usetmr    */
{                                  /* (done after passing all login tests) */
     if (sameas(usaptr->userid,"Sysop") || (usaptr->flags&HASMST)) {
          usrptr->flags|=MASTER;
     }
     if (usaptr->flags&GOINVB) {
          if (haskey(glbkeyi)) {
               usrptr->flags|=INVISB;
          }
          else {
               usaptr->flags&=~GOINVB;
          }
     }
     usrptr->usetmr=0;
}

GBOOL                              /*   logon ok? FALSE=reason in prfbuf   */
dftlon(VOID)                       /* final logon validation (usrnum impl) */
{                                  /* default (*vallon)() vector           */
     return(TRUE);
}

INT                                /* 1=switched, 0=left alone             */
lngswt(VOID)                       /* switch languages per recorded pref   */
{                                  /* (for use by applications only)       */
     INT ilingo;

     if (nlingo > 1
      && getgen(&genbuf,usaptr->userid)
      && (ilingo=lngfnd(genbuf.lngnam)) != -1) {
          usrptr->lingo=clingo=ilingo;
          rstrxf();
          return(1);
     }
     return(0);
}

VOID
othtrm(VOID)        /* other party in an SPX session terminated the session */
{
     if (usrptr->lcstat > LSSIDLE && usrptr->lcstat <= LSSESTB) {
          usrptr->lcstat=LSSIDLE;
     }
}

static
VOID
ssupipg(VOID)                      /* sign-up is in progress               */
{
     if (usrptr->flags&ISGCSU) {
          (*(VOIDFUNC *)(module[usrptr->state]->stsrou))();
     }
     else {
          switch (status) {
          case POLSTS:
               dopoll();
               break;
          case CRSTG:
               getin();
               if (!signup()) {
                    usrflags();
                    if ((*vallon)()) {
                         shochl(usaptr->userid,'',baudat(usrptr->baud,0));
                         audlon();
                         lonstf();
                    }
                    else {
                         byenow(PAMSG);
                    }
               }
               break;
          case CYCLE:
               supcyc();
               break;
          case OBFCLR:
               if (usrptr->substt == 1) {
                    clfile();
                    cntsup();
               }
               else {
                    usrptr->flags|=ABOIP;
                    clrinp();
                    signup();
                    usrptr->flags&=~ABOIP;
               }
               break;
          case ABOREQ:
               if (usrptr->substt == 1) {
                    clfile();
                    cntsup();
               }
               else {
                    btuclo(usrnum);
               }
               break;
          case CMDOK:
          case CMN2OK:
          case CM25OK:
          case IPXPSE:
          case 251:
          case RCVX29:
               break;
          case IPXRER:
          case IPXUNK:
               break;
          case SPXTRM:
          case SPXWDG:
               othtrm();
          default:
               suphup();
               rstchn();
          }
     }
}

static
VOID
susing(VOID)                       /* "using" channel status handler       */
{
     if (usrptr->flags&ISGCSU) {
          BEG_PHASE("C/S Status handling",0);
          (*(VOIDFUNC *)(module[usrptr->state]->stsrou))();
          END_PHASE("C/S Status handling",0);
     }
     else {
          valexi=TRUE;
          if (usrptr->usrcls > SUPLOF && setjmp(eximod)) {
               inpolr=-1;
               go2mnu(JSTRET);
          }
          else {
               switch (status) {
               case POLSTS:
                    dopoll();
                    break;
               case SPXTRM:
               case SPXWDG:
                    othtrm();
               case RING:
               case LOST2C:
               case LOST25:
                    (*(VOIDFUNC *)(module00.huprou))();
                    break;
               case CRSTG:
                    if (usrptr->flags&INJOIP) {
                         btuoes(usrnum,0);
                         usrptr->flags&=~INJOIP;
                    }
                    getin();
                    if (!(usrptr->flags&OPCHAT)) {
                         hdlcri();
                    }
                    break;
               case OBFCLR:
                    if (btuoba(usrnum) >= outbsz-2) {
                         usrptr->flags|=ABOIP;
                         injacr();
                         usrptr->flags&=~ABOIP;
                    }
                    break;
               case ABOREQ:
                    btuclo(usrnum);
                    usrptr->flags|=ABOIP;
                    injacr();
                    usrptr->flags&=~ABOIP;
                    break;
               case OUTMT:
                    if (usrptr->flags&INJOIP) {
                         btuoes(usrnum,0);
                         injacr();
                         break;
                    }
               default:
                    BEG_PHASE("Status handling",module[usrptr->state]->stsrou);
                    (*(VOIDFUNC *)(module[usrptr->state]->stsrou))();
                    END_PHASE("Status handling",0);
               }
          }
          valexi=FALSE;
          setmem(eximod,sizeof(jmp_buf),0);
     }
}

VOID
condex(VOID)                       /* conditional module-exit              */
{
     if (usrptr->flags&CONCEX) {
          if (valexi) {
               usrptr->flags&=~(ABOIP+INJOIP);
               longjmp(eximod,1);
          }
          else {
               shocst("INVALID CONDEX CALL","In: %s, substt %d",
                      module[usrptr->state]->descrp,usrptr->substt);
          }
     }
}

VOID
injacr(VOID)                       /* inject a <CR> to current channel     */
{                                  /*    (re-prompt current text)          */
     usrptr->flags|=INJOIP;
     status=CRSTG;
     clrinp();
     hdlinp();
     usrptr->flags&=~INJOIP;
}

VOID
clrinp(VOID)                       /* clear user-input buffer and qtys     */
{
     input[0]='\0';
     margv[0]=input;
     inplen=0;
     margc=0;
}

static VOID
inirel(VOID)                       /* real-time check for idle channels    */
{
     INT i;

     for (i=0 ; i < nterms ; i++) {
          curusr(i);
          if (!(usrptr->flags&NOHDWE)
             && usrptr->usrcls != VACANT) {
               if (!(usrptr->flags&ACTIVE)
                && !haskey(syskey)
                && usrptr->nazapc == 0) {
                    (*hdlzap)();
               }
               usrptr->flags&=~ACTIVE;
          }
     }
     rtkick(idlzap,inirel);
}

static VOID
dftzap(VOID)                       /* default (*hdlzap)() - zap idle chan  */
{
#ifdef GCDOS
     if (isuidc(usaptr->userid[0])
      && !((usrptr->flags&(ISGCSU|WSGCSU)) && !zapcsu)
      && !((usrptr->flags&ISRIAL) && !zapser)
      && !(chanty[grpnum[usrnum]] == SDFIPXD && !zapdlan)
      && !((chanty[grpnum[usrnum]] == SDFIPXV
         || chanty[grpnum[usrnum]] == SDFSPX) && !zapvlan)) {
#else
     if (isuidc(usaptr->userid[0])
      && !((usrptr->flags&(ISGCSU|WSGCSU)) && !zapcsu)
      && !((usrptr->flags&ISRIAL) && !zapser)
      && !(grtype[grpnum[usrnum]] == GTOTHER
           && sameas(grtsub[grpnum[usrnum]],"SPX")
           && !zapvlan)) {
#endif // GCDOS
          setmbk(mjrmb);
          prfmsg(NOTACV,idlzap/60);
          othusn=usrnum;
          injoth();
          if (!(usrptr->flags&ISGCSU)) {
               btucli(usrnum);
          }
          usrptr->nazapc=4;
     }
}

static VOID
inimzt(VOID)                       /* real-time check for idle modems      */
{
     setmbk(mjrmb);
     for (usrnum=0 ; usrnum < nterms ; usrnum++) {
          usrptr=usroff(usrnum);
          if ((usrptr->flags&(NOHDWE+ISX25+ISGCDI)) == 0
           && grtype[grpnum[usrnum]] != GTLAN
           && usrptr->usrcls == VACANT
           && usrptr->state != W4KILL
           && zaptbl[usrnum] == 0) {
               rstchn();
          }
     }
     rtkick(modzap,inimzt);
}

static VOID
inirch(VOID)                       /* real-time check for idle log-offs    */
{
     setmbk(mjrmb);
     for (usrnum=0 ; usrnum < nterms ; usrnum++) {
          curusr(usrnum);
          if ((usrptr->flags&BYEBYE) && --(usrptr->byecnt) == 0) {
               finbye();
          }
          if (usrptr->nazapc > 0 && --(usrptr->nazapc) == 0) {
               setmbk(mjrmb);
               byenow(NAZAPM);
          }
     }
     rtkick(15,inirch);
}

INT
sampok(VOID)                       /* is this line a free sample one?      */
{                                  /* default routine for (*hdlsmp)() vect */
     return((*alwsup)() && (usrnum < sampln || (usrptr->flags&NOHDWE)));
}

static GBOOL
sgnupk(VOID)                       /* allow new user account creation?     */
{
     return(TRUE);
}

VOID
hdlinp(VOID)                       /* handle simulated CR-terminated input */
{
     if (usrptr->entstt != 0 && sameas(input,"x")) {
          return;   /* don't allow auto-exiting when other guys not init'd */
     }
     hdlcri();
}

static VOID
hdlcri(VOID)                       /* handle CR-terminated input           */
{
     INT i;
     GBOOL (*rouptr)(VOID);
     INT rc;

     numcat=0;
     if (!(usrptr->flags&NOGLOB)) {
          BEG_PHASE("Global commands",0);
          for (i=0 ; i < nglobs ; i++) {
               BEG_PHASE("Global command",globs[i]);
               rc=(*(INT (*)(VOID))(globs[i]))();
               switch (rc) {
               case -2:
                    outprf(usrnum);
               case -1:
                    END_PHASE("Global commands",rc);
                    return;
               case 0:
                    break;
               default:
                    injacr();
                    END_PHASE("Global commands",rc);
                    return;
               }
          }
          END_PHASE("Global commands",0);
     }
     switch (usrptr->usrcls) {
     case SUPLON:
          if (usrptr->polrou == nxtlon) {
               return;
          }
          rouptr=module[usrptr->state]->lonrou;
          break;
     case SUPLOF:
          rouptr=module[usrptr->state]->lofrou;
          break;
     default:
          rouptr=module[usrptr->state]->sttrou;
     }
     BEG_PHASE("Text line input",0);
     if ((i=(*rouptr)()) == 0) {
          usrptr->substt=0;
          usrptr->crdrat=mmucrr;
          switch (usrptr->usrcls) {
          case SUPLON:
               nxtlon();
               break;
          case SUPLOF:
               if (usrptr->state == usrptr->lofstt) {
                    go2mnu(JSTRET);
               }
               else {
                    nxtlof();
               }
               break;
          default:
               go2mnu(JSTRET);
          }
     }
     else if (i == -1 && usrptr->usrcls == SUPLOF) {
          go2mnu(JSTRET);
     }
     END_PHASE("Text line input",i);
}

static GBOOL
dfterf(                            /* is this an "error free" channel?     */
INT chan)                          /*   channel to check on                */
{                                  /*   (default (*iserrf)() hook)         */
     switch (grtype[grpnum[chan]]) {
     case GTLAN:
          if (chanty[grpnum[chan]] == SDFSPX) {
               return(TRUE);
          }
          break;
     case GTOTHER:
          if (sameas(grtsub[grpnum[chan]],"TCP/IP")) {
               return(TRUE);
          }
          break;
     }
     return(FALSE);
}

GBOOL
uidxst(                            /* does this User-ID exist on this sys? */
const CHAR *uid)                   /*   User-ID to check                   */
{
     GBOOL flg;
     CHAR tmpuid[UIDSIZ];

     ASSERT(uid != NULL);
     dfaSetBlk(accbb);
     stlcpy(tmpuid,uid,UIDSIZ);
     flg=(dfaAcqEQ(NULL,tmpuid,0)
        && !(((struct usracc *)accbb->data)->flags&DELTAG));
     dfaRstBlk();
     return(flg);
}

CHAR *                             //   rets correct caps, NULL if not exists
uidCaps(                           // get userid's correct capitalization
CHAR *uid)                         //   user-ID to get caps for
{
     static CHAR tmpuid[UIDSIZ];

     ASSERT(uid != NULL);
     dfaSetBlk(accbb);
     stlcpy(tmpuid,uid,UIDSIZ);
     if (dfaAcqEQ(NULL,tmpuid,0)) {
          dfaRstBlk();
          return(((struct usracc *)accbb->data)->userid);
     }
     dfaRstBlk();
     return(NULL);
}

VOID
clrxrf(VOID)                       /* clear current user's x-ref data      */
{
     if (numxrf) {
          setmem(&xrfpos[xrfidx(0)],sizeof(LONG)*numxrf,0);
     }
}

VOID
addxrf(newuid)                     /* add new x-refs for a user-id         */
CHAR *newuid;                           /* new user-id to add              */
{
     xrfutl(newuid,1);
}

VOID
delxrf(userid)                     /* delete all x-refs for a user-id      */
CHAR *userid;                           /* user-id to delete x-refs for    */
{
     xrfutl(userid,0);
}

static VOID
xrfutl(userid,add)                 /* low-level x-ref add and delete util  */
CHAR *userid;                           /* user-id to add or delete        */
INT add;                                /* add this guy? (1=yes 0=no, del) */
{
     CHAR *bas;
     struct uidxrf tmpxrf;

     dfaSetBlk(xrfbb);
     stzcpy(uidxrf.userid,userid,UIDSIZ);
     bas=userid;
     do {
          bas=skpwht(bas);
          stzcpy(uidxrf.xrfstg,bas,XRFSIZ);
          uidxrf.xrfstg[XRFSIZ]=(sameas(uidxrf.xrfstg,uidxrf.userid) ? 0 : 1);
          if (add) {
               dfaInsert(&uidxrf);
          }
          else {
               if (dfaAcqEQ(&tmpxrf,uidxrf.xrfstg,0)) {
                    do {
                         if (sameas(uidxrf.userid,tmpxrf.userid)) {
                              dfaDelete();
                         }
                    } while (dfaAcqNX(&tmpxrf));
               }
          }
     } while (*(bas=skpwrd(bas)) != '\0');
     dfaRstBlk();
}

INT
hdluid(stg)                        /* handle the entering of a user-id     */
CHAR *stg;                              /* string entered as user-id       */
{
     CHAR uid[UIDSIZ];
     INT num,retval=1;

     dfaSetBlk(xrfbb);
     if (xrfpos[xrfidx(0)] != 0L && (numxrf == 1 ||
         xrfpos[xrfidx(1)] == 0L)) {
          if (lingyn(stg[0]) == 'Y') {
               dfaGetAbs(&uidxrf,xrfpos[xrfidx(0)],0);
               retval=UIDFND;
          }
          else {
               retval=UIDPMT;
          }
     }
     else {
          stzcpy(uid,stg,UIDSIZ);
          if (numxrf > 1 && isdigit(*uid) && strlen(uid) < 3 &&
              xrfpos[xrfidx(1)] != 0L) {
               num=atoi(uid);
               if (num > 0 && num <= numxrf && xrfpos[xrfidx(num-1)] != 0L) {
                    dfaGetAbs(&uidxrf,xrfpos[xrfidx(num-1)],0);
                    retval=UIDFND;
               }
          }
     }
     clrxrf();
     if (retval == 1) {
          retval=newxrf(uid);
     }
     dfaRstBlk();
     return(retval);
}

static INT
newxrf(uid)                        /* get new x-ref listing for user       */
CHAR *uid;                              /* for *this* partial user-id      */
{
     CHAR xrf[XRFSIZ+1];
     struct uidxrf tmpxrf;
     INT retval=UIDPMT,num=0;

     setmbk(mjrmb);
     setmem(&tmpxrf,sizeof(struct uidxrf),0);
     stzcpy(xrf,uid,XRFSIZ);
     xrf[XRFSIZ]='\0';        /* this char is a 1 or 0 in the data file    */
     if (!dfaAcqGE(&uidxrf,xrf,0)) {
          prfmsg(NOXREF);
     }
     else if (numxrf == 0) {
          if (sameas(uidxrf.userid,uid)) {
               retval=UIDFND;
          }
          else {
               prfmsg(NOXREF);
          }
     }
     else {
          prfmsg(sameas(uid,uidxrf.userid) ? XRFHDM : XRFHDR);
          do {
               if (!sameto(xrf,uidxrf.xrfstg)) {
                    break;
               }
               if (!sameas(uidxrf.userid,tmpxrf.userid)) {
                    xrfpos[xrfidx(num++)]=dfaAbs();
                    prfmsg(XRFLIN,num,uidxrf.userid);
               }
               if (!dfaQueryNX()) {
                    break;
               }
               movmem(&uidxrf,&tmpxrf,sizeof(struct uidxrf));
               movmem(xrfbb->data,&uidxrf,sizeof(struct uidxrf));
          } while (num < numxrf);
          if (num == 0) {
               clrprf();
               prfmsg(NOXREF);
          }
          else if (num == 1) {
               dfaGetAbs(&uidxrf,xrfpos[xrfidx(0)],0);
               clrprf();
               if (sameas(uid,uidxrf.userid)) {
                    xrfpos[xrfidx(0)]=0L;
                    retval=UIDFND;
               }
               else {
                    prfmsg(THSXRF,uidxrf.userid);
                    retval=UIDCAL;
               }
          }
          else {
               prfmsg(XRFOOT);
          }
     }
     rstmbk();
     return(retval);
}

VOID
delgen(                            /* delete generic data records for user */
const CHAR *userid)                /*   userid to delete records for       */
{
     CHAR usrmdl[UIDSIZ+MNMSIZ];

     dfaSetBlk(genbb);
     stzcpy(usrmdl,userid,UIDSIZ+MNMSIZ);
     if (dfaQueryGE(usrmdl,0)) {
          do {
               dfaAbsRec(NULL,0);
               if (!sameas((CHAR *)genbb->data,usrmdl)) {
                    break;
               }
               dfaDelete();
          } while (dfaQueryNX());
     }
     dfaRstBlk();
}

static GBOOL
prtpsk(                            /* validate the _PORT# pseudokey        */
INT unum,                          /*   user number trying to use key      */
const CHAR *lock)                  /*   lock name key is for               */
{
     INT chn;

     sscanf(lock+strlen("_PORT#"),"%x",&chn);
     return(channel[unum] == chn);
}

static GBOOL
grppsk(                            /* validate the _GROUP# pseudokey       */
INT unum,                          /*   user number trying to use key      */
const CHAR *lock)                  /*   lock name key is for               */
{
     INT grp;

     sscanf(lock+strlen("_GROUP#"),"%d",&grp);
     return(grpnum[unum] == grp);
}

static GBOOL
lngpsk(                            /* validate the _LANG= pseudokey        */
INT unum,                          /*   user number trying to use key      */
const CHAR *lock)                  /*   lock name key is for               */
{
     CHAR *ptr;
     static CHAR lngnam[LNGSIZ];

     strcpy(lngnam,languages[usroff(unum)->lingo]->name);
     if ((ptr=strchr(lngnam,'/')) != NULL) {
          *ptr='\0';
     }
     return(sameas(lngnam,&lock[6]));
}

static GBOOL
propsk(                            /* validate the _PROT= pseudokey        */
INT unum,                          /*   user number trying to use key      */
const CHAR *lock)                  /*   lock name key is for               */
{
     CHAR *ptr;
     static CHAR prot[LNGSIZ];

     strcpy(prot,languages[usroff(unum)->lingo]->name);
     if ((ptr=strchr(prot,'/')) != NULL) {
          strcpy(prot,ptr+1);
     }
     else {
          prot[0]='\0';
     }
     return(sameas(prot,&lock[6]));
}

static GBOOL
gcspsk(                            /* validate the _ISGCSU pseudokey       */
INT unum,                          /*   user number trying to use key      */
const CHAR *lock)                  /*   lock name key is for               */
{
     LONG flags;

     flags=usroff(unum)->flags;
     if (samend(lock,"_TMODE")) {        /* is in terminal mode */
          flags&=~ISGCSU;
     }
     else if (samend(lock,"_CS")) {     /* is not in terminal mode */
          flags&=~WSGCSU;
     }
     return((flags&(ISGCSU|WSGCSU)) != 0L);
}

INT
onsys(                             /* conditional check if user is on the  */
const CHAR *uid)                   /* system and class > SUPIPG            */
{
     return(onsysn(uid,0));
}

INT
onsysn(                            /* check "uid" to see if on the system  */
const CHAR *uid,                   /*   User-ID to look for                */
GBOOL invis)        /* 0=use INVISB flag in decision, 1=ignore INVISB flag */
{
     for (othusn=0 ; othusn < nterms ; othusn++) {
          othusp=usroff(othusn);
          othuap=uacoff(othusn);
          if (othusp->usrcls > SUPIPG && sameas(uid,othuap->userid)) {
               if (invis || !(othusp->flags&INVISB)) {
                    return(TRUE);
               }
          }
     }
     return(FALSE);
}

INT
uinsys(             /* conditional check to see if a user is on the system */
const CHAR *uid)                                       /* Even logging in! */
{
     return(onbbs(uid,0));
}

INT
onbbs(              /* is a userid ANYWHERE on the svr? (even logging in)  */
const CHAR *uid,
GBOOL invis)        /* 0=use INVISB flag in decision, 1=ignore INVISB flag */
{
     if (*uid != '\0') {
          for (uisusn=0 ; uisusn < nterms ; uisusn++) {
               if (uisusn != usrnum && sameas(uid,uacoff(uisusn)->userid)) {
                    if (invis || !(usroff(uisusn)->flags&INVISB)) {
                         return(TRUE);
                    }
               }
          }
     }
     return(FALSE);
}

INT
instat(                            /* is userid in a given state?          */
const CHAR *uid,
INT qstate)
{
     for (othusn=0 ; othusn < nterms ; othusn++) {
          othusp=usroff(othusn);
          othuap=uacoff(othusn);
          if (othusp->state == qstate && sameas(uid,othuap->userid)) {
               if (!(othusp->flags&INVISB)) {
                    return(TRUE);
               }
          }
     }
     return(FALSE);
}

CHAR *
catfix1(VOID)                      /* Worldgroup catfix1() for catamsg()   */
{
     static CHAR ufbuff[80];
     struct usracc *uap;

     if (user != NULL
      && usrnum >= 0
      && usrnum < nterms
      && (uap=uacoff(usrnum)) != NULL
      && goodstg(uap->userid)
      && channel != NULL) {
          sprintf(ufbuff,"CH%02X=%s:%d/%d",channel[usrnum],uap->userid,
                  usroff(usrnum)->state,usroff(usrnum)->substt);
     }
     else {
          sprintf(ufbuff,"U#%04X",usrnum);
     }
     return(ufbuff);
}

static
VOID
inimod(VOID)                       /* initialize the Worldgroup modules    */
{
     register_module(&module00);
     opngdp();
     inisysagt();
     if (ripdfd) {
          inirmdl();
     }
     initPaging();
     callinits();
     inigcsp();
}

VOID
prepff(VOID)                      /* prepare vacant chans for further stuff */
{   /* rsmode implicit input: NORMRS=incoming calls, BUSYRS/NANSRS=shutdown */
     for (usrnum=0 ; usrnum < nterms ; usrnum++) {
          usrptr=usroff(usrnum);
          rsmodes[usrnum]=rsmode;
          if (usrptr->usrcls == VACANT
            && (rsmode == NORMRS || usrptr->state != W4KILL)) {
               rstchn();
          }
     }
}

VOID
ckmcu(VOID)                        /* check for time for midnight cleanup  */
{
     INT hr;

     if ((hr=dthour(now())) != lasthr) {
          lasthr=hr;
          if (shterc[hr] >= 0) {
               mcuctr=0;
               errcod=shterc[dthour(now())];
               rsmode=rsetop;
               prepff();
          }
     }
     if (mcuctr >= 0 && mcuctr <= mcumin) {
          if (mcuctr == mcumin) {
               hupall();
          }
          else if (mcuctr >= mcumin-mcuwrn) {
               kalert(errcod == 0 ? GOINGD2 : GOINGM,mcumin-mcuctr);
          }
          mcuctr++;
     }
#ifndef GCWINNT
     if (sapsup) {
          sapmin();
     }
#endif // GCWINNT
     rtkick(60,ckmcu);
}

VOID
zapdec(VOID)                       /* decrement zap table counters         */
{
     INT i;

     for (i=0 ; i < nterms ; i++) {
          if (zaptbl[i] > 0) {
               zaptbl[i]=max(0,zaptbl[i]-ZAPINT);
               if (zaptbl[i] == 0) {
                    usrnum=i;
                    rstchn();
               }
          }
     }
     rtkick(ZAPINT,zapdec);
}

static VOID
updclk(VOID)                       /* update DOS clock from CMOS           */
{
#ifdef GCDOS
     union REGS regs;
     INT totsec,dtots;
     CHAR hour,min,sec,dh,dm,ds;

     regs.x.ax=0x0200;                  /* get CMOS time                   */
     int86(0x1A,&regs,&regs);
     if (regs.x.cflag == 0) {           /* (ignore errors until next pass) */
          hour=(regs.h.ch&0x0F)+(regs.h.ch>>4)*10;
          min=(regs.h.cl&0x0F)+(regs.h.cl>>4)*10;
          sec=(regs.h.dh&0x0F)+(regs.h.dh>>4)*10;
          totsec=min*60+sec;
          regs.x.ax=0x2C00;                  /* get DOS time                    */
          int86(0x21,&regs,&regs);
          dh=regs.h.ch;
          dm=regs.h.cl;
          ds=regs.h.dh;
          dtots=dm*60+ds;
          if (dh == hour && abs(totsec-dtots) > 60) {
               regs.x.ax=0x2D00;             /* set DOS time                    */
               regs.h.ch=hour;
               regs.h.cl=min;
               regs.h.dh=sec;
               regs.h.dl=0;
               int86(0x21,&regs,&regs);
          }
     }
     rtkick(120,updclk);
#endif // GCDOS
}

static VOID
kalert(                            /* system shutdown alert                */
INT msgnum,
INT nmins)
{
     setmbk(mjrmb);
     prfmlt(msgnum,nmins,(nmins == 1 ? "" : "s"));
     for (othusn=0 ; othusn < nterms ; othusn++) {
          if (usroff(othusn)->usrcls > SUPIPG) {
               injoth();
          }
     }
     clrmlt();
}

GBOOL
absdtdy(VOID)                      /* abnormal shutdown (crash) today?     */
{
     return(isfile("wgsabsd.flg"));
}

VOID
midnit(VOID)                       /* Worldgroup midnight cleanup function */
{
     INT i;
     VOID (*rouptr)(VOID);

     gsbldn();
     if (accmcu()) {
          for (i=1 ; i < nmods ; i++) {
               if ((rouptr=module[i]->mcurou) != NULL) {
                    clingo=0;
                    BEG_PHASE("Cleanup",i);
                    (*rouptr)();
                    END_PHASE("Cleanup",0);
               }
          }
          BEG_PHASE("Cleanup vectors",0);
          dovecs(mcuhdl);
     }
}

VOID
mjrdla(                            /* call all delete account hooks        */
CHAR *userid)
{
     INT i,nvecs;

     nvecs=ninarr(dlahdl);
     for (i=0 ; i < nvecs ; i++) {
          (*(VOID (**)(CHAR *))arrelem(dlahdl,i))(userid);
     }
     pageDel(userid);
}

static VOID
dopoll(VOID)                       /* call a user's polling routine now    */
{
     if (usrptr->polrou != NULL) {
          inpolr=usrnum;
          BEG_PHASE("Polling",usrptr->polrou);
          (*(VOIDFUNC *)(usrptr->polrou))();
          END_PHASE("Polling",0);
          inpolr=-1;
          if (usrptr->polrou != NULL) {
               btuinj(usrnum,POLSTS);
          }
     }
}

CHAR *
getin(VOID)                        /* get input, parse, return first arg   */
{
     paccin();
     parsin();
     return(margv[0]);
}

VOID
parsin(VOID)                       /* parse input into margv[], etc.       */
{
     CHAR *inpptr;

     margc=0;
     inplen=0;
     inpptr=input-1;
     while (1) {
          while (*++inpptr == ' ') {
          }
          if (*inpptr == '\0') {
               break;
          }
          margv[margc]=inpptr;
          while (*++inpptr != ' ') {
               if (*inpptr == '\0') {
                    margn[margc++]=inpptr;
                    inplen=(INT)(inpptr-input);
                    return;
               }
          }
          *inpptr='\0';
          margn[margc++]=inpptr;
     }
     if (margc != 0) {
          inplen=(INT)(margn[margc-1]-input);
          setmem(margn[margc-1],sizeof(input)-inplen,0);
     }
     else {
          margv[0]="";
     }
}

VOID
paccin(VOID)             /* get user input and do initial preprocessing    */
{
     inplen=btuinp(usrnum,input);
     paccit();
}

VOID
paccit(VOID)             /* show input on modem monitor, check profanity   */
{
     if (!(usrptr->flags&MONHID)) {
          (*shomal_hook)();
     }
     (*setpfn)(input);
}

VOID                     /*   sets global pfnlvl variable                  */
dftpfn(                  /* default profanity checker routine              */
CHAR *inp)               /*   input on which to check profanity for curusr */
{                        /* current user must be valid for this routine    */

     if ((pfnlvl=profan(inp)) != 0) {
          if (haskey(syskey)) {
               pfnlvl=0;
          }
          else if (pfnlvl > pfceil) {
               pfnlvl=pfceil;
          }
     }
     if (pfnlvl > 1) {
          usrptr->pfnacc+=pfnlvl;
     }
}

VOID
rstrin(VOID)                  /* restore parsed input to a single string   */
{
     INT i;

     for (i=0 ; i < margc-1 ; i++) {
          *margn[i]=' ';
     }
}

VOID
chkdft(                       /* check for default (<CR>) input            */
CHAR c)
{
     if (margc == 0) {
          if (c != '\0') {
               input[0]=c;
               input[1]='\0';
               margn[0]=input+1;
               margc=1;
          }
          margv[0]=input;
          inplen=strlen(input);
     }
}

CHAR
getdft(VOID)                  /* return default char based on cur prfbuf   */
{
     INT len;
     CHAR rv='\0',*ptr;
     static CHAR wrkbuf[80];

     if (prfptr != prfbuf && !isspace(*(prfptr-1))) {
          stzcpy(wrkbuf,ptr=lastwd(prfbuf),sizeof(wrkbuf));
          stpans(wrkbuf);
          if ((len=strlen(wrkbuf)) > 0) {
               rv=wrkbuf[len-1];
               if (len == strlen(ptr) || len == strlen(skpans(ptr))) {
                    *(--prfptr)='\0';
               }
               else {
                    *(skpans(ptr)+len-1)=' ';
               }
          }
     }
     return(rv);
}

static VOID
depadb(VOID)                       /* strip whitespace and \b's from prfbuf*/
{
     INT n;
     CHAR *ptr;

     for (n=strlen(prfbuf),ptr=prfbuf+n ; n > 0 ; n--) {
          ptr--;
          if (!isspace(*ptr) && *ptr != '\b') {
               *(ptr+1)='\0';
               break;
          }
     }
     prfptr=prfbuf+strlen(prfbuf);
}

static VOID
finbye(VOID)                       /* finish up byenow() processing        */
{
     usrptr->flags&=~BYEBYE;
     btuoes(usrnum,0);
     imdrop();
}

VOID
imdrop(VOID)                       /* immediately drop current user channel*/
{
     if (usrptr->usrcls > SUPIPG) {
          (*(VOIDFUNC *)(module00.huprou))();
     }
     else {
          if (usrptr->usrcls == SUPIPG) {
               suphup();
          }
          rstchn();
     }
}

LONG
lincst(                     /* find cost of using X.25 line for 15 seconds */
INT unum)                               /* for this user                   */
{
     INT grp;
     LONG deltap,deltab,rc;

     if (usroff(unum)->flags&ISX25) {
          grp=grpnum[unum];
          rc=(ksec[grp]>>2)+((usroff(unum)->minut4&3)<(ksec[grp]&3));
          deltab=btuset(unum,CNTCHR,0L);
          deltap=btuset(unum,CNTPAK,0L);
          rc+=(deltab*kchar[grp]+500)/1000+
              (deltap*kpak[grp]+500)/1000;
          deltab+=sv2.x25bs;
          sv2.x25mbs+=deltab/1000000L;
          sv2.x25bs  =deltab%1000000L;
          deltap+=sv2.x25ps;
          sv2.x25kps+=(INT)(deltap/1000L);
          sv2.x25ps  =(INT)(deltap%1000L);
          return(rc);
     }
     return(0L);
}

VOID
rstchn(VOID)                       /* completely reset a modem channel     */
{
     extern INT chnemd;

     if (usrnum == chnemd) {
          emdisp(" * RESET * ");
     }
     usrptr=usroff(usrnum);
     if (usrptr->lcstat == LSSESTB) {
          btucmd(usrnum,"T");
          if (catact == 0) {
               shochl("SPX Terminating...",'T',baudat(usrptr->baud,0));
          }
          usrptr->lcstat=LSSTERM;
          btuinj(usrnum,CYCLE);
          usrptr->usrcls=VACANT;
          usrptr->state=W4SPXT;
          usrptr->tspxt=btuTicker();
          return;
     }
     BEG_PHASE("Channel reset",0);
     (*hdlrst)();
     END_PHASE("Channel reset",0);
}

static VOID
dftrst(VOID)                       /* default (*hdlrst)() reset handler    */
{
     INT rc;

     gcsprst();
     if (usrptr->keys != NULL) {
          freekey();
     }
     setmem(usrptr,sizeof(struct user),0);
     mnuusr=mnuoff(usrnum);
     setmem(mnuusr,sizeof(struct usrmnu),0);
     setmem(uacoff(usrnum),sizeof(struct usracc),0);
     rcdbaud(mxbaudl[grpnum[usrnum]]);
     lincst(usrnum);
     usrptr->tckrst=lngtck;
     switch (rc=bturst(usrnum)) {
     case 3:
          rstlan(1);
          break;
     case 2:
          rstx25("X.25 channel ready for use",'.');
          break;
     case 4:
          if (grtype[grpnum[usrnum]] == GTOTHER) {
               rstcdi(1);
               break;
          }
     case 1:
          btubbr(usrnum,usrptr->baud);
          btuhwh(usrnum,outbsz-20);
          btuusp(usrnum,uspmod);
     case 0:
          if (btuffo(usrnum,0x07) != 0) {
               usrptr->flags|=NON550;
          }
          if (catact == 0) {
               shochl("Initializing...",'.',baudat(usrptr->baud,0)&0xF7);
          }
          if (rc == 0) {
               usrptr->flags|=IS1201;
          }
          btulok(usrnum,1);
          btucmd(usrnum,"^E=");
          break;
     default:
          usrptr->flags|=NOHDWE;
          switch (grtype[grpnum[usrnum]]) {
          case GTOTHER:
               rstcdi(0);
#ifndef GCDOS
               if (usrnum == nterms-1) {
                    locchn(usrnum);
                    shochl("Local Session ",'-',baudat(usrptr->baud,0)&0xF7);
               }
#endif // GCDOS
               break;
          case GTLAN:
               rstlan(0);
               break;
          case GTX25:
               rstx25("Non-Hardware X.25",'-');
               break;
          default:
               rcdbaud((LONG)maxpol);
               if (catact == 0) {
                    shochl(usrnum == nterms-1
                           ? "Local Session"
                           : "Non-Hardware",'-',baudat(usrptr->baud,0)&0xF7);
               }
               btulok(usrnum,1);
               btucmd(usrnum,"^E=");
#ifdef GCWINNT
               if (usrnum != nterms-1) {
                    usrptr->state=AWAITC;
               }
#endif
               break;
          }
     }
     btuscr(usrnum,'\n');
     btutru(usrnum,'O'&0x1F);
     btutrs(usrnum,1);
     btumil(usrnum,DFTIMX);
     btuech(usrnum,0);
     btucli(usrnum);
     zaptbl[usrnum]=0;
}

VOID
rstx25(                            /* reset-related x.25 activity          */
CHAR *dspstg,
CHAR dspc)
{
     usrptr->flags|=ISX25;
     rcdbaud(38400L);
     if (rsmodes[usrnum] != NORMRS) {
          kiletc("Disabled X.25");
     }
     else {
          if (catact == 0) {
               shochl(dspstg,dspc,baudat(usrptr->baud,0)&0xF7);
          }
          usrptr->state=AWAITC;
     }
}

VOID
rstlan(                                       /* reset-related LAN activity */
INT ishard)
{
     INT group,ci,i,chan,found;
     CHAR *cp;

     if (catact == 0) {
          shochl("LAN channel ready for use",'.',baudat(usrptr->baud,0)&0xF7);
     }
     rcdbaud(38400L);
     if (rsmodes[usrnum] != NORMRS) {
#ifndef GCWINNT
          saphdl(grpnum[usrnum],0);
#endif // GCWINNT
          kiletc("Disabled LAN");
          return;
     }
     usrptr->state=AWAITC;
     if (!ishard) {
          if (catact == 0) {
               shochl("Non-Hardware LAN",'-',baudat(usrptr->baud,0)&0xF7);
          }
          return;
     }
     switch (chanty[group=grpnum[usrnum]]) {
     case SDFIPXD:
          setmbk(mjrmb);
          cp=rawmsg(IPXADR1+(group-1)*(GROUP2-GROUP1));
          rstmbk();
          chan=usofgr[group];
          ci=0;
          found=0;
          stpans(cp);
          while (*cp != '\0') {
               while (*cp != '\0' && !isxdigit(*cp)) {
                    cp++;
               }
               if (*cp == '\0') {
                    break;
               }
               for (i=0 ; i < 24 && isxdigit(*cp) ; i++,cp++) {
                    *cp=toupper(*cp);          /* (btucmd() will need u.c.) */
               }
               if (i == 24 && !isxdigit(*cp)) {
                    if (chan+ci == usrnum) {
                         *cp='\0';
                         cp-=24;
                         found=1;
                         break;
                    }
                    ci++;
               }
               else {
                    while (isxdigit(*cp)) {
                         cp++;
                    }
               }
          }
          if (!found) {
               if (catact == 0) {
                    shochl("ERROR: bad IPX address",'#',0x9c);
               }
               return;
          }
          btucmd(usrnum,"W");
          btucmd(usrnum,cp);
          btucmd(usrnum,"M");
          usrptr->lcstat=LSDCOMM;
          break;
     case SDFIPXV:
          usrptr->lcstat=LSVRAWP;
          btutrg(usrnum,sizeof(struct ipxhdr));
          break;
     case SDFSPX:
          btucmd(usrnum,"L");
          usrptr->lcstat=LSSINWT;
          break;
     }
#ifndef GCWINNT
     saphdl(grpnum[usrnum],1);
#endif // GCWINNT
}

VOID
rstcdi(INT ishard)                 /* Reset a GCDI channel                 */
{
     CHAR *type;

     usrptr->flags|=ISGCDI;
     rcdbaud(38400L);
     type=grtsub[grpnum[usrnum]];
     if (rsmodes[usrnum] != NORMRS) {
          kiletc(spr("Disabled %s channel",type));
     }
     else {
          if (catact == 0) {
               shochl(spr(ishard ? "%s channel ready..."
                                 : "Non-Hardware %s channel",type),
                      ishard ? '.' : '-',baudat(usrptr->baud,0)&0xF7);
          }
          usrptr->state=AWAITC;
     }
}

INT
bbsfvc(                            /* find a vacant GCDI channel (-1=none) */
CHAR *chtype)                      /* corresponding to MDF "Channel type:" */
{
     INT unum;
     struct user *uptr;

     for (unum=0 ; unum < nterms ; unum++) {
       uptr=usroff(unum);
          if (uptr->usrcls == VACANT
           && uptr->state == AWAITC
           && (uptr->flags&(NOHDWE+ISGCDI)) == ISGCDI
           && sameas(chtype,grtsub[grpnum[unum]])) {
               return(unum);
          }
     }
     return(-1);
}

INT
numcdi(                            /* count number of GCDI channels        */
CHAR *chtype)                      /* corresponding to MDF "Channel type:" */
{                                  /* (or NULL to count all GCDI types)    */
     INT unum;
     INT num=0;

     for (unum=0 ; unum < nterms ; unum++) {
          if (grtype[grpnum[unum]] == GTOTHER
           && (chtype == NULL || sameas(chtype,grtsub[grpnum[unum]]))) {
               num++;
          }
     }
     return(num);
}

INT
numliccdi(                         /* count of licensed GCDI channels      */
CHAR *chtype)                      /* corresponding to MDF "Channel type:" */
{                                  /* (or NULL to count all GCDI types)    */
     INT unum;
     INT num=0;
     INT nlic;

     nlic=min(nterms,btusrs);
     for (unum=0 ; unum < nlic ; unum++) {
          if (grtype[grpnum[unum]] == GTOTHER
           && (chtype == NULL || sameas(chtype,grtsub[grpnum[unum]]))) {
               num++;
          }
     }
     return(num);
}

INT
ningrp(                            /* count the number of chans in a group */
INT group)                         /* channel group (1-16)                 */
{
     INT i,count;

     for (i=0,count=0 ; i < nterms ; i++) {
          if (grpnum[i] == group) {
               count++;
          }
     }
     return(count);
}

INT
numonl(                            /* count channels online in given module*/
INT state)                         /* module number / state code           */
{
     INT i,n=0;
     struct user *uptr;

     for (i=0 ; i < nterms ; i++) {
          uptr=usroff(i);
          if (uptr->state == state
           && (uptr->usrcls > SUPIPG
            || uptr->usrcls == BBSPRV)) {
               n++;
          }
     }
     return(n);
}

INT
bbscon(                            /* somebody wants to connect to the BBS */
INT unum)                          /* usrnum-style channel number          */
{                                  /* 1=ok 0=channel busy/inappropriate    */
     struct user *uptr;

     if (0 <= unum && unum < nterms) {
          uptr=usroff(unum);
          if (uptr->usrcls == VACANT
           && uptr->state == AWAITC
#ifdef GCDOS
           && !(uptr->flags&NOHDWE)
#endif
           && (uptr->flags&ISGCDI)) {
               uptr->state=WCDICN;
               btuinj(unum,CDICONN);
               return(1);
          }
     }
     return(0);
}

VOID
bbsdsc(                            /* disconnect an interactive user       */
INT unum)                          /* user number                          */
{
     kilchn(unum);
}

VOID
rstrxf(VOID)                       /* restore screen-length to usracc setting */
{
     btuxnf(usrnum,0,-19,usaptr->scnbrk-CTNUOS,scnpaus[usrptr->lingo]);
     btuhpk(usrnum,hpkrou);
     btupbc(usrnum,20);
     btucpc(usrnum,19);
}

INT
hpkrou(                            /* "handle pause key" irp routine       */
INT chan,
CHAR c)
{
     if (!isripo(chan)) {
          switch (toupper(c)) {
          case 'N':
               return(2);
          case 'Q':
               chiinj(chan,ABOREQ);
               chiout(chan,c);
               chious(chan,"\r\n");
               return(0);
          case 19:  /* XOFF char (CTRL-S)    */
          case 17:  /* XON char (CTRL-Q)     */
               return(0);
          default:
               return(1);
          }
     }
     else {
          switch (toupper(c)) {
          case 14:  /* CTRL-N */
              return(2);
          case 24:  /* CTRL-X */
               chiinj(chan,ABOREQ);
               chiout(chan,c);
               chious(chan,"\r\n");
               return(0);
          case '\r':
          case '\b':
               return(1);
          default:
               return(0);
          }
     }
}

VOID
stansi(VOID)                       /* set ANSI handling to usracc setting  */
{
     btucmd(usrnum,(usaptr->ansifl&ANSON) ? "[" : "]");
}

VOID
echon(VOID)                                                 /* turn echo on */
{
     echonu(usrnum);
}

VOID
echonu(                                             /* turn echo on utility */
INT usrnum)
{
     btuech(usrnum,echtyp[grpnum[usrnum]]);
     if (usrptr->wid > 0) {
          btuchi(usrnum,NULL);
          usrptr->wid=0;
     }
}

VOID
echsec(                            /* echo "secret" character code         */
CHAR ech,                          /* character to echo                    */
INT lwidth)                        /* maximum line width                   */
{
     btuech(usrnum,0);
     usrptr->col=0;
     usrptr->wid=(CHAR)(max(1,min(255,lwidth)));
     usrptr->ech=ech;
     btuchi(usrnum,secchi);
}

static CHAR
secchi(chan,c)                     /* btuchi() routine for '*' echoing     */
INT chan;
INT c;
{
     struct user *uptr;

     uptr=usroff(chan);
     switch (c&=eurmsk) {
     case '\r':
          uptr->col=0;
          chiout(chan,c);
          break;
     case '\b':
     case 0x7F:
          c='\b';
          if (uptr->col > 0) {
               uptr->col--;
               chiout(chan,c);
               chiout(chan,' ');
               chiout(chan,c);
          }
          break;
     default:
          if (c >= ' ' && uptr->col < uptr->wid) {
               uptr->col++;
               chiout(chan,uptr->ech);
          }
          else {
               c=0;
          }
          break;
     }
     return(c);
}

INT
injoth(VOID)                       /* inject a message into othusn's chan. */
{
     HMCVFILE savmb;
     INT retval=1,ilingo;
     GBOOL (*rouptr)(VOID);

     savmb=curmbk;
     if (mltflg) {
          prfbuf=ptrblok(prfbuffers,usroff(othusn)->lingo);
     }
     if (usroff(othusn)->flags&NOINJO) {
          retval=0;
     }
     else if ((rouptr=module[usroff(othusn)->state]->injrou) == NULL) {
          dftinj();
     }
     else {
          BEG_PHASE("Inject message",othusn);
          retval=rouptr();
          END_PHASE("Inject message",retval);
     }
     for (ilingo=0 ; ilingo < nlingo ; ilingo++) {
          prfpointers[ilingo]=ptrblok(prfbuffers,ilingo);
     }
     prfptr=prfbuf=prfpointers[0];
     curmbk=savmb;
     return(retval);
}

VOID
dftinj(VOID)
{
     btuxmn(othusn,prfbuf);
     btuoes(othusn,1);
     usroff(othusn)->flags|=INJOIP;
}

VOID
audlon(VOID)                       /* record logon to the audit trail      */
{
     INT i;

     logons[usrnum]=now();
     usrptr->tckonl=lngtck;
     if (lonaud) {
          if (usrptr->flags&ISX25) {
               shocst("USER LOGON VIA X.25","User-ID: %s",usaptr->userid);
          }
          else if ((i=chanty[grpnum[usrnum]]) != 0) {
               shocst(spr("USER LOGON VIA LAN (%s)",chantn[i-SDFIPXD]),
                      "User-ID: %s",usaptr->userid);
          }
          else if (usrptr->flags&ISGCDI) {
               (*loncdi)();
          }
          else {
               shocst(spr("USER LOGON AT %ldbps",usrptr->baud),
                      "User-ID: %s",usaptr->userid);
          }
          if (sopaud && (usrptr->flags&MASTER)) {
               btuoes(usrnum,0);
               usrptr->flags&=~INJOIP;
          }
     }
}

VOID
lonstf(VOID)                       /* supplemental log-on stuff            */
{
     usrptr->crdrat=mmucrr;
     usrptr->usrcls=SUPLON;
     if (!cyclon(0)) {
          go2mnu(JSTLON);
     }
}

static VOID
cdistb(VOID)                       /* default stub for (*loncdi)()         */
{
}

static INT
cyclon(INT firststt)               /* cycle logon routines efficiently     */
{                                  /*   returns: 0=done logging on, 1=cont */
     LONG routime;
     GBOOL (*rouptr)(VOID);
     INT i,savusn;

     routime=hrtval();
     savusn=usrnum;
     for (i=firststt ; i < nmods && !(usrptr->flags&BYEBYE) ; i++) {
          if (isneeded(i) && (rouptr=module[i]->lonrou) != NULL) {
               usrptr->state=i;
               usrptr->substt=0;
               clrmlt();
               usrptr->linlim=i+1;
               setmem(vdaptr,vdasiz,0);
               BEG_PHASE("Logon",i);
               if (!(*rouptr)()) {
                    END_PHASE("Logon",0);
                    curusr(savusn);
                    if (hrtval()-routime > LOGONPOL) {
                         begin_polling(usrnum,nxtlon);
                         return(1);
                    }
               }
               else {
                    END_PHASE("Logon",1);
                    curusr(savusn);
                    return(1);
               }
          }
     }
     usrptr->linlim=i;
     return(0);
}

static GBOOL
isneeded(                          /* is module needed for cur A/A session?*/
INT modnum)                        /*   module number to check on          */
{
     INT i,j;
     CHAR *nlst;

     if (usrptr->entstt == 0 || modnum == usrptr->entstt) {
          return(TRUE);
     }
     for (i=0 ; i < ninarr(nmehdl) ; i++) {
          nlst=*((CHAR **)arrelem(nmehdl,i));
          if (sameas(module[usrptr->entstt]->descrp,itemidx(nlst,0))) {
               for (j=1 ; j < itemcnt(nlst) ; j++) {
                    if (sameas(module[modnum]->descrp,itemidx(nlst,j))) {
                         return(TRUE);
                    }
               }
               return(FALSE);
          }
     }
     return(TRUE);
}

VOID
nxtlon(VOID)                       /* look for next logon sup routine      */
{
     stop_polling(usrnum);
     if (!cyclon(usrptr->state+1)) {
          go2mnu(JSTLON);
     }
}

VOID
bgnlof(VOID)                       /* begin logoff supplemental links      */
{
     if (module[usrptr->state]->lofrou == NULL) {
          go2mnu(JSTRET);
     }
     else {
          usrptr->lofstt=usrptr->state;
          usrptr->state=-1;
          nxtlof();
     }
}

VOID
nxtlof(VOID)                       /* look for next logoff sup routine     */
{
     INT i,ret;
     GBOOL (*rouptr)(VOID);

     usrptr->crdrat=mmucrr;
     usrptr->usrcls=SUPLOF;
     for (i=usrptr->state+1 ; i < nmods && !(usrptr->flags&BYEBYE) ; i++) {
          if (i != usrptr->lofstt && isneeded(i)
            && (rouptr=module[i]->lofrou) != NULL) {
               usrptr->state=i;
               usrptr->substt=0;
               setmem(vdaptr,vdasiz,0);
               clrmlt();
               BEG_PHASE("Logoff",i);
               if ((ret=(*rouptr)()) == 1) {
                    END_PHASE("Logoff",ret);
                    return;
               }
               else if (ret == -1) {
                    END_PHASE("Logoff",ret);
                    go2mnu(JSTRET);
                    return;
               }
          }
     }
     if (!(usrptr->flags&BYEBYE)) {
          usrptr->state=usrptr->lofstt;
          usrptr->substt=0;
          setmem(vdaptr,vdasiz,0);
          clrmlt();
          BEG_PHASE("Logoff self",0);
          if ((*(module[usrptr->lofstt]->lofrou))() != 1) {
               go2mnu(JSTRET);
          }
          END_PHASE("Logoff self",0);
     }
}

static VOID
go2mnu(                            /* go to the menuing system now         */
INT substt)                             /* substt to start off in          */
{
     usrptr->usrcls=ACTUSR;
     usrptr->state=0;
     usrptr->substt=substt;
     if (usrptr->entstt != 0) {
          switch (substt) {
          case JSTLON:
               cncall();
               if (entmdl(usrptr->entstt,pagcstg(mnuusr->curpag))) {
                    break;
               }
          case JSTRET:
               gcs2cs();
               break;
          }
     }
     else {
          prf("");
          outprf(usrnum);
          (*(VOIDFUNC *)(module00.sttrou))();
     }
}

GBOOL
mainlon(VOID)                      // main module logon routine
{
     if (!(usrptr->flags&WSGCSU)) {
          pageLogon();
     }
     return(FALSE);
}

GBOOL
mainu(VOID)                        /* main menuing command handler         */
{
     GBOOL oldval;
     INT i,c,took=0;
     jmp_buf oldexi;

     setmbk(mjrmb);
     btumil(usrnum,DFTIMX);
     usrptr->crdrat=mmucrr;
     usrptr->flags&=~NOZAP;
     bgncnc();
     switch (usrptr->substt) {
     case JSTLON:                  /* user has just logged on              */
          oldval=valexi;
          movmem(eximod,oldexi,sizeof(jmp_buf));
          valexi=TRUE;
          if (setjmp(eximod)) {
               inpolr=-1;
               go2mnu(JSTRET);
               valexi=oldval;
               movmem(oldexi,eximod,sizeof(jmp_buf));
               return(TRUE);
          }
          cncall();
          gopage("TOP",1,0);
          valexi=oldval;
          movmem(oldexi,eximod,sizeof(jmp_buf));
          break;
     case JSTRET:                  /* user is returning from a mod -> menu */
          cncall();
          gopage(mnuusr->parpag,(isripu() ? 1 : shortm),1);
          break;
     case SITTIN:                  /* user is selecting from a menu page   */
          if (margc == 0) {
               if (usrptr->flags&INJOIP) {
                    gopage(mnuusr->curpag,isripu(),0);
               }
               else {
                    gopage(mnuusr->curpag,1,0);
               }
               break;
          }
          do {
               bgncnc();
               if ((c=toupper(*nxtcmd)) == '?') {
                    setmbk(mjrmb);
                    prfmsg(HLPMSG);
                    if (mmuaud) {
                         shocst(spr("%s MENU SELECTION '%c'",
                                mnuusr->curpag,toupper(c)),
                                "User-ID: %s",usaptr->userid);
                    }
                    if (usrptr->flags&MASTER) {
                         setmbk(mjrmb);
                         prfmsg(SYSHLP);
                    }
                    cncall();
                    gopage(mnuusr->curpag,1,0);
                    break;
               }
               if (sameas(nxtcmd,"find")) {
                    cncall();
                    setmbk(mjrmb);
                    prfmsg(FNDHLP);
                    gopage(mnuusr->curpag,isripu(),0);
               }
               else if (sameto("find ",nxtcmd)) {
                    bgnfnd();
               }
               else if (usrptr->flags&MASTER && (sameto("disable",nxtcmd) ||
                        sameto("enable",nxtcmd))) {
                    cncwrd();
                    if (morcnc()) {
                         if (c == 'D') {
                              dispag(margv[1]);
                         }
                         else {
                              enapag(margv[1]);
                         }
                    }
                    else {
                         setmbk(mjrmb);
                         prfmsg(c == 'D' ? DISHLP : ENAHLP);
                    }
                    cncall();
                    gopage(mnuusr->curpag,isripu(),0);
               }
               else {
                    for (i=0 ; i < MAXSEL ; i++) {
                         if (c == mnuusr->selchrs[i]) {
                              if (mnuusr->keyreq[i] != -1 &&
                                  !haskno(mnuusr->keyreq[i])) {
                                   setmbk(mjrmb);
                                   if (mnuusr->optdsp[i] > 0) {
                                        prfmsg(NOAXSS,c);
                                   }
                                   else {
                                        prfmsg(NOTINL2,c);
                                   }
                                   cncall();
                                   gopage(mnuusr->curpag,isripu(),0);
                              }
                              else {
                                   if (mmuaud) {
                                        shocst(spr("%s MENU SELECTION '%c'",
                                               mnuusr->curpag,toupper(c)),
                                               "User-ID: %s",usaptr->userid);
                                   }
                                   gopage(mnuusr->pages[i],1,0);
                              }
                              took=1;
                              break;
                         }
                    }
                    if (!took && c == 'X') {
                         cncall();
                         if (mmuaud) {
                              shocst(spr("%s MENU SELECTION '%c'",
                                     mnuusr->curpag,toupper(c)),
                                     "User-ID: %s",usaptr->userid);
                         }
                         gopage(mnuusr->parpag,(isripu() ? 1 : shortm),1);
                    }
                    else if (!took) {
                         setmbk(mjrmb);
                         prfmsg(NOTINL2,c);
                         cncall();
                         gopage(mnuusr->curpag,isripu(),0);
                    }
                    took=0;
               }
          } while (usrptr->state == 0 && usrptr->substt == SITTIN && !endcnc());
          break;
     case MNUFIL:                  /* a menu file is being displayed       */
          clfile();
          gopage(mnuusr->curpag,0,0);
          break;
     case OTHFIL:                  /* any ol' file is being displayed      */
          clfile();
          if (!gopage(mnuusr->parpag,(isripu() ? 1 :shortm),1)) {
               catastro("BAD OR MISSING PARENT PAGE \"%s\"!",mnuusr->parpag);
          }
          break;
     case FINDIN:                  /* user is trying to "find" a page      */
          abtfnd();
          break;
     default:
          rstchn();
          return(TRUE);
     }
     if (usrptr->state == 0) {
          outprf(usrnum);
     }
     return(TRUE);
}

VOID
curusr(                                 /* set current user parameters     */
INT uno)
{
     if (0 <= uno && uno < nterms) {
          usrnum=uno;
          usrptr=usroff(usrnum);
          mnuusr=mnuoff(usrnum);
          usaptr=uacoff(usrnum);
          vdaptr=vdaoff(usrnum);
          clingo=usrptr->lingo;
     }
}

VOID
usrson(VOID)                            /* display-users-online utility    */
{
     (*hdlusrson)();
}

VOID
dftusrson(VOID)                         /* display-users-online utility    */
{
     INT res;

     setmbk(mjrmb);
     prfmsg(ULSHDR);
     for (othusn=0 ; othusn < nterms ; othusn++) {
          othusp=usroff(othusn);
          if ((res=incusr(othusn,TRUE,FALSE)) > VACANT) {
               othuap=uacoff(othusn);
               switch (res) {
               case ONLINE:
                    prfmsg(ULSLIN,channel[othusn],"(log-on)","");
                    break;
               case SUPIPG:
                    prfmsg(ULSLIN,channel[othusn],"(sign-up)","");
                    break;
               default:
                    prfmsg(ULSLIN,channel[othusn],othuap->userid,
                           module[othusp->state]->descrp);
               }
          }
     }
     prfmsg(ULSTRL);
     outprf(usrnum);
     prf("");
     rstmbk();
}

INT                                /* <1: don't inc, >=1(class): include   */
incusr(                            /* test if usr shld be incl'd in a list */
INT unum,                          /*   user number in question            */
GBOOL inccur,                      /*   include current user?              */
GBOOL iginv)                       /*   ignore INVISB flag?                */
{
     struct usracc *uap;

     ASSERT(unum >= 0 && unum < nterms);
     if (unum != usrnum || inccur) {
          switch (usroff(unum)->usrcls) {
          case VACANT:
               return(VACANT);
          case ONLINE:
          case SUPIPG:
               return(usroff(unum)->usrcls);
          default:
               uap=uacoff(unum);
               if (isuidc(uap->userid[0]) || uap->userid[0] == '(') {
                    if (!(usroff(unum)->flags&INVISB) || iginv) {
                         return(usroff(unum)->usrcls);
                    }
                    return(INVSON);
               }
               return(UIDNGD);
          }
     }
     return(ISCURU);
}

INT
glorys(VOID)                       /* global commands                      */
{
     INT retval;

     retval=globalgo();
     if (retval == 0) {
          retval=glorec();
     }
     if (retval == 0) {
          if (margc == 1 && sameas(margv[0],"/#")) {
               usrson();
               outprf(usrnum);
               retval=1;
          }
          else if (margc > 1 && sameas(margv[0],"/l") && haskey(glbkey)) {
               glolok();
               retval=1;
          }
          else if (margc == 1
                && sameas(margv[0],"/invis") && haskey(glbkeyi)) {
               gloinv();
               retval=1;
          }
     }
     return(retval);
}

INT
glorec(VOID)                       /* global /RECENT command handler       */
{
     INT i;
     CHAR timbuf[10];

     if (margc == 1 && sameas(margv[0],"/recent")) {
          setmbk(mjrmb);
          if (recents[0].userid[0] == '\0') {
               prfmsg(NORECS);
          }
          else {
               prfmsg(RCLHDR2);
               for (i=0 ; i < nreccl && recents[i].userid[0] != '\0' ; i++) {
                    strcpy(timbuf,nctime(recents[i].logon));
                    prfmsg(RCLLIN,recents[i].userid,timbuf,
                                  nctime(recents[i].logoff));
               }
               prfmsg(RCLTRL);
          }
          outprf(usrnum);
          return(1);
     }
     return(0);
}

VOID
glolok(VOID)                       /* global Sysop lookup command handler  */
{
     CHAR *uid;
     struct usracc *accptr=NULL;

     rstrin();
     nxtcmd=margv[1];
     uid=cncuid();
     if (onsysn(uid,1)) {
          accptr=othuap;
     }
     else {
          dfaSetBlk(accbb);
          if (dfaAcqEQ(vdatmp,uid,0)) {
               accptr=(struct usracc *)vdatmp;
          }
          else {
               setmbk(mjrmb);
               prfmsg(UNOTEX);
          }
     }
     if (accptr != NULL) {
          shwusr(accptr);
          prf("\r");
     }
     outprf(usrnum);
}

VOID
gloinv(VOID)                       /* global Sysop set invisibility        */
{
     setmbk(mjrmb);
     usrptr->flags^=INVISB;
     usaptr->flags^=GOINVB;
     prfmsg(SWINVI,((usrptr->flags&INVISB) ? "" : "de"));
     outprf(usrnum);
     if (tlcInvisRou != NULL) {
          (*tlcInvisRou)(usrnum);
     }
}

VOID
musthn(VOID)
{
     if (status == CYCLE && (usrptr->substt == MNUFIL
                             || usrptr->substt == OTHFIL)) {
          if (!rdfile()) {
               if (usrptr->substt == MNUFIL) {
                    usrptr->substt=SITTIN;
               }
               else {
                    if (!gopage(mnuusr->parpag,(isripu() ? 1 : shortm),1)) {
                         catastro("BAD OR MISSING PARENT PAGE \"%s\"!",
                                  mnuusr->parpag);
                    }
                    outprf(usrnum);
               }
          }
     }
     else {
          dfsthn();
     }
}

VOID
dfsthn(VOID)                            /* default status handler          */
{
     static GBOOL recurs=FALSE;

     switch (status) {
     case CMDOK:
     case INBLK:
     case OUTMT:
     case OBFCLR:
     case ABOREQ:
     case CMN2OK:
     case CM25OK:
     case RCVX29:
     case IPXRER:
     case IPXUNK:
     case CYCLE:
     case 251:
     case 252:
     case 253:
          break;
     default:
          if (!recurs) {
               recurs=TRUE;
               (*(VOIDFUNC *)(module00.huprou))();
               recurs=FALSE;
          }
     }
}

CHAR *
getMsgBlk(msgnum)                  /* get a message (by number, w/ tvars)  */
INT msgnum;                             /* message number to get           */
{
     return(xlttxv(rawmsg(msgnum),mxmssz));
}

VOID
kiloop(VOID)                       /* kill-system loop once per minute     */
{
     if (kilipg) {
#ifdef GCWINNT
          if (isRunAsService() && !isNTShutdown()) {  // make sure SCM do not get mad
               reportStatusToSCM(SERVICE_STOP_PENDING,NO_ERROR,30000);
          }
#endif
          if (kilctr == 0) {
               hupall();
          }
          else {
               if (kilctr > 0) {
                    kalert(GOING2,kilctr);
                    kilctr--;
               }
               rtkick(60,kiloop);
          }
     }
}

VOID
bootem(                            /* boot User-ID about to be killed      */
CHAR *who)
{
     if (onsysn(who,1)) {
          curusr(othusn);
          (*(VOIDFUNC *)(module00.huprou))();
     }
}

VOID
kilchn(                            /* kill a channel by channel number     */
INT num)
{
     if (usroff(num)->usrcls == VACANT) {
          usroff(num)->state=CRESET;
     }
     btucls(num);
     btuinj(num,RING);
}

VOID
loscar(VOID)                       /* lost carrier "hang up" routine       */
{
     GBOOL oldval;
     jmp_buf oldexi;

     if (isuidc(usaptr->userid[0])) {
          oldval=valexi;
          movmem(eximod,oldexi,sizeof(jmp_buf));
          valexi=FALSE;
          setmem(eximod,sizeof(jmp_buf),0);
          if (sysrec || !(usrptr->flags&MASTER)) {
               movmem(recents,&recents[1],sizeof(struct recalls)*(nreccl-1));
               recents[0].logon=(logons[usrnum] == 0 ? now() : logons[usrnum]);
               recents[0].logoff=now();
               strcpy(recents[0].userid,usaptr->userid);
          }
          if (!(usrptr->flags&WSGCSU)) {
               pageHup();
          }
          logons[usrnum]=0;
          gcsphup();
          aschup();
          curusr(usrnum);
          usaptr->usedat=today();
          updacc();
          if (lofaud) {
               shocst("USER LOGOFF","User-ID: %s",usaptr->userid);
          }
          if (status == RELOG && !(usrptr->flags&(ISGCSU|WSGCSU))) {
               (*hdlrlg)();
               setmbk(mjrmb);
               (*hdlcon)();
          }
          else {
               rstchn();
          }
          valexi=oldval;
          movmem(oldexi,eximod,sizeof(jmp_buf));
     }
     else {
          clfile();
          clrmlt();
          BEG_PHASE("Hangup process",0);
          (*(VOIDFUNC *)(module[usrptr->state]->stsrou))();
          END_PHASE("Hangup process",0);
     }
}

VOID
aschup(VOID)                       /* call appropriate A/A hangup handlers */
{
     INT i;
     static INT lstunm=-1,lststt;
     VOID (*rouptr)(VOID);

     clfile();
     if (lstunm == -1) {      /* in case of catastro(), save usrnum   */
          lstunm=usrnum;
          lststt=1;
     }
     for (i=(usrnum == lstunm ? lststt : 1) ; i < usrptr->linlim ; i++) {
          if (usrnum == lstunm) {
               lststt++;      /* ...and save huprous already called   */
          }
          if (isneeded(i) && (rouptr=module[i]->huprou) != NULL) {
               BEG_PHASE("Hangup user",i);
               curusr(usrnum);
               (*rouptr)();
               END_PHASE("Hangup user",0);
          }
     }
     if (hangups != NULL) {
          (*(VOIDFUNC *)hangups)();
     }
     ftfzer();
     if (usrnum == lstunm) {  /* if no catastro(), don't save usrnum  */
          lstunm=-1;
     }
     usrptr->linlim=0;
}

VOID
addannom(                          /* sends announcement with module ref.  */
CHAR *message,                     /*   the announcement                   */
CHAR *appid,                       /*   the appid of the module executable */
CHAR *cmdstg)                      /*   command string for module entry    */
{
     stp4cs(stlcpy(rsptmp,message,MAXTPSZ));
     stlcat(rsptmp,"\t",MAXTPSZ);
     stlcat(rsptmp,appid,MAXTPSZ);
     stlcat(rsptmp,"\t",MAXTPSZ);
     stlcat(rsptmp,cmdstg,MAXTPSZ);
}

VOID
addanno(                           /* tacks one announcement onto rsptmp   */
CHAR *message)                     /*   the new announcement               */
{
     addannom(message,"","");
}

VOID
dclanno(                           /* module declaration for anno per-req  */
INT size)
{
     dclmrq(size+ANNOSIZE);
}

static VOID
relog(VOID)                        /* Reset user info before Relogging on  */
{                                  /* (hdlrlg default pointing to relog()  */
     INT svlcs;
     ULONG svflgs;
     LONG svbaud;

     svflgs=(usrptr->flags&(NOHDWE+ACTIVE+IS2698+IS1201+
                            NON550+ISGCDI+ISRIAL+ISX25));
     svbaud=usrptr->baud;
     svlcs=usrptr->lcstat;
     if (usrptr->keys != NULL) {
          free(usrptr->keys);
     }
     setmem(usrptr,sizeof(struct user),0);
     setmem(usaptr,sizeof(struct usracc),0);
     usrptr->flags=svflgs;
     rcdbaud(svbaud);
     usrptr->lcstat=svlcs;
     zaptbl[usrnum]=0;
}

INT
oldbgnedt(                         /* API for binary compatibility         */
INT siz,                           /* Max size of text                     */
CHAR *buf,                         /* Buffer for text                      */
INT tsiz,                          /* Max size of topic                    */
CHAR *topic,                       /* Buffer for topic (NULL for none)     */
SHORT (*whndun)(SHORT),            /* ptr to exit/quit routine             */
INT flags)                         /* config flags                         */
{
     return((*bgnedt)(siz,buf,tsiz,topic,whndun,flags));
}

VOID
oldedtimr(                         /* API for binary compatibility         */
SHORT (*imradr)(SHORT))
{
     edtimr((GBOOL (*)())imradr);
}

INT
oldinedit(                         /* API for binary compatibility         */
INT usn,
SHORT (*exipnt)(SHORT))
{
     return((*inedit)(usn,exipnt));
}

VOID
byenow(                            /* log-off a user w/ message utility    */
INT msgnum,                        /* from app's .MSG file (NO_MESSAGE=silent)*/
...)                               /* .MSG text parameters                 */
{                                  /* usrnum is an implicit input          */
     va_list ap;
     CHAR *p1,*p2,*p3;

     va_start(ap,msgnum);
     p1=va_arg(ap,CHAR *);
     p2=va_arg(ap,CHAR *);
     p3=va_arg(ap,CHAR *);
     va_end(ap);
     if (msgnum != NO_MESSAGE && msgnum != PAMSG) {
          ASSERTM(msgnum > 0,spr("byenow(%d)",msgnum));
          if (msgnum > 0) {
               prfmsg(msgnum,p1,p2,p3);
          }
     }
     if (usroff(usrnum)->flags&ISGCSU) {
          if (msgnum != NO_MESSAGE) {
               stp4cs(prfbuf);
               if (bdelay) {
                    depadb();
               }
               senddpk(usrnum,"",CRASH,saunmu("byenow"),STGLEN,prfbuf,NULL);
               clrprf();
          }
          else {
               senddpk(usrnum,"",CRASH,saunmu("byenow"),0,"",NULL);
          }
     }
     else {
          btulok(usrnum,1);
          btubsz(usrnum,INPSIZ,OUTSIZ);
          btucli(usrnum);
          btuclo(usrnum);
          btuoes(usrnum,1);
          if (msgnum != NO_MESSAGE) {
               if (bdelay) {
                    depadb();
                    setmbk(mjrmb);
                    prfmsg(BYEDLY);
                    rstmbk();
               }
               outprf(usrnum);
               clrprf();
          }
          if (btuoba(usrnum) == OUTSIZ-1) {
               btuinj(usrnum,OUTMT);
          }
     }
     setbbye();
}

VOID
byendl(                            /* log-off a user (no BYEDLY, no depadb)*/
INT msgnum,                        /* from app's .MSG file (NO_MESSAGE=silent)*/
...)                               /* .MSG text parameters                 */
{                                  /* usrnum is an implicit input          */
     va_list ap;
     CHAR *p1,*p2,*p3;

     va_start(ap,msgnum);
     p1=va_arg(ap,CHAR *);
     p2=va_arg(ap,CHAR *);
     p3=va_arg(ap,CHAR *);
     va_end(ap);
     bdelay=FALSE;
     byenow(msgnum,p1,p2,p3);
     bdelay=TRUE;
}

VOID
setbbye(VOID)                      /* set BYEBYE for current usrnum        */
{
     usroff(usrnum)->flags|=BYEBYE;
     usroff(usrnum)->byecnt=2;
}

VOID
hupall(VOID)                       /* hang-up on all users                 */
{
     GBOOL bReset=FALSE;

     for (usrnum=0 ; usrnum < nterms ; usrnum++) {
#ifdef GCWINNT
          if (isRunAsService() && !isNTShutdown()) {  // make sure SCM do not get mad
               reportStatusToSCM(SERVICE_STOP_PENDING,NO_ERROR,30000);
          }
#endif
          curusr(usrnum);
          rsmodes[usrnum]=rsetop;
          if (usrptr->usrcls > SUPIPG) {
               status=RING;
               (*(VOIDFUNC *)(module00.huprou))();
               bReset=TRUE;
          }
          else if (usrptr->usrcls != VACANT || usrptr->state != W4KILL) {
               if (usrptr->usrcls == SUPIPG) {
                    suphup();
               }
               rstchn();
               bReset=TRUE;
          }
     }
     rtkick((bReset ? 20 : 1),failsf);
#ifdef GCWINNT
     if (isRunAsService() && !isNTShutdown()) {  // make sure SCM do not get mad
          reportStatusToSCM(SERVICE_STOP_PENDING,NO_ERROR,30000);
     }
#endif
}

#ifndef GCWINNT
static
VOID
mjrdblflt(VOID)                    /* Baseline GP double-fault handler     */
{
     gsbldn();
     (*olddblflt)();
}
#endif // GCWINNT

VOID
sawclan(                           /* set Gcomm-specific AWAITC LAN hook   */
VOID (*hookrou)(VOID),             /*   hook to put in place               */
CHAR *verstg)                      /*   verification string                */
{
     if (sameas(verstg,"Copyright (c) 1996 Galacticomm, Inc. "
                       "All Rights Reserved.")) {
          awclan=hookrou;
     }
}

static VOID
gsbldn(VOID)                       /* bring down the GSBL                  */
{
#ifndef GCWINNT
     INT i;
#endif // GCWINNT

     if (gsblup) {
#ifndef GCWINNT
          if (sapsup) {
               sapfin();
               for (i=btuTicker() ; btuTicker()-i < 3 && !sapfdn() ; ) {
               }            /* 3-second timeout for S.A.P. server shutdowns */
               sapsup=0;
          }
#endif // GCWINNT
          btuend();
          gsblup=0;
     }
}

VOID
mjrfin(VOID)                       /* finish up stuff; shutdown system     */
{
     INT i;
     VOID (*rouptr)(VOID);

#ifdef GCWINNT
     HANDLE hThread=NULL;

     sendStatusToMainWin("Shutting down ...");
     if (isRunAsService() && !isNTShutdown()) {
          hThread=runReportTh(FALSE);
     }
#endif // GCWINNT
     if (initdn) {
          hupall();
          for (i=1 ; i < nmods ; i++) {
               if ((rouptr=module[i]->finrou) != NULL) {
                    BEG_PHASE("Finalize",i);
                    (*rouptr)();
                    END_PHASE("Finalize",i);
               }
          }
          BEG_PHASE("Shutdown vectors",0);
          dovecs(finhdl);
          BEG_PHASE("Final shutdown vectors",0);
          dovecs(ffinhdl);
          clsgdp();
          gsbldn();
          clslnk();
          if (ripdfd) {
               ripcls();
          }
          clsmnu();
          dfaSetBlk(mstbb);
          for (i=0 ; i < nmods ; i++) {
               if (dfaAcqEQ(NULL,mdstats[i].mdname,0)) {
                    dfaUpdate(&mdstats[i]);
               }
               else {
                    dfaInsert(&mdstats[i]);
               }
          }
          finsup();
          clsacc();
     }
     clsmsg(mjrmb);
     dfaClose(mstbb);
     dfaClose(xrfbb);
     dfaClose(genbb);
     finsho();
     clspool();
     gsbldn();
     finmemdbg();
     cursiz(GVIDLILCURS);
     cursact(1);
     locate(0,23);
     clsvid();
#ifdef GCWINNT                     // DOS programms may expect BTV to be active
     dfaStop();
     closeVideoOutput();
     if (isRunAsService()) {
          serviceShutdown(hThread);
     }
#endif // GCWINNT
     exit(errcod);
}

struct user *                      /*   returns ptr to user structure      */
usroff(                            /* obtains the user struct at an offset */
INT usrnum)                        /*   offset to use                      */
{
     return((struct user *)ptrblok(user,usrnum));
}

#ifdef GCWINNT
GBOOL                              //   returns TRUE if run as a service
isRunAsService(VOID)               // is WGSERVER.EXE running as a service
{
     return(fRunAsService);
}

VOID
shutdownMainThread(VOID)           // shuts down main thread and WG Server
{
     PostThreadMessage(mainThId,WM_CLOSE,0,0);
}

static VOID
postGlobalExcpHandler(VOID)
{
     closeVideoOutput();
     errcod=99;                    // GP
     dfaStop();                    // DOS programms may expect BTV to be active
     if (isRunAsService()) {
          serviceShutdown(hReportTh);
     }
     exit(errcod);
}
#endif // GCWINNT
