/***************************************************************************
 *                                                                         *
 *   SCP.C                                                                 *
 *                                                                         *
 *   Copyright (c) 1995-1997 Galacticomm, Inc.    All Rights Reserved.     *
 *                                                                         *
 *   SLIP/CSLIP/PPP services for users.                                    *
 *                                                                         *
 *                                              - R. Stein  7/6/95         *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "majorbbs.h"
#include "remote.h"
#include "ftscope.h"
#include "tcpip.h"
#include "llconfig.h"
#include "cman.h"
#include "phasedbg.h"
#include "scp.h"
#include "galscp.h"
#include "crit.h"

#define FILREV "$Revision: 44 $"

static INT scpuid(CHAR *userid);
static INT scpacc(VOID);
static INT globalscp(VOID);
static GBOOL scpinp(VOID);
static VOID pmtipa(VOID);
static INT shouid(VOID);
static VOID scprange(VOID);
static VOID ex2conf(VOID);
static INT scpprep(VOID);
static VOID scpsts(VOID);
static VOID scpbeg(INT chan);
static VOID scpend(INT chan);
static VOID decscp(VOID);
static GBOOL chgcross(LONG oldcred,LONG addcred,LONG thresh);
static LONG tfcacct(INT unum);
static VOID swtphcls(INT unum,CHAR *clsnam);
static VOID enfkeys(INT unum);
static VOID setkeep(VOID);
static VOID scphup(VOID);
static INT scpocm(INT chn,CHAR *packet,UINT nbytes);
static VOID scpecm(INT chn,INT evcode,INT parm);
static VOID scpsys(VOID);
static VOID scpzap(VOID);
static VOID scprst(VOID);
static VOID scpclear(VOID);
static GBOOL scparsem(CHAR *mode);
static GBOOL scparse(CHAR *mode);
static VOID ipuinit(VOID);
static INT shobdy(VOID);
static CHAR *tvar_slip_type(VOID);
static CHAR *tvar_slip_ip(VOID);
static CHAR *tvar_slip_mtu(VOID);
static CHAR *tvar_slip_userid(VOID);
static INT scpdedcrd(INT unum,CHAR *uid,LONG amt,INT real,INT asmuch);
static LONG tfccst(LONG nbytes,LONG credpk);
static INT mrgpar(CHAR *parm);

INT scpstt;                        /* SLIP/CSLIP/PPP module state number   */
struct module scpmodule={          /* module interface block               */
     "",                           /*    name used to refer to this module */
     NULL,                         /*    user logon supplemental routine   */
     scpinp,                       /*    input routine if selected         */
     scpsts,                       /*    status-input routine if selected  */
     NULL,                         /*    "injoth" routine for this module  */
     NULL,                         /*    user logoff supplemental routine  */
     scphup,                       /*    hangup (lost carrier) routine     */
     NULL,                         /*    midnight cleanup routine          */
     NULL,                         /*    delete-account routine            */
     NULL                          /*    finish-up (sys shutdown) routine  */
};

CHAR *nametbl[]={                  /* SLIP type names                      */
     "SLIP","CSLIP","PPP"};
INT llaftbl[]={                    /* SLIP type link layer addr families   */
     AF_SLIP,AF_SLIP,AF_PPP};
INT cmotbl[]={                     /* SLIP type flags                      */
     0,CMO_CSLIP,0};
CHAR *keytbl[NSTYPES];             /* SLIP type keys required (CNF lev 3)  */
CHAR uscchr[]={'s','c','p'};       /* SLIP type characters for <Alt-U> scn */
                                   /* (BBSPRV) when using User-ID/ppp logon*/
CHAR usochr[]={'s','c','p'};       /* SLIP type characters for <Alt-U> scn */
                                   /* (ACTUSR) when using /ppp global cmd  */
struct scpinf *scpinf;             /* array of per-user non-VDA storage    */

                                   /* SLIP/CSLIP/PPP substates (all types) */
#define SCPPRE  -1                 /* "Entering" notice being output       */
#define SCPUP   -2                 /* up and active                        */
#define SCPXIT  -3                 /* exiting to parent menu               */
     /* SCPABORT */                /* exiting (aborted due to timeout)     */
     /* SCPTERM  */                /* exiting (terminated normally)        */

CHAR scpnam[MNMSIZ]=               /* Module name for use in BBSGEN.DAT    */
     "SLIP/CSLIP/PPP services";

#define TRMWAIT (2*16)             /* grace for terminate message to output*/

#define TDTHRESH (15*60)           /* secs accum. phantom-alone time today */
#define MVTHRESH (5*60)            /* secs accumulating phantom-alone chgs */
                                   /* before updating user's record on disk*/
                                   /* thereby preventing ming vase giveaway*/

VOID (*oldsys)(VOID);              /* saved value of (*syscyc)()           */
INT (*oldocm)(                     /* saved value of (*output_cman)()      */
     INT,CHAR *,UINT);
VOID (*oldecm)(                    /* saved value of (*event_cman)()       */
     INT,INT,INT);
INT (*olduid)(CHAR *userid);       /* saved value of (*chkuid)()           */
INT (*oldacc)(VOID);               /* saved value of (*chkacc)()           */
VOID (*oldzap)(VOID);              /* saved value of (*hdlzap)()           */
VOID (*oldrst)(VOID);              /* saved value of (*hdlrst)()           */
VOID (*olddec)(VOID);              /* for recording (*decusr)() vector     */

HMCVFILE scpmb;                    /* GALSCP.MCV message file handle       */
CHAR *lonkey;                      /* able to login as User-ID/slip        */
CHAR *confkey;                     /* able to configure IP addrs (-c)?     */
CHAR *mulskey;                     /* allow multiple phantom SLIP sessions?*/
CHAR *keepkey;                     /* keep user's SLIP connection alive?   */
CHAR *proxkey;                     /* allow proxy SLIP/CSLIP/PPP?          */
CHAR *dynkey;                      /* allow dynamic SLIP/CSLIP/PPP?        */
GBOOL scpdprox;                    /* proxy mode is default (false=dynamic)*/
INT scpmax;                        /* max simultaneous SLIP sessions       */
GBOOL userips;                     /* user assigned & dynamic IP addresses?*/
CHAR *scpip;                       /* base IP address, text                */
ULONG scpipb;                      /* base IP address, network byte order  */
ULONG scpipbh;                     /* base IP address, host byte order     */
CHAR *scpmsk;                      /* [sub]network mask, text              */
ULONG scpmkb;                      /* [sub]network mask, network byte order*/
UINT scpmtu;                       /* Maximum Transmission Unit, SLIP users*/
INT scpaln;                        /* audit User-ID/slip logins?           */
INT scpatb;                        /* audit SLIP user time & bytes?        */
LONG scpchgl;                      /* charge per Kbyte of local traffic    */
LONG scpchgr;                      /* charge per Kbyte of remote traffic   */
INT scppmin;                       /* charge per minute for proxy/dyn usage*/
INT scpamin;                       /* charge per minute for assigned-IP use*/
INT dualfact;                      /* percentage of charge when dual-online*/
INT scpmincg;                      /* minimum charge per minute, phantom   */
GBOOL realcr;                      /* real (no debt, no exemption) credits?*/
GBOOL credzap;                     /* disconnect users when out of credits?*/
CHAR *safezap;                     /* key to avoid credzap                 */
GBOOL ppplof=TRUE;                 /* PPPXIT:  true=LOGOFF, false=MENU     */
GBOOL pfxlogon;                    /* permit the SLIP:User-ID style logon? */
UINT scpsiz;                       /* size of CMAN channel buffer          */
ULONG numip;                       /* number of possible IP addresses      */
UINT *ipused=NULL;                 /* bit-array of assigned IP addresses   */
#define IPUWRDS ((UINT)((numip+sizeof(UINT)*8-1)/ \
                                       sizeof(UINT)*8))
                                   /* number of words in ipused array      */
INT gotin;                         /* used to detect /go entry to module   */

VOID EXPORT
init__galscp(VOID)                 /* Initialize SLIP/CSLIP/PPP            */
{
     struct in_addr ipaddri;

     init__tcpip();
     stzcpy(scpmodule.descrp,gmdnam("galscp.mdf"),MNMSIZ);
     scpstt=register_module(&scpmodule);
     scpmb=opnmsg("galscp.mcv");
     keytbl[SLPIDX]=stgopt(SLPKEY);
     keytbl[CSLIDX]=stgopt(CSLKEY);
     keytbl[PPPIDX]=stgopt(PPPKEY);
     lonkey=stgopt(LONKEY);
     confkey=stgopt(CONFKEY);
     mulskey=stgopt(MULSKEY);
     keepkey=stgopt(KEEPKEY);
     proxkey=stgopt(PROXKEY);
     dynkey=stgopt(DYNKEY);
     scpmax=numopt(SCPMAX,0,250);
     userips=ynopt(USERIPS);
     scpchgl=lngopt(SCPCHGL,-2000000000L,2000000000L);
     scpchgr=lngopt(SCPCHGR,-2000000000L,2000000000L);
     scppmin=numopt(SCPPMIN,-32767,32767);
     scpamin=numopt(SCPAMIN,-32767,32767);
     dualfact=numopt(DUALFACT,0,100);
     scpmincg=numopt(SCPMINCG,-32767,32767);
     realcr=ynopt(REALCR);
     credzap=ynopt(CREDZAP);
     safezap=stgopt(SAFEZAP);
     pfxlogon=ynopt(PFXLOGON);
     scpmtu=numopt(SCPMTU,46,1500);
     scpaln=ynopt(SCPALN);
     scpatb=ynopt(SCPATB);
     maxpxp=numopt(MAXPXP,4,28);
     main_ip=ipaddrb;
     scpinf=(struct scpinf *)alczer(nterms*sizeof(struct scpinf));
     if (userips) {
          scpip=getmsg(SCPIP);
          if ((scpipb=inet_addr(scpip)) == INET_ADDR_ERR) {
               catastro("Configuration option SCPIP is not a valid\n"
                        "IP address: \"%s\".",scpip);
          }
          if (sameas(scpmsk=getmsg(SCPMASK),"AUTO")) {
               if (!dftnetmask(scpipb,&scpmkb)) {
                    catastro("Option SCPMASK cannot be set to \"AUTO\" when\n"
                             "option SCPIP is not "
                             "a class A, B, or C address.");
               }
          }
          else {
               if ((scpmkb=inet_addr(scpmsk)) == INET_ADDR_ERR) {
                    catastro("Configuration option SCPMASK is not a valid\n"
                             "IP address: \"%s\".",scpmsk);
               }
          }
          if (!chknetmask(scpmkb)) {
               catastro("Configuration option SCPMASK is not a valid\n"
                        "network mask: \"%s\".",scpmsk);
          }
          numip=(~ntohl(scpmkb))-2;  /* .0 .254 .255 are reserved IP addrs */
          if (numip > 16384UL) {
               catastro("Option SCPMASK specifies too large a subnet:\n%s",
                        scpmsk);
          }
          scpipb=htonl(scpipbh=ntohl(scpipb&scpmkb)|1);
          ipaddri.s_addr=scpipb;
          scpip=strdup(inet_ntoa(ipaddri));
          ipuinit();
          ipaddri.s_addr=scpmkb;
          scpmsk=strdup(inet_ntoa(ipaddri));
          scpdprox=sameas(lastwd(getmsg(SCPDMODE)),"PROXY");
          if (netmaskb != 0UL
           && ((      scpipb          &scpmkb)   == (ipaddrb&scpmkb)
            || (      scpipb          &netmaskb) == (ipaddrb&netmaskb)
            || (htonl(scpipbh+numip-1)&netmaskb) == (ipaddrb&netmaskb))) {
               catastro("Your User-SLIP subnet overlaps your Ethernet subnet."
                      "\nChange USERIPS to NO, or choose new settings for"
                      "\noptions IPADDR, NETMASK, SCPIP or SCPMASK.");
          }
     }
     else {
          scpipbh=ntohl(scpipb=inet_addr(scpip=STANDINIP));
          scpmkb=inet_addr(scpmsk="255.255.255.255");
          scpdprox=0;
          numip=0;
     }
     if ((tcpip_errno=ll_config("cman",htonl(scpipbh+numip),scpmkb,
                                                            scpmtu)) != 0) {
          catastro("SLIP/CSLIP/PPP configuration error %d: %s",
                   tcpip_errno,tcpErrStg(tcpip_errno));
     }
     if ((tcpip_errno=init_cman(nterms,&scpsiz,scpipb,
                                (UINT)numip)) != 0) {
          catastro("SLIP/CSLIP/PPP initialization error %d: %s",
                   tcpip_errno,tcpErrStg(tcpip_errno));
     }
     dclvda(sizeof(struct scpvda)-1+scpsiz);
     dclvda(OUTSIZ);
     dclvda(GENSIZ);
     oldocm=output_cman;
     output_cman=scpocm;
     oldecm=event_cman;
     event_cman=scpecm;
     oldsys=syscyc;
     syscyc=scpsys;
     olduid=chkuid;
     chkuid=scpuid;
     oldacc=chkacc;
     chkacc=scpacc;
     oldzap=hdlzap;
     hdlzap=scpzap;
     oldrst=hdlrst;
     hdlrst=scprst;
     olddec=decusr;
     decusr=decscp;
     globalcmd(globalscp);
     register_textvar("SLIP_TYPE",tvar_slip_type);
     register_textvar("SLIP_IP",tvar_slip_ip);
     register_textvar("SLIP_MTU",tvar_slip_mtu);
     register_textvar("SLIP_USERID",tvar_slip_userid);
}

VOID EXPORT
initwc__galscp(VOID)
{
     init__galscp();
}


/*

Note:  scpuid() properly handles 29-character User-IDs with "/CSLIP"
appended, but to do so it must avoid passing the aggregate to (*olduid)(),
which would report a false positive.

The scpuid() routine would also properly handle a User-ID on file as
"FRED/SLIP" if such a thing were valid (isuidc() would appear to reject it).
Furthermore, such a user could log in as "FRED/SLIP/SLIP" to go directly
into SLIP mode.  However, User-ID "FRED" would not, in this situation, be able
to log on directly into SLIP mode.

*/

static INT                         /*   see LONXXX return codes, MAJORBBS.H*/
scpuid(                            /* SLIP/CSLIP/PPP (*chkuid)() vector    */
CHAR *userid)                      /*   proposed User-ID                   */
{
     INT lonxxx=LONUNK;
     CHAR realuid[UIDSIZ+1+20];
     CHAR *cp;
     GBOOL sufx;
     GBOOL prfx;

     scpclear();
     stzcpy(realuid,userid,sizeof(realuid));
     sufx=(cp=strrchr(realuid,'/')) != NULL && scparsem(cp);   /* uid/slip */
     if (strlen(userid) > UIDSIZ-1 && sufx
      || (lonxxx=(*olduid)(userid)) == LONUNK && sufx) {
          *cp='\0';
          if ((lonxxx=(*olduid)(realuid)) == LONOK) {
               scpinf[usrnum].flags|=SCPQLN;
          }
     }
     if (pfxlogon && lonxxx == LONUNK) {
          prfx=(cp=strchr(realuid,':')) != NULL;               /* slip:uid */
          if (prfx) {
               *cp='\0';
               prfx=scparsem(realuid);
               *cp=':';
          }
          if (strlen(userid) > UIDSIZ-1 && prfx
           || (lonxxx=(*olduid)(userid)) == LONUNK && prfx) {
               if ((lonxxx=(*olduid)(cp+1)) == LONOK) {
                    scpinf[usrnum].flags|=SCPQLN;
               }
          }
     }
     return(lonxxx);
}

static INT                         /*   ret 1=online, 0=handled otherwise  */
scpacc(VOID)                       /* SLIP/CSLIP/PPP (*chkacc)() vector    */
{
     INT rc;
     INT prepped;
     CHAR *stname;
     INT stnlen;

     if ((rc=(*oldacc)()) != 0 && scpinf[usrnum].flags&SCPQLN) {
          usrptr->usrcls=BBSPRV;
          usrptr->state=scpstt;
          setmem(scpptr,sizeof(struct scpvda),0); /* (sim module entry)    */
          stnlen=strlen(stname=nametbl[scpinf[usrnum].idx]);
          prf("\r");
          setmbk(scpmb);
          if (!haskey(lonkey)) {
               prfmsg(SCPNOLON,stname);
               prepped=0;
          }
          else {
               prepped=scpprep();
          }
          rstmbk();
          if (prepped) {
               outprf(usrnum);
               sprintf(usaptr->userid,"(%0.*s/%s)",UIDSIZ-1-1-stnlen-1-1,
                       scpptr->realuid,stname);
               shochl(usaptr->userid,uscchr[scpinf[usrnum].idx],
                      baudat(usrptr->baud,0));
               if (scpaln) {
                    shocst(spr("USER LOGON DIRECT TO %s",stname),
                           "User-ID: %s, %ld bps",
                           scpptr->realuid,usrptr->baud);
               }
               usrptr->tckonl=lngtck;
          }
          else {
               setmbk(scpmb);
               byenow(PAMSG);
               rstmbk();
          }
          rc=0;
     }
     return(rc);
}

static INT
globalscp(VOID)                    /* global "/slip", "/cslip", "/ppp" cmds*/
{
     INT rc=0;

     if (margc >= 1 && margv[0][0] == '/' && scparse(margv[0])) {
          rstrin();
          stzcpy(input,spr("/go slip %0.100s",input),INPSIZ);
          parsin();
          gotin=0;
          rc=globalgo();
          if (usrptr->state != scpstt && usrptr->flags&MASTER && !gotin) {
               setmbk(scpmb);
               prfmsg(SCPNOPAG);
               outprf(usrnum);
               rstmbk();
          }
     }
     return(rc);
}

static GBOOL
scpinp(VOID)                       /* SLIP/CSLIP/PPP input handler         */
{
     INT conf;
     INT rc=TRUE;
     ULONG newip;
     INT unum;
     INT ualr;

     setmbk(scpmb);
     switch (usrptr->substt) {
     case 0:
          scpclear();
          gotin=1;
          bgncnc();
          cncchr();
          endcnc();
          conf=(mrgpar("/C") || mrgpar("-C")) && haskey(confkey);
          rstrin();
          if (!scparsem(input)) {
               prfmsg(SCPBAD,input);
               rc=FALSE;
          }
          else if (conf) {
               if (userips) {
                    prfmsg(usrptr->substt=SCPCONF);
               }
               else {
                    prfmsg(SCPNOCNF);
                    rc=FALSE;
               }
          }
          else if (!scpprep()) {
               rc=FALSE;
          }
          break;
     case SCPCONF:
          if (margc == 0) {
               clrxrf();
               prfmsg(SCPCONF);
          }
          else if (sameas(margv[0],"?")) {
               clrxrf();
               dfaSetBlk(genbb);
               if (dfaAcqGE(vdatmp,scpnam,1)
                && sameas(((struct bbgscp *)vdatmp)->modnam,scpnam)) {
                    scpcptr->bbgpos=dfaAbs();
                    prfmsg(LSTHDR);
                    usrptr->substt=LSTBDY;
                    btuinj(usrnum,CYCLE);
               }
               else {
                    prfmsg(SCPNONE);
                    ex2conf();
               }
          }
          else if (margc == 1 && sameas(margv[0],"X")) {
               clrxrf();
               rc=FALSE;
          }
          else {
               rstrin();
               switch (hdluid(input)) {
               case UIDFND:
                    setmem(&scpcptr->bbgscp,sizeof(struct bbgscp),0);
                    strcpy(scpcptr->bbgscp.userid,uidxrf.userid);
                    strcpy(scpcptr->bbgscp.modnam,scpnam);
                    pmtipa();
                    break;
               case UIDPMT:
                    prfmsg(SCPCONF);
                    break;
               }
          }
          break;
     case SCPALR:
     case SCPNEW:
          rstrin();
          if (usrptr->flags&INJOIP) {
               pmtipa();
               break;
          }
          if (margc == 0 || (margc == 1 && sameas(margv[0],"X"))) {
               shouid();
               ex2conf();
               break;
          }
          if (sameas(margv[0],"?") || margc != 1) {
               scprange();
               pmtipa();
               break;
          }
          if (sameas(margv[0],"NONE") || sameas(margv[0],"\"NONE\"")) {
               dfaSetBlk(genbb);
               if (dfaAcqEQ(&scpcptr->bbgscp,&scpcptr->bbgscp,0)) {
                    ipunassign(scpcptr->bbgscp.ipaddr);
                    dfaDelete();
               }
               shouid();
               ex2conf();
               break;
          }
          if (sameas(margv[0],"ANY") || sameas(margv[0],"\"ANY\"")) {
               newip=ipdynamic();
          }
          else if ((newip=inet_addr(margv[0])) == INET_ADDR_ERR) {
               prfmsg(SCPSYNTX,margv[0]);
               pmtipa();
               break;
          }
          if (!ipvalid(newip)) {
               prfmsg(SCPRERR);
               scprange();
               pmtipa();
               break;
          }
          if ((unum=chan_of_ip[(INT)(ntohl(newip)-scpipbh)]) != IPNOCH
           && !(inscp(unum)
                && sameas(scpcptr->bbgscp.userid,scpusr(unum)->realuid))) {
               prfmsg(SCPONNOW,margv[0],uacoff(unum)->userid,channel[unum]);
               scprange();
               pmtipa();
               break;
          }
          dfaSetBlk(genbb);
          ualr=dfaAcqEQ(&scpcptr->bbgscp,&scpcptr->bbgscp,0);
          if (ipassigned(newip)
           && !(ualr && newip == scpcptr->bbgscp.ipaddr)) {
               prfmsg(SCPCONFL,margv[0]);
               scprange();
               pmtipa();
               break;
          }
          if (ualr) {
               ipunassign(scpcptr->bbgscp.ipaddr);
               scpcptr->bbgscp.ipaddr=newip;
               dfaUpdateV(&scpcptr->bbgscp,sizeof(struct bbgscp));
               ipassign(newip);
          }
          else {
               scpcptr->bbgscp.ipaddr=newip;
               dfaInsertV(&scpcptr->bbgscp,sizeof(struct bbgscp));
               ipassign(newip);
          }
          shouid();
          ex2conf();
          break;
     case SCPXIT:
     default:
          if (usrptr->usrcls == ACTUSR && !ppplof) {
               usrptr->flags&=~(NOGLOB+NOINJO);
               rc=FALSE;
          }
          break;
     }
     outprf(usrnum);
     return(rc);
}

static VOID
pmtipa(VOID)                       /* prompt for specific User-ID          */
{
     prfmsg(usrptr->substt=(shouid() ? SCPALR : SCPNEW));
}

static INT                         /*   returns 1=has IP, 0=doesn't have   */
shouid(VOID)                       /* display user's IP addr assignment    */
{                                  /*   scpcptr->bbgscp expected to be set */
     struct in_addr userip;
     INT rc;

     dfaSetBlk(genbb);
     if (dfaAcqEQ(&scpcptr->bbgscp,&scpcptr->bbgscp,0)) {
          userip.s_addr=scpcptr->bbgscp.ipaddr;
          prfmsg(SCPHAS,scpcptr->bbgscp.userid,inet_ntoa(userip));
          rc=1;
     }
     else {
          prfmsg(SCPHASN,scpcptr->bbgscp.userid);
          rc=0;
     }
     return(rc);
}

static VOID
scprange(VOID)                     /* display range of valid IP addresses  */
{
     struct in_addr userip;

     userip.s_addr=htonl(scpipbh+numip-1);
     prfmsg(SCPRANG2,scpip,inet_ntoa(userip));
}

static VOID
ex2conf(VOID)                      /* exit to the SCPCONF prompt           */
{
     prfmsg(usrptr->substt=SCPCONF);
}

static INT                         /*   returns 1=ok, 0=can't do           */
scpprep(VOID)                      /* prepare to begin live SLIP/CSLIP/PPP */
{                                  /*   (expects curusr() to be in effect) */
     struct bbgscp *bbgptr;
     struct in_addr userip;
     INT isasg;
     INT cmo_flags;

     if (!haskey(keytbl[scpinf[usrnum].idx])) {
          prfmsg(SCPLOCK,nametbl[scpinf[usrnum].idx]);
     }
     else if (numscp(usrnum) >= scpmax) {
          prfmsg(SCPFULL,scpmax);
     }
     else if (numscpuid(usrnum,usaptr->userid) != 0 && !haskey(mulskey)) {
          prfmsg(SCPONE);
     }
     else {
          setkeep();
          strcpy(scpptr->realuid,usaptr->userid);
          scpptr->tckstt=lngtck;
          scpptr->nbytes=0UL;
          scpptr->mvtimer=MVTHRESH;
          bbgptr=(struct bbgscp *)vdatmp;
          strcpy(bbgptr->userid,usaptr->userid);
          strcpy(bbgptr->modnam,scpnam);
          if (!userips || scpinf[usrnum].flags&SCPPRX) {
               scpinf[usrnum].flags|=SCPPRX;
               scpinf[usrnum].flags&=~SCPDYN;
               if (!haskey(proxkey)) {
                    prfmsg(SCPPLOCK,nametbl[scpinf[usrnum].idx]);
                    return(0);
               }
          }
          else if (scpinf[usrnum].flags&SCPDYN) {
               scpinf[usrnum].flags&=~SCPPRX;
               if (!haskey(dynkey)) {
                    prfmsg(SCPDLOCK,nametbl[scpinf[usrnum].idx]);
                    return(0);
               }
          }
          else {
               dfaSetBlk(genbb);
               isasg=userips && dfaAcqEQ(bbgptr,bbgptr,0);
               dfaRstBlk();
               if (isasg) {
                    if (!ipvalid(userip.s_addr=bbgptr->ipaddr)) {
                         prfmsg(SCPIVAL,inet_ntoa(userip));
                         scprange();
                         return(0);
                    }
               }
               else if (haskey(scpdprox ? proxkey : dynkey)) {
                    scpinf[usrnum].flags|=(scpdprox ? SCPPRX : SCPDYN);
               }
               else if (haskey(!scpdprox ? proxkey : dynkey)) {
                    scpinf[usrnum].flags|=(!scpdprox ? SCPPRX : SCPDYN);
               }
               else {
                    prfmsg(SCPDPLCK,nametbl[scpinf[usrnum].idx]);
                    return(0);
               }
          }
          cmo_flags=cmotbl[scpinf[usrnum].idx];
          if (scpinf[usrnum].flags&SCPDYN) {
               if ((userip.s_addr=ipdynamic()) == CHNOIP) {
                    prfmsg(SCPNODYN);
                    return(0);
               }
          }
          else if (scpinf[usrnum].flags&SCPPRX) {
               cmo_flags|=CMO_PROXY;
               userip.s_addr=CHNOIP;
          }
          if ((tcpip_errno=open_cman(usrnum,llaftbl[scpinf[usrnum].idx],
                                            cmo_flags,
                                            scpptr->buffer,
                                            userip.s_addr)) != 0) {
               prfmsg(SCPERR,tcpip_errno,tcpErrStg(tcpip_errno),
                             inet_ntoa(userip));
               return(0);
          }
          shochl(usaptr->userid,usochr[scpinf[usrnum].idx],
                 baudat(usrptr->baud,0));
          usrptr->substt=SCPPRE;
          prfmsg(scpinf[usrnum].flags&SCPPRX ? SCPPHLO3 : SCPAHLO3);
          usrptr->flags|=NOGLOB+NOINJO;
          scpinf[usrnum].flags|=SCPALV;
          btuoes(usrnum,1);
          return(1);
     }
     return(0);
}

INT
numscp(                            /* num of a users in SLIP/CSLIP/PPP     */
INT excchn)                        /* exception user number (-1=no except) */
{
     INT numonl;
     INT chn;

     numonl=0;
     for (chn=0 ; chn < nterms ; chn++) {
          if (chn != excchn
           && inscp(chn)) {
               numonl++;
          }
     }
     return(numonl);
}

INT
numscpuid(                         /* qty of a User-ID in SLIP/CSLIP/PPP   */
INT excchn,                        /* exception user number (-1=no except) */
CHAR *uid)                         /* User-ID                              */
{
     INT numonl;
     INT chn;

     numonl=0;
     for (chn=0 ; chn < nterms ; chn++) {
          if (chn != excchn
           && inscp(chn)
           && sameas(uid,scpusr(chn)->realuid)) {
               numonl++;
          }
     }
     return(numonl);
}

static VOID
scpsts(VOID)                       /* SLIP/CSLIP/PPP status                */
{
     INT nactual;

     setmbk(scpmb);
     switch (status) {
     case OUTMT:
          btuoes(usrnum,0);
          if (usrptr->substt == SCPPRE) {
               scpbeg(usrnum);
               usrptr->substt=SCPUP;
               btuinj(usrnum,CYCLE);
               if (whomon == usrnum) {
                    (*ftscope)(FTSCMT,spr("***** %s session begins",
                               nametbl[scpinf[usrnum].idx]));
               }
          }
          break;
     case CYCLE:
          switch (usrptr->substt) {
          case SCPUP:
               nactual=0;
               if (btuict(usrnum,vdatmp) == -2 && ictact > 0) {
                    nactual=input_cman(usrnum,vdatmp,ictact);
                    ASSERT(0 <= nactual && nactual <= ictact);
                    if (nactual > 0) {
                         btutrg(usrnum,nactual);
                         btuict(usrnum,vdatmp);
                         btutrg(usrnum,OUTSIZ);
                         scpptr->nbytes+=nactual;
                         if (whomon == usrnum) {
                              (*ftscope)(FTSINS,vdatmp,nactual);
                         }
                    }
               }
               if (nactual == 0 && !(scpinf[usrnum].flags&SCPKAL)) {
                    actdet=0;
               }
               btuinj(usrnum,CYCLE);
               break;
          case SCPABORT:
          case SCPTERM:
               if (btuoba(usrnum) == OUTSIZ/2-1
                || (USHORT)(hrtval()>>12)-scpptr->sttime > TRMWAIT) {
                    if (usrptr->usrcls == BBSPRV || ppplof) {
                         byenow(usrptr->substt);
                    }
                    else {
                         prfmsg(usrptr->substt);
                         outprf(usrnum);
                         shochl(usaptr->userid,'',baudat(usrptr->baud,0));
                         btuinj(usrnum,CRSTG);
                         usrptr->substt=SCPXIT;
                    }
               }
               else {
                    actdet=0;
                    btuinj(usrnum,CYCLE);
               }
               break;
          case LSTBDY:
               if (btuoba(usrnum) < 1024 || shobdy()) {
                    btuinj(usrnum,CYCLE);
               }
               else {
                    prfmsg(usrptr->substt=SCPCONF);
                    outprf(usrnum);
               }
               break;
          }
          break;
     default:
          if (usrptr->usrcls == ACTUSR) {
               dfsthn();
          }
          else {
               switch (status) {
               case SPXTRM:
               case SPXWDG:
               case RING:
               case LOST2C:
               case LOST25:
                    rstchn();
                    break;
               }
          }
          break;
     }
}

static VOID
scpbeg(INT chan)                   /* Begin SLIP/CSLIP/PPP connection      */
{
     btutrg(chan,OUTSIZ);
     btubsz(chan,OUTSIZ/2,OUTSIZ/2);
     scpusr(chan)->whyend=NULL;
     usrptr->crdrat=0;
}

static VOID
scpend(INT chan)                   /* Terminate SLIP/CSLIP/PPP connection  */
{
     CHAR buffer[80];
     cman_tbs *cmp;
     LONG secs;
     struct scpvda *scpu;
     GBOOL upddsk;
     use_critical;
     INT usnsav;

     if (!(scpinf[chan].flags&SCPALV)) {
          return;
     }
     scpinf[chan].flags&=~SCPALV;
     cmp=&cman_tbp[chan];
     scpu=scpusr(chan);
     secs=lngtck-scpu->tckstt;
     if (scpatb) {
          critical;
          sprintf(buffer,"%ld secs, %ld bytes",
                  secs,cmp->lclbytes+cmp->rembytes);
          normal;
          if (cmp->errtoom > 0UL
           || cmp->errfrag > 0UL
           || cmp->errspur > 0UL) {
               strcat(buffer,",");
               if (cmp->errtoom+cmp->errspur > 0UL) {
                    strcat(buffer," ");
                    strcat(buffer,ul2as(cmp->errtoom+cmp->errspur));
                    strcat(buffer," S-");
               }
               if (cmp->errfrag > 0UL) {
                    strcat(buffer," ");
                    strcat(buffer,ul2as(cmp->errfrag));
                    strcat(buffer," F-");
               }
               strcat(buffer,"ERRORS");
          }
          if (scpu->whyend == NULL) {
               if (kilipg || errcod != 1) {
                    scpu->whyend="system shutdown";
               }
               else {
                    switch (status) {
                    case RING:
                         scpu->whyend="kill channel";
                         break;
                    case LOST2C:
                         scpu->whyend="lost carrier";
                         break;
                    case LOST25:
                    case SPXTDN:
                    case SPXWDG:
                         scpu->whyend="disconnect";
                         break;
                    default:
                         if (!(usroff(chan)->flags&ACTIVE)) {
                              scpu->whyend="inactivity timeout";
                         }
                         else {
                              scpu->whyend="end of session";
                         }
                         break;
                    }
               }
          }
          usnsav=usrnum;
          if (whomon == chan) {
               (*ftscope)(FTSCMT,spr("***** %s session ends",
                          nametbl[scpinf[chan].idx]));
          }
          curusr(chan);
          shocst(spr("%s SERVER SESSION ENDED",nametbl[scpinf[chan].idx]),
                 "%s, %s, %s",
                 scpu->realuid,buffer,scpu->whyend);
          curusr(usrnum=usnsav);
     }
     scpdedcrd(chan,scpu->realuid,scpu->chgdue+tfcacct(chan),realcr,1);
     scpu->chgdue=0L;
     upddsk=FALSE;
     dfaSetBlk(accbb);
     if (uisusn == -1
      && dfaAcqEQ(NULL,scpu->realuid,0)
      && ((struct usracc *)accbb->data)->usedat != today()) {
          upddsk=TRUE;
     }
     dfaRstBlk();
     if (scpu->timdue != 0L) {
          if (uisusn != -1) {
               if (usroff(uisusn)->usrcls == ONLINE) {
                    upddsk=TRUE;
               }
               uacoff(uisusn)->timtdy+=scpu->timdue;
          }
          else {
               upddsk=TRUE;
          }
     }
     if (upddsk) {
          dfaSetBlk(accbb);
          if (dfaAcqEQ(NULL,scpu->realuid,0)) {
               ((struct usracc *)accbb->data)->timtdy+=scpu->timdue;
               ((struct usracc *)accbb->data)->usedat=today();
               dfaUpdate(NULL);
          }
          dfaRstBlk();
     }
     scpu->timdue=0L;
     close_cman(chan);
     btutrg(chan,0);
     btubsz(chan,INPSIZ,OUTSIZ);
     usroff(chan)->crdrat=mmucrr;
}

static VOID
decscp(VOID)                       /* SLIP/CSLIP/PPP (*decusr)() handler   */
{
     GBOOL phantom;
     GBOOL enuf;
     struct clstab *cltptr;
     struct scpvda *scpu;
     LONG onlprc;
     LONG tfcprc;
     LONG lim;
     INT realusn;
     GBOOL outacred;
     GBOOL swclass;
     static struct usracc tmpacc;

     if (decusp->state == scpstt && (scpinf[decusn].flags&SCPALV)) {
          phantom=decusp->usrcls == BBSPRV;
          scpu=scpusr(decusn);
          tfcprc=tfcacct(decusn);
          if ((scpinf[decusn].flags&(SCPPRX+SCPDYN)) != 0) {
               onlprc=scppmin;
          }
          else {
               onlprc=scpamin;
          }
          if (phantom) {
               if (onsysn(scpu->realuid,1)) {          /* phantom dual     */
                    realusn=othusn;
                    scpu->timdue+=(15L*dualfact+50L)/100L;
                    othuap->timtdy+=scpu->timdue;
                    scpu->timdue=0L;
                    decuap->timtdy=othuap->timtdy;
                    onlprc=((onlprc*dualfact+50L)/100L);
                    if (othusp->crdrat+onlprc < scpmincg) {
                         onlprc=scpmincg-othusp->crdrat;
                    }
               }
               else {                                  /* phantom alone    */
                    realusn=-1; /* includes phantom w/real guy entering pw */
                    scpu->timdue+=15L;
               }
          }
          decusp->minut4++;
          onlprc=(onlprc+(decusp->minut4&3))>>2;  /* avoid roundoff errors */
          if (!phantom && dectdy() == -1) {
               scpu->whyend="out of time for today";
               kilchn(decusn);
               return;
          }
          else if (deccal() == -1) {
               scpu->whyend="out of time for call";
               kilchn(decusn);
               return;
          }
          else {
               dec15s();
               if (phantom) {
                    cltptr=NULL;
                    if (realusn != -1) {               /* phantom dual     */
                         enuf=odedcrd(realusn,tfcprc+onlprc,realcr,1);
                         if (!enuf && credzap
                          && !gen_haskey(safezap,decusn,decusp)) {
                              scpu->whyend="out of credits";
                              kilchn(decusn);
                              return;
                         }
                         decuap->creds=uacoff(realusn)->creds;
                         cltptr=usroff(realusn)->cltptr;
                         scpu->mvtimer=0;
                    }
                    else {                             /* phantom alone    */
                         lim=decusp->cltptr->limday;
                         swclass=FALSE;
                         if ((decuap->timtdy+scpu->timdue)/60L >= lim
                          && lim != -1) {
                              if (decusp->cltptr->flags&KCKOFF) {
                                   if (!(decusp->flags&NOZAP)) {
                                        scpu->whyend="out of time for today";
                                        kilchn(decusn);
                                        return;
                                   }
                              }
                              else {
                                   swclass=TRUE;
                              }
                         }
                         if (realcr) {
                              outacred=chgcross(decuap->creds-scpu->chgdue,
                                                -tfcprc-onlprc,
                                                0L);
                         }
                         else if (decusp->cltptr->flags&CRDXMT) {
                              outacred=FALSE;
                         }
                         else if (decusp->cltptr->dbtlmt == -1L) {
                              outacred=FALSE;
                         }
                         else {
                              outacred=chgcross(decuap->creds-scpu->chgdue,
                                                -tfcprc-onlprc,
                                                -decusp->cltptr->dbtlmt);
                         }
                         scpu->chgdue+=tfcprc+onlprc;
                         scpu->mvtimer+=15;
                         if (outacred || scpu->mvtimer > MVTHRESH || swclass) {
                              scpu->mvtimer=0;
                              enuf=scpdedcrd(decusn,scpu->realuid,
                                                    scpu->chgdue,realcr,1);
                              if (!enuf && credzap
                               && !gen_haskey(safezap,decusn,decusp)) {
                                   scpu->whyend="out of credits";
                                   kilchn(decusn);
                                   return;
                              }
                              scpu->chgdue=0L;
                              dfaSetBlk(accbb);
                              if (dfaAcqEQ(&tmpacc,scpu->realuid,0)) {
                                   if (scpu->timdue > TDTHRESH || swclass) {
                                        if (uisusn != -1) { /* (pw entry?) */
                                             uacoff(uisusn)->timtdy+=
                                                  scpu->timdue;
                                        }
                                        tmpacc.timtdy+=scpu->timdue;
                                        scpu->timdue=0;
                                        if (swclass) {
                                             swtcls(&tmpacc,0,decusp->cltptr
                                                    ->nxtcls[DOUTTIM],0,0);
                                             /* swtcls updates disk's timtdy */
                                        }
                                        else {
                                             dfaUpdate(&tmpacc);
                                        }
                                   }
                                   cltptr=fndcls(tmpacc.curcls);
                                   decuap->timtdy=tmpacc.timtdy;
                                   decuap->creds=tmpacc.creds;
                              }
                              dfaRstBlk();
                              if (cltptr == NULL) {
                                   scpu->whyend="deleted account";
                                   kilchn(decusn);
                                   return;
                              }
                         }
                    }
                    if (cltptr != NULL
                     && !sameas(decuap->curcls,cltptr->clname)) {
                         swtphcls(decusn,cltptr->clname);
                    }
               }
               else {
                    enuf=ldedcrd(decuap,tfcprc+onlprc,realcr,1);
                    if (!enuf && credzap
                     && !gen_haskey(safezap,decusn,decusp)) {
                         scpu->whyend="out of credits";
                         kilchn(decusn);
                         return;
                    }
               }
          }
          enfkeys(decusn);
     }
     else {
          (*olddec)();
     }
}

static GBOOL                       /*   returns true=crossed, false=not    */
chgcross(                          /* will upcoming chgs cross a threshold?*/
LONG oldcred,                      /*   old balance of credits             */
LONG addcred,                      /*   amount to be added at some point   */
LONG thresh)                       /*   threshold to check for crossing    */
{
     return((oldcred <= thresh) != (oldcred+addcred <= thresh));
}

static LONG                        /*   returns charges in credits         */
tfcacct(                           /* traffic accounting                   */
INT unum)                          /*   online user number                 */
{
     cman_tbs *cmp;
     LONG retval;
     struct scpvda *scpu;
     use_critical;

     cmp=&cman_tbp[unum];
     scpu=scpusr(unum);
     critical;
     retval=tfccst(cmp->lclbytes-scpu->lclbytes,scpchgl)
           +tfccst(cmp->rembytes-scpu->rembytes,scpchgr);
     scpu->lclbytes=cmp->lclbytes;
     scpu->rembytes=cmp->rembytes;
     normal;
     return(retval);
}

static VOID
swtphcls(                          /* switch phantom user's class          */
INT unum,                          /*   online user number                 */
CHAR *clsnam)                      /*   class name                         */
{
     CHAR tmpuid[UIDSIZ];
     INT savusn;
     struct clstab *cltptr;

     savusn=usrnum;
     curusr(unum);
     strcpy(tmpuid,usaptr->userid);
     strcpy(usaptr->userid,scpptr->realuid);
     strcpy(usaptr->curcls,clsnam);
     if ((cltptr=fndcls(clsnam)) == NULL) {
          scpptr->whyend="deleted account";
          kilchn(usrnum);
     }
     else {
          usrptr->cltptr=cltptr;
          loadkeys(clsnam);
     }
     strcpy(usaptr->userid,tmpuid);
     curusr(savusn);
     usrnum=savusn;
}

static VOID
enfkeys(                           /* enforce SLIP/CSLIP/PPP security      */
INT unum)                          /*   online user number                 */
{
     INT savusn;

     savusn=usrnum;
     curusr(unum);
     if (!haskey(keytbl[scpinf[usrnum].idx])
      || (usrptr->usrcls == BBSPRV && !haskey(lonkey))
      || numscpuid(usrnum,usaptr->userid) > 1 && !haskey(mulskey)
      || (scpinf[usrnum].flags&SCPPRX) && !haskey(proxkey)
      || (scpinf[usrnum].flags&SCPDYN) && !haskey(dynkey)) {
          scpptr->whyend="access terminated";
          kilchn(usrnum);
     }
     setkeep();
     curusr(savusn);
     usrnum=savusn;
}

static VOID
setkeep(VOID)                      /* handle setting of keep-alive flag    */
{                                  /*   curusr() expected to be in effect  */
     if (haskey(keepkey)) {
          scpinf[usrnum].flags|=SCPKAL;
     }
     else {
          scpinf[usrnum].flags&=~SCPKAL;
     }
}

static VOID
scphup(VOID)                       /* SLIP/CSLIP/PPP hangup routine        */
{
     if (usrptr->state == scpstt) {
          scpend(usrnum);
          clrxrf();
     }
}

static INT                         /*   returns actual sent (0 or nbytes)  */
scpocm(                            /* SLIP/CSLIP/PPP output from cman      */
INT chn,                           /*   channel index (0 to numchn-1)      */
CHAR *packet,                      /*   whole packet(s) only               */
UINT nbytes)                       /*   number of bytes in packet(s)       */
{
     UINT nroom;

     ASSERT(0 <= chn && chn < nterms);
     if (inscp(chn) && usroff(chn)->substt == SCPUP) {
          nroom=btuoba(chn);
          if (nbytes > nroom) {         /* min OUTSIZ/2-1 always > max MTU */
               return(0);
          }
          else {
               btuxct(chn,nbytes,packet);
               if (whomon == chn) {
                    (*ftscope)(FTSOUS,packet,nbytes);
               }
               scpusr(chn)->nbytes+=nbytes;
               return(nbytes);
          }
     }
     else {
          return((*oldocm)(chn,packet,nbytes));
     }
}

static VOID
scpecm(                            /* SLIP/CSLIP/PPP event from cman       */
INT chn,                           /*   channel index (0 to numchn-1)      */
INT evcode,                        /*   event code, see CME_XXXX in CMAN.H */
INT parm)                          /*   event-specific info, if any        */
{
     struct scpvda *scpu;

     if (chn >= 0
      && chn < nterms
      && inscp(chn)
      && usroff(chn)->substt == SCPUP) {
          scpu=scpusr(chn);
          switch (evcode) {
          case CME_ABORT:
               usroff(chn)->substt=SCPABORT;
               scpu->sttime=(USHORT)(hrtval()>>12);
               if (scpu->whyend == NULL) {
                    scpu->whyend="ppp-aborted";
               }
               scpend(chn);
               break;
          case CME_TERM:
               usroff(chn)->substt=SCPTERM;
               scpu->sttime=(USHORT)(hrtval()>>12);
               if (scpu->whyend == NULL) {
                    scpu->whyend="ppp-terminated";
               }
               scpend(chn);
               break;
          }
     }
     else {
          (*oldecm)(chn,evcode,parm);
     }
}

GBOOL                              /*   1=yes 0=no                         */
inscp(                             /* is channel in SLIP/CSLIP/PPP mode?   */
INT chn)                           /*   user number, 0 to nterms-1         */
{
     struct user *u;

     u=usroff(chn);
     return((u->usrcls == BBSPRV || u->usrcls == ACTUSR)
          && u->state == scpstt
          && (u->substt == SCPPRE || u->substt == SCPUP));
}

static VOID
scpsys(VOID)                       /* SLIP/CSLIP/PPP system cycle          */
{

     BEG_PHASE("System cycle/SLIP/PPP",0);
     cycle_cman();
     END_PHASE("System cycle/SLIP/PPP",0);
     (*oldsys)();
}

static VOID
scpzap(VOID)
{
     if (usrptr->state == scpstt && usrptr->usrcls == BBSPRV) {
          if (!(usrptr->flags&ISRIAL)
           && !(chanty[grpnum[usrnum]] == SDFIPXD)) {
               if (!(usrptr->flags&ISGCSU)) {
                    btucli(usrnum);
               }
               usrptr->nazapc=4;
          }
     }
     else {
          (*oldzap)();
     }
}

static VOID
scprst(VOID)                       /* SLIP/CSLIP/PPP reset-time handling   */
{
     scpend(usrnum);
     scpclear();
     (*oldrst)();
}

/*--- command line parsing utilities ---*/

static VOID
scpclear(VOID)                     /* clear SCP information for channel    */
{
     setmem(&scpinf[usrnum],sizeof(struct scpinf),0);
}

static GBOOL                       /*   returns TRUE=any options specified */
scparsem(                          /* parse series of modes and/or options */
CHAR *mdop)                        /*   modes and options                  */
                                   /*   usrnum is implicit input           */
                                   /*   (may ret FALSE & set option flags) */
{                                  /*   zeros out scpinf[] structure       */
     GBOOL rc=FALSE;
     CHAR *cp;

     for (cp=firstwd(mdop) ; *cp != '\0' ; cp=nextwd()) {
          if (scparse(cp)) {
               rc=TRUE;
          }
     }
     return(rc);
}

static GBOOL                       /*   returns TRUE=mode specified        */
scparse(                           /* parse SLIP/CSLIP/PPP or -p or -d opt */
CHAR *mdop)                        /*   mode or option                     */
                                   /*   usrnum is implicit input           */
{                                  /*   (may ret FALSE & set option flags) */
     GBOOL rc=TRUE;

     if (sameas(mdop,"SLIP") || sameas(mdop,"/SLIP")) {
          scpinf[usrnum].idx=SLPIDX;
     }
     else if (sameas(mdop,"CSLIP") || sameas(mdop,"/CSLIP")) {
          scpinf[usrnum].idx=CSLIDX;
     }
     else if (sameas(mdop,"PPP") || sameas(mdop,"/PPP")) {
          scpinf[usrnum].idx=PPPIDX;
     }
     else {
          rc=FALSE;
     }
     if (*mdop == '-' || *mdop == '/') {
          mdop++;
          if (sameas(mdop,"p")) {
               scpinf[usrnum].flags|=SCPPRX;
               scpinf[usrnum].flags&=~SCPDYN;
          }
          if (sameas(mdop,"d")) {
               scpinf[usrnum].flags|=SCPDYN;
               scpinf[usrnum].flags&=~SCPPRX;
          }
     }
     return(rc);
}

/*--- IP address assignment utilities ---*/

static VOID
ipuinit(VOID)                      /* init IP-addr-in-use bit array        */
{
     ipused=(UINT *)alczer(sizeof(UINT)*IPUWRDS);
     dfaSetBlk(genbb);
     if (dfaAcqGE(NULL,scpnam,1)
      && sameas(genbbscp->modnam,scpnam)) {
          do {
               dfaAbsRec(NULL,1);
               if (dfaLastLen() > sizeof(struct bbgscp)) {
                    dfaUpdateV(NULL,sizeof(struct bbgscp));
               }    /* truncate to downgrade data (ICO3->ICO2 or something)*/
               ipassign(genbbscp->ipaddr);
          } while (dfaQueryNX()
                && sameas(genbbscp->modnam,scpnam));
     }
}

static INT                         /*   returns 1=continuing, 0=list done  */
shobdy(VOID)                       /* show next line of list of IP addrs   */
{                                  /*   (calls outprf() itself)            */
     struct in_addr userip;
     INT rc;

     dfaSetBlk(genbb);
     setmem(&scpcptr->bbgscp,sizeof(struct bbgscp),0);
     if (!dfaAcqAbs(&scpcptr->bbgscp,scpcptr->bbgpos,1)
      || !sameas(scpcptr->bbgscp.modnam,scpnam)) {
          prfmsg(LSTABT);
          rc=0;
     }
     else {
          userip.s_addr=scpcptr->bbgscp.ipaddr;
          prfmsg(LSTBDY,scpcptr->bbgscp.userid,inet_ntoa(userip));
          if (!dfaQueryNX() || !sameas(genbb->key,scpnam)) {
               prfmsg(LSTFTR);
               rc=0;
          }
          else {
               scpcptr->bbgpos=dfaAbs();
               rc=1;
          }
     }
     outprf(usrnum);
     return(rc);
}

GBOOL                              /*   TRUE=in SCPMASK-specified range    */
ipvalid(                           /* is IP address valid assignable?      */
ULONG ipadr)                       /*   IP address, network byte order     */
{
     return((ipadr&scpmkb) == (scpipb&scpmkb)
         && (ipadr&~scpmkb) != 0             /* excl x.x.x.0               */
         && (ipadr&~scpmkb) < (~scpmkb)-1);  /* excl x.x.x.254, x.x.x.255  */
}

GBOOL                              /*   TRUE=in range and in database      */
ipassigned(                        /* is IP address available for use?     */
ULONG ipadr)                       /*   IP address, network byte order     */
{
     INT idx;

     idx=(INT)(ntohl(ipadr)-scpipbh);
     return(ipvalid(ipadr)
        && (ipused[idx/(sizeof(UINT)*8)]
             &(1<<(idx%(sizeof(UINT)*8)))) != 0);
}

VOID
ipassign(                          /* flag an IP address as assigned       */
ULONG ipadr)                       /*   IP address, network byte order     */
{
     INT idx;

     idx=(INT)(ntohl(ipadr)-scpipbh);
     if (ipvalid(ipadr)) {
          ipused[idx/(sizeof(UINT)*8)]|=
            (1<<(idx%(sizeof(UINT)*8)));
     }
}

VOID
ipunassign(                        /* flag an IP address as not assigned   */
ULONG ipadr)                       /*   IP address, network byte order     */
{
     INT idx;

     idx=(INT)(ntohl(ipadr)-scpipbh);
     if (ipvalid(ipadr)) {
          ipused[idx/(sizeof(UINT)*8)]&=~
            (1<<(idx%(sizeof(UINT)*8)));
     }
}

ULONG                              /*   IP address, netw order, CHNOIP=none*/
ipdynamic(VOID)                    /* find unused IP address for dynamic   */
{                                  /*   assignment to user (rotating scan) */
     static INT idx=-1;            /*   start with 1st addr (e.g. x.x.x.1) */
     INT i;

     for (idx++,i=0 ; i < numip ; i++,idx++) {
          if (idx >= numip) {
               idx=0;
          }
          if ((ipused[idx/(sizeof(UINT)*8)]
                &(1<<(idx%(sizeof(UINT)*8)))) == 0
           && chan_of_ip[idx] == IPNOCH) {
               return(htonl(scpipbh+idx));
          }
     }
     return(CHNOIP);
}

/*--- text variables ---*/

static CHAR *
tvar_slip_type(VOID)               /* SLIP_TYPE text variable              */
{
     INT idx;

     if (usrnum < 0
      || usrnum >= nterms
      || (idx=scpinf[usrnum].idx) < 0
      || idx >= NSTYPES) {
          return("UNKNOWN SLIP TYPE");
     }
     return(nametbl[idx]);
}

static CHAR *
tvar_slip_ip(VOID)                 /* SLIP_IP address text variable        */
{
     struct in_addr userip;
     static CHAR iptext[3+1+3+1+3+1+3+1];

     if (usrnum < 0
      || usrnum >= nterms
      || ip_of_chan == NULL
      || (userip.s_addr=ip_of_chan[usrnum]) == CHNOIP) {
          return("UNKNOWN SLIP IP");
     }
     return(stzcpy(iptext,inet_ntoa(userip),sizeof(iptext)));
}

static CHAR *
tvar_slip_mtu(VOID)                /* SLIP_MTU maximum transmission unit   */
{
     return(spr("%u",scpmtu));
}

static CHAR *
tvar_slip_userid(VOID)             /* SLIP_USERID real user's User-ID      */
{
     if (0 <= usrnum && usrnum < nterms && inscp(usrnum)) {
          return(scpusr(usrnum)->realuid);
     }
     return("N/A");
}

static INT                              /* 1=user had enough, 0=didn't     */
scpdedcrd(                         /* SLIP/CSLIP/PPP credit deduction      */
INT unum,                               /* channel online (for stats only) */
CHAR *uid,                              /* userid of user to deduct from   */
LONG amt,                               /* amount to deduct                */
INT real,                               /* 1=no exemptions, no debts       */
INT asmuch)                             /* 1=take as much as poss., 0=none */
                                        /* sets uisusn=-1 if not online    */
{
     INT retval;
     INT savusn;
     GBOOL upddsk;
     LONG newcreds=0L;
     LONG oldcreds=0L;
     INT savuis;
     ULONG oldmstat;
     LONG oldhstat;
     struct usracc *uaptr;

     savusn=usrnum;
     usrnum=-1;
     if (onbbs(uid,1)) {
          savuis=uisusn;
     }
     else {
          savuis=-1;
     }
     usrnum=savusn;
     if (savuis != -1 && grpnum[savuis] != 0) {
          oldmstat=mdstats[usroff(savuis)->state].creds;
          oldhstat=sv3.crdghr[grpnum[savuis]-1][dthour(now())];
     }
     if (amt == 0L) {
          retval=1;
          upddsk=FALSE;
     }
     else {
          if (savuis != -1) {
               uaptr=uacoff(savuis);
               oldcreds=uaptr->creds;
               retval=ldedcrd(uaptr,amt,real,asmuch);
               newcreds=uaptr->creds;
               upddsk=(usroff(savuis)->usrcls == ONLINE);
          }
          else {
               retval=1;
               upddsk=TRUE;
          }
     }
     if (upddsk) {
          dfaSetBlk(accbb);
          if (dfaAcqEQ(&acctmp,uid,0)) {
               oldcreds=acctmp.creds;
               retval=ldedcrd(&acctmp,amt,real,asmuch) && retval;
               newcreds=acctmp.creds;
               if (newcreds != oldcreds) {
                    dfaGetEQ(&acctmp,acctmp.userid,0);
                    acctmp.creds=newcreds;
                    dfaUpdate(&acctmp);
               }
          }
          else {
               retval=0;
          }
          dfaRstBlk();
     }
     if (savuis != -1 && grpnum[savuis] != 0) {
          mdstats[usroff(savuis)->state].creds=oldmstat;
          sv3.crdghr[grpnum[savuis]-1][dthour(now())]=oldhstat;
     }
     if (newcreds < oldcreds
      && grpnum[unum] != 0
      && !gen_haskey(syskey,unum,usroff(unum))) {
          mdstats[scpstt].creds+=oldcreds-newcreds;
          sv3.crdghr[grpnum[unum]-1][dthour(now())]+=newcreds-oldcreds;
     }
     uisusn=savuis;
     return(retval);
}

/*--- The following routine may one day be moved to more general  ---*/
/*--- purpose location, such as ACCOUNT.C (where it could be used ---*/
/*--- as the guts of tfcchg()).                                   ---*/
/*--- The DLL created by this source will work after that point,  ---*/
/*--- but this routine might need to be removed from the source   ---*/
/*--- before it can be compiled again.                            ---*/

static LONG
tfccst(                            /* compute cost of traffic              */
LONG nbytes,                            /* number of bytes of traffic      */
LONG credpk)                            /* charge, in credits/1024 bytes   */
{                                       /* returns number of credits cost  */
     LONG charge=0L;
     GBOOL neg;
     INT n;

     neg=credpk < 0L;
     if (neg) {
          credpk=-credpk;
     }
     for (n=10 ; n > 0 && nbytes > (1L<<15) ; n--) {
          nbytes>>=1;
     }
     for (charge=credpk ; n > 0 && charge > (1L<<15) ; n--) {
          charge>>=1;
     }
     charge*=nbytes;
     if (n > 0) {
          charge>>=n;
     }                             /* IOW: charge=credpk*nbytes/1024       */
     if (neg) {
          charge=-charge;
     }
     return(charge);
}

/*--- The following routine may one day be moved to more general  ---*/
/*--- purpose location, such as PHGCOMM.LIB.  (See TELNET.C.)     ---*/
/*--- The DLL created by this source will work after that point,  ---*/
/*--- but this routine might need to be removed from the source   ---*/
/*--- before it can be compiled again.                            ---*/

static INT
mrgpar(                            /* detects and removes margv[] parameter*/
CHAR *parm)                        /* parameter, e.g. "-a", case ignored   */
                                   /* returns number of occurances         */
                                   /* expects parsin() to be in effect     */
{                                  /* (general purpose)                    */
     INT i;
     INT cnt=0;

     for (i=0 ; i < margc ; i++) {
          if (sameas(margv[i],parm)) {
               cnt++;
               rstrin();
               if (i+1 < margc) {
                    strcpy(margv[i],margv[i+1]);
               }
               else {
                    margv[i][0]='\0';
               }
               parsin();
          }
     }
     return(cnt);
}

