/***************************************************************************
 *                                                                         *
 *   ACCOUNT.C                                                             *
 *                                                                         *
 *   Copyright (c) 1987-1997 Galacticomm, Inc.    All Rights Reserved.     *
 *                                                                         *
 *   This is the Worldgroup online accounting utility.                     *
 *                                                                         *
 *                                            - T. Stryker 7/6/86          *
 *                 "class" accounting update  - C. Robert  2/2/92          *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "majorbbs.h"
#include "gme.h"
#include "wgsacct.h"

#define FILREV "$Revision: 21 $"

HMCVFILE acnmb;               /* accounting named-message file block hdl   */
FILE *repfp;                  /* debt report file pointer                  */

DFAFILE *accbb,               /* user accounts btrieve data file           */
        *clsbb,               /* "class" btrieve block pointer             */
        *svbb;                /* system variables btrieve block pointer    */

static VOID *usracc;          /* pointer for use with ptrblok()            */

struct usracc *usaptr,        /* user accounting block pointer for usrnum  */
              *othuap,        /* gen purp other-user accounting data ptr   */
               acctmp;        /* temporary user account storage area       */
static
struct usracc *accptr;        /* general purpose pointer to user account   */

static GBOOL gotsv=FALSE;     /* loaded sv yet? (in case of early catastro)*/

INT numcls=0;                 /* total # of classes (! including disabled) */
USHORT dtrack;                /* number of HOURS since stats were zero'd   */

struct clstab *clshead,       /* head record for class table               */
              *clsptr;        /* handle ptr                                */

struct acclass clsrec;        /* one in-memory class record for temp use   */

INT msgsiz;                   /* size of text message area buffer          */

struct sysvbl sv;             /* system variables (part 1 of 3)            */
struct sysvb2 sv2;            /* system variables (part 2 of 3)            */
struct sysvb3 sv3;            /* system variables (part 3 of 3)            */

GBOOL inmcu=FALSE;            /* are we in midnight cleanup now? (1 or 0)  */

                              /*-- variables for decevy() & (*decusr)() ---*/
INT decusn;                   /* user number                               */
struct user *decusp;          /* user online information (MAJORBBS.H)      */
struct usracc *decuap;        /* user account information (USRACC.H)       */
LONG deccst;                  /* cost of online time since last (*decusr)()*/
VOID (*decusr)(VOID);         /* decevy() service vector                   */

/*--- OPTIONS FROM BBSACCT.MSG ---*/

INT pester,                   /* pester users with "how to buy creds" msg? */
    hwtlog,                   /* Time limit for logging in during hi  usage*/
    lwtlog,                   /* Time limit for logging in during low usage*/
    hwtsup,                   /* Time limit for signing up during hi  usage*/
    lwtsup,                   /* Time limit for signing up during low usage*/
    swteml;                   /* send Class Switch Notification E-mail?    */

static INT  lowdel(const CHAR *userid);
static VOID inicls(VOID);
static VOID inista(VOID);
static VOID repusr(const struct usracc *uacc);
static VOID svtask(INT taskid);
static VOID usetdy(ULONG acsb4d);
static VOID clscls(VOID);
static VOID clssta(VOID);
static VOID pbf2txt(VOID);
VOID decevy(VOID);
VOID decdft(VOID);
VOID chkliu(VOID);
VOID savvbl(VOID);
VOID updtrk(VOID);

VOID put2scn(struct usracc *accptr);

VOID
iniacc(VOID)                       /* initialize accounting                */
{
     decusr=decdft;
     inicls();
     cntdir("wgsdbt.rpt");
     if ((repfp=fopen("wgsdbt.rpt",FOPAA)) == NULL) {
          shocst("CAN'T RECORD DEBTS TO DISK!",
                 "(ERROR: Can't open \"wgsdbt.rpt\" for updating!)");
     }
     else if (numfils == 0L) {
          fprintf(repfp,"User-ID                            Debt (in credits)\n");
          fprintf(repfp,"----------------------------------------------------\n\n");
     }
     acnmb=opnmsg("wgsacct.mcv");
     accbb=dfaOpen("wgsusr2.dat",sizeof(struct usracc),NULL);
#ifdef STARTER
     if (dfaCountRec() > MAXSUSER) {
          catastro("Worldgroup Starter permits only %d user accounts.\n"
                   "If you would like to upgrade and take full advantage of\n"
                   "Worldgroup's capabilities, call 1-800-328-1128.",MAXSUSER);
     }
#endif
     usracc=alcblok(nterms,sizeof(struct usracc));
     msgsiz=outbsz-384;
     dclvda(msgsiz);
     pester=ynopt(PESTER);
     hwtlog=numopt(HWTLOG,-32767,32767);
     lwtlog=numopt(LWTLOG,-32767,32767);
     hwtsup=numopt(HWTSUP2,-32767,32767);
     lwtsup=numopt(LWTSUP2,-32767,32767);
     swteml=ynopt(SWTEML);
     rtkick(15,decevy);
     inista();
}

struct usracc *
uacoff(                            /* get pointer to user's account rec    */
INT unum)                          /*   user number to grab                */
{
     if (usracc != NULL) {
          return((struct usracc *)ptrblok(usracc,(USHORT)unum));
     }
     return(NULL);
}

static VOID
inicls(VOID)                       /* fill in class table from data file   */
{
     clsbb=dfaOpen("wgsclas2.dat",sizeof(struct acclass),NULL);
     clshead=NULL;
     if (dfaQueryLO(0)) {
          dfaAbsRec(NULL,0);
          do {
               movmem(clsbb->data,&clsrec,sizeof(struct acclass));
               if (clshead == NULL) {
                    clshead=(struct clstab *)alcmem(sizeof(struct clstab));
                    clsptr=clshead;
               }
               else {
                    clsptr->next=(struct clstab *)alcmem(sizeof(struct clstab));
                    clsptr=clsptr->next;
               }
               clsptr->next=NULL;
               movmem(&clsrec,clsptr,sizeof(struct clstab)-sizeof(struct clstab *));
               if (!(clsptr->flags&HASCRD && clsptr->flags&NOCRED)) {
                    numcls++;
               }
          } while (dfaQueryNX());
     }
     else {
          catastro("EMPTY %s!  RUN WGSSETCL UTILITY!",fnmcse("wgsclas2.dat"));
     }
}

static VOID
inista(VOID)                       /* initialize the statistics collector  */
{
     svbb=dfaOpen("wgsvbl2.dat",sizeof(struct sysvb3),NULL);
     dfaGetEQ(&sv,"key",0);
     dfaGetEQ(&sv2,"ky2",0);
     dfaGetEQ(&sv3,"ky3",0);
     updtrk();
     gotsv=TRUE;
     rtkick(60,chkliu);
     if (svrate) {
          rtkick(svrate,savvbl);
     }
}

INT
crtclass(                          /* add a class to class table and file  */
struct acclass *cptr)              /*   pointer to data to be added        */
{
     if (clshead == NULL) {
          clshead=(struct clstab *)malloc(sizeof(struct clstab));
          if (clshead == NULL) {
               return(0);
          }
          clsptr=clshead;
     }
     else {
          clsptr=clshead;
          while (clsptr->next != NULL) {
               clsptr=clsptr->next;
          }
          clsptr->next=(struct clstab *)malloc(sizeof(struct clstab));
          if (clsptr->next == NULL) {
               return(0);
          }
          clsptr=clsptr->next;
     }
     setmem(clsptr,sizeof(struct clstab),0);
     movmem(cptr,clsptr,sizeof(struct clstab)-sizeof(struct clstab *));
     dfaSetBlk(clsbb);
     dfaInsert(cptr);
     dfaRstBlk();
     numcls++;
     return(1);
}

struct clstab *
fndcls(                            /* find this guy's class in the table   */
const CHAR *clsname)               /*   what class name to look for        */
{
     struct clstab *fndptr;

     if (clsname == NULL || clshead == NULL) {
          return(NULL);
     }
     for (fndptr=clshead ; fndptr != NULL ; fndptr=fndptr->next) {
          if (sameas(clsname,fndptr->clname)) {
               return(fndptr);
          }
     }
     return(NULL);
}

VOID
swtcls(                            /* switch user to another class         */
struct usracc *uacc,               /*   usracc to switch to new class      */
GBOOL makprm,                      /*   make this permanent? (1 or 0)      */
const CHAR *clsnam,                /*   class name to switch to            */
INT dest,                          /*   destination result of (0-4)        */
INT days)                          /*   # of days 'till expire (0=dft)     */
{
     VOID uintfy(INT chan,CHAR *uid,struct clstab *cltptr);

     INT savnum,svlngo;
     HMCVFILE savmb;
     CHAR swtto[KEYSIZ];
     struct clstab *tclptr;
     static INT numitr=0;
     static struct message notmsg;

     if (++numitr >= 10) {
          catastro("BAD CLASS SETUP!  CLASSES ARE IMMEDIATELY EXPIRING TO\n"
                   "ONE-ANOTHER (INVOLVING THE %s CLASS)!",clsnam);
     }
     savmb=curmbk;
     if ((tclptr=fndcls(clsnam)) == NULL) {
          delacct(uacc->userid);
     }
     else if (((tclptr->flags&NOCRED) && uacc->creds <= 0L)
              || ((tclptr->flags&HASCRD) && uacc->creds > 0L)) {
          strcpy(swtto,tclptr->nxtcls[DCREDIT]);
          swtcls(uacc,makprm,swtto,1,0);
     }
     else {
          if ((clsptr=fndcls(uacc->curcls)) != NULL) {
               if (clsptr->users > 0) {
                    clsptr->users--;
               }
          }
          tclptr->users++;
          strcpy(clsrec.clname,uacc->curcls);
          strcpy(uacc->curcls,tclptr->clname);
          if (makprm) {
               strcpy(uacc->prmcls,tclptr->clname);
               if (tclptr->flags&DAYEXP) {
                    uacc->daystt=(days == 0 ? tclptr->dftday : days);
               }
               else {
                    uacc->daystt=0;
               }
               uacc->fgvdys=0;
          }
          prf("");
          outprf(usrnum);
          clrprf();
          setmbk(acnmb);
          savnum=usrnum;
          usrnum=-1;
          if (onbbs(uacc->userid,1)) {
               usrnum=savnum;
               othusn=uisusn;
               othusp=usroff(othusn);
               othuap=uacoff(othusn);
          }
          else {
               othusn=-1;
               svlngo=clingo;
               clingo=0;
          }
          if (dest < 2) {
               dfaSetBlk(clsbb);
               if (dfaAcqEQ(&clsrec,clsrec.clname,0)
                && clsrec.msgs[dest][0] != '\0') {
                    pmlt(clsrec.msgs[dest]);
               }
               dfaRstBlk();
          }
          if (prfbuf[0] == '\0') {
               if (makprm) {
                    prfmlt(dest == 3 ? SYSSWT : NEWCLS,tclptr->clname,
                           tclptr->limcal == -1 ? "UNLIMITED" :
                           spr("%d minutes",tclptr->limcal),
                           tclptr->limday == -1 ? "UNLIMITED" :
                           spr("%d minutes",tclptr->limday));
               }
               else {
                    prfmlt(dest == 0 ? TMPCLS : NWTCLS,tclptr->clname,
                           uacc->prmcls,tclptr->limcal == -1 ? "UNLIMITED"
                           : spr("%d minutes",tclptr->limcal),
                           tclptr->limday == -1 ? "UNLIMITED"
                           : spr("%d minutes",tclptr->limday));
               }
               if (shwcrd) {
                    prfmlt(CRDCLS,spr(uacc->creds > 0L ? "+%ld"
                            : "%ld",uacc->creds),
                           !(tclptr->flags&CRDXMT) ? ""
                            : "(Exempt from credit charges)");
                    if (!(tclptr->flags&CRDXMT)) {
                         prfmlt(LMTCLS,tclptr->dbtlmt == -1L ? "UNLIMITED"
                          : spr("%s credits",l2as(tclptr->dbtlmt*-1L)));
                    }
               }
               prfmlt(BOTCLS);
          }
          updaccu(uacc);
          if (othusn == -1) {
               if (swteml) {
                    setmem(&notmsg,sizeof(struct message),0);
                    stlcpy(notmsg.to,uacc->userid,MAXADR);
                    stlcpy(notmsg.from,"Sysop",MAXADR);
                    stlcpy(notmsg.topic,"Class Switch Notification",TPCSIZ);
                    pbf2txt();
                    simpsnd(&notmsg,vdatmp,"");
               }
               clrmlt();
               rstmbk();
               clingo=svlngo;
               usrnum=savnum;
          }
          else {
               injoth();
               clrmlt();
               rstmbk();
               othusp->cltptr=tclptr;
               movmem(uacc,othuap,sizeof(struct usracc));
               savnum=usrnum;
               curusr(othusn);
               loadkeys(tclptr->clname);
               curusr(savnum);
               if (othusp->flags&ISGCSU) {
                    uintfy(othusn,othuap->userid,tclptr);
               }
          }
     }
     setmbk(savmb);
     numitr--;
}

INT
namacls(                           /* check a proposed new class name      */
CHAR *clsname)                     /*   class name to check                */
{
     CHAR *ptr;

     if (strlen(clsname) > KEYSIZ-1 || strlen(clsname) < 3) {
          return(FALSE);
     }
     for (ptr=strupr(clsname) ; *ptr != '\0' ; ptr++) {
          if (!isalnum(*ptr) && *ptr != '_') {
               return(FALSE);
          }
     }
     return(TRUE);
}

LONG
tfcchg(                            /* charge user for traffic              */
LONG nbytes,                       /* number of bytes of traffic           */
LONG credpk)                       /* charge, in credits/1024 bytes        */
{                                  /* returns actual number deducted       */
     LONG charge;
     INT n;

     if (credpk > 0L) {
          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       */
          return(hdedcrd(uacoff(usrnum),charge,0,1));
     }
     return(0L);
}

INT
dedcrd(                            /* deduct credits from an online account*/
LONG amt,                          /* amount to deduct                     */
GBOOL asmuch)                      /* take as much as possible?            */
{
     return(ldedcrd(uacoff(usrnum),amt,0,asmuch));
}

INT
rdedcrd(                           /* deduct real credits from online guy  */
LONG amt,                          /* amount to deduct                     */
GBOOL asmuch)                      /* take as much as possible? 1 or 0     */
{
     return(ldedcrd(uacoff(usrnum),amt,1,asmuch));
}

INT
odedcrd(                           /* deduct credits from an online account*/
INT unum,                          /* user # of user to deduct from        */
LONG amt,                          /* amount to deduct                     */
GBOOL real,                        /* real creds only? (no debt=1)         */
GBOOL asmuch)                      /* take as much as possible? 1 or 0     */
{
     return(ldedcrd(uacoff(unum),amt,real,asmuch));
}

INT
ndedcrd(                           /* deduct credits from an offline acct  */
const CHAR *uid,                   /* userid of user to deduct from        */
LONG amt,                          /* amount to deduct                     */
GBOOL real,                        /* real creds only? (no debt=1)         */
GBOOL asmuch)                      /* take as much as possible? 1 or 0     */
{
     LONG creds;
     GBOOL retval=FALSE;

     dfaSetBlk(accbb);
     if (dfaAcqEQ(&acctmp,uid,0)) {
          retval=ldedcrd(&acctmp,amt,real,asmuch);
          creds=acctmp.creds;
          dfaGetEQ(&acctmp,acctmp.userid,0);
          acctmp.creds=creds;
          dfaUpdate(&acctmp);
     }
     dfaRstBlk();
     return(retval);
}

INT
gdedcrd(                           /* general deduct creds: online or off  */
const CHAR *uid,                   /* userid of user to deduct from        */
LONG amt,                          /* amount to deduct                     */
GBOOL real,                        /* real creds only? (no debt=1)         */
GBOOL asmuch)                      /* take as much as possible? 1 or 0     */
{
     if (onsysn(uid,TRUE)) {
          return(ldedcrd(othuap,amt,real,asmuch));
     }
     return(ndedcrd(uid,amt,real,asmuch));
}

INT
ldedcrd(                           /* low-level deduct credits facility    */
struct usracc *uptr,               /* user # of user to deduct from        */
LONG amt,                          /* amount to deduct                     */
GBOOL real,                        /* real creds only? (no debt=1)         */
GBOOL asmuch)                      /* take as much as possible? 1 or 0     */
{
     INT tmpunm,loop;
     GBOOL retval=FALSE;
     struct clstab *tabptr;

     othusn=-1;
     for (loop=0 ; loop < nterms ; loop++) {
          if (sameas(uptr->userid,uacoff(loop)->userid)
             && usroff(loop)->usrcls > SUPIPG) {
               othusn=loop;
               break;
          }
     }
     tabptr=fndcls(uptr->curcls);
     if (amt <= 0L) {
          uptr->creds-=amt;
          return(TRUE);
     }
     else if (!real && tabptr != NULL && (tabptr->flags&CRDXMT)) {
          return(TRUE);
     }
     switch (real || tabptr == NULL ? 0L : tabptr->dbtlmt) {
     case -1L:
          uptr->creds-=amt;
          retval=TRUE;
          break;
     case 0L:
          if (asmuch || uptr->creds >= amt) {
               uptr->creds-=amt;
               retval=(uptr->creds >= 0L);
               if (uptr->creds <= 0L) {
                    uptr->creds=0L;
                    if (tabptr != NULL && tabptr->flags&NOCRED) {
                         tmpunm=othusn;
                         swtcls(uptr,sameas(uptr->curcls,uptr->prmcls),
                                tabptr->nxtcls[DCREDIT],1,0);
                         othusn=tmpunm;
                    }
                    if (pester && othusn != -1 && usrnum != -1) {
                         prf("");
                         outprf(usrnum);
                         setmbk(acnmb);
                         prfmlt(HOWBUY,chgmin,chgtime,company,addres1,addres2);
                         rstmbk();
                         injoth();
                         clrmlt();
                    }
               }
          }
          break;
     default:
          if (asmuch || (uptr->creds-amt) >= -tabptr->dbtlmt) {
               uptr->creds-=amt;
               retval=(uptr->creds >= -tabptr->dbtlmt);
               if (uptr->creds <= -tabptr->dbtlmt) {
                    if (tabptr->flags&HITLMT) {
                         if (tabptr->flags&REPDBT) {
                              repusr(uptr);
                         }
                         uptr->creds=0L;
                    }
                    else {
                         uptr->creds=-tabptr->dbtlmt;
                    }
                    if (tabptr->flags&DBTLMT) {
                         swtcls(uptr,sameas(uptr->curcls,uptr->prmcls),
                                tabptr->nxtcls[DCREDIT],1,0);
                    }
               }
          }
     }
     if (retval && othusn != -1) {
          if (!gen_haskey(syskey,othusn,(VOID *)usroff(othusn))
           && grpnum[othusn] != 0) {
               mdstats[usroff(othusn)->state].creds+=amt;
               sv3.crdghr[grpnum[othusn]-1][dthour(now())]+=amt;
          }
     }
     return(retval);
}

LONG
hdedcrd(                           /* deduct credits, return num. deducted */
struct usracc *uptr,               /* account of user to deduct from       */
LONG amt,                          /* amount to deduct                     */
GBOOL real,                        /* real creds only? (no debt=1)         */
GBOOL asmuch)                      /* take as much as possible? 1 or 0     */
{
     LONG origcred;

     origcred=uptr->creds;
     if (ldedcrd(uptr,amt,real,asmuch)) {
          return(origcred-uptr->creds); /* return number of credits taken */
     }
     return(0L);
}

INT
tstcrd(                            /* does user have enough creds? (1 or 0)*/
LONG amt)                          /* amount of creds to test for          */
{
     return(ltstcrd(usaptr,amt,FALSE));
}

INT
rtstcrd(                           /* does user have enough REAL credits?  */
LONG amt)                          /* amount of creds to test for          */
{
     return(ltstcrd(usaptr,amt,TRUE));
}

INT
otstcrd(                           /* check if other user can spare creds  */
INT unum,                          /* user # of user to deduct from        */
LONG amt,                          /* proposed amount to deduct            */
GBOOL real)                        /* real creds? (1=no debt or xmpt)      */
{
     return(ltstcrd(uacoff(unum),amt,real));
}

INT
ntstcrd(                           /* see if offline account has amt creds */
const CHAR *uid,                   /* userid of user to look at            */
LONG amt,                          /* proposed amount to deduct            */
GBOOL real)                        /* real creds? (1=no debt or xmpt)      */
{
     dfaSetBlk(accbb);
     if (dfaAcqEQ(&acctmp,uid,0)) {
          dfaRstBlk();
          return(ltstcrd(&acctmp,amt,real));
     }
     dfaRstBlk();
     return(FALSE);
}

INT
gtstcrd(                           /* general test credits: online or off  */
const CHAR *uid,                   /* userid of user to look at            */
LONG amt,                          /* proposed amount to deduct            */
GBOOL real)                        /* real creds? (1=no debt or xmpt)      */
{
     if (onsysn(uid,TRUE)) {
          return(ltstcrd(othuap,amt,real));
     }
     return(ntstcrd(uid,amt,real));
}

INT
ltstcrd(                           /* can user spare creds?  (low-level)   */
const struct usracc *uptr,         /* user account to look at              */
LONG amt,                          /* proposed amount to deduct            */
GBOOL real)                        /* real creds? (1=no debt or xmpt)      */
{
     GBOOL retval=FALSE;
     struct clstab *uclptr;

     if ((uclptr=fndcls(uptr->curcls)) == NULL) {
          return(FALSE);
     }
     if (amt <= 0L || (!real && (uclptr->flags&CRDXMT))) {
          return(TRUE);
     }
     switch (real ? 0L : uclptr->dbtlmt) {
     case -1L:
          retval=TRUE;
          break;
     case 0L:
          if (uptr->creds >= amt) {
               retval=TRUE;
          }
          break;
     default:
          if ((uptr->creds-amt) >= -uclptr->dbtlmt) {
               retval=TRUE;
          }
     }
     return(retval);
}

VOID
dltcls(                            /* delete an existing class             */
struct clstab *tabptr)             /* pointer to class table entry         */
{
     INT savnum,unum;
     HMCVFILE savmb;
     CHAR kring[UIDSIZ];
     struct clstab *tmpptr,*prev=NULL;

     savmb=curmbk;
     savnum=usrnum;
     for (unum=0 ; unum < nterms ; unum++) {
          if (sameas(uacoff(unum)->curcls,tabptr->clname)) {
               curusr(unum);
               imdrop();
          }
     }
     curusr(savnum);
     dfaSetBlk(clsbb);
     dfaGetEQ(NULL,tabptr->clname,0);
     dfaDelete();
     if (!(tabptr->flags&HASCRD && tabptr->flags&NOCRED)) {
          numcls--;
     }
     dfaRstBlk();
     kring[0]=RINGID;
     strcpy(kring+1,tabptr->clname);
     dlkeys(kring);
     for (tmpptr=clshead ; tmpptr != NULL ; tmpptr=tmpptr->next) {
          if (tmpptr == tabptr) {
               if (tmpptr == clshead) {
                    clshead=tmpptr->next;
               }
               else {
                    prev->next=tmpptr->next;
               }
               free(tmpptr);
               break;
          }
          prev=tmpptr;
     }
     setmbk(savmb);
}

INT
addcrd(                       /* utility for posting credits to an account */
CHAR *keyuid,
const CHAR *tckstg,
GBOOL real)
{
     INT ison;

     if ((ison=crdusr(keyuid,tckstg,real,1)) >= 0) {
          if (real) {
               sv2.paidpst+=atol(tckstg);
          }
          else {
               sv2.freepst+=atol(tckstg);
          }
          shocst(spr("%s CREDITS POSTED",(real ? "PAID" : "FREE")),
                 "User-ID: %-29s Credits posted: %7s",keyuid,tckstg);
     }
     return(ison);
}

INT
crdusr(                            /* low-level utility for posting credits */
CHAR *keyuid,
const CHAR *tckstg,
GBOOL real,
GBOOL affall)
{
     INT ison;
     LONG ticks;

     dfaSetBlk(accbb);
     if (!dfaAcqEQ(&acctmp,keyuid,0)) {
          dfaRstBlk();
          return(-1);
     }
     ison=onsysn(keyuid,1);
     if (ison) {
          accptr=othuap;
     }
     else if (onbbs(keyuid,1)) {
          accptr=uacoff(uisusn);
     }
     else {
          accptr=&acctmp;
     }
     if (accptr->flags&DELTAG) {
          dfaRstBlk();
          return(-1);
     }
     ticks=atol(tckstg);
     accacct(accptr,-1);
     accptr->creds+=ticks;
     if (affall) {
          if ((accptr->totcreds+=ticks) < 0L) {
               accptr->totcreds=0;
          }
          if (real && (accptr->totpaid+=ticks) < 0L) {
               accptr->totpaid=0;
          }
     }
     accacct(accptr,1);
     if (accptr->creds > 0L && (clsptr=fndcls(accptr->curcls)) != NULL) {
          if (accptr->creds > 0L && (clsptr->flags&HASCRD)) {
               ison=0;
               swtcls(accptr,sameas(accptr->curcls,accptr->prmcls),
                      clsptr->nxtcls[DCREDIT],1,0);
          }
          else {
               dfaUpdate(accptr);
          }
     }
     else {
          dfaUpdate(accptr);
     }
     strcpy(keyuid,accptr->userid);
     dfaRstBlk();
     put2scn(accptr);
     return(ison);
}

INT
saycrd(                       /* notify creditee (if online) of credits    */
const CHAR *tckstg,
GBOOL real)
{
     setmbk(acnmb);
     prfmlt(real ? YOUCRD : YCRFRE,tckstg);
     rstmbk();
     return(injoth());
}

VOID
cpykey(                       /* format data (src) for Btrieve use (dest)  */
CHAR *dest,
const CHAR *src,
INT len)
{
     INT dstlen;

     movmem(src,dest,len);
     *(dest+len-1)='\0';
     dstlen=strlen(dest);
     setmem(dest+dstlen,len-dstlen,0);
}

VOID
decevy(VOID)                  /* rtkick() to gobble credits of users online*/
{
     static INT altbnf;

     setmbk(acnmb);
     for (decusn=0 ; decusn < nterms ; decusn++) {
          decusp=usroff(decusn);
          if (!(decusp->flags&BYEBYE)) {
               decuap=uacoff(decusn);
               deccst=lincst(decusn);
               (*decusr)();
               if ((altbnf&1) && decusp->pfnacc != 0) {
                    decusp->pfnacc--;
               }
          }
     }
     altbnf++;
     rtkick(15,decevy);
}

INT
declog(VOID)                       /* check logon time limit               */
{                                  /* 1=staying online, 0=forced logoff    */
     return(decusp->usetmr++ < wtwait(lwtlog,hwtlog));
}

INT
decsup(VOID)                       /* check signup time limit              */
{                                  /* 1=staying online, 0=forced logoff    */
     return(decusp->usetmr++ < wtwait(lwtsup,hwtsup));
}

INT
dectdy(VOID)                       /* check today's time limit             */
{                                  /* -1=log off, 1=warn, 0=nothing        */
     LONG lim;

     lim=decusp->cltptr->limday;
     if ((decuap->timtdy+=15L)/60L >= lim && lim != -1) {
          if (decusp->cltptr->flags&KCKOFF) {
               if (!(decusp->flags&NOZAP)) {
                    return(-1);
               }
          }
          else {
               swtcls(decuap,FALSE,decusp->cltptr->nxtcls[DOUTTIM],0,0);
          }
     }
     else if (!(decusp->flags&ONEMIN) && lim != -1
              && decuap->timtdy/60L >= lim-1
              && decusp->cltptr->flags&KCKOFF) {
          decusp->flags|=ONEMIN;
          return(1);
     }
     return(0);
}

INT
deccal(VOID)                       /* check per/call time limit            */
{                                  /* -1=log off, 1=warn, 0=nothing        */
     LONG lim;

     lim=decusp->cltptr->limcal;
     if ((decusp->minut4>>2) >= lim && lim != -1) {
          if (!(decusp->flags&NOZAP)) {
               return(-1);
          }
     }
     else if (!(decusp->flags&ONEMIN) && lim != -1
              && (decusp->minut4>>2) >= lim-1) {
          decusp->flags|=ONEMIN;
          return(1);
     }
     return(0);
}

VOID
dec15s(VOID)                       /* count out 15 sec of statistics       */
{
     decusp->cltptr->seconds+=15L;
     if (!gen_haskey(syskey,decusn,(VOID *)decusp)
      && grpnum[decusn] != 0) {
          mdstats[decusp->state].seconds+=15L;
          sv3.secghr[grpnum[decusn]-1][dthour(now())]+=15L;
     }
}

VOID
deccrd(                            /* decrement credits for 15 sec online  */
LONG surdsc)                       /* +surcharge / -discount for 15 sec    */
{
     deccst+=(decusp->crdrat>>2)+((decusp->minut4&3) < (decusp->crdrat&3));
     deccst+=surdsc;
     usrnum=-1;
     odedcrd(decusn,deccst,FALSE,TRUE);
}

VOID
decdft(VOID)                       /* 15sec per-channel handler            */
{                                  /* default (*decusr)() vector           */
     if (decusp->usrcls != VACANT) {
          decusp->minut4++;
     }
     switch (decusp->usrcls) {
     case VACANT:
          break;
     case ONLINE:
          if (!declog()) {
               curusr(decusn);
               byenow(SEEYEZ);
          }
          break;
     case BBSPRV:
          break;
     case SUPIPG:
          if (!decsup()) {
               curusr(decusn);
               byenow(SEEYEZ);
          }
          break;
     case SUPLON:
     case SUPLOF:
     case ACTUSR:
          switch (dectdy()) {
          case -1:
               curusr(decusn);
               byenow(OUTTDY);
               break;
          case 1:
               prfmlt(ALMTDY);
               othusn=decusn;
               injoth();
               clrmlt();
               break;
          }
          switch (deccal()) {
          case -1:
               curusr(decusn);
               byenow(OUTOFT);
               break;
          case 1:
               prfmlt(ALMOFT);
               othusn=decusn;
               injoth();
               clrmlt();
               break;
          }
          dec15s();
          deccrd(0L);
          break;
     }
}

VOID
chkliu(VOID)                       /* rtkick()'d checker of lines in use   */
{
     INT hr,min;
     static INT nsamps=0,totliu=0;

     nsamps++;
     totliu+=nliniu();
     if ((min=dtmin(now())) == 0 || min == 30) {
          min=(min == 30 ? 1 : 0);
          if ((hr=dthour(now())) == 0) {
               sv2.nliniu[min ? 0 : 47]+=(totliu+nsamps/2)/nsamps;
          }
          else {
               sv2.nliniu[hr*2+min-1]+=(totliu+nsamps/2)/nsamps;
          }
          nsamps=totliu=0;
     }
     rtkick(61,chkliu);
}

VOID
updtrk(VOID)                       /* rtkick()'d updating of dtrack        */
{
     extern INT mcuhr;

     if (sv2.lstzer == 0) {
          dtrack=24;
     }
     else {
          if ((dtrack=(cofdat(today())-cofdat(sv2.lstzer))*24) == 0) {
               dtrack=24;
          }
          else if (mcuhr > dthour(now())) {
               dtrack+=(23-mcuhr);
               dtrack+=dthour(now());
          }
          else {
               dtrack+=(dthour(now())-mcuhr);
          }
     }
     rtkick(1800,updtrk);
}

VOID
savvbl(VOID)                       /* rtkick()'d system variable saver     */
{
     initask(svtask);
}

static VOID
svtask(                            /* system variable saver background task*/
INT taskid)
{
     static INT countr=0;

     dfaSetBlk(svbb);
     switch (countr++) {
     case 0:
          dfaGetEQ(NULL,"key",0);
          dfaUpdateV(&sv,sizeof(struct sysvbl));
          break;
     case 1:
          dfaGetEQ(NULL,"ky2",0);
          dfaUpdateV(&sv2,sizeof(struct sysvb2));
          break;
     default:
          dfaGetEQ(NULL,"ky3",0);
          dfaUpdateV(&sv3,sizeof(struct sysvb3));
          countr=0;
          mfytask(taskid,NULL);
          rtkick(svrate,savvbl);
     }
     dfaRstBlk();
}

INT
imbump(                  /* immediate bump?  logon or post-sign-up cases    */
GBOOL logon)
{
     INT bmpval;

     bmpval=(*hdlbump)(logon);
     setmbk(acnmb);
     switch (bmpval) {
     case BMPTIM:
          byenow(NOTIME);
          break;
     case BMPLNV:
          byenow(IMMEDL);
          break;
     case BMPSNV:
          byenow(IMMEDB);
          break;
     case BMPOTH:
          byenow(PAMSG);
          break;
     }
     rstmbk();
     return(bmpval != BMPAOK);
}

INT
csbump(                            /* low-level imbump() (used for C/S)    */
GBOOL logon)                       /*   is this a logon? (1=yes, 0=signup) */
{                                  /* (default (*hdlbump) handler          */
     INT limcal,limday;

     clsptr=usrptr->cltptr;
     if (((clsptr->flags&NOCRED) && usaptr->creds <= 0L)
      || ((clsptr->flags&HASCRD) && usaptr->creds > 0L)) {
          swtcls(usaptr,sameas(usaptr->curcls,usaptr->prmcls),
                 clsptr->nxtcls[DCREDIT],1,0);
          if (usaptr->flags&DELTAG) {
               return(BMPDEL);
          }
     }
     limcal=usrptr->cltptr->limcal;
     limday=usrptr->cltptr->limday;
     if (limcal == 0
      || (limday != -1 && ((usaptr->timtdy+30)/60) >= limday
       && usrptr->cltptr->flags&KCKOFF)) {
          return(logon ? ((!limcal || !limday) ? BMPLNV : BMPTIM) : BMPSNV);
     }
     return(BMPAOK);
}

INT
wtwait(                  /* interpolate between time limits for lo/hi usage */
INT lomin,                         /* returns time in 15 sec units         */
INT himin)
{
     return(((max(nliniu(),1)-1)*(himin-lomin)<<2)/max(nterms-1,1)+(lomin<<2));
}

INT
nliniu(VOID)                  /* returns number of system lines "in use"   */
{
     INT i,retval=0;

     for (i=0 ; i < nterms ; i++) {
          if (usroff(i)->usrcls != VACANT) {
               retval++;
          }
     }
     return(retval);
}

VOID
updacc(VOID)                       /* update current user's account        */
{
     updaccu(usaptr);
}

VOID
updaccu(                           /* update a user's account utility      */
struct usracc *puac)               /*   buffer with account info           */
{
     dfaSetBlk(accbb);
     if (!dfaAcqEQ(NULL,puac->userid,0)) {
          catastro("UPDACC ERROR: \"%s\" (%s)",
                   puac->userid,spr("%p",puac->userid));
     }
     fixBirthdate(puac->birthd);
     if (memcmp(accbb->data,puac,sizeof(struct usracc)) != 0) {
          dfaUpdate(puac);
     }
     dfaRstBlk();
}

INT
accmcu(VOID)                       /* account midnight cleanup routine     */
{
     ULONG numact;
     struct usracc mcuact;
     struct usracc actcmp;
     struct clstab *mclptr;
     INT mcudys,ctoday,tday,bomonth;

     usrnum=-2;
     if (ddyear(tday=today()) == 1980) {
          shocst("CLEANUP NOT ATTEMPTED","(DATE NOT SET:  %s)",ncdatel(tday));
          return(FALSE);
     }
     inmcu=TRUE;
     ctoday=cofdat(tday);
     mcudys=sv2.lastmcu == 0 ? 1 : (ctoday-cofdat(sv2.lastmcu));
     bomonth=(ddday(tday) == 1);
     numact=sv2.numact;
     sv2.numact=sv2.numfem=sv2.numcor=sv2.numans=0;
     setmem(sv2.matrix,sizeof(sv2.matrix),0);
     if (clshead != NULL) {
          for (mclptr=clshead ; mclptr != NULL ; mclptr=mclptr->next) {
               mclptr->users=0;
          }
     }
     dfaSetBlk(accbb);
     if (dfaAcqLO(NULL,0)) {
          do {
               movmem(accbb->data,&mcuact,sizeof(struct usracc));
               movmem(accbb->data,&actcmp,sizeof(struct usracc));
               if (asktbl[TBLBDY][0]) {
                    mcuact.age=calcage(mcuact.birthd);
               }
               if (mcuact.systyp >= NACMTY) {
                    mcuact.systyp=0;
               }
               accacct(&mcuact,1);
               mcuact.timtdy=0L;
               if (!sameas(mcuact.curcls,mcuact.prmcls)) {
                    strcpy(mcuact.curcls,mcuact.prmcls);
               }
               mclptr=fndcls(mcuact.curcls);
               if (mclptr != NULL && !(mcuact.flags&DELTAG)) {
                    mclptr->users++;
                    if (((mclptr->flags&NUMDAY) && (mcuact.fgvdys+=mcudys)
                         >= mclptr->fgvday) || (mcudys != 0 && daytoday() == 1
                        && (mclptr->flags&MONDAY))
                        || (mcudys != 0 && bomonth && (mclptr->flags&FSTMTH))) {
                         mcuact.fgvdys=0;
                         if (mcuact.creds < 0L) {
                              if (mclptr->flags&REPDBT) {
                                   repusr(&mcuact);
                              }
                              mcuact.creds=0L;
                         }
                    }
                    if ((mclptr->flags&DAYEXP) && mcuact.daystt == 0) {
                         mcuact.daystt=mclptr->dftday;
                    }
                    if ((mclptr->flags&DAYEXP) && (mcuact.daystt < mcudys
                        || (mcuact.daystt-=mcudys) == 0)) {
                         mcuact.daystt=0;
                         swtcls(&mcuact,TRUE,mclptr->nxtcls[DEXPIRE],2,0);
                    }
                    else if ((mclptr->flags&IDLEXP)
                        && ctoday-cofdat(mcuact.usedat) >= mclptr->idlday) {
                         swtcls(&mcuact,TRUE,mclptr->nxtcls[DLOAFER],4,0);
                    }
                    else {
                         if ((memcmp(&mcuact,&actcmp,sizeof(struct usracc)))
                              != 0) {
                              dfaUpdate(&mcuact);
                         }
                    }
               }
               else {
                    if (mclptr != NULL) {
                         mclptr->users++;
                    }
                    lowdel(mcuact.userid);
               }
               dfaSetBlk(accbb);
          } while (dfaQueryNX());
     }
     sv2.lastmcu=today();
     inmcu=FALSE;
     usetdy(numact);
     dfaSetBlk(svbb);
     dfaGetEQ(NULL,"key",0);
     dfaUpdateV(&sv,sizeof(struct sysvbl));
     dfaGetEQ(NULL,"ky2",0);
     dfaUpdateV(&sv2,sizeof(struct sysvb2));
     dfaGetEQ(NULL,"ky3",0);
     dfaUpdateV(&sv3,sizeof(struct sysvb3));
     return(TRUE);
}

static VOID
usetdy(                       /* find and report today's use (hrs & creds) */
ULONG acsb4d)                      /* number of accounts before deletions  */
{
     INT i,j;
     LONG totsecs=0L,totcreds=0L;
     static struct sysvbl osv;
     static struct sysvb2 osv2;
     static struct sysvb3 osv3;

     dfaSetBlk(svbb);
     if (!dfaAcqEQ(&osv,"old",0)) {
          setmem(&osv,sizeof(struct sysvbl),0);
          setmem(&osv2,sizeof(struct sysvb2),0);
          setmem(&osv3,sizeof(struct sysvb3),0);
     }
     else {
          dfaGetEQ(&osv2,"ol2",0);
          dfaGetEQ(&osv3,"ol3",0);
          if (sv2.lstzer != osv2.lstzer) {
               setmem(osv3.secghr,(NUMGRPS-1)*24*sizeof(LONG),0);
               setmem(osv3.crdghr,(NUMGRPS-1)*24*sizeof(LONG),0);
          }
     }
     for (i=0 ; i < NUMGRPS-1 ; i++) {
          for (j=0 ; j < 24 ; j++) {
               totsecs+=(sv3.secghr[i][j]-osv3.secghr[i][j]);
               totcreds+=(sv3.crdghr[i][j]-osv3.crdghr[i][j]);
          }
     }
     shocst("SYSTEM USAGE SINCE LAST CLEANUP","%s%s",
            spr("Calls: %-6ldSignups: %-5u",
                sv2.totcalls-osv2.totcalls,acsb4d-osv2.numact),
            spr("Hrs used: %-5ldCreds used: %ld",
          (totsecs+1800L)/3600L,totcreds));
     movmem(&sv,&osv,sizeof(struct sysvbl));
     movmem(&sv2,&osv2,sizeof(struct sysvb2));
     movmem(&sv3,&osv3,sizeof(struct sysvb3));
     strcpy(osv.key,"old");
     strcpy(osv2.ky2,"ol2");
     strcpy(osv3.ky3,"ol3");
     if (!dfaAcqEQ(NULL,"old",0)) {
          dfaInsertV(&osv,sizeof(struct sysvbl));
          dfaInsertV(&osv2,sizeof(struct sysvb2));
          dfaInsertV(&osv3,sizeof(struct sysvb3));
     }
     else {
          dfaUpdateV(&osv,sizeof(struct sysvbl));
          dfaGetEQ(NULL,"ol2",0);
          dfaUpdateV(&osv2,sizeof(struct sysvb2));
          dfaGetEQ(NULL,"ol3",0);
          dfaUpdateV(&osv3,sizeof(struct sysvb3));
     }
     dfaRstBlk();
}

VOID
accacct(                      /* calculate system statistics for accounts  */
const struct usracc *accptr,
INT plusor)
{
     INT i;
     static INT agebra[NAGEBK]={20,30,40,50,9999};

     sv2.numact+=plusor;
     if (accptr->sex != 'M') {
          sv2.numfem+=plusor;
     }
     if (accptr->usrad1[0] != '\0') {
          sv2.numcor+=plusor;
     }
     if (accptr->ansifl&ANSON) {
          sv2.numans+=plusor;
     }
     for (i=0 ; i < NAGEBK ; i++) {
          if (accptr->age < agebra[i]) {
               sv2.matrix[(SHORT)accptr->systyp][i]+=plusor;
               break;
          }
     }
}

INT
delacct(                           /* delete an account from system routine*/
const CHAR *userid)
{
     INT savusn;

     if (inmcu) {
          return(lowdel(userid));
     }
     else {
          dfaSetBlk(accbb);
          if (!dfaAcqEQ(&acctmp,userid,0) || (acctmp.flags&UNDAXS)) {
               return(-1);
          }
          acctmp.flags|=DELTAG;
          dfaUpdate(&acctmp);
          dfaRstBlk();
          savusn=usrnum;
          usrnum=-1;
          if (onbbs(userid,1)) {
               uacoff(uisusn)->flags|=DELTAG;
               kilchn(uisusn);
          }
          usrnum=savusn;
     }
     return(0);
}

static INT
lowdel(                            /* low-level delete user func (MCU only)*/
const CHAR *userid)                /* user-id to delete                    */
{
     INT i;
     VOID (*rouptr)(CHAR *);

     dfaSetBlk(accbb);
     if (!dfaAcqEQ(&acctmp,userid,0) || (acctmp.flags&UNDAXS)) {
          return(-1);
     }
     dfaDelete();
     delxrf(acctmp.userid);
     if ((clsptr=fndcls(acctmp.curcls)) != NULL) {
          clsptr->users--;
     }
     accacct(&acctmp,-1);
     for (i=0 ; i < nmods ; i++) {
          if ((rouptr=module[i]->dlarou) != NULL) {
               (*rouptr)((CHAR *)userid);
          }
     }
     dlkeys(userid);
     delgen(userid);
     shocst("USER ACCOUNT DELETED","User-ID: %-29s (Had %ld credits)",
            userid,acctmp.creds);
     dfaRstBlk();
     return(0);
}

INT
kiluid(                            /* "kill" the User-ID just by setting flag*/
const CHAR *userid)
{
     INT savusn;

     dfaSetBlk(accbb);
     if (!dfaAcqEQ(&acctmp,userid,0)) {
          return(-1);
     }
     acctmp.flags|=SUSPEN;
     dfaUpdate(&acctmp);
     dfaRstBlk();
     savusn=usrnum;
     usrnum=-1;
     if (onbbs(userid,1)) {
          uacoff(uisusn)->flags|=SUSPEN;
          kilchn(uisusn);
     }
     usrnum=savusn;
     shocst("USER ACCOUNT SUSPENDED","Suspended User-ID: %-29s",userid);
     return(0);
}

VOID
howbuy(VOID)                       /* emit the "how to buy creds" msg      */
{
     if (pester) {
          setmbk(acnmb);
          prfmsg(HOWBUY,chgmin,chgtime,company,addres1,addres2);
          rstmbk();
     }
}

static VOID
repusr(                            /* add user to debt report              */
const struct usracc *uacc)         /* ptr to user account to report        */
{
     if (repfp != NULL) {
          fprintf(repfp,"%-34s %ld\n",uacc->userid,-uacc->creds);
     }
}

static VOID
clssta(VOID)                       /* close down the statistic collector   */
{
     if (gotsv) {
          dfaSetBlk(svbb);
          dfaGetEQ(NULL,"key",0);
          dfaUpdateV(&sv,sizeof(struct sysvbl));
          dfaGetEQ(NULL,"ky2",0);
          dfaUpdateV(&sv2,sizeof(struct sysvb2));
          dfaGetEQ(NULL,"ky3",0);
          dfaUpdateV(&sv3,sizeof(struct sysvb3));
          dfaClose(svbb);
          gotsv=FALSE;
     }
}

VOID
clsacc(VOID)                       /* close account-related files for shutd*/
{
     clscls();
     clssta();
     dfaClose(accbb);
     clsmsg(acnmb);
     if (repfp != NULL) {
          fclose(repfp);
     }
}

static VOID
clscls(VOID)                       /* save class data back to disk         */
{
     if (clshead != NULL) {
          dfaSetBlk(clsbb);
          for (clsptr=clshead ; clsptr != NULL ; clsptr=clsptr->next) {
               dfaGetEQ(&clsrec,clsptr->clname,0);
               movmem(clsptr,&clsrec,
                      sizeof(struct clstab)-sizeof(struct clstab *));
               dfaUpdate(&clsrec);
          }
          dfaClose(clsbb);
     }
}

static VOID
pbf2txt(VOID)                      /* xfer prfbuf to message text area     */
{
     CHAR *cp;

     stpans(prfbuf);
     if (strlen(prfbuf) >= msgsiz) {
          prfbuf[msgsiz-1]='\0';
     }
     for (cp=prfbuf ; *cp != '\0' ; cp++) {
          if (*cp == '\n') {
               *cp='\r';
          }
          else if (*cp == '') {
               *cp=' ';
          }
     }
     strcpy(vdatmp,prfbuf);
     clrmlt();
}
