/***************************************************************************
 *                                                                         *
 *   NNTPRECV.C                                                            *
 *                                                                         *
 *   Copyright (c) 1995-1997 Galacticomm, Inc.    All Rights Reserved.     *
 *                                                                         *
 *   NNTP server for receiving Usenet news to Worldgroup's GME from        *
 *   the Internet, per RFC 977 and 1036.                                   *
 *                                                                         *
 *                               6/26/95 - Bert Love,                      *
 *                                         Ilya Minkin &                   *
 *                                         Charles Dunn                    *
 *                                                                         *
 ***************************************************************************/

#include <sys/stat.h>
#include "gcomm.h"
#include "majorbbs.h"
#include "gme.h"
#include "galtext.h"
#include "remote.h"
#include "tcpip.h"
#include "dns.h"
#include "nntp.h"
#include "smtpexp.h"
#include "galnntpd.h"

#ifndef ICO2
#define tcpip_errno ips_errno
#define TNFACCP TNFRECV
#define TNFCONN TNFSEND
#endif

#define FILREV "$Revision: 36 $"

#define MAXXPST  100               /* maximum number of cross postings     */
#define BUFSPLIT (outbsz/2)
#define NOCART   0L                /* CAP no current article pointed to    */
#define NOCGRP   EMLID             /* no current group pointed to          */

                                   /* NNTP header parsing state codes      */
#define HEADER    0                /*   parsing message header state       */
#define CTRLRD    1                /*   processing control message         */
#define BODY      2                /*   parsing message body state         */
#define SUBMIT    3                /*   submitting message state           */
#define CYCIMP    4                /*   GME cycling message import         */
#define CTRLDEL   5                /*   GME cycling message delete         */
#define CHKRMT    6                /*   checking if need to send to server */
#define COPYOUT   7                /*   copying message to outgoing queue  */

                                   /* NNTP server state codes              */
#define WAITCMD   0                /*   waiting for command                */
#define RECVBODY  1                /*   receiving message header/body      */
#define LISTLIST  3                /*   processing LIST command            */
#define LISTGRP   4                /*   processing LISTGROUP command       */
#define SNDBODY   5                /*   sending article body               */
#define NEWGRPS   6                /*   sending new newsgroups             */
#define NEWNEWS   7                /*   sending new news                   */
#define OUTXHDR   8                /*   processing XHDR command            */
#define OUTXOVR   9                /*   processing XOVER command           */
#define UUECODE   10               /*   uuencode file attachment           */

                                   /* log function codes                   */
#define INCMING   0                /*   incoming data                      */
#define OUTGING   1                /*   outgoing data                      */
#define SHOWLOG   2                /*   log entry via shologd()             */

                                   /* command codes for prcabhs()          */
#define CMDARTL   0                /*   ARTICLE command                    */
#define CMDBODY   1                /*   BODY command                       */
#define CMDHEAD   2                /*   HEAD command                       */
#define CMDSTAT   3                /*   STAT command                       */

                                   /* types of LIST command                */
#define PLAINLST  0                /*   LIST cmd by RFC977 !!!             */
#define GROUPLST  1                /*   LIST NEWSGROUP                     */

static CHAR empt[]="";             /* blank string                         */
static CHAR xposted[]="XPOSTED";   /* extra hdr. for POSTed vs. IHAVEd msg */

static VOID nntdincall(INT gotchn);
static GBOOL nntdinp(VOID);
static VOID nntdlin(CHAR *linput);
static VOID nntdsts(VOID);
static VOID nntdscn(VOID);
static VOID filtsk(INT taskid);
static VOID nntdtsk(INT taskid);
static ULONG impSlice(VOID);
static GBOOL prp4ctl(CHAR *ctlstr);
static GBOOL ctrlrd(VOID);
static GBOOL ctrldel(VOID);
static VOID clndir(VOID);
static GBOOL anytime(CHAR *linput);
static VOID clsfil(GBOOL good);
static GBOOL renwrk(CHAR *wrkfil);
static VOID makwrk(VOID);
static USHORT nws2frm(CHAR *nwsgrp,GBOOL *isusenet);
static GBOOL valhdr(VOID);
static VOID enatmo(VOID);
static VOID distmo(VOID);
static GBOOL chkrmt(VOID);
static VOID finfil(INT taskid);
static VOID prcinp(VOID);
static VOID biglin(VOID);
static VOID nntprf(VOID);
static VOID lognntd(CHAR *logstr,INT inout);
static VOID shologd(CHAR *header,CHAR *footer,...);
static VOID nntdmcu(VOID);
static VOID delHook(INT deltype,const struct message *msg,const CHAR *text);
static GBOOL chkmsg(CHAR *msgid,struct midinf *midinf);
static GBOOL listlist(VOID);
static GBOOL listgrp(VOID);
static GBOOL getlohi(USHORT forum,ULONG *lmsgno,ULONG *hmsgno);
static CHAR *getarg(CHAR *cmdstr);
static GBOOL hasracc(USHORT forum, GBOOL *readonly);
static GBOOL getmidx(USHORT forum,ULONG msgidx,struct midinf *midinf);
static VOID prp4rcv(VOID);
static VOID setfltr(CHAR *linput);
static GBOOL testfltr(CHAR *newsname);
static VOID prcabhs(CHAR *linput,INT command);
static VOID outabhs(INT command,struct midinf *pminf,INT errmsg);
static VOID outhdr(struct message *msg,struct midinf *mid);
static GBOOL outbody(VOID);
static GBOOL uuecode(VOID);
static GBOOL prp2uuc(VOID);
static GBOOL msgxst(const struct message *msg,struct midinf *mid);
static CHAR *getenam(USHORT forum);
static GBOOL getdat(CHAR *date,CHAR *time,GBOOL isgmt);
static GBOOL newgrps(VOID);
static GBOOL newnews(VOID);
static VOID getnfrm(VOID);
static GBOOL prcxhdr(CHAR *linput);
static GBOOL cycxhdr(VOID);
static VOID outxhdr(struct midinf *pminf,const CHAR *midstr);
static GBOOL prcxovr(CHAR *linput);
static GBOOL cycxovr(VOID);
static VOID outxovr(struct midinf *pminf);
static GBOOL getXRange(CHAR *range);
static GBOOL nextXMsg(struct midinf *pminf);
static VOID prepRead(USHORT forum,LONG msgid);
static VOID prgfin(INT msgno);
static GBOOL checkip(ULONG ip);
static VOID setipmsk(VOID);
static VOID ip2ipmsk(CHAR *ipstr,ULONG *ip,ULONG *msk,CHAR *fl);
static VOID valngrp(CHAR *inbuf);
static VOID initXref(VOID);
static VOID xrfForCrt(const struct fordsk *newdef,const CHAR *desc,
                      const CHAR *echoes);
static VOID xrfForMod(const struct fordef *fordef,const CHAR *desc,
                      const CHAR *echoes);
static VOID xrfForDel(USHORT forum);
static VOID xrfNewFor(const adr_t *echo,INT necho,USHORT forum);
static VOID xrfAddXref(const adr_t *echo,INT necho,USHORT forum);
static VOID xrfSortList(VOID);
static INT xrfGroupIdx(const CHAR *group);
static INT xrfNearGrp(INT *pcomp,const CHAR *group);

static INT nntdstt;                /* NNTP server module state number      */

struct module nntdmodule={         /* module interface block               */
     "",                           /*    name used to refer to this module */
     NULL,                         /*    user logon supplemental routine   */
     nntdinp,                      /*    input routine if selected         */
     nntdsts,                      /*    status-input routine if selected  */
     NULL,                         /*    "injoth" routine for this module  */
     NULL,                         /*    user logoff supplemental routine  */
     NULL,                         /*    hangup (lost carrier) routine     */
     nntdmcu,                      /*    midnight cleanup routine          */
     NULL,                         /*    delete-account routine            */
     NULL                          /*    finish-up (sys shutdown) routine  */
};

                                   /* options from GALNNTPD.MCV            */
static GBOOL nntdonl;              /*   NNTP server online?                */
static GBOOL nntdacp;              /*   allow NNTP clients to post?        */
static GBOOL nntdrej;              /*   reject or ignore calls (nntdonl=0) */
GBOOL saveHdr;                     /*   save headers on NNTP messages?     */
INT nntdprt;                       /*   NNTP port number                   */
static INT maxnntd;                /*   maximum NNTP requests at a time    */
static GBOOL audnntd;              /*   audit successful NNTP requests?    */
static GBOOL audnntde;             /*   audit invalid NNTP requests?       */
static UINT nntdtmo;               /*   timeout in seconds                 */
static LONG mindsk;                /*   minimum amount of disk space req'd */
static CHAR wrkdir[GCMAXPTH];      /*   NNTP server work directory         */
static INT nntdqsi;                /*   NNTP server queue scan interval    */
static CHAR nntdcls[KEYSIZ];       /*   class for NNTP users               */
static GBOOL nntdlog;              /*   is receive log currently active?   */
static CHAR *nntdlnam;             /*   receive log file name              */

static CHAR rdyspec[GCMAXPTH];     /* file spec for scanning inbound dir   */
static CHAR *textbuf;              /* pointer to gen purpose text buffer   */
static CHAR *hdrbuf;               /* buffer for saving headers            */
static UINT txtbufsz;              /* size of 'textbuf'                    */
static CHAR *impbuf;               /* import buffer for GME                */
static struct ffblk nntdfb;        /* current file being imported          */
static INT bsize;                  /* size of input buffer                 */
static CHAR *ibuff;                /* input buffer                         */
static CHAR *iline;                /* input line                           */

#define IPMSKSZ     16             /* number of IP addresses               */

                                   /* values for 'flag'                    */
#define MCH_NONE    0              /*   match no IP                        */
#define MCH_MASK    1              /*   match mask/ip combination          */

struct tagipmsk {                  /* IP we accept calls from              */
     ULONG ip;                     /*   IP                                 */
     ULONG mask;                   /*   significant bits                   */
     CHAR flag;                    /*   type of entry                      */
};

struct tagipmsk ipmsk[IPMSKSZ];    /* IP we accept calls from              */

struct tagfltr {                   /* newsgroup name filter                */
     CHAR *beg;                    /*   begining part                      */
     CHAR *end;                    /*   ending part                        */
     GBOOL isneg;                  /*   is a negative match                */
};

static struct midinf ctlinf;       /* control message information          */

#define   FLTRSIZ   16             /* max number of filters                */

struct nntdusr {                   /* NNTP VDA info structure              */
     FILE *fp;                     /*   current file pointer               */
     INT xpstcnt;                  /*   cross posting counter              */
     USHORT sttime;                /*   start timer for timeout purposes   */
     GBOOL posting;                /*   are we POSTing or IHAVE instead?   */
     CHAR filnam[GCMAXPTH];        /*   current file name                  */
     CHAR wrkbuf[GMEWRKSZ];        /*   work buffer for GME                */
     CHAR lstfor[FORNSZ];          /*   last forum name accessed           */
     INT listmode;                 /*   type of LIST command               */
     ULONG lstaptr;                /*   currently listed article pointer   */
     ULONG curaptr;                /*   current article pointer (index)    */
     ULONG prvaptr;                /*   previous article pointer (index)   */
     ULONG msglim;                 /*   high message index for XOVER/XHDR  */
     USHORT curgrp;                /*   current newsgroup (forum)          */
     USHORT prvgrp;                /*   previous selected newsgroup        */
     USHORT newdat;                /*   date for NEWGROUPS/NEWNEWS command */
     USHORT newtim;                /*   time for NEWGROUPS/NEWNEWS command */
     INT newgrpi;                  /*   current forum index for NEWNEWS cmd*/
     CHAR xhdrlin[NNTLSZ];         /*   header line for XHDR command       */
     CHAR fltrstr[NNTLSZ];         /*   newsgroup filter string            */
     struct tagfltr fltr[FLTRSIZ]; /*   newsgroup filters                  */
     INT fltrnum;                  /*   number of active filters           */
     GBOOL exact;                  /*   fltrstr specifies the exact match  */
     FILE *attfp;                  /*   attachment file pointer            */
     CHAR attfnam[GCMAXPTH];       /*   full path to attachment file       */
     CHAR relnam[FNMSIZ];          /*   real attachment name               */
     CHAR *mbpos;                  /*   position in message body buffer    */
     GBOOL inhdr;                  /*   receiving message header           */
     CHAR msgbody[1];              /*   buffer for message body            */
};

#ifdef DEBUG
struct nntdusr *nntdptr;
#else
#define nntdptr ((struct nntdusr *)vdaptr)
#endif // DEBUG

static struct header {             /* incoming header information          */
     INT state;                    /*   parsing state                      */
     CHAR orgmid[MIDSIZ];          /*   original msg-id from IHAVE command */
     CHAR msgid[MIDSIZ];           /*   current message-id (for each part) */
     CHAR from[NNTLSZ];            /*   from header line                   */
     CHAR repto[MAXADR];           /*   reply-to address                   */
     CHAR ngrps[MAXXPST][MAXADR];  /*   list of newsgroups                 */
     INT xpstcnt;                  /*   number of cross postings           */
     USHORT forum[MAXXPST];        /*   array of forum ID's to post to     */
     GBOOL isusenet[MAXXPST];      /*   forum has Usenet echo              */
     INT forcnt;                   /*   current forum to post              */
     CHAR date[DATESZ];            /*   date line                          */
     CHAR topic[TPCSIZ];           /*   message topic                      */
     FILE *fp;                     /*   current message file pointer       */
     FILE *fpout;                  /*   output file for copying message    */
     CHAR filnam[GCMAXPTH];        /*   current message path               */
     CHAR outfil[GCMAXPTH];        /*   output file name for copying msg   */
     LONG bsofar;                  /*   bytes read so far into text buffer */
     INT partno;                   /*   current message part number        */
     CHAR *lstread;                /*   return value of last read          */
     GBOOL posted;                 /*   msg was poster via newsreader(POST)*/
} header;

static struct grpxref {            /* forum to newsgroup crossreference    */
     USHORT forum;                 /*   forum ID for newsgroup             */
     UCHAR flags;                  /*   status flags                       */
     CHAR group[MAXADR];           /*   newsgroup name                     */
} **xrfList=NULL;                  /* pointer to list of xrefs             */

                                   /* xref flag values                     */
#define XFLGNET 0x01               /*   this is a USENET group (vs. local) */

#define XRFBLKSZ 16                /* # elements to reallocate at a time   */

static INT xrfInUse;               /* # xref array elements in use         */
static INT xrfAlloc;               /* # xref array elements allocated      */

ULONG maxTicks;                    /* maximum importer time slice          */
ULONG minTicks;                    /* minimum importer time slice          */
ULONG zerTicks;                    /* no-load importer time slice          */
ULONG rtFactor;                    /* response time reduction factor       */
ULONG chFullPct;                   /* % nterms representing full load      */
ULONG timeDebt=0;                  /* excess time used in importer task    */

VOID
ini_nntrcv(VOID)                   /* NNTP server init called by NNTPSEND  */
{
     setmbk(nntdmb);
     nntdonl=ynopt(NNTDONL);
     nntdrej=ynopt(NNTDREJ);
     if (nntdonl || nntdrej) {
          setipmsk();
          nntdacp=ynopt(NNTDACP);
          /* make sure txtbuf is always large enough */
          txtbufsz=max(TXTLEN,(NNTLSZ*4));
          textbuf=alczer(txtbufsz);
          saveHdr=ynopt(NNTDSHDR);
          if (saveHdr) {
               hdrbuf=alcmem(TXTLEN);
          }
          impbuf=alczer(GMEWRKSZ);
          nntdprt=numopt(NNTDPRT,1,32767);
          bsize=BUFSPLIT+1;
          ibuff=alczer(bsize);
          iline=alczer(bsize);
          maxnntd=numopt(MAXNNTD,1,64);
          audnntd=ynopt(AUDNNTD);
          audnntde=ynopt(AUDNNTDE);
          nntdtmo=(UINT)numopt(NNTDTMO,0,1800);
          mindsk=(LONG)numopt(MINDSK,1,100)*1024L;
          if (!fmdir(stlcpy(wrkdir,getmsg(WRKDIR),GCMAXPTH))) {
               shocst("NNTP SERVER NOT INTIALIZED",
                      "Unable to create inbound work directory");
               return;
          }
          fixpth(wrkdir);
          nntdqsi=numopt(NNTDQSI,5,3600);
          maxTicks=(65536UL*numopt(IMAXTIM,1,32767))/1000;
          minTicks=(65536UL*numopt(IMINTIM,1,32767))/1000;
          zerTicks=(65536UL*numopt(IZERTIM,1,32767))/1000;
          rtFactor=numopt(IRSPTIM,1,32767);
          chFullPct=numopt(INCHAN,1,100);
          stlcpy(nntdcls,getmsg(NNTDCLS),KEYSIZ);
          nntdlog=ynopt(NNTDLOG);
          nntdlnam=stgopt(NNTDLNAM);
          if (nntdlog) {
               shocst("NNTP RECEIVE LOG ENABLED",
                      "All NNTP receive transactions will be logged");
               if (maxnntd > 1) {
                    maxnntd=1;
                    shocst("NNTP RECV CHANNELS LIMITED",
                           "Receive channels limited to one due to logging");
               }
          }
          dclvda(fldoff(nntdusr,msgbody)+TXTLEN);
          dclvda(outbsz);
          regtcpsvr(NNTNAME,nntdprt,NNTBACKLOG,nntdincall);
          sprintf(rdyspec,"%s*.rdy",wrkdir);
          stlcpy(nntdmodule.descrp,gmdnam("galnntp.mdf"),MNMSIZ);
          nntdstt=register_module(&nntdmodule);
          rtkick(nntdqsi,nntdscn);
          clndir();
          if (ynopt(USEXRF)) {
               initXref();
          }
          hook_gme(GMEHOOK_NOT_DELMSG,(voidfunc)delHook);
     }
}

static VOID
nntdincall(                        /* begin incoming NNTP session          */
INT gotchn)                        /* 1=chan (curusr) assigned, 0=not avail*/
{                                  /* implicit:  clskt=socket to client    */
     CHAR *ipptr;

#ifdef DEBUG
     nntdptr=(struct nntdusr *)vdaptr;
#endif // DEBUG
     setmbk(nntdmb);
     clrprf();
     if (!gotchn) {
          sprintf(prfbuf,xlttxv(stpans(getasc(kilipg || errcod != 1 ?
                                SRVSHUT : SRVFULL)),mxmssz),numcdi("TCP/IP"));
          send(clskt,prfbuf,strlen(prfbuf),0);
     }
     else {
          setmem(nntdptr,sizeof(struct nntdusr),0);
          nntdptr->curgrp=NOCGRP;
          nntdptr->curaptr=NOCART;
          usrptr->usrcls=BBSPRV;
          usrptr->state=nntdstt;
          usrptr->substt=WAITCMD;
          usrptr->flags|=NOGLOB+NOINJO;
          loadkeys(nntdcls);
          btuech(usrnum,0);
          btutrg(usrnum,outbsz-1);
          stansi();
          ipptr=inet_ntoa(tcpipinf[usrnum].inaddr);
          if (!nntdonl) {
               byetcp(NNTNOTON);
               rejinc(NNTNAME,"NNTP server disabled");
          }
          else if (numonl(nntdstt) > maxnntd) {
               byetcp(NNTDMANY,maxnntd);
               rejinc(NNTNAME,spr("%d NNTP server connections",maxnntd));
          }
          else if (dskfre(wrkdir) < mindsk) {
               byetcp(NNTDNOD);
               shologd("NNTP DISK SPACE LOW",
                       "Not enough disk space to process news");
          }
          else if (!checkip(tcpipinf[usrnum].inaddr.s_addr)) {
               byetcp(NNTDINS);
               shologd("NEWS SERVER CONNECTION REJECTED",
                       "Connection attempt rejected from %s",ipptr);
          }
          else {
               shochl(spr("NNTP contact from %s",ipptr),'N',0x1F);
               lognntd(spr("NNTP contact from %s",ipptr),SHOWLOG);
               sktnfy(TNFRECV,clskt,tcpinc,&tcpipinf[usrnum],usrnum);
               sprintf(usaptr->userid,"(%s)",ipptr);
               btuinj(usrnum,CYCLE);
               btubsz(usrnum,BUFSPLIT,BUFSPLIT);
               prfmsg(nntdacp ? NNTDOKY : NNTDOKN);
               nntprf();
               enatmo();
          }
     }
     rstmbk();
}

static GBOOL
nntdinp(VOID)                      /* stub input handler (see nntdlin())   */
{
     return(TRUE);
}

static VOID
nntdlin(                           /* NNTP server input handler            */
CHAR *linput)
{
     CHAR *ptr,*secptr,*newsfltr,*gmtptr;
     USHORT forum;
     GBOOL isgmt;
     GBOOL dummy;
     LONG nmsg;
     ULONG lmsgno,hmsgno;
     struct cmpkey ckey;
     struct midinf midinf;

#ifdef DEBUG
     nntdptr=(struct nntdusr *)vdaptr;
#endif // DEBUG
     setmbk(nntdmb);
     enatmo();
     clrprf();
     if (usrptr->substt != RECVBODY) {
          lognntd(linput,INCMING);
          if (anytime(linput)) {
               return;
          }
     }
     switch (usrptr->substt) {
     case WAITCMD:
          if (sameto("ARTICLE",linput)) {
               prcabhs(linput,CMDARTL);
          }
          else if (sameto("BODY",linput)) {
               prcabhs(linput,CMDBODY);
          }
          else if (sameto("GROUP",linput)) {
               if ((ptr=getarg(linput)) == NULL) {
                    prfmsg(NNTDSYN);
               }
               else if ((nntdptr->curgrp=nws2frm(ptr,&dummy)) == NOCGRP
                || !hasracc(nntdptr->curgrp,NULL)) {
                    nntdptr->curgrp=nntdptr->prvgrp;
                    prfmsg(NNTDNSG);
               }
               else {
                    if (getlohi(nntdptr->curgrp,&lmsgno,&hmsgno)) {
                         nntdptr->prvaptr=nntdptr->curaptr=lmsgno;
                         nmsg=hmsgno-lmsgno+1;
                    }
                    else {
                         nntdptr->prvaptr=nntdptr->curaptr=NOCART;
                         nmsg=0L;
                    }
                    nntdptr->prvgrp=nntdptr->curgrp;
                    prfmsg(NNTDSLCT,nmsg,l2as(lmsgno),l2as(hmsgno),ptr);
               }
          }
          else if (sameto("HEAD",linput)) {
               prcabhs(linput,CMDHEAD);
          }
          else if (sameto("IHAVE",linput)) {
               if ((ptr=strchr(linput,'<')) != NULL
                && (secptr=strchr(ptr,'>')) != NULL) {
                    *(++secptr)='\0';
                    if (strlen(ptr) > 2 && !chkmsg(ptr,&midinf)) {
                         nntdptr->posting=FALSE;
                         prp4rcv();
                         nntdptr->inhdr=TRUE;
                    }
                    else {
                         prfmsg(NNTDANW);
                    }
               }
               else {
                    prfmsg(NNTDSYN);
               }
          }
          else if (sameto("LAST",linput)) {
               if ((ptr=getarg(linput)) == NULL) {
                    if (nntdptr->curgrp == NOCGRP) {
                         prfmsg(NNTDNGS);
                    }
                    else if (nntdptr->curaptr == NOCART) {
                         prfmsg(NNTDNAS);
                    }
                    else {
                         dfaSetBlk(midbb);
                         ckey.forum=nntdptr->curgrp;
                         ckey.msgidx=nntdptr->curaptr;
                         if (dfaAcqLT(&midinf,&ckey,FIDKEY)
                          && midinf.forum == nntdptr->curgrp) {
                              nntdptr->prvaptr=nntdptr->curaptr;
                              nntdptr->curaptr=midinf.msgidx;
                              prfmsg(NNTDLAST,l2as(midinf.msgidx),
                                     midinf.midstr);
                         }
                         else {
                              prfmsg(NNTDNPVA);
                         }
                         dfaRstBlk();
                    }
               }
               else {
                    prfmsg(NNTDSYN);
               }
          }
          else if (sameto("LISTGROUP",linput)) {
               if ((ptr=getarg(linput)) == NULL) {
                    prfmsg(NNTDSYN);
               }
               else if ((nntdptr->curgrp=nws2frm(ptr,&dummy)) == NOCGRP
                || !hasracc(nntdptr->curgrp,NULL)) {
                    nntdptr->curgrp=nntdptr->prvgrp;
                    prfmsg(NNTDNSG);
               }
               else {
                    if (getlohi(nntdptr->curgrp,&lmsgno,&hmsgno)) {
                         nntdptr->prvaptr=nntdptr->curaptr=lmsgno;
                    }
                    else {
                         nntdptr->prvaptr=nntdptr->curaptr=NOCART;
                    }
                    nntdptr->prvgrp=nntdptr->curgrp;
                    prfmsg(NNTDLGRP);
                    if (nntdptr->curaptr != NOCART) {
                         nntdptr->lstaptr=nntdptr->curaptr-1;
                         usrptr->substt=LISTGRP;
                    }
                    else {
                         prf(".\n");
                    }
               }
          }
          else if (sameto("LIST",linput)) {
               if ((ptr=getarg(linput)) != NULL) {
                    if (sameto("OVERVIEW.FMT",ptr)) {
                         prfmsg(NNTDLFRM);
                    }
                    else if (sameto("NEWSGROUPS",ptr)) {
                         prfmsg(NNTDLST0);
                         *nntdptr->lstfor='\0';
                         nntdptr->listmode=GROUPLST;
                         usrptr->substt=LISTLIST;
                    }
                    else if (sameto("ACTIVE",ptr)) {
                         prfmsg(NNTDLST0);
                         *nntdptr->lstfor='\0';
                         nntdptr->listmode=PLAINLST;
                         usrptr->substt=LISTLIST;
                    }
                    else {
                         prfmsg(NNTDSYN);
                    }
               }
               else {
                    prfmsg(NNTDLST0);
                    *nntdptr->lstfor='\0';
                    nntdptr->listmode=PLAINLST;
                    usrptr->substt=LISTLIST;
               }
          }
          else if (sameto("NEWGROUPS",linput)) {
               if ((ptr=getarg(linput)) == NULL
                || (secptr=getarg(ptr)) == NULL) {
                    prfmsg(NNTDSYN);
               }
               else {
                    /* GMT for time zone is optional and is not currently */
                    /* supported                                          */
                    isgmt=FALSE;
                    if ((gmtptr=getarg(secptr)) != NULL
                     && (sameas("GMT",gmtptr) || sameto("GMT ",gmtptr))) {
                         isgmt=TRUE;
                    }
                    if (getdat(ptr,secptr,isgmt)) {
                         *nntdptr->lstfor='\0';
                         prfmsg(NNTDNGRP);
                         usrptr->substt=NEWGRPS;
                    }
                    else {
                         prfmsg(NNTDSYN);
                    }
               }
          }
          else if (sameto("NEWNEWS",linput)) {
               if ((newsfltr=getarg(linput)) == NULL
                || (ptr=getarg(newsfltr)) == NULL
                || (secptr=getarg(ptr)) == NULL) {
                    prfmsg(NNTDSYN);
               }
               else {
                    *(ptr-1)='\0';
                    *(secptr-1)='\0';
                    setfltr(newsfltr);
                    /* GMT for time zone is optional and is not currently */
                    /* supported                                          */
                    isgmt=((gmtptr=getarg(secptr)) != NULL
                        && (sameas("GMT",gmtptr) || sameto("GMT ",gmtptr)));
                    if (getdat(ptr,secptr,isgmt)) {
                         if (nntdptr->exact) {
                              if ((forum=nws2frm(newsfltr,&dummy)) != NOCGRP
                               && hasracc(forum,NULL)) {
                                   prepRead(forum,FIRSTM);
                              }
                              else {
                                   prfmsg(NNTDNSG);
                                   break;
                              }
                         }
                         else {
                              nntdptr->newgrpi=0;
                              getnfrm();
                         }
                         prfmsg(NNTDNNEW);
                         usrptr->substt=NEWNEWS;
                    }
                    else {
                         prfmsg(NNTDSYN);
                    }
               }
          }
          else if (sameto("NEXT",linput)) {
               if ((ptr=getarg(linput)) == NULL) {
                    if (nntdptr->curgrp == NOCGRP) {
                         prfmsg(NNTDNGS);
                    }
                    else if (nntdptr->curaptr == NOCART) {
                         prfmsg(NNTDNAS);
                    }
                    else {
                         dfaSetBlk(midbb);
                         ckey.forum=nntdptr->curgrp;
                         ckey.msgidx=nntdptr->curaptr;
                         if (dfaAcqGT(&midinf,&ckey,FIDKEY)
                          && midinf.forum == nntdptr->curgrp) {
                              nntdptr->prvaptr=nntdptr->curaptr;
                              nntdptr->curaptr=midinf.msgidx;
                              prfmsg(NNTDNEXT,l2as(midinf.msgidx),
                                     midinf.midstr);
                         }
                         else {
                              prfmsg(NNTDNNXA);
                         }
                         dfaRstBlk();
                    }
               }
               else {
                    prfmsg(NNTDSYN);
               }
          }
          else if (sameto("POST",linput)) {
               if (!nntdacp) {
                    prfmsg(NNTDNPST);
               }
               else {
                    nntdptr->posting=TRUE;
                    prp4rcv();
                    nntdptr->inhdr=TRUE;
               }
          }
          else if (sameto("STAT",linput)) {
               prcabhs(linput,CMDSTAT);
          }
          else if (sameto("XHDR",linput)) {
               if (prcxhdr(linput)) {
                    usrptr->substt=OUTXHDR;
               }
          }
          else if (sameto("XOVER",linput)) {
               if (prcxovr(linput)) {
                    usrptr->substt=OUTXOVR;
               }
          }
          else {
               prfmsg(NNTDCNR);
          }
          break;
     case RECVBODY:
          if (sameas(linput,".")) {
               clsfil(TRUE);
               if (!renwrk(nntdptr->filnam)) {
                    shocst("NNTP SERVER RENAME FAILED",
                           "Unable to rename work file \"%s\"",
                           nntdptr->filnam);
               }
               prfmsg(nntdptr->posting ? NNTDPOK : NNTDOK);
               usrptr->substt=WAITCMD;
          }
          else {
               ASSERT(nntdptr->fp != NULL);
               if (nntdptr->inhdr && nntdptr->posting) {
                    if (sameto("Newsgroups:",linput)) {
                         valngrp(linput);
                    }
                    if (sameto("Message-ID:",linput)
                     || sameto("Path:",linput)) {
                         return;
                    }
               }
               if (*(ptr=linput) == '.') {
                    ptr++;
               }
               if (nntdptr->inhdr && sameto("Path:",ptr)) {
                    ptr=skptwht(ptr+CSTRLEN("Path:"));
                    fprintf(nntdptr->fp,"Path: %s!",smtpHost());
               }
               if (fprintf(nntdptr->fp,"%s\n",ptr) == EOF) {
                    prfmsg(nntdptr->posting ? NNTDPFL : NNTDTAL);
                    clsfil(FALSE);
                    usrptr->substt=WAITCMD;
               }
               if (linput[0] == '\0') {
                    nntdptr->inhdr=FALSE;
               }
               if (nntdptr->inhdr) {
                    lognntd(ptr,INCMING);
               }
          }
          break;
     }
     rstmbk();
     nntprf();
}

static VOID
valngrp(                           /* validates that newsgroup has access  */
CHAR *inbuf)                       /* input (Newsgroups: alt.test, .., ..) */
{
     USHORT forum;
     GBOOL dummy,readonly;
     CHAR *scp,*ecp,group[MAXADR];

     ASSERT(sameto("Newsgroups:",inbuf));
     scp=crpstr(inbuf,':');
     do {
          if ((ecp=strchr(scp,',')) == NULL) {
               stlcpy(group,scp,MAXADR);
               ecp=scp+strlen(scp);
          }
          else {
               stlcpy(group,scp,min((size_t)((ecp-scp)+1),MAXADR));
               ecp=skptwht(ecp+1);
          }
          unpad(group);
          if ((forum=nws2frm(group,&dummy)) == NOCGRP
           || !hasracc(forum,&readonly)
           || readonly) {
               strmove(scp,ecp);
          }
          else {
               scp=ecp;
          }
     } while (!NULSTR(ecp));
     if (samend(unpad(inbuf),",")) {
          inbuf[strlen(inbuf)-1]='\0';
     }
}

static VOID
nntdsts(VOID)                      /* status handling                      */
{
#ifdef DEBUG
     nntdptr=(struct nntdusr *)vdaptr;
#endif // DEBUG
     setmbk(nntdmb);
     clrprf();
     if (status == RING) {
          prgfin(-1);
          return;
     }
     if (status != CYCLE) {
          dfsthn();
          return;
     }
     if (usrptr->flags&BYEBYE) {
          return;
     }
     if (nntdtmo > 0 && btuTicker()-nntdptr->sttime > nntdtmo
      && nntdptr->sttime != 0) {
          prgfin(NNTDTMOT);
          if (audnntde) {
               shologd("NNTP SERVER TIMEOUT",
                       "Client connection timed out with %s",
                       inet_ntoa(tcpipinf[usrnum].inaddr));
          }
          return;
     }
     if (usrptr->substt == LISTLIST) {
          distmo();
          if (!listlist()) {
               enatmo();
               usrptr->substt=WAITCMD;
          }
     }
     if (usrptr->substt == LISTGRP) {
          distmo();
          if (!listgrp()) {
               enatmo();
               usrptr->substt=WAITCMD;
          }
          nntprf();
          clrprf();
     }
     else if (usrptr->substt == SNDBODY) {
          distmo();
          if (!outbody()) {
               if (nntdptr->attfnam[0] != '\0') {
                    usrptr->substt=UUECODE;
               }
               else {
                    enatmo();
                    usrptr->substt=WAITCMD;
               }
          }
     }
     else if (usrptr->substt == UUECODE) {
          nntdptr->msgbody[0]='\0';
          nntdptr->mbpos=nntdptr->msgbody;
          if (nntdptr->attfp == NULL) {
               if (!prp2uuc()) {
                    nntdptr->attfnam[0]='\0';
               }
          }
          if (nntdptr->attfp != NULL && !uuecode()) {
               nntdptr->attfnam[0]='\0';
               fclose(nntdptr->attfp);
               nntdptr->attfp=NULL;
          }
          usrptr->substt=SNDBODY;
     }
     else if (usrptr->substt == NEWGRPS) {
          distmo();
          if (!newgrps()) {
               enatmo();
               usrptr->substt=WAITCMD;
          }
          nntprf();
          clrprf();
     }
     else if (usrptr->substt == NEWNEWS) {
          distmo();
          if (!newnews()) {
               enatmo();
               usrptr->substt=WAITCMD;
          }
          nntprf();
          clrprf();
     }
     else if (usrptr->substt == OUTXHDR) {
          distmo();
          clrprf();
          if (!cycxhdr()) {
               enatmo();
               usrptr->substt=WAITCMD;
          }
          nntprf();
          clrprf();
     }
     else if (usrptr->substt == OUTXOVR) {
          distmo();
          clrprf();
          if (!cycxovr()) {
               enatmo();
               usrptr->substt=WAITCMD;
          }
          nntprf();
          clrprf();
     }
     else {
          prcinp();
     }
     rstmbk();
     btuinj(usrnum,CYCLE);
}

static VOID
nntdscn(VOID)                      /* NNTP inbound news scan task          */
{
     if (fnd1st(&nntdfb,rdyspec,0)) {
          filtsk(-1);
     }
     else {
          rtkick(nntdqsi,nntdscn);
     }
}

static VOID
filtsk(                           /* process incoming NNTP news            */
INT taskid)
{
     GBOOL found=TRUE;

     if (taskid != -1) {
          found=fndnxt(&nntdfb);
     }
     else {
          taskid=initask(filtsk);
     }
     if (!found) {
          mfytask(taskid,NULL);
          rtkick(nntdqsi,nntdscn);
          return;
     }
     setmem(&header,sizeof(header),0);
     header.state=HEADER;
     stlcpy(header.filnam,wrkdir,GCMAXPTH);
     stlcat(header.filnam,nntdfb.ff_name,GCMAXPTH);
     mfytask(taskid,nntdtsk);
}

static VOID
nntdtsk(                           /* NNTP Server task routine (import)    */
INT taskid)
{
     CHAR *ptr,*secptr;
     ULONG timeSlice,begTime,timeUsed;
     INT lincnt,rc,i;
     static LONG thrid;
     static CHAR inbuf[NNTLSZ];
     static struct addrinf addrinf,addrinf1;
     static struct message msg;
     static struct midinf midinf;

     if (timeDebt >= (timeSlice=impSlice())) {
          timeDebt-=timeSlice;
          return;
     }
     begTime=hrtval()-timeDebt;
     setmbk(nntdmb);
     dfaSetBlk(midbb);
     do {
          switch (header.state) {
          case HEADER:
               thrid=0L;
               if ((header.fp=fopen(header.filnam,FOPRA)) == NULL) {
                    shologd("NNTP SERVER OPEN ERROR",
                            "News file: \"%s\"",header.filnam);
                    mfytask(taskid,filtsk);
                    timeDebt=0;
                    return;
               }
               lincnt=0;
               if (saveHdr) {
                    *hdrbuf='\0';
                    *textbuf='\0';
               }
               while (fgets(inbuf,NNTLSZ,header.fp) != NULL) {
                    if (inbuf[0] == '\n' || lincnt++ > NUMLINS) {
                         break;
                    }
                    unpad(strrpl(inbuf,'\t',' '));
                    if (strcmp(inbuf,xposted) == 0) {
                         header.posted=TRUE;
                    }
                    else if (saveHdr) {
                         stlcat(textbuf,inbuf,TXTLEN);
                         stlcat(textbuf,"\n",TXTLEN);
                    }
                    if (sameto("From:",inbuf)) {
                         stlcpy(header.from,inbuf,NNTLSZ);
                    }
                    else if (sameto("Reply-To:",inbuf)) {
                         if (!extractAddr(header.repto,inbuf)
                          || !smtval(header.repto)) {
                              *header.repto='\0';
                              if (audnntde) {
                                   shologd("NNTP INVALID REPLY-TO ADDR",
                                           "%0.65s",inbuf);
                              }
                         }
                    }
                    else if (sameto("Newsgroups:",inbuf)) {
                         if ((ptr=strtok(crpstr(inbuf,':'),",")) != NULL) {
                              do {
                                   stlcpy(header.ngrps[header.xpstcnt++],
                                          skptwht(unpad(ptr)),MAXADR);
                              } while (header.xpstcnt < MAXXPST
                                    && (ptr=strtok(NULL,",")) != NULL);
                         }
                    }
                    else if (sameto("Control:",inbuf)) {
                         if (!prp4ctl(crpstr(inbuf,':'))) {
                              finfil(taskid);
                              return;
                         }
                         break;
                    }
                    else if (sameto("Subject:",inbuf)) {
                         stlcpy(header.topic,crpstr(inbuf,':'),TPCSIZ);
                         if (sameto("cmsg",header.topic)) {
                              if (!prp4ctl(crpstr(header.topic,'g'))) {
                                   finfil(taskid);
                                   return;
                              }
                              break;
                         }
                    }
                    else if (sameto("Date:",inbuf)) {
                         stlcpy(header.date,crpstr(inbuf,':'),DATESZ);
                    }
                    else if (sameto("Message-ID:",inbuf)) {
                         stlcpy(header.orgmid,crpstr(inbuf,':'),MIDSIZ);
                    }
                    else if (sameto("References:",inbuf)) {
                         if ((ptr=strrchr(inbuf,'<')) != NULL) {
                              if ((secptr=strchr(ptr,'>')) != NULL) {
                                   *(++secptr)='\0';
                                   if (chkmsg(ptr,&midinf)) {
                                        thrid=midinf.thrid;
                                   }
                              }
                         }
                    }
               }
               if (header.state != HEADER) {
                    break;
               }
               if (!valhdr() || lincnt > NUMLINS) {
                    if (audnntde) {
                         shologd("NNTP SERVER HEADER ERROR",
                                 "Invalid header info in news file");
                    }
                    finfil(taskid);
                    return;
               }
               setmem(header.forum,sizeof(header.forum),0);
               setmem(header.isusenet,sizeof(header.isusenet),0);
               for (i=0 ; i < header.xpstcnt ; i++) {
                    header.forum[i]=nws2frm(header.ngrps[i],
                                            &(header.isusenet[i]));
               }
               header.forcnt=-1;
               header.state=BODY;
               stlcpy(header.msgid,header.orgmid,MIDSIZ);
               if (saveHdr) {
                    unpad(textbuf);
                    cfgSetStr(NNTPSECTHDR,NULL,textbuf,hdrbuf,TXTLEN);
               }
               textbuf[0]='\0';
               parseAddrLine(header.from,&addrinf);
               parseIntAddr(header.repto,&addrinf1);
               if (sameas(addrinf.usrals,addrinf1.usrals)
                && sameas(addrinf.domain,addrinf1.domain)) {
                    header.repto[0]='\0';
               }
               break;
          case CTRLRD:
               if (!ctrlrd()) {
                    finfil(taskid);
                    return;
               }
               break;
          case CTRLDEL:
               if (!ctrldel()) {
                    finfil(taskid);
                    return;
               }
               break;
          case BODY:
               if (header.bsofar == 0L) {         /* first chunk of any part?   */
                    clrprf();
                    prf("\n");
                    if (++header.partno > 1) {    /* 2nd or later part?         */
                         nmsgid(header.msgid);
                         prfmsg(NNTDPTN,header.partno);
                    }
                    else if (header.repto[0] != '\0') {
                         prfmsg(NNTDORG,crpstr(header.from,':'));
                    }
                    stpans(prfbuf);
                    stlcpy(textbuf,prfbuf,TXTLEN);
                    header.bsofar=(LONG)strlen(prfbuf);
                    clrprf();
               }
               for (lincnt=0 ; lincnt < NUMLINS ; lincnt++) {
                    if ((header.lstread=fgets(inbuf,NNTLSZ,header.fp)) == NULL) {
                         header.state=SUBMIT;
                         break;
                    }
                    stlcat(textbuf,inbuf,TXTLEN);
                    header.bsofar+=(LONG)strlen(inbuf);
                    if (header.bsofar > ((LONG)TXTLEN-(LONG)NNTLSZ)) {
                         clrprf();                /* end of current part?       */
                         prfmsg(NNTDCNM);
                         stlcat(textbuf,stpans(prfbuf),TXTLEN);
                         clrprf();
                         header.state=SUBMIT;
                         break;
                    }
               }
               break;
          case SUBMIT:
               setmem(&msg,sizeof(struct message),0);
               msg.thrid=thrid;
               if (header.repto[0] != '\0') {
                    parseIntAddr(header.repto,&addrinf);
               }
               else {
                    parseAddrLine(header.from,&addrinf);
               }
               smtpCvtFrom(msg.from,&addrinf);
               strcpy(msg.to,ALLINF);
               stlcpy(msg.topic,header.topic[0] != '\0' ? header.topic
                                                        : "<none>",TPCSIZ);
               while (++header.forcnt < header.xpstcnt
                   && header.forum[header.forcnt] == EMLID) {
               }
               if (header.forcnt < header.xpstcnt) {
                    msg.forum=header.forum[header.forcnt];
                    msg.crdate=msg.crtime=0U;
                    inigmerq(impbuf);
                    if (saveHdr && header.partno <= 1) {
                         gmeSetAppInfo(impbuf,hdrbuf);
                    }
                    if (!header.isusenet[header.forcnt]) {
                         gmeForceEcho(impbuf);
                    }
                    header.state=CYCIMP;
               }
               else if (header.lstread == NULL) {
                    header.state=CHKRMT;
                    break;
               }
               else {
                    header.forcnt=-1;
                    header.state=BODY;
                    textbuf[0]='\0';
                    header.bsofar=0L;
               }
               strrpl(textbuf,'\n','\r');
               break;
          case CYCIMP:
               nntpImporting=TRUE;
               rc=impmsg(impbuf,&msg,textbuf,NULL);
               nntpImporting=FALSE;
               if (rc > GMEAGAIN) {
                    add2db(&msg,header.msgid);
                    header.state=SUBMIT;
                    if (header.lstread == NULL) {
                         if (audnntd) {
                              shologd("NNTP NEWS RECEIVED",
                                      "To: %s",getfnm(header.forum[header.forcnt]));
                         }
                    }
               }
               else if (rc < GMEAGAIN) {
                    header.state=SUBMIT;
                    if (audnntde) {
                         shologd("NNTP IMPORT ERROR",
                                 "Return code from GME: %d - State: %d",
                                 rc,header.state);
                    }
                    break;
               }
               break;
          case CHKRMT:
               if (!chkrmt()) {
                    finfil(taskid);
                    return;
               }
               break;
          case COPYOUT:
               if (fgets(inbuf,NNTLSZ,header.fp) == NULL) {
                    fclose(header.fpout);
                    header.fpout=NULL;
                    ASSERT(NNTLSZ >= GCMAXPTH);
                    stlcpy(inbuf,nntobd,NNTLSZ);
                    if (!makeUniqueFName(inbuf,"$$R")) {
                         shologd("NNTP SERVER OPEN ERROR",
                                 "News file: \"%s\"",inbuf);
                    }
                    else if (rename(header.outfil,inbuf) != 0) {
                         shologd("NNTP SERVER MOVE ERROR",
                                 "Error moving \"%s\" to \"%s\"",
                                 header.outfil,inbuf);
                    }
                    finfil(taskid);
                    return;
               }
               else if (!sameto(xposted,inbuf)) {
                    if ((ptr=strchr(inbuf,'\n')) != NULL) {
                         *ptr='\0';
                    }
                    fputs(inbuf,header.fpout);
                    if (ptr != NULL) {
                         fputs("\r\n",header.fpout);
                    }
               }
               break;
          }
          timeUsed=hrtval()-begTime;    /* NOTE: relies on unsigned wrap   */
     } while (timeUsed < timeSlice);
     timeDebt=timeUsed-timeSlice;
}

static ULONG
impSlice(VOID)                     /* import time slice based on load      */
{
     ULONG n,retval;

     if ((n=nliniu()) == 0) {
          timeDebt=0;
          return(zerTicks);
     }
     retval=maxTicks-((n-1)*(maxTicks-minTicks))/((chFullPct*nterms)/100);
     if ((n=rsptim/rtFactor) < retval) {
          retval-=n;
          if (retval > maxTicks) {
               retval=maxTicks;
          }
          else if (retval < minTicks) {
               retval=minTicks;
          }
     }
     else {
          retval=minTicks;
     }
     return(retval);
}

static GBOOL                       /*   return TRUE if info is obtained    */
prp4ctl(                           /* prepare to process control message   */
CHAR *ctlstr)                      /*   control message string             */
{
     CHAR *ptr,*secptr;

     if (sameto("cancel",ctlstr)
      && (ptr=strchr(ctlstr,'<')) != NULL
      && (secptr=strrchr(ptr,'>')) != NULL) {
          *(++secptr)='\0';
          if (chkmsg(ptr,&ctlinf) && fidxst(ctlinf.forum)) {
               inigmerq(impbuf);
               inormrd(impbuf,"",ctlinf.forum,FIRSTM);
               header.state=CTRLRD;
               return(TRUE);
          }
     }
     return(FALSE);
}

static GBOOL                       /*   returns FALSE if aborted           */
ctrlrd(VOID)                       /* read message to delete               */
{
     INT rc;

     if ((rc=gmeGReadGlob(impbuf,&ctlinf.globid,(struct message *)vdatmp,
                          (CHAR *)vdatmp+sizeof(struct message))) > GMEAGAIN) {
          header.state=CTRLDEL;
     }
     else if (rc < GMEAGAIN) {
          clsgmerq(impbuf);
          return(FALSE);
     }
     return(TRUE);
}

static GBOOL                       /*   returns FALSE if finished          */
ctrldel(VOID)                      /* delete message                       */
{
     if (gmeGDeleteMsg(impbuf) != GMEAGAIN) {
          clsgmerq(impbuf);
          return(FALSE);
     }
     return(TRUE);
}

static VOID
clndir(VOID)                       /* removes WRK files from inbound dir   */
{
     INT clncnt;
     struct ffblk fb;
     CHAR clnspec[GCMAXPTH];

     clncnt=0;
     sprintf(clnspec,"%s*.wrk",wrkdir);
     if (fnd1st(&fb,clnspec,0)) {
          do {
               sprintf(clnspec,"%s%s",wrkdir,fb.ff_name);
               unlink(clnspec);
               clncnt++;
          } while (fndnxt(&fb));
     }
     if (clncnt > 0) {
          shocst("NNTP WORK FILE CLEANUP",
                 "Removed %d old work file(s)",clncnt);
     }
}

static GBOOL                       /*   TRUE=always-valid cmd, handled     */
anytime(                           /* checks input for always-valid cmd    */
CHAR *linput)                      /*   line of input to check             */
{
     GBOOL retval=TRUE;

     clrprf();
     setmbk(nntdmb);
     if (sameto("SLAVE",linput)) {
          prfmsg(NNTDSLV);
     }
     else if (sameto("MODE",linput)) {
          prfmsg(nntdacp ? NNTDOKY : NNTDOKN);
     }
     else if (sameto("QUIT",linput)) {
          prgfin(NNTDBYE2);
     }
     else {
          retval=FALSE;
     }
     nntprf();
     rstmbk();
     return(retval);
}

static VOID
clsfil(                            /* NNTP server close current news file  */
GBOOL good)                        /*   TRUE=good message received         */
{
     if (nntdptr->fp != NULL) {
          fclose(nntdptr->fp);
          nntdptr->fp=NULL;
          if (!good) {
               unlink(nntdptr->filnam);
          }
     }
}

static GBOOL
renwrk(                            /* rename "work" file to "ready" file   */
CHAR *wrkfil)                      /*   "work" file to rename              */
{
     CHAR *ptr;
     CHAR tmpfil[GCMAXPTH];

     stlcpy(tmpfil,wrkfil,GCMAXPTH);
     if ((ptr=strrchr(tmpfil,'.')) != NULL) {
          *ptr='\0';
     }
     stlcat(tmpfil,".rdy",GCMAXPTH);
     if (rename(wrkfil,tmpfil) == -1) {
          return(FALSE);
     }
     return(TRUE);
}

static VOID                        /*   returns pointer to work file name  */
makwrk(VOID)                       /* make work file for incoming news     */
{
     CHAR *ptr;
     CHAR tmpfil[GCMAXFNM];
     USHORT tiebrk=0;

     while (1) {
          if (tiebrk == 1000U) {
               catastro("NNTP Server: Cannot create work file!");
          }
          stzcpy(nntdptr->filnam,wrkdir,GCMAXPTH);
          stlcat(nntdptr->filnam,
                 stzcpy(tmpfil,spr("%x%x.rdy",now(),tiebrk++),FNEXSZ),GCMAXPTH);
          if (access(nntdptr->filnam,0) == -1) {
               ptr=strrchr(nntdptr->filnam,'.');
               *ptr='\0';
               stlcat(nntdptr->filnam,".wrk",GCMAXPTH);
               if (access(nntdptr->filnam,0) == -1) {
                    return;
               }
          }
     }
}

static USHORT                      /*   forum-ID or NOCGRP if not found    */
nws2frm(                           /* convert newsgroup to forum           */
CHAR *nwsgrp,                      /*   newsgroup name (with prefix)       */
GBOOL *isusenet)                   /*   is this Usenet or local newsgroup  */
{
     INT i,j;
     const adr_t *echo;
     const struct fordef *fordef;

     if (xrfList == NULL) {
          for (i=0 ; i < numforums() ; i++) {
               if ((fordef=fiddefp(i)) != NULL) {
                    echo=(const adr_t *)fordef->echoes;
                    for (j=0 ; j < fordef->necho ; j++) {
                         if (sameas(nwsgrp,crpstr(echo[j],':'))) {
                              if (sameto(nntfpx,echo[j])) {
                                   *isusenet=TRUE;
                                   return(fordef->forum);
                              }
                              if (sameto(locfpx,echo[j])) {
                                   *isusenet=FALSE;
                                   return(fordef->forum);
                              }
                         }
                    }
               }
          }
     }
     else if ((i=xrfGroupIdx(nwsgrp)) != NOIDX) {
          *isusenet=((xrfList[i]->flags&XFLGNET) != 0);
          return(xrfList[i]->forum);
     }
     return(NOCGRP);
}

static GBOOL                       /*   return TRUE if header is valid     */
valhdr(VOID)                       /* do we have a valid message header?   */
{
     return(header.from[0] != '\0' && header.ngrps[0][0] != '\0'
         && header.orgmid[0] != '\0');
}

static VOID
enatmo(VOID)                       /* enable channel timeout               */
{
     if ((nntdptr->sttime=btuTicker()) == 0) {
          nntdptr->sttime++;
     }
}

static VOID
distmo(VOID)                       /* disable channel timeout              */
{
     nntdptr->sttime=0;
}

static GBOOL                       /*   returns FALSE if aborted           */
chkrmt(VOID)                       /* check for need to copy to network    */
{
     INT i;
     struct qinfo qinfo;

     if (header.posted) {
          for (i=0 ; i < header.xpstcnt ; ++i) {
               if (header.isusenet[i]) {
                    header.state=COPYOUT;
                    stlcpy(header.outfil,wrkdir,GCMAXPTH);
                    if (!makeUniqueFName(header.outfil,"$$R")) {
                         shologd("NNTP SERVER OPEN ERROR",
                                 "News file: \"%s\"",header.outfil);
                         break;
                    }
                    if ((header.fpout=fopen(header.outfil,FOPWB)) == NULL) {
                         shologd("NNTP SERVER OPEN ERROR",
                                 "News file: \"%s\"",header.outfil);
                         break;
                    }
                    memset(&qinfo,0,sizeof(struct qinfo));
                    stlcpy(qinfo.name,NNTNAME,QNMSIZ);
                    qinfo.lsttim=qinfo.qtime=time(NULL);
                    stlcpy(qinfo.id,header.orgmid,MIDSIZ);
                    stlcpy(qinfo.to,header.ngrps[i],MAXADR);
                    if (fwrite(&qinfo,1,sizeof(struct qinfo),header.fpout)
                     != sizeof(struct qinfo)) {
                         shologd("NNTP SERVER WRITE ERROR",
                                 "Could not write header");
                         fclose(header.fpout);
                         unlink(header.outfil);
                         break;
                    }
                    rewind(header.fp);
                    return(TRUE);
               }
          }
     }
     return(FALSE);
}

static VOID
finfil(                            /* finish up current news file          */
INT taskid)                        /*   task to cancel                     */
{
     timeDebt=0;
     if (header.fp != NULL) {
          fclose(header.fp);
          header.fp=NULL;
     }
     unlink(header.filnam);
     if (header.fpout != NULL) {
          fclose(header.fpout);
          header.fpout=NULL;
     }
     unlink(header.outfil);
     mfytask(taskid,filtsk);
}

static VOID
prcinp(VOID)                       /* process input line from NNTP client  */
{
     INT nbytes;
     CHAR *ptr;

     while (TRUE) {
          if (btuibw(usrnum) >= bsize) {
               biglin();
               break;
          }
          btuict(usrnum,ibuff);
          ibuff[ictact]='\0';
          if ((ptr=strchr(ibuff,'\n')) != NULL) {
               *ptr='\0';
               nbytes=strlen(ibuff);
               btuica(usrnum,iline,nbytes+1);
               iline[nbytes]='\0';
               nntdlin(strstp(iline,'\r'));
          }
          else {
               break;
          }
     }
}

static VOID
biglin(VOID)                       /* process a big line (fills rcvbuf)    */
{
     prgfin(NNTDTAL);
}

static VOID
nntprf(VOID)                       /* our own outprf() function            */
{
     if (prfbuf[0] != '\0') {
          stpans(prfbuf);
          lognntd(prfbuf,OUTGING);
          outprf(usrnum);
     }
}

static VOID
lognntd(                           /* log receive transaction              */
CHAR *logstr,
INT type)
{
     FILE *logfp;
     CHAR wrkbuf[NNTLSZ];

     if (!nntdlog) {
          return;
     }
     if ((logfp=fopen(nntdlnam,FOPAA)) == NULL) {
          nntdlog=FALSE;
          shocst("NNTP RECEIVE LOG OPEN ERROR",
                 "Could not open \"%s\".  Logging disabled.",nntdlnam);
          return;
     }
     stzcpy(wrkbuf,logstr,NNTLSZ);
     if (fprintf(logfp,"%s %s: %s%s\n",ncdate(today()),nctime(now()),
                 type == OUTGING ? "-->" : type == INCMING ? "<--" : "***",
                 unpad(wrkbuf)) == EOF) {
          nntdlog=FALSE;
          shocst("NNTP RECEIVE LOG WRITE ERROR",
                 "Could not write to receive log.  Logging disabled.");
     }
     fclose(logfp);
}

static VOID
shologd(                            /* prints to audit trail, logs to file  */
CHAR *header,
CHAR *footer,
...)
{
     va_list ftlist;
     CHAR wrkbuf[NNTLSZ];
     CHAR logbuf[32+2+64+1];

     va_start(ftlist,footer);
     vsprintf(wrkbuf,footer,ftlist);
     va_end(ftlist);
     shocst(header,"%0.64s",wrkbuf);
     sprintf(logbuf,"%0.32s: %0.64s",header,wrkbuf);
     lognntd(logbuf,SHOWLOG);
}

static VOID
nntdmcu(VOID)                      /* cleanup routine                      */
{
     LONG curtim;
     const struct fordef *fordef;
     struct midinf midinf;
     struct cmpkey ckey;

     curtim=time(NULL);
     fordef=NULL;
     ckey.forum=0;
     ckey.msgidx=0L;
     dfaSetBlk(midbb);
     while (dfaAcqGT(&midinf,&ckey,FIDKEY)) {
          ASSERT(midinf.forum != EMLID);
          if (midinf.forum != ckey.forum) {
               fordef=getdefp(midinf.forum);
          }
          ckey.forum=midinf.forum;
          ckey.msgidx=midinf.msgidx;
          if (fordef == NULL) {
               dfaDelete();
          }
          else if (fordef->msglif >= 0
                && (curtim-midinf.timadd)/86400L > fordef->msglif) {
               dfaDelete();
          }
          else {
               ckey.msgidx=LASTI;
          }
     }
     dfaRstBlk();
}

static VOID
delHook(                           /* message has been deleted hook        */
INT deltype,                       /*   how new message was deleted code   */
const struct message *msg,         /*   new message header                 */
const CHAR *text)                  /*   new message text                   */
{
     (VOID)deltype;
     (VOID)text;
     dfaSetBlk(midbb);
     if (dfaAcqEQ(NULL,&msg->gmid,MSGKEY)) {
          dfaDelete();
     }
     dfaRstBlk();
}

static GBOOL                       /*   return TRUE if we have message     */
chkmsg(                            /* get message info by message ID string*/
CHAR *msgid,                       /*   message ID string                  */
struct midinf *midinf)             /*   buffer to receive info             */
{
     ULONG msgcrc;
     GBOOL retval=FALSE;

     dfaSetBlk(midbb);
     msgcrc=crc32(msgid,strlen(msgid));
     if (dfaQueryEQ(&msgcrc,MIDKEY)) {
          do {
               dfaAbsRec(midinf,MIDKEY);
               if (midinf->midcrc != msgcrc) {
                    break;
               }
               if (sameas(msgid,midinf->midstr)) {
                    retval=TRUE;
                    break;
               }
          } while (dfaQueryNX());
     }
     dfaRstBlk();
     return(retval);
}

static GBOOL                       /*   return TRUE if newsgroup listed    */
listlist(VOID)                     /* display next newsgroup for LIST cmd  */
{
     INT i;
     GBOOL retval,readonly;
     const adr_t *echo;
     ULONG lmsgno,hmsgno;
     const struct fordef *fordef;

     clrprf();
     if ((fordef=nxtdefp(nntdptr->lstfor)) != NULL) {
          stlcpy(nntdptr->lstfor,fordef->name,FORNSZ);
          echo=(const adr_t *)fordef->echoes;
          for (i=0 ; i < fordef->necho ; i++) {
               if (sameto(nntfpx,echo[i]) || sameto(locfpx,echo[i])) {
                    if (hasracc(fordef->forum,&readonly)) {
                         getlohi(fordef->forum,&lmsgno,&hmsgno);
                         if (nntdptr->listmode == PLAINLST) {
                              prfmsg(NNTDLST1,crpstr(echo[i],':'),
                                     l2as(hmsgno),l2as(lmsgno),
                                     readonly ? 'n' : 'y');
                         }
                         else if (nntdptr->listmode == GROUPLST) {
                              prfmsg(NNTDLST2,crpstr(echo[i],':'),
                                     fordef->topic);
                              strrpl(prfbuf,'þ','\x09');
                         }
                    }
               }
          }
          retval=TRUE;
     }
     else {
          *nntdptr->lstfor='\0';
          retval=FALSE;
          prf(".\n");
     }
     nntprf();
     return(retval);
}

static GBOOL                       /*   return TRUE if more messages       */
listgrp(VOID)                      /* process next message for LISTGROUP   */
{
     struct cmpkey ckey;
     struct midinf midinf;

     dfaSetBlk(midbb);
     ckey.forum=nntdptr->curgrp;
     ckey.msgidx=nntdptr->lstaptr;
     if (dfaAcqGT(&midinf,&ckey,FIDKEY) && midinf.forum == nntdptr->curgrp) {
          prf("%lu\n",nntdptr->lstaptr=midinf.msgidx);
          return(TRUE);
     }
     prf(".\n");
     return(FALSE);
}

static GBOOL                       /*   return TRUE if have forum ref      */
getlohi(                           /* get low/high msg indices in forum    */
USHORT forum,                      /*   forum number                       */
ULONG *lmsgno,                     /*   low message number                 */
ULONG *hmsgno)                     /*   high message number                */
{
     struct cmpkey ckey;
     struct midinf midinf;

     dfaSetBlk(midbb);
     ckey.forum=forum;
     ckey.msgidx=0L;
     if (dfaAcqGT(&midinf,&ckey,FIDKEY) && midinf.forum == forum) {
          *lmsgno=midinf.msgidx;
          ckey.msgidx=LASTI;
          if (dfaAcqLT(&midinf,&ckey,FIDKEY) && midinf.forum == forum) {
               *hmsgno=midinf.msgidx;
               dfaRstBlk();
               return(TRUE);
          }
     }
     *lmsgno=*hmsgno=0;
     dfaRstBlk();
     return(FALSE);
}

static CHAR *
getarg(                            /* return pointer to command argument   */
CHAR *cmdstr)                      /*   entire command string              */
{
     CHAR *ptr;

     if ((ptr=strchr(cmdstr,' ')) != NULL) {
          ptr++;
          while (isspace(*ptr)) {
               ptr++;
          }
          if (*ptr == '\0') {
               return(NULL);
          }
          else {
               return(ptr);
          }
     }
     else {
          return(NULL);
     }
}

static GBOOL                       /*   return TRUE if group is accessible */
hasracc(                           /* do we have access to this group?     */
USHORT forum,                      /*   forum (group) to check access for  */
GBOOL *readonly)
{
     const struct fordef *fordef;
     GBOOL tmpronly=TRUE,retval=FALSE;
     INT access;

     if ((fordef=getdefp(forum)) != NULL) {
          if (low_haskey(fordef->forlok,usrnum,usrptr)) {
               access=fordef->dfprv;
          }
          else {
               access=fordef->dfnpv;
          }
          if (access >= RDAXES && access < NOTSET) {
               retval=TRUE;
          }
          if (access >= WRAXES) {
               tmpronly=FALSE;
          }
     }
     if (readonly != NULL) {
          *readonly=tmpronly;
     }
     return(retval);
}

static GBOOL
getmidx(                           /* get message info from message index  */
USHORT forum,                      /*   forum ID to get from               */
ULONG msgidx,                      /*   message index                      */
struct midinf *midinf)             /*   buffer to receive message info     */
{
     GBOOL rc;
     struct cmpkey ckey;

     dfaSetBlk(midbb);
     ckey.forum=forum;
     ckey.msgidx=msgidx;
     rc=dfaAcqEQ(midinf,&ckey,FIDKEY);
     dfaRstBlk();
     return(rc);
}

static VOID
prp4rcv(VOID)                      /* prepare to receive inbound message   */
{
     ASSERT(nntdptr->fp == NULL);
     nntdptr->xpstcnt=0;
     makwrk();
     if ((nntdptr->fp=fopen(nntdptr->filnam,FOPWA)) == NULL) {
          prfmsg(NNTDLERR);
     }
     else {
          prfmsg(nntdptr->posting ? NNTDSPST : NNTDNTM);
          if (nntdptr->posting) {
               fprintf(nntdptr->fp,"%s\n",xposted);
               fprintf(nntdptr->fp,"Message-ID: %s\n",nmsgid(vdatmp));
               fprintf(nntdptr->fp,"Path: %s\n",smtpHost());
          }
          usrptr->substt=RECVBODY;
     }
}

static VOID
setfltr(                           /* set up filter for NEWNEWS search     */
CHAR *linput)
{
     CHAR *ptr;
     INT i;
     GBOOL astr=FALSE;

     stlcpy(nntdptr->fltrstr,linput,NNTLSZ);
     setmem(nntdptr->fltr,sizeof(nntdptr->fltr),0);
     rmvwht(nntdptr->fltrstr);
     for (nntdptr->fltrnum=0,ptr=nntdptr->fltrstr ; nntdptr->fltrnum < FLTRSIZ
        ; nntdptr->fltrnum++) {
          if (*ptr == '!') {
               nntdptr->fltr[nntdptr->fltrnum].beg=++ptr;
               nntdptr->fltr[nntdptr->fltrnum].isneg=TRUE;
          }
          else {
               nntdptr->fltr[nntdptr->fltrnum].beg=ptr;
               nntdptr->fltr[nntdptr->fltrnum].isneg=FALSE;
          }
          if ((ptr=strchr(ptr,',')) != NULL) {
               *ptr++='\0';
          }
          else {
               nntdptr->fltrnum++;
               break;
          }
     }
     for (i=0 ; i < nntdptr->fltrnum ; i++) {
          if ((ptr=strchr(nntdptr->fltr[i].beg,'*')) != NULL) {
               *ptr++='\0';
               nntdptr->fltr[i].end=ptr;
               astr=TRUE;
          }
          else {
               nntdptr->fltr[i].end=empt;
          }
     }
     if (!astr && nntdptr->fltrnum == 1 && !nntdptr->fltr[0].isneg) {
          nntdptr->exact=TRUE;
     }
     else {
          nntdptr->exact=FALSE;
     }
}

static GBOOL
testfltr(                          /* does newsgroup name match filter?    */
CHAR *newsname)
{
     INT i;
     GBOOL mach=FALSE;

     for (i=0 ; i < nntdptr->fltrnum ; i++) {
          if (!nntdptr->fltr[i].isneg) {
               if (sameto(nntdptr->fltr[i].beg,newsname)
                && sameto(nntdptr->fltr[i].end,newsname+strlen(newsname)-
                 strlen(nntdptr->fltr[i].end))) {
                    mach=TRUE;
                    break;
               }
          }
     }
     if (mach) {
          for (i=0 ; i < nntdptr->fltrnum ; i++) {
               if (nntdptr->fltr[i].isneg) {
                    if (sameto(nntdptr->fltr[i].beg,newsname)
                     && sameto(nntdptr->fltr[i].end,newsname+strlen(newsname)
                                              -strlen(nntdptr->fltr[i].end))) {
                         mach=FALSE;
                         break;
                    }
               }
          }
     }
     return(mach);
}

static VOID
prcabhs(                           /* prc ARTICLE, HEAD, BODY, STAT cmds   */
CHAR *linput,                      /*   input line                         */
INT command)                       /*   command to process                 */
{
     CHAR *ptr,*secptr;
     LONG count;
     struct midinf midinf;

     if ((ptr=getarg(linput)) == NULL) {
          if (nntdptr->curgrp == NOCGRP) {
               prfmsg(NNTDNGS);
          }
          else if (nntdptr->curaptr == NOCART) {
               prfmsg(NNTDNAS);
          }
          else {
               if (getmidx(nntdptr->curgrp,nntdptr->curaptr,&midinf)) {
                    outabhs(command,&midinf,NNTDNSAG);
               }
               else {
                    prfmsg(NNTDNSAG);
               }
          }
     }
     else if (ptr[0] == '<') {
          if ((secptr=strchr(ptr,'>')) != NULL) {
               *(++secptr)='\0';
               if (strlen(ptr) > 2 && chkmsg(ptr,&midinf)) {
                    outabhs(command,&midinf,NNTDNSAF);
               }
               else {
                    prfmsg(NNTDNSAF);
               }
          }
          else {
               prfmsg(NNTDSYN);
          }
     }
     else {
          if (nntdptr->curgrp == NOCGRP) {
               prfmsg(NNTDNGS);
          }
          else if ((count=atol(ptr)) == 0L) {
               prfmsg(NNTDNSAG);
          }
          else {
               if (getmidx(nntdptr->curgrp,count,&midinf)) {
                    nntdptr->curaptr=midinf.msgidx;
                    outabhs(command,&midinf,NNTDNSAG);
               }
               else {
                    prfmsg(NNTDNSAG);
               }
          }
     }
}

static VOID
outabhs(                           /* output msg article/head/body/stat    */
INT command,                       /*   command to process                 */
struct midinf *pminf,              /*   info for message to output         */
INT errmsg)                        /*   MSG # to output if not found       */
{
     struct message msg;

     prepRead(pminf->forum,FIRSTM);
     if (gmeGReadGlob(nntdptr->wrkbuf,&pminf->globid,&msg,nntdptr->msgbody)
       > GMEAGAIN) {
          if (msg.flags&FILATT) {
               stlcpy(nntdptr->attfnam,dlname(&msg),GCMAXPTH);
               stlcpy(nntdptr->relnam,msg.attname,GCMAXFNM);
               strlwr(nntdptr->relnam);
          }
          switch (command) {
          case CMDARTL:
               prfmsg(NNTDARTL,l2as(pminf->msgidx),pminf->midstr);
               outhdr(&msg,pminf);
               gmePlainText(nntdptr->msgbody);
               nntdptr->mbpos=nntdptr->msgbody;
               usrptr->substt=SNDBODY;
               break;
          case CMDBODY:
               prfmsg(NNTDBODY,l2as(pminf->msgidx),pminf->midstr);
               gmePlainText(nntdptr->msgbody);
               nntdptr->mbpos=nntdptr->msgbody;
               usrptr->substt=SNDBODY;
               break;
          case CMDHEAD:
               prfmsg(NNTDHEAD,l2as(pminf->msgidx),pminf->midstr);
               outhdr(&msg,pminf);
               prf(".\n");
               break;
          case CMDSTAT:
               prfmsg(NNTDSTAT,l2as(pminf->msgidx),pminf->midstr);
               break;
          }
     }
     else {
          prfmsg(errmsg);
     }
}

static VOID
outhdr(                            /* output header to user                */
struct message *msg,
struct midinf *mid)
{
     const CHAR *cp;
     size_t len;
     CHAR tmpBuf[MAXADR];

     len=strlen(prfbuf);
     cp=gmeGetAppInfo();
     if (!NULSTR(cp)
      && *cfgGetStr(NNTPSECTHDR,NULL,"",prfbuf+len,outbsz-len,cp) != '\0') {
          strrpl(prfbuf+len,'\r','\n');
          stlcat(prfbuf,"\n",outbsz);
     }
     else {
          prf("Message-ID: %s\n",mid->midstr);
          cvtFrom(tmpBuf,msg->from);
          prf("From: %s\n",tmpBuf);
          prf("Newsgroups: %s\n",getenam(msg->forum));
          prf("Subject: %s\n",msg->topic[0] != '\0' ? msg->topic : "<none>");
          prf("Date: %s\n",cvtDate(msg->crtime,msg->crdate));
          if ((cp=getref(msg)) != NULL) {
               prf("References: %s\n",cp);
          }
     }
}

static GBOOL                       /*   returns TRUE if more bytes to send */
outbody(VOID)                      /* output body to user                  */
{
     INT oba,size,i;
     GBOOL isend=FALSE;

     if ((size=strlen(nntdptr->mbpos)) == 0) {
          isend=TRUE;
          size=5;
     }
     /* '10' is just a "random" number to avoid off by one errors */
     if ((oba=min(btuoba(usrnum)-10,size)) <= 0) {
          return(TRUE);
     }
     if (isend) {
          if (nntdptr->attfnam[0] == '\0') {
               btuxmt(usrnum,"\r.\r");
          }
          return(FALSE);
     }
     else {
          for (i=0 ; i < oba && *nntdptr->mbpos != '\0' ; ) {
               if (*nntdptr->mbpos == '.'
                && (nntdptr->mbpos == nntdptr->msgbody
                 || *(nntdptr->mbpos-1) == '\r')) {
                    vdatmp[i++]='.';
               }
               vdatmp[i++]=*nntdptr->mbpos++;
          }
          vdatmp[i]='\0';
          btuxmt(usrnum,vdatmp);
          return(TRUE);
     }
}

static GBOOL
msgxst(                            /* does message exist in database?      */
const struct message *msg,         /*   GME header of message to find      */
struct midinf *mid)                /*   buffer to return info in           */
{
     GBOOL retval;

     dfaSetBlk(midbb);
     if ((retval=dfaAcqEQ(mid,(VOID *)&msg->gmid,MSGKEY)) == FALSE) {
          setmem(mid,sizeof(struct midinf),0);
     }
     dfaRstBlk();
     return(retval);
}

static CHAR *                      /*   return pointer to echo name        */
getenam(                           /* get echo name from forum             */
USHORT forum)                      /*   forum to grab echo from            */
{
     INT i;
     const adr_t *echo;
     const struct fordef *fordef;

     if ((fordef=getdefp(forum)) != NULL) {
          echo=(const adr_t *)fordef->echoes;
          for (i=0 ; i < fordef->necho ; i++) {
               if (sameto(nntfpx,echo[i]) || sameto(locfpx,echo[i])) {
                    return(crpstr(echo[i],':'));
               }
          }
     }
     return(empt);
}

static GBOOL
getdat(                            /* get date and time for search         */
CHAR *date,                        /*   date passed from user              */
CHAR *time,                        /*   time passed from user              */
GBOOL isgmt)                       /*   is this a local time or GMT        */
{
     CHAR *cp;
     INT y,m,d,h,n,s,thisYear;
     size_t l,i,j;
     CHAR buf[sizeof("1997")];

     /* GMT for time zone is optional and is not currently */
     /* supported                                          */
     (VOID)isgmt;
     if ((cp=strchr(date,' ')) != NULL) {
          *cp='\0';
     }
     l=strlen(date);
     if ((l != CSTRLEN("YYMMDD") && l != CSTRLEN("YYYYMMDD")) || !alldgs(date)
      || strlen(time) != CSTRLEN("HHMMSS") || !alldgs(time)) {
          return(FALSE);
     }
     i=j=0;
     buf[i++]=date[j++];
     buf[i++]=date[j++];
     if (l == CSTRLEN("YYYYMMDD")) {
          buf[i++]=date[j++];
          buf[i++]=date[j++];
     }
     buf[i]='\0';
     y=atoi(buf);
     if (l == CSTRLEN("YYMMDD")) {
          thisYear=ddyear(today());
          y+=(thisYear/100)*100;
          if (thisYear < y) {
               if ((y-thisYear) >= 50) {
                    y-=100;
               }
          }
          else {
               if ((thisYear-y) > 50) {
                    y+=100;
               }
          }
     }
     i=0;
     buf[i++]=date[j++];
     buf[i++]=date[j++];
     buf[i]='\0';
     m=atoi(buf);
     i=0;
     buf[i++]=date[j++];
     buf[i++]=date[j];
     buf[i]='\0';
     d=atoi(buf);
     if (y < 1980) {
          y=1980;
          m=d=1;
     }
     nntdptr->newdat=dddate(m,d,y);
     i=j=0;
     buf[i++]=time[j++];
     buf[i++]=time[j++];
     buf[i]='\0';
     h=atoi(buf);
     i=0;
     buf[i++]=time[j++];
     buf[i++]=time[j++];
     buf[i]='\0';
     n=atoi(buf);
     i=0;
     buf[i++]=time[j++];
     buf[i++]=time[j];
     buf[i]='\0';
     s=atoi(buf);
     nntdptr->newtim=dttime(h,n,s);
     return(TRUE);
}

static GBOOL
newgrps(VOID)                      /* output new groups list               */
{
     const struct fordef *fordef;
     CHAR *s;

     if ((fordef=nxtdefp(nntdptr->lstfor)) != NULL) {
          stlcpy(nntdptr->lstfor,fordef->name,FORNSZ);
          if (nntdptr->newdat < fordef->crdate
           || (nntdptr->newdat == fordef->crdate
            && nntdptr->newtim < fordef->crtime)) {
               s=getenam(fordef->forum);
               if (!NULSTR(s)) {
                    prfmsg(NNTDNGPN,s);
               }
          }
          return(TRUE);
     }
     prf(".\n");
     return(FALSE);
}

static GBOOL
newnews(VOID)                      /* output new news list                 */
{
     struct message msg;
     struct midinf midinf;

     if (nntdptr->newgrpi == NOIDX) {
          prf(".\n");
          return(FALSE);
     }
     if (grdmsg(nntdptr->wrkbuf,RDNEXT,&msg,vdatmp) == GMEOK) {
          if (msgxst(&msg,&midinf) && nntdptr->newdat < msg.crdate
           || (nntdptr->newdat == msg.crdate && nntdptr->newtim < msg.crtime)) {
               prf("%s\n",midinf.midstr);
          }
     }
     else if (!nntdptr->exact) {
          getnfrm();
     }
     else {
          nntdptr->newgrpi=NOIDX;
     }
     return(TRUE);
}

static VOID
getnfrm(VOID)                      /* selects next forum for NEWNEWS cmd.  */
{
     INT i,j;
     const adr_t *echo;
     const struct fordef *fordef;

     for (i=nntdptr->newgrpi ; i < numforums() ; ++i) {
          if ((fordef=fiddefp(i)) != NULL && hasracc(fordef->forum,NULL)) {
               echo=(const adr_t *)fordef->echoes;
               for (j=0 ; j < fordef->necho ; j++) {
                    if (testfltr(crpstr(echo[j],':'))) {
                         nntdptr->newgrpi=i+1;
                         prepRead(fordef->forum,FIRSTM);
                         return;
                    }
               }
          }
     }
     nntdptr->newgrpi=NOIDX;
}

static GBOOL                       /*   return TRUE to continue processing */
prcxhdr(                           /* process XHDR command                 */
CHAR *linput)
{
     CHAR *ptr,*secptr;
     struct midinf midinf;
     struct cmpkey ckey;

     prepRead(nntdptr->curgrp,FIRSTM);
     clrprf();
     if ((ptr=getarg(linput)) == NULL) {
          prfmsg(NNTDSYN);
          return(FALSE);
     }
     if ((secptr=getarg(ptr)) != NULL) {
          *(secptr-1)='\0';
     }
     unpad(stlcpy(nntdptr->xhdrlin,ptr,NNTLSZ));
     if (secptr != NULL && secptr[0] == '<') {
          if ((ptr=strchr(secptr,'>')) != NULL) {
               *(++ptr)='\0';
               if (strlen(secptr) > 2 && chkmsg(secptr,&midinf)) {
                    prfmsg(NNTDXHFL,nntdptr->xhdrlin);
                    outxhdr(&midinf,secptr);
                    prf(".\n");
               }
               else {
                    prfmsg(NNTDNSAF);
               }
          }
          else {
               prfmsg(NNTDSYN);
          }
          return(FALSE);
     }
     if (!getXRange(secptr)) {
          return(FALSE);
     }
     prfmsg(NNTDXHFL,nntdptr->xhdrlin);
     if (nntdptr->msglim != 0L) {
          return(cycxhdr());
     }
     dfaSetBlk(midbb);
     ckey.forum=nntdptr->curgrp;
     ckey.msgidx=nntdptr->lstaptr;
     if (dfaAcqEQ(&midinf,&ckey,FIDKEY)) {
          outxhdr(&midinf,NULL);
     }
     prf(".\n");
     dfaRstBlk();
     return(FALSE);
}

static GBOOL                       /*   return TRUE if more messages       */
cycxhdr(VOID)                      /* cycled XHDR handler                  */
{
     GBOOL rc;
     struct midinf midinf;

     if ((rc=nextXMsg(&midinf)) != FALSE) {
          outxhdr(&midinf,NULL);
     }
     else {
          prf(".\n");
     }
     return(rc);
}

static VOID
outxhdr(                           /* output message for XHDR cmd          */
struct midinf *pminf,              /*   info for message to output         */
const CHAR *midstr)                /*   msg ID string or NULL for number   */
{
     const CHAR *cp;
     CHAR tmpBuf[MAXADR];
     struct message msg;

     if (gmeGReadGlob(nntdptr->wrkbuf,&pminf->globid,&msg,vdatmp) > GMEAGAIN) {
          if (midstr == NULL) {
               midstr=l2as(pminf->msgidx);
          }
          if (sameas("Message-ID",nntdptr->xhdrlin)) {
               prfmsg(NNTDXHDR,midstr,pminf->midstr);
          }
          else if (sameas("From",nntdptr->xhdrlin)) {
               cvtFrom(tmpBuf,msg.from);
               prfmsg(NNTDXHDR,midstr,tmpBuf);
          }
          else if (sameas("Newsgroups",nntdptr->xhdrlin)) {
               prfmsg(NNTDXHDR,midstr,getenam(msg.forum));
          }
          else if (sameas("Subject",nntdptr->xhdrlin)) {
               prfmsg(NNTDXHDR,midstr,msg.topic[0] != '\0' ? msg.topic
                                                         : "(none)");
          }
          else if (sameas("Date",nntdptr->xhdrlin)) {
               prfmsg(NNTDXHDR,midstr,cvtDate(msg.crtime,msg.crdate));
          }
          else if (sameas("References",nntdptr->xhdrlin)) {
               if ((cp=getref(&msg)) == NULL) {
                    cp="(none)";
               }
               prfmsg(NNTDXHDR,midstr,cp);
          }
          else {
               prfmsg(NNTDXHDR,midstr,"(none)");
          }
     }
}

static GBOOL                       /*   return TRUE if range specified     */
prcxovr(                           /* process XOVER command                */
CHAR *linput)
{
     struct cmpkey ckey;
     struct midinf midinf;

     clrprf();
     prepRead(nntdptr->curgrp,FIRSTM);
     if (!getXRange(getarg(linput))) {
          return(FALSE);
     }
     prfmsg(NNTDDFLW);
     if (nntdptr->msglim != 0L) {
          return(cycxovr());
     }
     dfaSetBlk(midbb);
     ckey.forum=nntdptr->curgrp;
     ckey.msgidx=nntdptr->lstaptr;
     if (dfaAcqEQ(&midinf,&ckey,FIDKEY)) {
          outxovr(&midinf);
     }
     prf(".\n");
     dfaRstBlk();
     return(FALSE);
}

static GBOOL                       /*   return TRUE if more messages       */
cycxovr(VOID)                      /* cycled XOVER handler                 */
{
     GBOOL rc;
     struct midinf midinf;

     if ((rc=nextXMsg(&midinf)) != FALSE) {
          outxovr(&midinf);
     }
     else {
          prf(".\n");
     }
     return(rc);
}

static VOID
outxovr(                           /* process next message for XOVER cmd   */
struct midinf *pminf)              /*   info for message to output         */
{
     const CHAR *ref;
     CHAR tmpBuf[MAXADR];
     INT numlin,i,msgsiz;
     struct message msg;

     if (gmeGReadGlob(nntdptr->wrkbuf,&pminf->globid,&msg,vdatmp) > GMEAGAIN) {
          gmePlainText(vdatmp);
          for (i=numlin=0,msgsiz=strlen(vdatmp) ; i < msgsiz ; i++) {
               if (vdatmp[i] == '\r') {
                    numlin++;
               }
          }
          cvtFrom(tmpBuf,msg.from);
          prfmsg(NNTDXOV1,l2as(pminf->msgidx),
                 msg.topic[0] != '\0' ? msg.topic : "<none>",
                 tmpBuf,
                 cvtDate(msg.crtime,msg.crdate),
                 pminf->midstr,
                 (ref=getref(&msg)) == NULL ? "" : ref,
                 msgsiz,numlin);
          strrpl(prfbuf,'þ','\x09');
     }
}

static GBOOL                       /*   returns TRUE if another msg found  */
getXRange(                         /* get message # range for XHDR/XOVER   */
CHAR *range)                       /*   range string (NULL for none)       */
{
     CHAR *cp;

     if (nntdptr->curgrp == NOCGRP) {
          prfmsg(NNTDNGS);
          return(FALSE);
     }
     nntdptr->msglim=0L;
     if (range != NULL) {
          if ((cp=strchr(range,'-')) != NULL) {
               *cp++='\0';
               if (*cp == '\0') {
                    nntdptr->msglim=LASTI;
               }
               else if ((nntdptr->msglim=atol(cp)) == 0L) {
                    prfmsg(NNTDSYN);
                    return(FALSE);
               }
          }
          if ((nntdptr->lstaptr=atol(range)) == 0L) {
               prfmsg(NNTDSYN);
               return(FALSE);
          }
     }
     else if ((nntdptr->lstaptr=nntdptr->curaptr) == NOCART) {
          prfmsg(NNTDNAS);
          return(FALSE);
     }
     return(TRUE);
}

static GBOOL                       /*   returns TRUE if another msg found  */
nextXMsg(                          /* get next XHDR/XOVER message          */
struct midinf *pminf)              /*   buffer to receive message info     */
{
     GBOOL rc;
     struct cmpkey ckey;

     dfaSetBlk(midbb);
     ckey.forum=nntdptr->curgrp;
     ckey.msgidx=nntdptr->lstaptr;
     rc=(dfaAcqGE(pminf,&ckey,FIDKEY)
      && pminf->forum == nntdptr->curgrp
      && pminf->msgidx <= nntdptr->msglim);
     if (rc) {
          nntdptr->lstaptr=pminf->msgidx+1;
     }
     dfaRstBlk();
     return(rc);
}

static VOID
prepRead(                          /* set-up to read messages from a group */
USHORT forum,                      /*   forum ID of group to read from     */
LONG msgid)                        /*   starting message ID                */
{
     if (!gmerqopn(nntdptr->wrkbuf)) {
          inigmerq(nntdptr->wrkbuf);
     }
     inormrd(nntdptr->wrkbuf,"",forum,msgid);
}

static VOID
prgfin(                            /* finish up NNTP session               */
INT msgno)                         /*   message to output                  */
{
     setmbk(nntdmb);
     clsfil(FALSE);
     if (gmerqopn(nntdptr->wrkbuf)) {
          clsgmerq(nntdptr->wrkbuf);
     }
     if (msgno != -1) {
          if (audnntd) {
               shologd("NNTP SESSION ENDED",
                       "User ended NNTP session");
          }
          byetcp(msgno);
     }
     else {
          if (audnntde) {
               shologd("NNTP SESSION ABORTED",
                       "User dropped connection");
          }
          rstchn();
     }
     rstmbk();
}

static GBOOL                       /*   TRUE is OK to connect              */
checkip(                           /* check if IP is allowed to connect    */
ULONG ip)
{
     INT i;

     for (i=0 ; i < IPMSKSZ ; i++) {
          if (ipmsk[i].flag == MCH_MASK
           && (ipmsk[i].ip&ipmsk[i].mask) == (ip&ipmsk[i].mask)) {
               return(TRUE);
          }
     }
     return(FALSE);
}

static VOID
setipmsk(VOID)                     /* sets allowed IP from MSG file        */
{
     INT i;
     CHAR *ptr;

     setmem(ipmsk,sizeof(ipmsk),0);
     for (i=0 ; i < IPMSKSZ ; i++) {
          ptr=getmsg(NNTIP1+i);
          if (ptr[0] != '\0') {
               ip2ipmsk(ptr,&ipmsk[i].ip,&ipmsk[i].mask,&ipmsk[i].flag);
          }
          else {
               break;
          }
     }
}

static VOID
ip2ipmsk(                          /* converts IP string to 'tagipmsk'     */
CHAR *ipstr,                       /*  IP string                           */
ULONG *ip,                         /*  IP as a long                        */
ULONG *mask,                       /*  significant bits                    */
CHAR *flag)                        /*  falgs                               */
{
     INT i,j,len;
     CHAR *ptr,*secptr;
     ULONG tmpip;

     *ip=0L;
     *mask=0L;
     *flag=MCH_NONE;
     for (i=0,ptr=ipstr ; i < 4 && ptr != NULL ; i++,ptr=secptr) {
          if ((secptr=strchr(ptr,'.')) != NULL) {
               *secptr++='\0';
          }
          if (*ptr == '*') {
               *flag=MCH_MASK;
               return;
          }
          if ((len=strlen(ptr)) > 3 || len == 0) {
               *ip=0L;
               *mask=0L;
               return;
          }
          for (j=0 ; j < len ; j++) {
               if (!isdigit(ptr[j])) {
                    *ip=0L;
                    *mask=0L;
                    return;
               }
          }
          tmpip=(ULONG)atoi(ptr);
          if (tmpip > 255) {
               *ip=0L;
               *mask=0L;
               return;
          }
          *ip|=(tmpip<<(8*i));
          *mask<<=8;
          *mask|=0xFFL;
     }
     if (*mask == 0xFFFFFFFFL) {
          *flag=MCH_MASK;
     }
     else {
          *flag=MCH_NONE;
     }
}

static GBOOL
prp2uuc(VOID)                      /* prepare for uuencoding               */
{
     struct stat sbuf;

     if ((nntdptr->attfp=fopen(nntdptr->attfnam,FOPRB)) == NULL) {
          shocst("NNTP OPEN FILE ERROR",
                 "prp2uuc(): Could not open attachment file");
          strcpy(nntdptr->msgbody,getmsg(BADATT));
          return(FALSE);
     }
     else {
          fstat(fileno(nntdptr->attfp),&sbuf);
          sprintf(nntdptr->msgbody,"\rbegin %o %s\r",sbuf.st_mode&0777,
                                                     nntdptr->relnam);
          return(TRUE);
     }
}

#define BLOCKSIZ  4500

static GBOOL
uuecode(VOID)                      /* uuencode file attachment             */
{
     INT pos,nread,n,i,j;
     UINT c1,c2,c3,c4;
     UINT e1,e2,e3,e4;

     pos=strlen(nntdptr->msgbody);
     if ((nread=fread(vdatmp,1,BLOCKSIZ,nntdptr->attfp)) <= 0) {
          strcat(nntdptr->msgbody,"\rend\r");
          return(FALSE);
     }
     j=0;
     while (nread > 0) {
          n=min(45,nread);
          nread-=n;
          nntdptr->msgbody[pos++]=ENC(n);
          for (i=0 ; i < n ; i+=3,j+=3) {
               c1=vdatmp[j] >> 2;
               c2=((vdatmp[j] << 4) & 060) | ((vdatmp[j+1] >> 4) & 017);
               c3=((vdatmp[j+1] << 2) & 074) | ((vdatmp[j+2] >> 6) & 03);
               c4=((vdatmp[j+2]) & 077);
               if ((e1=ENC(c1)) == ' ') {
                    e1='`';
               }
               if ((e2=ENC(c2)) == ' ') {
                    e2='`';
               }
               if ((e3=ENC(c3)) == ' ') {
                    e3='`';
               }
               if ((e4=ENC(c4)) == ' ') {
                    e4='`';
               }
               nntdptr->msgbody[pos++]=e1;
               nntdptr->msgbody[pos++]=e2;
               nntdptr->msgbody[pos++]=e3;
               nntdptr->msgbody[pos++]=e4;
          }
          nntdptr->msgbody[pos++]='\r';
     }
     nntdptr->msgbody[pos]='\0';
     return(TRUE);
}

static VOID
initXref(VOID)                     /* initialize group-forum xref list     */
{
     INT i,j,nfors;
     const struct fordef *fdef;
     const adr_t *echo;

     xrfInUse=0;
     nfors=numforums();
     for (i=0 ; i < nfors ; ++i) {
          fdef=fiddefp(i);
          echo=(const adr_t *)fdef->echoes;
          for (j=0 ; j < fdef->necho ; ++j) {
               if (sameto(nntfpx,echo[j]) || sameto(locfpx,echo[j])) {
                    ++xrfInUse;
               }
          }
     }
     xrfAlloc=((xrfInUse/XRFBLKSZ)+1)*XRFBLKSZ;
     xrfList=alcmem(xrfAlloc*sizeof(struct grpxref *));
     xrfInUse=0;
     for (i=0 ; i < nfors ; ++i) {
          fdef=fiddefp(i);
          xrfAddXref((const adr_t *)fdef->echoes,fdef->necho,fdef->forum);
     }
     xrfSortList();
     hook_gme(GMEHOOK_NOT_NEWFOR,(voidfunc)xrfForCrt);
     hook_gme(GMEHOOK_NOT_UPDFOR,(voidfunc)xrfForMod);
     hook_gme(GMEHOOK_NOT_DELFORL,(voidfunc)xrfForDel);
}

static VOID
xrfForCrt(                         /* forum has been created hook          */
const struct fordsk *newdef,       /*   new forum definition structure     */
const CHAR *desc,                  /*   descriptive text                   */
const CHAR *echoes)                /*   pointer to array of echo addresses */
{
     (VOID)desc;
     xrfNewFor((const adr_t *)echoes,newdef->necho,newdef->forum);
}

static VOID
xrfForMod(                         /* forum has been modified hook         */
const struct fordef *fordef,       /*   forum definition structure         */
const CHAR *desc,                  /*   descriptive text                   */
const CHAR *echoes)                /*   pointer to array of echo addresses */
{
     (VOID)desc;
     if (echoes != NULL) {
          xrfForDel(fordef->forum);
          xrfNewFor((const adr_t *)echoes,fordef->necho,fordef->forum);
     }
}

static VOID
xrfForDel(                         /* forum deleted hook                   */
USHORT forum)                      /*   forum ID of deleted forum          */
{
     INT i;

     for (i=0 ; i < xrfInUse ; ++i) {
          if (xrfList[i]->forum == forum) {
               free(xrfList[i]);
               --xrfInUse;
               memmove(&xrfList[i],&xrfList[i+1],
                       (xrfInUse-i)*sizeof(struct grpxref *));
          }
     }
}

static VOID
xrfNewFor(                         /* handle adding new forum              */
const adr_t *echo,                 /*   array of echo addresses            */
INT necho,                         /*   number of addresses in array       */
USHORT forum)                      /*   forum ID                           */
{
     INT i,n;
     size_t oldsiz;

     for (i=0,n=0 ; i < necho ; ++i) {
          if (sameto(nntfpx,echo[i]) || sameto(locfpx,echo[i])) {
               ++n;
          }
     }
     if (n != 0) {
          if (xrfInUse+n > xrfAlloc) {
               oldsiz=xrfAlloc*sizeof(struct grpxref *);
               xrfAlloc=(((xrfInUse+n)/XRFBLKSZ)+1)*XRFBLKSZ;
               xrfList=alcrsz(xrfList,oldsiz,xrfAlloc*sizeof(struct grpxref *));
          }
          xrfAddXref(echo,necho,forum);
          xrfSortList();
     }
}

static VOID
xrfAddXref(                        /* add cross reference(s) to list       */
const adr_t *echo,                 /*   array of echo addresses            */
INT necho,                         /*   number of addresses in array       */
USHORT forum)                      /*   forum ID                           */
{                                  /*   (xrfList must already be resized)  */
     INT i;
     struct grpxref *pxrf;
     CHAR *cp;
     GBOOL netflg;

     for (i=0 ; i < necho ; ++i) {
          if ((netflg=sameto(nntfpx,echo[i])) != FALSE
           || sameto(locfpx,echo[i])) {
               cp=crpstr((CHAR *)echo[i],':');
               xrfList[xrfInUse++]=pxrf=alczer(fldoff(grpxref,group)
                                              +strlen(cp)+1);
               pxrf->forum=forum;
               strcpy(pxrf->group,cp);
               if (netflg) {
                    pxrf->flags|=XFLGNET;
               }
          }
     }
}

static VOID
xrfSortList(VOID)                  /* sort crossreference list             */
{
     INT i,j;
     struct grpxref *swap;

     for (i=0 ; i < xrfInUse-1 ; ++i) {
          for (j=i+1 ; j < xrfInUse ; ++j) {
               if (stricmp(xrfList[i]->group,xrfList[j]->group) > 0) {
                    swap=xrfList[i];
                    xrfList[i]=xrfList[j];
                    xrfList[j]=swap;
               }
          }
     }
}

static INT                         /*   (returns NOIDX if not found)       */
xrfGroupIdx(                       /* get index of group in xref list      */
const CHAR *group)                 /*   newsgroup name                     */
{
     INT i,cmp;

     i=xrfNearGrp(&cmp,group);
     if (cmp == 0) {
          return(i);
     }
     return(NOIDX);
}

static INT
xrfNearGrp(                        /* get index of "nearest" group         */
INT *pcomp,                        /*   buffer for result of last stricmp()*/
const CHAR *group)                 /*   newsgroup name to search for       */
{
     INT lo,md,hi,cmp;

     lo=0;
     hi=xrfInUse-1;
     while (lo <= hi) {
          md=lo+(hi-lo)/2;
          if ((cmp=stricmp(xrfList[md]->group,group)) < 0) {
               lo=md+1;
          }
          else if (cmp > 0) {
               hi=md-1;
          }
          else {
               break;
          }
     }
     *pcomp=cmp;
     return(md);
}
