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

#include <sys\stat.h>
#include "gcomm.h"
#include "majorbbs.h"
#include "time.h"
#include "gme.h"
#include "tcpip.h"
#include "dns.h"
#include "nntp.h"
#include "ftscope.h"
#include "tno.h"
#include "galnntp.h"
#include "smtpexp.h"

#ifndef ICO2
#define tcpip_errno ips_errno
#define TNFACCP TNFRECV
#define TNFCONN TNFSEND
#define INET_ADDR_ERR 0xFFFFFFFFL
#define nfyskt fdscskt
#endif

#define FILREV "$Revision: 26 $"

#define KICKINT 5                  /* rtkick interval                      */
#define EXPINT  86400L             /* how long to keep msg file (24 hours) */
#define SNDBSIZ 2048               /* TCP/IP send buffer size              */
#define RCVBSIZ 1024               /* TCP/IP receive buffer size           */
#define SBUFSIZ 4096               /* maximum file storage buf size        */
#define NNTOUT  1                  /* maximum outbound nntp connections    */

                                   /* NNTP 'nntstt' state codes            */
#define DIALADR 0                  /*   looking up address record          */
#define WAITRDY 1                  /*   waiting for 200 or 201             */
#define SNTIHAV 2                  /*   sent 'IHAVE'                       */
#define PRCTEXT 3                  /*   processing message body            */
#define FINSHUP 4                  /*   end current message                */
#define SENTDOT 5                  /*   sent message terminator            */
#define CLSCONN 6                  /*   close connection to server         */

                                   /* file attachment state codes          */
#define START   0                  /*   beginning attachment checks        */
#define CHK4BIN 1                  /*   check for bin data in atchmnt      */
#define WRTBAD  2                  /*   bad attachment, add notice to msg  */
#define CPYATT  3                  /*   copying atchmnt in ascii mode      */
#define UUENCD  4                  /*   uuencoding attachment              */
#define MSGRDY  5                  /*   message ready to be sent           */

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

                                   /* process task substate codes          */
#define TSKIDL   0                 /*   task is currently idle             */
#define TCPDRV   1                 /*   process being driven by TCP/IP     */
#define PRSHDR   2                 /*   parsing header information         */
#define DNSDIAL  3
#define UUENCODE 4                 /*   uuencoding file attachment         */

VOID ini_nntrcv(VOID);             /* in nntprecv.c                        */

static const CHAR *nnthlp(VOID);
static const CHAR *nntasp(const CHAR *to,const struct message *msg);
static const CHAR *lochlp(VOID);
static INT locsnd(const CHAR *to,const struct message *msg,const CHAR *text,
                  const CHAR *filatt);
VOID locHook(const struct message *pMsg,const CHAR *text);
VOID locStartMaint(VOID);
VOID locMaint(INT taskID);
ULONG locSlice(VOID);
VOID locSleep(VOID);
GBOOL isLocalGroup(const struct fordef *pDef);

static VOID nntscn(VOID);
static VOID nntcbk(struct dns *dnsptr);
static VOID bgnnnt(struct sndprc *prcs);
static VOID nntrecv(struct sndprc *prcs);
static GBOOL prclin(struct sndprc *prcs);
static GBOOL anytime(struct sndprc *prcs);
static VOID hdlbcmd(struct sndprc *prcs);
static GBOOL sndsvr(struct sndprc *prcs,CHAR *fmtstg,...);
static VOID nntsend(struct sndprc *prcs);
static VOID enatmo(struct sndprc *prcs);
static VOID distmo(struct sndprc *prcs);
static VOID lognnt(CHAR *logstr,INT inout);
static VOID sholog(CHAR *header,CHAR *footer,...);

static GBOOL inuse(CHAR *name);
static VOID addlf(CHAR *textbuf);
static GBOOL fndfil(CHAR *tmpnam);
static VOID tskhdl(INT taskid);
static struct sndprc *gprcinf(INT taskid);
static struct sndprc *fnduprc(INT unum);
static VOID freprc(struct sndprc *prcs,GBOOL delfil);
static VOID prshdr(struct sndprc *prcs);
static VOID prsuuc(struct sndprc *prcs);
static VOID dnsdial(struct sndprc *prcs);
static VOID nntcall(struct sndprc *prcs);
static VOID nntshdn(VOID);

struct exporter nntexp={           /* NNTP exporter block for GME          */
     "",                           /*   address prefix for this exporter   */
     "",                           /*   name of exporter                   */
     "",                           /*   description of exporter            */
     "",                           /*   example address to this exporter   */
     "",                           /*   key required to use this exporter  */
     0,                            /*   per-message surcharge              */
     "",                           /*   key required for file attachments  */
     0,                            /*   attachment surcharge               */
     0,                            /*   attachment per-kbyte surcharge     */
     "",                           /*   key required to request return recp*/
     0,                            /*   return receipt request surcharge   */
     "",                           /*   key required to send priority msg  */
     0,                            /*   priority surcharge                 */
     EXPATT,                       /*   supported features flags           */
     nnthlp,                       /*   get help message for this exporter */
     nntval,                       /*   is this a valid address vector     */
     nntasp,                       /*   path+file name for attachments     */
     nntsnd                        /*   send message vector                */
};

struct exporter locexp={           /* local exporter block for GME         */
     "",                           /*   address prefix for this exporter   */
     "",                           /*   name of exporter                   */
     "",                           /*   description of exporter            */
     "",                           /*   example address to this exporter   */
     "",                           /*   key required to use this exporter  */
     0,                            /*   per-message surcharge              */
     "",                           /*   key required for file attachments  */
     0,                            /*   attachment surcharge               */
     0,                            /*   attachment per-kbyte surcharge     */
     "",                           /*   key required to request return recp*/
     0,                            /*   return receipt request surcharge   */
     "",                           /*   key required to send priority msg  */
     0,                            /*   priority surcharge                 */
     0,                            /*   supported features flags           */
     lochlp,                       /*   get help message for this exporter */
     nntval,                       /*   is this a valid address vector     */
     NULL,                         /*   path+file name for attachments     */
     locsnd                        /*   send message vector                */
};

static struct sndprc {             /* send prcs information structure      */
     INT taskid;                   /*   task id                            */
     INT tskstt;                   /*   primary task state                 */
     INT nntstt;                   /*   NNTP transfer substates            */
     INT uuestt;                   /*   UUENCODING substates               */
     INT sockfd;                   /*   socket file descriptor             */
     USHORT sttime;                /*   timeout countdown                  */
     INT usrnum;                   /*   our pseudo-user                    */
     FILE *fp;                     /*   handle for file                    */
     CHAR msgfil[GCMAXPTH];        /*   file name with extension           */
     FILE *attfp;                  /*   handle for attachment file         */
     struct qinfo qinfo;           /*   message file header                */
     CHAR sndbuf[SNDBSIZ];         /*   TCP/IP send buffer                 */
     CHAR rcvbuf[RCVBSIZ];         /*   TCP/IP receive buffer              */
     UINT sndcnt;                  /*   number of bytes in send buffer     */
     UINT rcvcnt;                  /*   number of bytes in recive buffer   */
     struct dns dns;
} *sndprcs;

struct maint {                     /* local xref mainenance process info   */
     struct message msg;           /*   buffer for current message header  */
     ULONG maxTicks;               /*   max ticks at low load              */
     ULONG minTicks;               /*   min ticks at high load             */
     ULONG zerTicks;               /*   ticks at no load                   */
     ULONG rtFactor;               /*   response time factor               */
     ULONG timeDebt;               /*   current time slice debt            */
     INT fullPct;                  /*   full load percentage               */
     INT sleep;                    /*   between-cycles sleep time          */
     INT sleepCounter;             /*   countdown to next maintenance cycle*/
     INT state;                    /*   processing state                   */
     INT forumIndex;               /*   index of current forum             */
     CHAR gmeWork[GMEWRKSZ];       /*   GME work buffer                    */
} maint;                           /* single instance for use              */

                                   /* local xref maintenance states        */
#define MAINT_NEXTFOR 0            /*   getting next forum to process      */
#define MAINT_NEXTMSG 1            /*   reading next message               */
#define MAINT_CHECKUP 2            /*   check for crossreference           */

DFAFILE *midbb;                    /* btrieve pointer to message ID data   */

HMCVFILE nntmb;                    /* file pointer to GALNNTP.MCV          */
HMCVFILE nntdmb;                   /* .MCV file pointer for GALSMPTD.MSG   */

GBOOL nntpImporting=FALSE;         /* NNTP is importing a message          */

                                   /* --- options in GALNNTP.MCV --------- */
CHAR nntobd[GCMAXPTH];             /*   NNTP outbound work directory       */
static UINT nnttmo;                /*   NNTP transmission timeout          */
static LONG nntsndi;               /*   NNTP send interval in seconds      */
static LONG nntues;                /*   NNTP UUENCODE split size in bytes  */
static GBOOL nntaud;               /*   audit successful NNTP deliveries   */
static GBOOL nntaude;              /*   audit NNTP delivery errors         */
static CHAR *nntnsvr;              /*   NNTP news server name or ip addr   */
static CHAR *nntorg;               /*   NNTP organization line             */
static GBOOL nntlog;               /*   is send log currently active?      */
static CHAR *nntlnam;              /*   send log file name                 */

CHAR nntfpx[PFXSIZ+1];             /* full prefix for Usenet(incl. ':')    */
CHAR locfpx[PFXSIZ+1];             /* full prefix for local news (inc. ':')*/
static CHAR scnpth[GCMAXPTH+4];    /* queue scan path (nntobd + *.*)       */
static CHAR nntobdp[GCMAXPTH];     /* outbound parcel directory            */
static struct ffblk nntfb;         /* file currently being processed       */
static CHAR *textbuf;              /* pointer to gen purpose text buffer   */
static INT nntnout;                /* max number of processes (NNTP outs)  */
static struct in_addr nsvrip;      /* news server IP address               */

VOID EXPORT
init__galnntp(VOID)               /* module init routine                  */
{
     CHAR *usnews;
     INT i;

     usnews=msgscan("galgwi.msg","USNEWS");
     if (access("galgwi.mdf",0) != -1
      && usnews != NULL
      && sameas(usnews,"YES")) {
          shocst("NNTP NOT INITIALIZED","MG/I processing news");
          return;
     }
     init__tcpip();
     init__galme();
     init__galsmtp();
     nntmb=opnmsg("galnntp.mcv");
     nntdmb=opnmsg("galnntpd.mcv");
     setmbk(nntmb);
     nntnsvr=stgopt(NNTNSVR);
     if (nntnsvr[0] == '\0') {
          shocst("NNTP NOT INITIALIZED","No news server configured");
          return;
     }
     stlcpy(nntobd,getmsg(NNTOBD),GCMAXPTH);
     stlcpy(nntobdp,spr("%s\\parcel",nntobd),GCMAXPTH);
     if (!fmdir(nntobd) || !fmdir(nntobdp)) {
          clsmsg(nntmb);
          shocst("NNTP NOT INITIALIZED","Couldn't find/make outbound directory");
          return;
     }
     fixpth(nntobd);
     fixpth(nntobdp);
     setmbk(nntmb);
     textbuf=alczer(TXTLEN*2);
     midbb=dfaOpen("galmsgi3.dat",sizeof(struct midinf),NULL);
     hook_finalshutdown(nntshdn);
     stlcpy(nntfpx,spr("%s:",getmsg(NNTPFX)),PFXSIZ+1);
     stlcpy(locfpx,spr("%s:",getmsg(LOCPFX)),PFXSIZ+1);
     stlcpy(nntexp.prefix,getmsg(NNTPFX),PFXSIZ);
     stlcpy(locexp.prefix,getmsg(LOCPFX),PFXSIZ);
     stlcpy(nntexp.name,getmsg(NNTNAM),EXPNSZ);
     stlcpy(locexp.name,getmsg(LOCNAM),EXPNSZ);
     stlcpy(nntexp.desc,getmsg(NNTDSC),EXPDSZ);
     stlcpy(locexp.desc,getmsg(LOCDSC),EXPDSZ);
     stlcpy(nntexp.exmp,getmsg(NNTXMP),MAXADR);
     stlcpy(locexp.exmp,getmsg(LOCXMP),MAXADR);
     stlcpy(nntexp.wrtkey,getmsg(NNTKEY),KEYSIZ);
     stlcpy(locexp.wrtkey,getmsg(NNTKEY),KEYSIZ);
     nntexp.wrtchg=numopt(NNTCHG,-32767,32767);
     locexp.wrtchg=numopt(NNTCHG,-32767,32767);
     stlcpy(nntexp.attkey,getmsg(NNTATKY),KEYSIZ);
     stlcpy(locexp.attkey,getmsg(NNTATKY),KEYSIZ);
     nntexp.attchg=numopt(NNTATCH,-32767,32767);
     locexp.attchg=numopt(NNTATCH,-32767,32767);
     nntexp.apkchg=numopt(NNTPKCH,-32767,32767);
     locexp.apkchg=numopt(NNTPKCH,-32767,32767);
     nntnout=NNTOUT;
     sndprcs=(struct sndprc *)alczer(nntnout*sizeof(struct sndprc));
     for (i=0 ; i < nntnout ; i++) {
          sndprcs[i].taskid=sndprcs[i].sockfd=-1;
     }
     nntsndi=(LONG)(numopt(NNTSNDI,1,120)*60L);
     nntues=(ULONG)numopt(NNTUES,0,2048)*1024UL;
     nnttmo=(UINT)numopt(NNTTMO,30,1800);
     nntaud=ynopt(NNTAUD);
     nntaude=ynopt(NNTAUDE);
     nntorg=stgopt(NNTORG);
     nntlog=ynopt(NNTLOG);
     nntlnam=stgopt(NNTLNAM);
     register_exp(&nntexp);
     register_exp(&locexp);
     hook_gme(GMEHOOK_NOT_NEWMSGL,(voidfunc)locHook);
     if (ynopt(LCMAINT)) {
          memset(&maint,0,sizeof(maint));
          maint.maxTicks=(65536UL*numopt(MMAXTIM,1,32767))/1000;
          maint.minTicks=(65536UL*numopt(MMINTIM,1,32767))/1000;
          maint.zerTicks=(65536UL*numopt(MZERTIM,1,32767))/1000;
          maint.rtFactor=numopt(MRSPTIM,1,32767);
          maint.fullPct=numopt(MLOADPCT,1,100);
          maint.sleep=numopt(MSLEEP,0,32767);
          locStartMaint();
          dclvda(TXTLEN);
     }
     sprintf(scnpth,"%s*.*",nntobd);
     if (nntlog) {
          shocst("NNTP SEND LOG ENABLED",
                 "All NNTP send transactions will be logged");
          if (nntnout > 1) {
               nntnout=1;
               shocst("NNTP SEND CHANNELS LIMITED",
                      "Send channels limited to one due to logging");
          }
     }
     ini_nntrcv();
     rtkick(KICKINT,nntscn);
}

VOID EXPORT
initwc__galnntp(VOID)
{
     init__galnntp();
}

static const CHAR *
nnthlp(VOID)                       /* get help message for NNTP exporter   */
{
     CHAR *cp;

     setmbk(nntmb);
     cp=stpans(getmsg(NNTHELP));
     rstmbk();
     return(cp);
}

static const CHAR *
nntasp(                            /* get outbound att file path+name      */
const CHAR *to,                    /*   address to which message being sent*/
const struct message *msg)         /*   header of message being sent       */
{
     UINT ctr;
     static CHAR attpath[GCMAXPTH];

     (VOID)to;
     (VOID)msg;
     ctr=0U;
     do {
          stlcpy(attpath,nntobdp,GCMAXPTH);
          stlcat(attpath,tmpnam(NULL),GCMAXPTH);
          if (++ctr == TMP_MAX) {
               catastro("Too many temporary files in outbound attachment\n"
                        "directory: %s",nntobdp);
          }
     } while (access(attpath,0) == 0);
     return(attpath);
}

INT                                /*   returns GME error codes            */
nntsnd(                            /* send NNTP message                    */
const CHAR *to,                    /*   to field                           */
const struct message *omsg,        /*   header of message to send off      */
const CHAR *text,                  /*   message text buffer                */
const CHAR *filatt)                /*   path+file name of attachment       */
{
     FILE *fp;
     GBOOL isrep;
     const CHAR *cp;
     CHAR tmpBuf[MAXADR];
     struct addrinf addrinf;
     CHAR * pTextBuf;
     INT rc;
     struct message * msg;
     struct message msgbuf;
     static CHAR tmpname[GCMAXPTH];
     static struct qinfo qinfo;

     /* let hooks have a crack at message */
     msg=&msgbuf;
     memcpy(msg,omsg,sizeof(struct message));
     pTextBuf=stlcpy(textbuf,text,TXTLEN);
     if (hookHandleExp(&rc,msg,&to,&filatt,&pTextBuf,2*TXTLEN)) {
          ASSERT(rc != GMEAGAIN);
          return(rc);
     }

     /* initialize file */
     stlcpy(tmpname,nntobd,GCMAXPTH);
     if (!makeUniqueFName(tmpname,"$$S")) {
          shocst("NNTP OPEN FILE ERROR",
                 "nntsnd(): Could not open message file (bin)");
          if (msg->flags&FILATT) {
               unlink(filatt);
          }
          return(GMEERR);
     }
     if ((fp=fopen(tmpname,FOPWB)) == NULL) {
          shocst("NNTP OPEN FILE ERROR",
                 "nntsnd(): Could not re-open message file (bin)");
          if (msg->flags&FILATT) {
               unlink(filatt);
          }
          return(GMEERR);
     }

     /* save message */
     setmem(&qinfo,sizeof(struct qinfo),0);
     stlcpy(qinfo.name,NNTNAME,QNMSIZ);
     qinfo.lsttim=qinfo.qtime=time(NULL);
     if (msg->flags&FILATT) {
          stlcpy(qinfo.attfil,filatt,GCMAXPTH);
          stlcpy(qinfo.relnam,msg->attname,GCMAXFNM);
          strlwr(qinfo.relnam);
     }
     nmsgid(qinfo.id);
     stlcpy(qinfo.to,crpstr(to,':'),MAXADR);
     if (fwrite(&qinfo,1,sizeof(struct qinfo),fp) != sizeof(struct qinfo)) {
          shocst("NNTP FILE WRITE ERROR","nntsnd(): Could not write header");
          fclose(fp);
          unlink(tmpname);
          if (msg->flags&FILATT) {
               unlink(filatt);
          }
          return(GMEERR);
     }
     fprintf(fp,"Date: %s\r\n",cvtDate(msg->crtime,msg->crdate));
     cvtFrom(tmpBuf,msg->from);
     parseIntAddr(tmpBuf,&addrinf);
     if (curHost(addrinf.domain)) {
          fprintf(fp,"Path: %s!%s\r\n",smtpHost(),addrinf.usrals);
     }
     else {
          fprintf(fp,"Path: %s!%s!%s\r\n",
                  smtpHost(),addrinf.domain,addrinf.usrals);
     }
     fprintf(fp,"Newsgroups: %s\r\n",crpstr(to,':'));
     fprintf(fp,"From: %s\r\n",tmpBuf);
     fprintf(fp,"Message-ID: %s\r\n",qinfo.id);
     if (nntorg[0] != '\0') {
          fprintf(fp,"Organization: %s\r\n",nntorg);
     }
     isrep=(msg->rplto.sysid != 0L && msg->rplto.msgid != 0L);
     if ((cp=getref(msg)) != NULL) {
          fprintf(fp,"References: %s\r\n",cp);
     }
     if (msg->topic[0] == '\0') {
          fprintf(fp,"Subject: <none>\r\n");
     }
     else if (isrep && !sameto("RE:",msg->topic)) {
          fprintf(fp,"Subject: Re: %s\r\n",msg->topic);
     }
     else {
          fprintf(fp,"Subject: %s\r\n",msg->topic);
     }
     addlf(pTextBuf);
     if (fwrite(pTextBuf,1,strlen(pTextBuf),fp) != strlen(pTextBuf)) {
          sholog("NNTP WRITE ERROR","Error writing message body");
          fclose(fp);
          unlink(tmpname);
          if (msg->flags&FILATT) {
               unlink(filatt);
          }
          return(GMEERR);
     }
     setmbk(nntmb);
     clrprf();
     prfmsg(NNTTAG);
     if (prfbuf[0] != '\0') {
          fprintf(fp,"\r\n--\r\n");
          addlf(stpans(prfbuf));
          fwrite(prfbuf,1,strlen(prfbuf),fp);
          clrprf();
     }
     rstmbk();
     fclose(fp);
     add2db(msg,qinfo.id);
     return(GMEOK);
}

static const CHAR *
lochlp(VOID)                       /* get help message for local exporter  */
{
     CHAR *cp;

     setmbk(nntmb);
     cp=stpans(getmsg(LOCHLP));
     rstmbk();
     return(cp);
}

static INT                         /*   returns GME error codes            */
locsnd(                            /* add entry to the database            */
const CHAR *to,                    /*   to field                           */
const struct message *msg,         /*   header of message to send off      */
const CHAR *text,                  /*   message text buffer                */
const CHAR *filatt)                /*   path+file name of attachment       */
{
     (VOID)to;
     (VOID)text;
     (VOID)filatt;
     if (msg->forum == EMLID) {
          return(GMEERR);
     }
     return(GMEOK);
}

VOID
locHook(                           /* new message hook (for local groups)  */
const struct message *pMsg,        /*   new message header                 */
const CHAR *text)                  /*   new message text                   */
{
     const struct fordef *pDef;
     CHAR idstr[MIDSIZ];

     (VOID)text;
     if (!nntpImporting
      && pMsg->forum != EMLID
      && (pDef=getdefp(pMsg->forum)) != NULL
      && isLocalGroup(pDef)) {
          add2db(pMsg,nmsgid(idstr));
     }
}

VOID
locStartMaint(VOID)                /* start up maintenance cycle           */
{
     maint.state=MAINT_NEXTFOR;
     maint.forumIndex=0;
     maint.timeDebt=0;
     initask(locMaint);
}

VOID
locMaint(                          /* task to maintain local group xref    */
INT taskID)
{
     const struct fordef *pDef;
     ULONG timeSlice,begTime,timeUsed;
     INT rc;
     GBOOL cont;
     CHAR idstr[MIDSIZ];

     timeSlice=locSlice();
     if (maint.timeDebt >= timeSlice) {
          maint.timeDebt-=timeSlice;
          return;
     }
     cont=TRUE;
     begTime=hrtval()-maint.timeDebt;
     do {
          switch (maint.state) {
          case MAINT_NEXTFOR:
               if (maint.forumIndex < numforums()) {
                    pDef=fiddefp(maint.forumIndex);
                    ++maint.forumIndex;
                    if (pDef != NULL && isLocalGroup(pDef)) {
                         inigmerq(maint.gmeWork);
                         inormrd(maint.gmeWork,"",pDef->forum,FIRSTM);
                         maint.state=MAINT_NEXTMSG;
                    }
               }
               else {
                    cont=FALSE;
                    mfytask(taskID,NULL);
                    if (maint.sleep != 0) {
                         maint.sleepCounter=maint.sleep;
                         rtkick(60*60,locSleep);  /* wait an hour at a time*/
                    }
               }
               break;
          case MAINT_NEXTMSG:
               rc=grdmsg(maint.gmeWork,RDNEXT,&maint.msg,vdatmp);
               if (rc > GMEAGAIN) {
                    maint.state=MAINT_CHECKUP;
               }
               else if (rc < GMEAGAIN) {
                    clsgmerq(maint.gmeWork);
                    maint.state=MAINT_NEXTFOR;
               }
               else {
                    cont=FALSE;
               }
               break;
          case MAINT_CHECKUP:
               dfaSetBlk(midbb);
               if (!dfaQueryEQ(&maint.msg.gmid,MSGKEY)) {
                    add2db(&maint.msg,nmsgid(idstr));
               }
               dfaRstBlk();
               maint.state=MAINT_NEXTMSG;
               break;
          }
          timeUsed=hrtval()-begTime;    /* NOTE: relies on unsigned wrap   */
     } while (cont && timeUsed < timeSlice);
     maint.timeDebt=(timeUsed > timeSlice ? timeUsed-timeSlice : 0);
}

ULONG
locSlice(VOID)                     /* maintenance time slice based on load */
{
     ULONG n,retval;

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

VOID
locSleep(VOID)                     /* maintenance sleep timer              */
{
     if (--maint.sleepCounter == 0) {
          locStartMaint();
     }
     else {
          rtkick(60*60,locSleep);  /* wait an hour at a time */
     }
}

GBOOL
isLocalGroup(                      /* is this a local newsgroup?           */
const struct fordef *pDef)         /*   forum to check                     */
{
     const adr_t *echo;
     INT i;
     GBOOL isLocal;

     isLocal=FALSE;
     echo=(const adr_t *)pDef->echoes;
     for (i=0 ; i < pDef->necho ; ++i) {
          if (sameto(locfpx,echo[i])) {
               isLocal=TRUE;
          }
          else if (sameto(nntfpx,echo[i])) {
               return(FALSE);
          }
     }
     return(isLocal);
}

static VOID
addlf(                             /* adds line feeds after C/Rs           */
CHAR *textbuf)
{
     CHAR *ptr;

     ptr=strrpl(textbuf,'\n','\r');
     while ((ptr=strchr(ptr,'\r')) != NULL) {
          ptr++;
          movmem(ptr,ptr+1,strlen(ptr)+1);
          *ptr='\n';
     }
}

static VOID
nntscn(VOID)                       /* scan send queue                      */
{
     INT taskid,index;
     CHAR tmpnam[GCMAXPTH];

     for (index=0 ; index < nntnout ; index++) {
          if (sndprcs[index].tskstt == TSKIDL) {
               if (fndfil(tmpnam)) {
                    taskid=initask(tskhdl);
                    setmem(&sndprcs[index],sizeof(struct sndprc),0);
                    sndprcs[index].taskid=taskid;
                    sndprcs[index].tskstt=PRSHDR;
                    sndprcs[index].sockfd=-1;
                    stlcpy(sndprcs[index].msgfil,tmpnam,GCMAXPTH);
               }
               else {
                    break;
               }
          }
     }
     rtkick(KICKINT,nntscn);
}

static GBOOL                       /*   returns TRUE if file is found      */
fndfil(                            /* finds next file to process           */
CHAR *tmpnam)                      /*   full path & file name if found     */
{
     CHAR oldnam[GCMAXFNM];

     oldnam[0]='\0';
     while (TRUE) {
          if (nntfb.ff_name[0] == '\0') {
               if (!fnd1st(&nntfb,scnpth,0)) {
                    return(FALSE);
               }
          }
          else if (!fndnxt(&nntfb)) {
               setmem(&nntfb,sizeof(nntfb),0);
               if (!fnd1st(&nntfb,scnpth,0)) {
                    return(FALSE);
               }
          }
          if (oldnam[0] == '\0') {
               strcpy(oldnam,nntfb.ff_name);
          }
          else if (sameas(oldnam,nntfb.ff_name)) {
               return(FALSE);
          }
          sprintf(tmpnam,"%s%s",nntobd,nntfb.ff_name);
          if (!inuse(tmpnam)) {
               return(TRUE);
          }
     }
}

static GBOOL
inuse(                             /* check if file being processed        */
CHAR *name)
{
     INT i;

     for (i=0 ; i < nntnout ; i++) {
          if (sndprcs[i].tskstt != TSKIDL
           && sameas(sndprcs[i].msgfil,name)) {
               return(TRUE);
          }
     }
     return(FALSE);
}

static VOID
tskhdl(                            /* NNTP send task handler               */
INT taskid)                        /*   send task ID                       */
{
     USHORT curtim;
     struct sndprc *prcs;

     prcs=gprcinf(taskid);
     curtim=btuTicker()-prcs->sttime;
     if (prcs->tskstt != TSKIDL
      && prcs->sttime != 0
      && curtim > nnttmo) {
          distmo(prcs);
          if (nntaude) {
               sholog("NNTP CONNECTION TIMEOUT","Connection timed out");
          }
          if (prcs->tskstt == TCPDRV && prcs->nntstt == DIALADR) {
               usrnum=prcs->usrnum;
               dnsabt();
          }
          freprc(prcs,FALSE);
     }
     switch (prcs->tskstt) {
     case TSKIDL:
     case TCPDRV:
          break;
     case PRSHDR:
          prshdr(prcs);
          break;
     case UUENCODE:
          prsuuc(prcs);
          break;
     case DNSDIAL:
          dnsdial(prcs);
          break;
     }
}

static struct sndprc *
gprcinf(                           /* get process information              */
INT taskid)
{
     INT i;

     for (i=0 ; i < nntnout ; i++) {
          if (sndprcs[i].taskid == taskid) {
               return(&sndprcs[i]);
          }
     }
     catastro("NNTP: Fatal error reading task information!");
     return(NULL);
}

static struct sndprc *
fnduprc(                           /* find the prcs associated with unum   */
INT unum)
{
     INT i;

     for (i=0 ; i < nntnout ; i++) {
          if (sndprcs[i].usrnum == unum
           && sndprcs[i].tskstt != TSKIDL) {
               return(&sndprcs[i]);
          }
     }
     return(NULL);
}

static VOID
freprc(                            /* release a prcs and its resources     */
struct sndprc *prcs,
GBOOL delfil)
{
     if (prcs->fp != NULL) {
          if (!delfil) {
               fseek(prcs->fp,0L,SEEK_SET);
               fwrite(&prcs->qinfo,1,sizeof(struct qinfo),prcs->fp);
          }
          fclose(prcs->fp);
          prcs->fp=NULL;
     }
     if (prcs->sockfd >= 0) {
          clsskt(prcs->sockfd);
     }
     mfytask(prcs->taskid,NULL);
     if (delfil) {
          unlink(prcs->msgfil);
     }
     setmem(prcs,sizeof(struct sndprc),0);
     prcs->taskid=prcs->sockfd=-1;
}

static VOID
prshdr(                            /* process file header                 */
struct sndprc *prcs)
{
     LONG tmo;
     CHAR fileName[GCMAXFNM];

     if ((prcs->fp=fopen(prcs->msgfil,FOPRWB)) == NULL) {
          fileparts(GCPART_FNAM,prcs->msgfil,fileName,sizeof(fileName));
          sholog("NNTP OPEN ERROR",
                 "prshdr(): Could not open file %s",fileName);
          freprc(prcs,FALSE);
          return;
     }
     if (fread(&prcs->qinfo,sizeof(struct qinfo),1,prcs->fp) != 1) {
          sholog("NNTP READ ERROR","prshdr(): Could not read message info");
          freprc(prcs,TRUE);
          return;
     }
     if (!sameas(prcs->qinfo.name,NNTNAME)) {
          sholog("NNTP READ ERROR","prshdr(): Invalid message info");
          freprc(prcs,TRUE);
          return;
     }
     if (prcs->qinfo.qtime != prcs->qinfo.lsttim) {
          tmo=time(NULL)-prcs->qinfo.lsttim;
          if (tmo < nntsndi) {
               freprc(prcs,FALSE);
               return;
          }
          else if (tmo > EXPINT) {
               freprc(prcs,TRUE);
               return;
          }
     }
     prcs->qinfo.lsttim=time(NULL);
     if (prcs->qinfo.attfil[0] != '\0') {
          prcs->tskstt=UUENCODE;
          prcs->uuestt=START;
     }
     else {
          prcs->tskstt=DNSDIAL;
     }
}

static VOID
dnsdial(
struct sndprc *prcs)
{
     INT savunm=usrnum;

     if ((usrnum=dnsfvc()) == -1) {
          usrnum=savunm;
          freprc(prcs,FALSE);
          return;
     }
     prcs->sockfd=-1;
     prcs->tskstt=TCPDRV;
     prcs->nntstt=DIALADR;
     prcs->usrnum=usrnum;
     distmo(prcs);
     stzcpy(prcs->dns.name,
            nsvrip.s_addr == 0L ? nntnsvr : inet_ntoa(nsvrip),DNSNSZ);
     prcs->dns.numaddr=1;
     prcs->dns.callbk=nntcbk;
     dnsn2a(&prcs->dns);
     usrnum=savunm;
}

static VOID
nntcbk(                            /* NNTP address lookup callback vector  */
struct dns *dnsptr)
{
     struct sndprc *prcs;

     if ((prcs=fnduprc(usrnum)) == NULL) {
          dnsfre(usrnum);
          usrnum=-1;
          sholog("NNTP DNS C/B TOO LATE","DNS callback too late");
          return;
     }
     if (dnsptr->status == DNSABT) {
          dnsfre(usrnum);
          freprc(prcs,FALSE);
          return;
     }
     usrnum=-1;
     if (dnsptr->status >= 0) {
          if (nsvrip.s_addr == 0L) {
               nsvrip=dnsptr->inaddr[0];
          }
          nntcall(prcs);
     }
     else {
          if (nntaude) {
               sholog("NNTP INVALID NEWS SERVER","%s (%s)",dnsptr->name,dnsemg);
          }
          freprc(prcs,FALSE);
     }
     dnsfre(prcs->usrnum);
     prcs->usrnum=0;
}

static VOID
nntcall(                           /* try to connect to NNTP server        */
struct sndprc *prcs)
{
     int rc;

     rc=tcpdial(nsvrip,htons(NNTPORT),0,&prcs->sockfd);
     switch (rc) {
     default:
          if (nntaude) {
               sholog("NNTP DIAL FAILED",
                      "Errno: %d - Host: %s",tcpip_errno,inet_ntoa(nsvrip));
          }
          freprc(prcs,FALSE);
          break;
     case DLCNOW:
     case DLCING:
          sktnfy(TNFCONN,prcs->sockfd,bgnnnt,prcs,-1);
          enatmo(prcs);
          break;
     }
}

static VOID
bgnnnt(                             /* Connection has begun                 */
struct sndprc *prcs)                /* The prcs that is handling this conn  */
{
     enatmo(prcs);
     prcs->nntstt=WAITRDY;
     sktcnc(TNFCONN,prcs->sockfd);
     sktnfy(TNFRECV,prcs->sockfd,nntrecv,prcs,-1);
}

static VOID
nntrecv(                           /* NNTP client receive-from-server      */
struct sndprc *prcs)               /* The prcs that is handling this conn  */
{
     INT nroom,nactual;
     CHAR *ptr;
     CHAR fileName[GCMAXFNM];

     nroom=RCVBSIZ-prcs->rcvcnt-1;
     if (nroom <= 0) {
          return;
     }
     if ((nactual=recv(prcs->sockfd,prcs->rcvbuf+prcs->rcvcnt,nroom,0)) < 0) {
          if (nntaude) {
               fileparts(GCPART_FNAM,prcs->msgfil,fileName,sizeof(fileName));
               sholog("NNTP RECEIVE ERROR",
                      "States: %d,%d - Errno: %d - File: %s",
                      prcs->tskstt,prcs->nntstt,tcpip_errno,fileName);
          }
          freprc(prcs,FALSE);
     }
     else if (nactual == 0) {
          if (nntaude) {
               sholog("NNTP CONNECTION CLOSED","NNTP server closed connection");
          }
          freprc(prcs,FALSE);
     }
     else {
          prcs->rcvcnt+=nactual;
          prcs->rcvcnt=memstp(prcs->rcvbuf,prcs->rcvcnt,'\0');
          prcs->rcvbuf[prcs->rcvcnt]='\0';
          prcs->rcvcnt=strlen(strstp(prcs->rcvbuf,'\r'));
          while ((ptr=strchr(prcs->rcvbuf,'\n')) != NULL) {
               *ptr='\0';
               if (!prclin(prcs)) {
                    prcs->rcvbuf[0]='\0';
                    prcs->rcvcnt=0;
                    break;
               }
               strcpy(prcs->rcvbuf,ptr+1);
               prcs->rcvcnt=strlen(prcs->rcvbuf);
          }
     }
}

static VOID
nntsend(                           /* try to send data in buffer to server */
struct sndprc *prcs)               /*   NNTP client prcs info              */
{
     CHAR fileName[GCMAXFNM];

     switch (sndmgr(prcs->sndbuf,&prcs->sndcnt,prcs->sockfd)) {
     case -1:
          fileparts(GCPART_FNAM,prcs->msgfil,fileName,sizeof(fileName));
          sholog("NNTP SEND ERROR","States: %d,%d - Errno: %d - File: %s",
                  prcs->tskstt,prcs->nntstt,tcpip_errno,fileName);
          freprc(prcs,FALSE);
          break;
     case 0:
          switch (prcs->nntstt) {
          case PRCTEXT:
          case FINSHUP:
               prclin(prcs);
               break;
          default:
               sktcnc(TNFSEND,prcs->sockfd);
               break;
          }
          break;
     case 1:
          break;
     }
}

static GBOOL                       /*   returns FALSE if done with message */
prclin(                            /* process a line from the NNTP server  */
struct sndprc *prcs)
{
     INT lines;
     GBOOL retval=TRUE;

     enatmo(prcs);
     if (strlen(skptwht(prcs->rcvbuf)) > 0) {
          lognnt(prcs->rcvbuf,INCMING);
     }
     if (anytime(prcs)
      || (strlen(prcs->rcvbuf) >= 4 && prcs->rcvbuf[3] == '-')) {
          return(retval);
     }
     switch (prcs->nntstt) {
     case WAITRDY:
          if (sameto("200",prcs->rcvbuf) || sameto("201",prcs->rcvbuf)) {
               sndsvr(prcs,"IHAVE %s\r\n",prcs->qinfo.id);
               prcs->nntstt=SNTIHAV;
          }
          else {
               hdlbcmd(prcs);
               retval=FALSE;
          }
          break;
     case SNTIHAV:
          if (sameto("435",prcs->rcvbuf)) {
               freprc(prcs,TRUE);
               retval=FALSE;
          }
          else if (sameto("335",prcs->rcvbuf)) {
               sktnfy(TNFSEND,prcs->sockfd,nntsend,prcs,-1);
               prcs->nntstt=PRCTEXT;
          }
          else {
               hdlbcmd(prcs);
               retval=FALSE;
          }
          break;
     case PRCTEXT:
          if (prcs->rcvcnt > 0) {
               hdlbcmd(prcs);
               retval=FALSE;
               break;
          }
          lines=NUMLINS;
          while (lines-- > 0 && (SNDBSIZ-prcs->sndcnt > NNTLSZ-4)) {
               /* '.'+'\r'+'\n'+'\0' = 4 */
               if (fgets(vdatmp,NNTLSZ-4,prcs->fp) == NULL) {
                    prcs->nntstt=FINSHUP;
                    break;
               }
               if (vdatmp[0] == '.') {
                    sndsvr(prcs,".%s",vdatmp);
               }
               else {
                    sndsvr(prcs,"%s",vdatmp);
               }
          }
          break;
     case FINSHUP:
          if (prcs->rcvcnt > 0) {
               hdlbcmd(prcs);
               retval=FALSE;
               break;
          }
          sndsvr(prcs,"\r\n.\r\n");
          prcs->nntstt=SENTDOT;
          sktnfy(TNFRECV,prcs->sockfd,nntrecv,prcs,-1);
          break;
     case SENTDOT:
          if (sameto("235",prcs->rcvbuf)) {
               sndsvr(prcs,"QUIT\r\n");
               prcs->nntstt=CLSCONN;
          }
          else if (sameto("437",prcs->rcvbuf)) {
               freprc(prcs,TRUE);
               retval=FALSE;
          }
          else {
               hdlbcmd(prcs);
               retval=FALSE;
          }
          break;
     case CLSCONN:
          if (sameto("205",prcs->rcvbuf)) {
               if (nntaud) {
                    sholog("NNTP NEWS DELIVERED",
                           "To: %s",prcs->qinfo.to);
               }
               freprc(prcs,TRUE);
               retval=FALSE;
          }
          else {
               hdlbcmd(prcs);
               retval=FALSE;
          }
          break;
     }
     return(retval);
}

static GBOOL
anytime(                           /* handle responses that occur anytime  */
struct sndprc *prcs)
{

     if (sameto("100",prcs->rcvbuf) || sameto("199",prcs->rcvbuf)) {
          return(TRUE);
     }
     return(FALSE);
}

static VOID
hdlbcmd(                           /* handle abnormal responses from server*/
struct sndprc *prcs)
{
     CHAR fileName[GCMAXFNM];

     if (nntaude) {
          fileparts(GCPART_FNAM,prcs->msgfil,fileName,sizeof(fileName));
          sholog("NNTP CLIENT DISCONNECT",
                 "%d %s (%s)",prcs->nntstt,prcs->rcvbuf,fileName);
     }
     freprc(prcs,FALSE);
}

static GBOOL                       /*   FALSE=Could not send, TRUE=sent    */
sndsvr(                            /* send ASCIIZ string to server         */
struct sndprc *prcs,
CHAR *fmtstg,
...)
{
     va_list ap;
     INT len;
     CHAR tmpbuf[NNTLSZ];

     va_start(ap,fmtstg);
     vsprintf(tmpbuf,fmtstg,ap);
     va_end(ap);
     if ((len=strlen(tmpbuf)) > SNDBSIZ-prcs->sndcnt) {
          return(FALSE);
     }
     movmem(tmpbuf,prcs->sndbuf+prcs->sndcnt,len);
     prcs->sndcnt+=len;
     sktnfy(TNFSEND,prcs->sockfd,nntsend,prcs,-1);
     if (prcs->nntstt != PRCTEXT) {
          lognnt(tmpbuf,OUTGING);
     }
     return(TRUE);
}

static VOID
prsuuc(                          /* UUENCODE a file attachment           */
struct sndprc *prcs)
{
     struct stat sbuf;
     INT i,nread;
     INT uuipg;
     CHAR *ptr;

     setmbk(nntmb);
     switch (prcs->uuestt) {
     case START:
          fseek(prcs->fp,0L,SEEK_END);
          if ((prcs->attfp=fopen(prcs->qinfo.attfil,FOPRB)) == NULL) {
               shocst("NNTP OPEN FILE ERROR",
                      "prsuuc(START): Could not open attachment file");
               prcs->uuestt=WRTBAD;
          }
          else {
               prcs->uuestt=CHK4BIN;
          }
          break;
     case CHK4BIN:
          fstat(fileno(prcs->attfp),&sbuf);
          fprintf(prcs->fp,"\r\nbegin %o %s\r\n",sbuf.st_mode&0777,
                                                 prcs->qinfo.relnam);
          if ((nread=fread(vdatmp,1,vdasiz,prcs->attfp)) < 0) {
               shocst("NNTP READ FILE ERROR",
                      "prsuue(CHK4BIN): Unable to read attachment file");
               prcs->uuestt=WRTBAD;
          }
          else {
               for (i=0 ; i < nread ; i++) {
                    if (vdatmp[i] > 127) {
                         fseek(prcs->attfp,0L,SEEK_SET);
                         prcs->uuestt=UUENCD;
                         break;
                    }
               }
               if (nread < vdasiz && prcs->uuestt != UUENCD) {
                    fseek(prcs->attfp,0L,SEEK_SET);
                    prcs->uuestt=CPYATT;
               }
          }
          break;
     case CPYATT:
          if ((nread=fread(vdatmp,1,vdasiz,prcs->attfp)) < 0) {
               shocst("NNTP READ FILE ERROR",
                      "prsuue(CPYATT): Unable to read attachment");
               prcs->uuestt=WRTBAD;
          }
          else if (fwrite(vdatmp,1,nread,prcs->fp) != nread) {
               shocst("NNTP WRITE FILE ERROR",
                      "prsuue(CPYATT): Unable to copy attachment");
               prcs->uuestt=WRTBAD;
          }
          else if (nread != vdasiz) {
               prcs->uuestt=MSGRDY;
          }
          break;
     case UUENCD:
          uuipg=uuencode(prcs->attfp,prcs->fp);
          if (ferror(prcs->attfp)) {
               shocst("NNTP WRITE FILE ERROR",
                      "prsuue(UUENCD): Unable to copy attachment");
               prcs->uuestt=WRTBAD;
          }
          else if (!uuipg) {
               prcs->uuestt=MSGRDY;
          }
          break;
     case WRTBAD:
          fseek(prcs->fp,0L,SEEK_END);
          ptr=getasc(BADATT);
          fwrite(ptr,1,strlen(ptr),prcs->fp);
     case MSGRDY:
          if (prcs->attfp != NULL) {
               fclose(prcs->attfp);
          }
          unlink(prcs->qinfo.attfil);
          prcs->qinfo.attfil[0]='\0';
          fseek(prcs->fp,sizeof(struct qinfo),SEEK_SET);
          prcs->tskstt=DNSDIAL;
          break;
     }
     rstmbk();
}

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

static VOID
distmo(                            /* disable timeout                      */
struct sndprc *prcs)
{
     prcs->sttime=0;
}

static VOID
lognnt(                            /* log send transaction                 */
CHAR *logstr,
INT type)
{
     FILE *logfp;
     CHAR *cp;

     if (!nntlog) {
          return;
     }
     if ((logfp=fopen(nntlnam,FOPAA)) == NULL) {
          nntlog=FALSE;
          shocst("NNTP SEND LOG OPEN ERROR",
                 "Could not open \"%s\".  Logging disabled.",nntlnam);
          return;
     }
     cp=skptwht(logstr);
     if (fprintf(logfp,"%s %s: %s%0.*s\n",ncdate(today()),nctime(now()),
                 type == OUTGING ? "-->" : type == INCMING ? "<--" : "***",
                 strpln(cp),cp) == EOF) {
          nntlog=FALSE;
          shocst("NNTP SEND LOG WRITE ERROR",
                 "Could not write to send log.  Logging disabled.");
     }
     fclose(logfp);
}

static VOID
sholog(                            /* prints to audit trail, logs to file  */
CHAR *header,
CHAR *footer,
...)
{
     va_list ftlist;
     CHAR wrkbuf[NNTLSZ];
     CHAR logbuf[(AUDBRIEFSIZ-1)+CSTRLEN(": ")+(AUDDETSIZ-1)+1];

     va_start(ftlist,footer);
     vsprintf(wrkbuf,footer,ftlist);
     va_end(ftlist);
     shocst(header,"%.*s",AUDDETSIZ,wrkbuf);
     sprintf(logbuf,"%.*s: %.*s",AUDBRIEFSIZ-1,header,AUDDETSIZ-1,wrkbuf);
     lognnt(logbuf,SHOWLOG);
}

static VOID
nntshdn(VOID)                      /* finish-up (system shutdown) routine  */
{
     clsmsg(nntmb);
     clsmsg(nntdmb);
     dfaClose(midbb);
}
