/***************************************************************************
 *                                                                         *
 *   SMTPSEND.C                                                            *
 *                                                                         *
 *   Copyright (c) 1995-1996 Galacticomm, Inc.  All rights reserved.       *
 *                                                                         *
 *   SMTP sender for transmitting e-mail from Worldgroup's GME to the      *
 *   Internet, per RFC 821.                                                *
 *                                                                         *
 *                               5/28/95 - Bert Love,                      *
 *                                         Scott Brinker,                  *
 *                                         Charles Dunn &                  *
 *                                         Mahesh Neelakanta               *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "majorbbs.h"
#include "fcntl.h"
#include "sys\stat.h"
#include "time.h"
#include "limits.h"
#include "gme.h"
#include "tcpip.h"
#include "dns.h"
#include "smtpexp.h"
#include "smtp.h"
#include "ftscope.h"
#include "tno.h"
#include "alias.h"
#include "galsmtp.h"
#include "mimectyp.h"

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

#define FILREV "$Revision: 57 $"

                                   /* SMTP 'sndstt' state codes            */
#define IDLE        0              /*   idle - waiting for prcs            */
#define DIALCNAME   1              /*   looking up CNAME record            */
#define DIALMX      2              /*   looking up MX record               */
#define DIALADR     3              /*   looking up address record          */
#define CONNING     4              /*   connecting to host                 */
#define TRANSFR     5              /*   transferring message to host       */

                                   /* SMTP 'smtstt' state codes            */
#define WAITRDY 0                  /*   waiting for 220 - svc ready        */
#define SNTHELO 1                  /*   sent 'HELO'                        */
#define SNTFROM 2                  /*   sent 'MAIL FROM:'                  */
#define SNTRCPT 3                  /*   sent 'RCPT TO:'                    */
#define SNTDATA 4                  /*   sent 'DATA'                        */
#define PRCTEXT 5                  /*   processing message body            */
#define FINSHUP 6                  /*   end current message                */
#define SENTDOT 7                  /*   sent message terminator            */
#define CLSCONN 8                  /*   close connection to server         */

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

                                   /* 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 PRSRCP   3                 /*   parsing recipient information      */
#define TRYCONN  4                 /*   try to connect                     */
#define MIMECODE 5                 /*   MIME encoding file attachment      */
#define ENDTSK   6                 /*   ending the current task            */

#define KICKINT  5                 /* rtkick interval for scanning obd     */
#define SMTPDERR "SMTP Delivery Error" /* Subject for SMTP delivery error  */
#define HSTBUFSZ  256
#define MAXIPADD  16
                              /* this struct needs to be above prototypes! */
static struct sndprc {             /* send prcs information structure      */
     INT taskid;                   /*   task ID for this process           */
     INT sndstt;                   /*   send prcs state, 0=inactive        */
     INT smtstt;                   /*   SMTP transfer state                */
     INT tskstt;                   /*   task substate                      */
     INT mimstt;                   /*   MIME encoding substate             */
     INT sockfd;                   /*   socket file descriptor             */
     USHORT sttime;                /*   timeout countdown                  */
     INT usrnum;                   /*   our pseudo-user                    */
     FILE *fp;                     /*   handle for file                    */
     FILE *mimfp;                  /*   handle for attachment file         */
     GBOOL binatt;                 /*   binary attachment?                 */
     LONG rcptpos;                 /*   current recipient record position  */
     CHAR msgfil[GCMAXPTH];        /*   file name with extension           */
     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   */
     ULONG bsofar;                 /*   bytes sent so far in this segment  */
     ULONG byttfc;                 /*   # of bytes sent/recv'd via conn    */
     struct minfo minfo;           /*   message information structure      */
     struct rinfo rinfo;           /*   recipient informatin structure     */
     struct dns dns;               /*   structure for DNS                  */
     struct in_addr inaddr[MAXIPADD-1];/*add'l entries for IP addresses    */
     struct dnshstex dnshstex;     /*   extended DNS structure             */
     CHAR hstbuf[HSTBUFSZ];        /*   buffer to store hostnames          */
     INT currip;                   /*   current inaddr to try              */
     INT currname;                 /*   current host name to try           */
     CHAR logmsg0[AUDBRIEFSIZ];    /*   message for audit trail if failed  */
     CHAR logmsg1[AUDDETSIZ];      /*   message for audit trail if failed  */
} *sndprcs;

static VOID smtscn(VOID);
static const CHAR *smthlp(VOID);
static const CHAR *smtasp(const CHAR *to,const struct message *msg);
INT smtsnd(const CHAR *to,const struct message *msg,const CHAR *text,
           const CHAR *filatt);
static VOID prshdr(struct sndprc *prcs);
static VOID cvtFrom(CHAR *dst,const CHAR *src);
static VOID prsrcp(struct sndprc *prcs);
static VOID tryconn(struct sndprc *prcs);
static GBOOL inuse(CHAR *name);
static struct sndprc *fnduprc(INT unum);
static VOID smtcbk(struct dns *dnsptr);
static GBOOL smtcall(struct in_addr *inaddr,struct sndprc *prcs);
static VOID bgnsmt(struct sndprc *prcs);
static VOID smtrecv(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 smtsend(struct sndprc *prcs);
static VOID ret2snd(struct sndprc *prcs,CHAR *reason,INT msgno,...);
static VOID enatmo(struct sndprc *prcs);
static VOID distmo(struct sndprc *prcs);
static VOID wrtrinf(struct sndprc *prcs);
static VOID freprc(struct sndprc *prcs,GBOOL delfil);
static GBOOL fndfil(CHAR *tmpnam);
static VOID tskhdl(INT taskid);
static struct sndprc *gprcinf(INT taskid);
static VOID smtpmim(struct sndprc *prcs);
static GBOOL isipadr(CHAR *ip);

struct exporter smtexp={           /* SMTP 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           */
     smthlp,                       /*   get help message for this exporter */
     smtval,                       /*   is this a valid address vector     */
     smtasp,                       /*   path+file name for attachments     */
     smtsnd                        /*   send message vector                */
};

HMCVFILE smtmb=NULL;               /* file handle to GALSMTP.MCV           */
                                   /* --- options in GALSMTP.MCV --------- */
CHAR smtobd[GCMAXPTH];             /*   SMTP outbound work directory       */
static USHORT smttmo;              /*   SMTP transmission timeout          */
static INT smtnout;                /*   max number of processes (SMTP outs)*/
static LONG smtrsd;                /*   SMTP resend interval in secs       */
static LONG smtund;                /*   SMTP unable to snd intervl in secs */
static GBOOL smtaud;               /*   audit successful SMTP deliveries   */
static GBOOL smtaude;              /*   audit SMTP delivery errors         */
GBOOL smtihst;                     /*   include hostname in outgoing msgs  */
CHAR *smttmz;                      /*   local time zone                    */
static CHAR *smtsmrt;              /*   SMTP smarthost name or ip          */
GBOOL smtlog;                      /*   is send log currently active?      */
CHAR *smtlnam;                     /*   send log file name                 */

GBOOL usemgi;                      /* using MG/I instead of SMTP           */
GBOOL smtponl;                     /* SMTP exporter enabled?               */
CHAR smtfpx[PFXSIZ+1];             /* full prefix (including trailing ':') */
CHAR maildaem[]=MAILDAEM;          /* "From:" line for bounced e-mail      */

static CHAR scnpth[GCMAXPTH+4];    /* queue scan path (smtobd + *.*)       */
static CHAR smtobdp[GCMAXPTH];     /* outbound parcel directory            */
static struct ffblk smtfb;         /* file currently being processed       */
static CHAR *textbuf;              /* pointer to gen purpose text buffer   */
static GBOOL didInit=FALSE;        /* have we been initialized yet?        */

INT curtask = 0;
const INT notask = 100;

/*
 * retrieve the index of an entry in the sndprcs array, for logging
 */

#define prcidx(p)    ((INT)((p)-sndprcs))

VOID
init__galsmtp(VOID)                /* module init routine                  */
{
     CHAR *cp;
     INT i;

     if (didInit) {
          return;
     }
     didInit=TRUE;
     cp=msgscan("galgwi.msg","USEMGI");
     usemgi=access("galgwi.mdf",0) != -1 && cp != NULL && sameas(cp,"YES");
     if (usemgi) {
          shocst("SMTP NOT INITIALIZED","MG/I processing e-mail");
          return;
     }
     smtmb=opnmsg("galsmtp.mcv");
     stlcpy(smtfpx,spr("%s:",getmsg(SMTPFX)),PFXSIZ+1);
     smtihst=ynopt(SMTIHST);
     smttmz=stgopt(SMTTMZ);
     smtponl=ynopt(SMTPONL);
     if (smtponl) {
          init__tcpip();
          init__galme();
          setmbk(smtmb);
          textbuf=alczer(TXTLEN*2);
          stlcpy(smtexp.prefix,getmsg(SMTPFX),PFXSIZ);
          stlcpy(smtexp.name,getmsg(SMTNAM),EXPNSZ);
          stlcpy(smtexp.desc,getmsg(SMTDSC),EXPDSZ);
          stlcpy(smtexp.exmp,getmsg(SMTXMP),MAXADR);
          stlcpy(smtexp.wrtkey,getmsg(SMTKEY),KEYSIZ);
          smtexp.wrtchg=numopt(SMTCHG,-32767,32767);
          stlcpy(smtexp.attkey,getmsg(SMTATKY),KEYSIZ);
          smtexp.attchg=numopt(SMTATCH,-32767,32767);
          smtexp.apkchg=numopt(SMTPKCH,-32767,32767);
#ifdef GCDOS
          smtnout=numopt(SMTNOUT,1,8);
          ASSERT((LONG)(smtnout)*(LONG)(sizeof(struct sndprc)) < 65500L);
#else
          smtnout=numopt(SMTNOUT,1,32);
#endif // GCDOS
          sndprcs=(struct sndprc *)alczer(smtnout*sizeof(struct sndprc));
          for (i=0 ; i < smtnout ; i++) {
               sndprcs[i].taskid=sndprcs[i].sockfd=-1;
          }
          smtrsd=(LONG)numopt(SMTRSD,1,1440)*60L;
          smtund=(LONG)numopt(SMTUND,1,168)*3600L;
          smttmo=(USHORT)numopt(SMTTMO,30,1800);
          smtaud=ynopt(SMTAUD);
          smtaude=ynopt(SMTAUDE);
          smtsmrt=stgopt(SMTSMRT);
          smtlog=ynopt(SMTLOG);
          smtlnam=stgopt(SMTLNAM);
          stlcpy(smtobd,getmsg(SMTOBD),GCMAXPTH);
          stlcpy(smtobdp,spr("%s\\parcel",smtobd),GCMAXPTH);
          if (!fmdir(smtobd) || !fmdir(smtobdp)) {
               clsmsg(smtmb);
               smtmb=NULL;
               shocst("SMTP NOT INITIALIZED",
                      "Couldn't find/make outbound directory");
               return;
          }
          register_exp(&smtexp);
          fixpth(smtobd);
          fixpth(smtobdp);
          sprintf(scnpth,"%s"STAR,smtobd);
          if (smtlog) {
               shocst("SMTP SEND LOG ENABLED",
                      "All SMTP send transactions will be logged");
          }
          dclvda(IBUFSZ+OBUFSZ);
          setmem(&smtfb,sizeof(smtfb),0);
          rtkick(KICKINT,smtscn);
     }
     else {
          clsmsg(smtmb);
          smtmb=NULL;
     }
     ini_smtrcv();
     initAddrXlat();
}

VOID EXPORT
initwc__galsmtp(VOID)
{
     init__galsmtp();
}

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

     for (index=0 ; index < smtnout ; index++) {
          if (sndprcs[index].tskstt == TSKIDL) {
               if (fndfil(tmpnam)) {
                    ASSERT(sndprcs[index].taskid == -1);
                    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);
                    sndprcs[index].dnshstex.buf=sndprcs[index].hstbuf;
                    sndprcs[index].dnshstex.bufsiz=
                         sizeof(sndprcs[index].hstbuf);
                    sndprcs[index].dnshstex.maxofhst=MAXHSTS;
               }
               else {
                    break;
               }
          }
     }
     rtkick(KICKINT,smtscn);
}

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];
#if defined( EXTRA_LOGGING )
     CHAR tmpbuf[1024];
#endif
     INT nFirsts=0;

     oldnam[0]='\0';

#if defined( EXTRA_LOGGING )
     logsmt(notask,"Finding files",OUTGING);
#endif
     while (TRUE) {
          if (smtfb.ff_name[0] == '\0') {
               if (++nFirsts > 2) {
#if defined( EXTRA_LOGGING )
                    logsmt(notask,"fnd1st() called more than twice in fndfil().",OUTGING);
#endif
                    return(FALSE);
               }
               if (!fnd1st(&smtfb,scnpth,0)) {
#if defined( EXTRA_LOGGING )
                    logsmt(notask,"smtfb.ff_name[0] == NULL, no files found.",OUTGING);
#endif
                    return(FALSE);
               }
          }
          else if (!fndnxt(&smtfb)) {
#if defined( EXTRA_LOGGING )
               logsmt(notask,"fndnxt() failed",OUTGING);
#endif
               setmem(&smtfb,sizeof(smtfb),0);
               if (++nFirsts > 2) {
#if defined( EXTRA_LOGGING )
                    logsmt(notask,"fnd1st() called more than twice in fndfil().",OUTGING);
#endif
                    return(FALSE);
               }
               if (!fnd1st(&smtfb,scnpth,0)) {
#if defined( EXTRA_LOGGING )
                    logsmt(notask,"fnd1st() after fndnxt() failed.",OUTGING);
#endif
                    return(FALSE);
               }
          }
          if (oldnam[0] == '\0') {
#if defined( EXTRA_LOGGING )
               sprintf(tmpbuf,"oldnam[0] == NULL, setting to %s",smtfb.ff_name);
               logsmt(notask,tmpbuf,OUTGING);
#endif
               strcpy(oldnam,smtfb.ff_name);
          }
          else if (sameas(oldnam,smtfb.ff_name)) {
#if defined( EXTRA_LOGGING )
               sprintf(tmpbuf,"oldnam == smtfb.ff_name (%s)",oldnam);
               logsmt(notask,tmpbuf,OUTGING);
#endif
               return(FALSE);
          }
          sprintf(tmpnam,"%s%s",smtobd,smtfb.ff_name);
#if defined( EXTRA_LOGGING )
          sprintf(tmpbuf,"Setting tmpnam=%s",tmpnam);
          logsmt(notask,tmpbuf,OUTGING);
#endif
          if (!inuse(tmpnam)) {
#if defined( EXTRA_LOGGING )
               logsmt(notask,"tmpnam not in use, ok to use",OUTGING);
#endif
               return(TRUE);
          }
     }
}


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

     setmbk(smtmb);
     cp=stpans(getmsg(SMTHELP));
     rstmbk();
     return(cp);
}

static const CHAR *
smtasp(                            /* 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 {
#ifdef UNIX
          stlcpy(attpath,tempnam(smtobdp,NULL),GCMAXPTH);
#else
          stlcpy(attpath,smtobdp,GCMAXPTH);
          stlcat(attpath,tmpnam(NULL),GCMAXPTH);
#endif
          if (++ctr == TMP_MAX) {
               catastro("Too many temporary files in outbound attachment\n"
                        "directory: %s",smtobdp);
          }
     } while (access(attpath,0) == 0);
     return(attpath);
}

INT                                /*   returns GME error codes            */
smtsnd(                            /* send SMTP 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;
     CHAR *ptr;
     LONG filepos;
     CHAR tmpname[GCMAXPTH];
     CHAR * pTextBuf;
     INT rc,i;
     struct message * msg;
     struct message msgbuf;
     static struct minfo minfo;
     static struct rinfo rinfo;
     static CHAR smtpline[SMTLSZ];

     /* 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);
     }

	 // bug in win 2k. Internet Mail addressed to users on same system not processing.
	 if (samend(to,spr("@%s",domain))) {
		 i=strchr(msg->to,'@')-msg->to;
		 stlcpy(msg->to,msg->to,i+1);
		 stlcpy(msg->to,msg->to + strlen(smtexp.prefix)+1,i+1);
		 simpsnd(msg,textbuf,filatt);
		 return(GMEOK);
	 }

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

     /* save message */
     setmem(&minfo,sizeof(struct minfo),0);
     setmem(&rinfo,sizeof(struct rinfo),0);
     stlcpy(minfo.name,SMTNAME,QNMSIZ);
     minfo.qtime=minfo.lsttim=time(NULL);
     minfo.flags|=LOCSNDR;
     stlcpy(minfo.from,msg->from,MAXADR);
     stlcpy(rinfo.to,to,MAXADR);
     rinfo.flags&=~LOCRCPT;
     if (msg->flags&FILATT) {
          gmimid(minfo.mimid,msg->msgid);
          minfo.flags|=HASATT;
          stlcpy(minfo.attfil,filatt,GCMAXPTH);
          stlcpy(minfo.relnam,msg->attname,GCMAXFNM);
          strlwr(minfo.relnam);
     }
     if (fwrite(&minfo,1,sizeof(struct minfo),fp) != sizeof(struct minfo)) {
          sholog(notask,"SMTP WRITE ERROR","smtsnd(): Error writing message info");
          fclose(fp);
          unlink(tmpname);
          if (msg->flags&FILATT) {
               unlink(filatt);
          }
          return(GMEERR);
     }
     if (fwrite(&rinfo,1,sizeof(struct rinfo),fp) != sizeof(struct rinfo)) {
          sholog(notask,"SMTP WRITE ERROR","smtsnd(): Error writing recipient info");
          fclose(fp);
          unlink(tmpname);
          if (msg->flags&FILATT) {
               unlink(filatt);
          }
          return(GMEERR);
     }
     minfo.hdrpos=ftell(fp);
     if (inetinfo != NULL) {  /* message is auto-forwarded and we already  */
                              /* have an Internet header for it            */
          filepos=ftell(inetinfo->fp);
          fseek(inetinfo->fp,inetinfo->hdrpos,SEEK_SET);
          while (fgets(smtpline,SMTLSZ-4,inetinfo->fp) != NULL) {
               if (sameas(smtpline,"\r\n")) {
                    break;
               }
               fputs(smtpline,fp);
          }
          fseek(inetinfo->fp,filepos,SEEK_SET);
     }
     else {                   /* new message, generate a header            */
          /* message is being forwarded */
          if (*gmexinf() != '\0' && !sameas(minfo.from,gmexinf())) {
               cvtFrom(smtpline,gmexinf());
               fprintf(fp,"Resent-From: %s\r\n",smtpline);
               fprintf(fp,"Resent-Date: %s\r\n",cvtDate(now(),today()));
          }
          cvtFrom(smtpline,minfo.from);
          fprintf(fp,"From: %s\r\n",smtpline);
          fprintf(fp,"To: %s\r\n",rinfo.to);
          fprintf(fp,"Date: %s\r\n",cvtDate(msg->crtime,msg->crdate));
          fprintf(fp,"Subject: %s\r\n",msg->topic[0] == '\0' ? "<none>"
                                                             : msg->topic);
     }
     if (msg->flags&FILATT) {
          fprintf(fp,"MIME-Version: 1.0\r\n");
          fprintf(fp,"Content-Type: multipart/mixed; boundary=\"%s\"\r\n",
                  minfo.mimid);
     }
     minfo.bdypos=ftell(fp)+2;
     /* skip 2 bytes because next "\r\n" is a separator  */
     /* between header and body                          */
     if (msg->flags&FILATT) {
          setmbk(smtmb);
          ptr=getmsg(SMTPMWR);
          rstmbk();
          addlf(ptr);
          fprintf(fp,ptr);
          fprintf(fp,"\r\n--%s\r\n",minfo.mimid);
          fprintf(fp,"Content-Type: text/plain\r\n");
          fprintf(fp,"Content-Transfer-Encoding: 7bit\r\n");
     }
     addlf(pTextBuf);
     if (fwrite(pTextBuf,1,strlen(pTextBuf),fp) != strlen(pTextBuf)) {
          sholog(notask,"SMTP WRITE ERROR"
                ,"smtsnd(): Error writing message body");
          fclose(fp);
          unlink(tmpname);
          if (msg->flags&FILATT) {
               unlink(filatt);
          }
          return(GMEERR);
     }
     minfo.endpos=ftell(fp);
     minfo.rmtrcpt=1;
     fseek(fp,0L,SEEK_SET);
     if (fwrite(&minfo,1,sizeof(struct minfo),fp) != sizeof(struct minfo)) {
          sholog(notask,"SMTP WRITE ERROR","smtsnd(): Error rewriting message info");
          fclose(fp);
          unlink(tmpname);
          if (msg->flags&FILATT) {
               unlink(filatt);
          }
          return(GMEERR);
     }
     fclose(fp);
     return(GMEOK);
}

static VOID
tskhdl(                            /* SMTP send task handler               */
INT taskid)                        /*   send task ID                       */
{
     struct sndprc *prcs;          /* task process info                    */
     ULONG curtim;                 /* current btu ticker value (adjusted)  */
     ULONG prctim;                 /* process-started btu ticker value     */

     prcs=gprcinf(taskid);
     curtask=prcidx(prcs);
     curtim=(ULONG)btuTicker();
     prctim=(ULONG)prcs->sttime;

     /*
      *   correct for btuTicker() rolling over (approx) every 18 hours
      */
     if (curtim < prctim) {
          curtim += (ULONG)USHRT_MAX;
     }

     if (prcs->tskstt == TCPDRV
      && prcs->sttime != 0
      && (curtim-prctim) > smttmo) {
          distmo(prcs);
          if (prcs->sndstt == CONNING) {
               prcs->tskstt=TRYCONN;
          }
          else {
               if (prcs->sndstt == DIALADR
                || prcs->sndstt == DIALMX
                || prcs->sndstt == DIALCNAME) {
                    usrnum=prcs->usrnum;
                    dnsabt();
                    if (smtaude) {
                         sholog(prcidx(prcs),"SMTP SEND DNS TIMEOUT",
                                "DNS lookup timed out for %s",prcs->rinfo.to);
                    }
               }
               else if (smtaude) {
                    sholog(prcidx(prcs),"SMTP SEND CONNECTION TIMEOUT",
                           "Connection timed out for %s",prcs->rinfo.to);
               }
               wrtrinf(prcs);
          }
     }
     switch (prcs->tskstt) {
     case TSKIDL:
     case TCPDRV:
          break;
     case PRSHDR:
          prshdr(prcs);
          break;
     case PRSRCP:
          prsrcp(prcs);
          break;
     case TRYCONN:
          tryconn(prcs);
          break;
     case MIMECODE:
          smtpmim(prcs);
          break;
     case ENDTSK:
          if (prcs->minfo.rmtrcpt == 0) {
               freprc(prcs,TRUE);
          }
          else {
               freprc(prcs,FALSE);
          }
     }
     /* relies on initask() being called only from an rtkick() routine     */
     ASSERTM(prcs->taskid == taskid || prcs->taskid == -1,
             spr("Task ID corrupted %d -> %d",taskid,prcs->taskid));

}

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

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

static VOID
prshdr(                            /* process e-mail header                 */
struct sndprc *prcs)
{
     if (!isfile(prcs->msgfil)) {
          freprc(prcs,FALSE);
          return;
     }
     if ((prcs->fp=fopen(prcs->msgfil,FOPRWB)) == NULL) {
          sholog(prcidx(prcs),"SMTP SEND OPEN ERROR",
                 "prshdr(): Could not open file %s",strupr(prcs->msgfil));
          freprc(prcs,FALSE);
          return;
     }
     if (fread(&prcs->minfo,sizeof(struct minfo),1,prcs->fp) != 1) {
          sholog(prcidx(prcs),"SMTP SEND READ ERROR","prshdr(): Could not read message info");
          freprc(prcs,TRUE);
          return;
     }
     if (!sameas(prcs->minfo.name,SMTNAME)) {
          sholog(prcidx(prcs),"SMTP SEND READ ERROR","prshdr(): Invalid message info");
          freprc(prcs,TRUE);
          return;
     }
     if (prcs->minfo.qtime != prcs->minfo.lsttim
      && (LONG)(time(NULL)-prcs->minfo.lsttim) < smtrsd) {
          prcs->tskstt=ENDTSK;
     }
     else {
          prcs->minfo.lsttim=time(NULL);
          prcs->rcptpos=ftell(prcs->fp);
          if ((prcs->minfo.flags&HASATT)) {
               prcs->tskstt=MIMECODE;
               prcs->mimstt=START;
          }
          else {
               prcs->tskstt=PRSRCP;
          }
     }
}

static VOID
cvtFrom(                           /* convert From addr to internet format */
CHAR *dst,                         /*   destination buffer (must be MAXADR)*/
const CHAR *src)                   /*   source buffer                      */
{
     stlcpy(dst,src,MAXADR);
     if (!xltGME2Int(dst)) {
          stlcpy(dst,(*alsofusr)((CHAR *)src),MAXADR);
          stlcat(dst,"@",MAXADR);
          stlcat(dst,smtpHost(),MAXADR);
     }
}

static VOID
prsrcp(                            /* get next recipient information       */
struct sndprc *prcs)
{
     INT savunm;
     CHAR *delim;
     static CHAR tmpaddr[MAXADR];

#if defined( EXTRA_LOGGING )
     logsmt(prcidx(prcs),"Entering prsrcp()",OUTGING);
#endif
     if (prcs->minfo.hdrpos == prcs->rcptpos) {
          prcs->tskstt=ENDTSK;
#if defined( EXTRA_LOGGING )
          logsmt(prcidx(prcs),"prcs->minfo.hdrpos == prcs->rcptpos",OUTGING);
#endif
          return;
     }
     fseek(prcs->fp,prcs->rcptpos,SEEK_SET);
     if (fread(&prcs->rinfo,1,sizeof(struct rinfo),prcs->fp) !=
         sizeof(struct rinfo)) {
          sholog(prcidx(prcs),"SMTP SEND READ ERROR","prsrcp(): Could not read message info");
          freprc(prcs,TRUE);
          return;
     }
#if defined( EXTRA_LOGGING )
     logsmt(prcidx(prcs),spr("Working with %s",prcs->rinfo.to),OUTGING);
#endif
     if ((prcs->rinfo.flags&LOCRCPT) || (prcs->rinfo.flags&MSGSNT)) {
          prcs->rcptpos+=sizeof(struct rinfo);
#if defined( EXTRA_LOGGING )
          logsmt(prcidx(prcs),"prcs->rcptpos+=sizeof(struct rinfo)",OUTGING);
#endif
          return;
     }
     if ((LONG)(time(NULL)-prcs->minfo.qtime) > smtund) {
          prcs->rinfo.flags|=MSGSNT;
          wrtrinf(prcs);
          ret2snd(prcs,"Message expired in queue",SMTEXP,(INT)(smtund/3600L),
                  prcs->rinfo.to);
#if defined( EXTRA_LOGGING )
          logsmt(prcidx(prcs),"prcs->minfo.qtime > smtund",OUTGING);
#endif
          return;
     }
     stlcpy(tmpaddr,prcs->rinfo.to,MAXADR);
     if ((delim=strchr(tmpaddr,'@')) == NULL) {
          if ((delim=strchr(tmpaddr,'!')) == NULL) {
               prcs->rinfo.flags|=MSGSNT;
               wrtrinf(prcs);
               ret2snd(prcs,"Invalid To: Address",SMTEXP1,prcs->rinfo.to);
               sholog(prcidx(prcs),"SMTP SEND INVALID TO: ADDRESS",
                      "%s",tmpaddr);
               return;
          }
          else {
               *delim='\0';
               delim=tmpaddr;
          }
     }
     else {
          delim++;
     }
     prcs->currip=prcs->currname=0;
     if (smtsmrt[0] == '\0' && isipadr(delim)) {
          delim++;
          delim[strlen(delim)-1]='\0';
          prcs->smtstt=WAITRDY;
#ifdef UNIX
          prcs->dns.inaddr[0].s_addr=inet_addr(delim);
#else
          prcs->dns.inaddr[0].S_un.S_addr=inet_addr(delim);
#endif
          prcs->dns.numaddr=1;
          prcs->dnshstex.numofhst=0;
          prcs->tskstt=TRYCONN;
#if defined( EXTRA_LOGGING )
          logsmt(prcidx(prcs),"Setting TRYCONN in prsrcp()",OUTGING);
#endif
     }
     else {
          savunm=usrnum;
          if ((usrnum=dnsfvc()) == -1) {
               usrnum=savunm;
               wrtrinf(prcs);
#if defined( EXTRA_LOGGING )
               logsmt(prcidx(prcs),"No outgoing usrnums available",OUTGING);
#endif
               return;
          }
          distmo(prcs);
          if (smtsmrt[0] != '\0') {
               stzcpy(prcs->dns.name,smtsmrt,DNSNSZ);
          }
          else {
               stzcpy(prcs->dns.name,delim,DNSNSZ);
          }
          prcs->sockfd=-1;
          prcs->sndstt=DIALCNAME;
          prcs->smtstt=WAITRDY;
          prcs->usrnum=usrnum;
          prcs->tskstt=TCPDRV;
          prcs->dns.numaddr=MAXIPADD;
          prcs->dns.callbk=smtcbk;
#if defined( EXTRA_LOGGING )
          logsmt(prcidx(prcs),"dns2nat() prsrcp()",OUTGING);
#endif
          dnsn2at(&prcs->dns,TYPECNAME);
          usrnum=savunm;
     }
}

static VOID
smtcbk(                            /* SMTP address lookup callback vector  */
struct dns *dnsptr)
{
     struct sndprc *prcs;
     CHAR *namptr;
     INT namlen;

     if ((prcs=fnduprc(usrnum)) == NULL) {
          dnsfre(usrnum);
          usrnum=-1;
          sholog(notask,"SMTP SEND DNS FAILURE","DNS callback too late");
          return;
     }
#if defined( EXTRA_LOGGING )
     logsmt(prcidx(prcs),"smtcbk() called",OUTGING);
#endif

     if (dnsptr->status == DNSABT) {
#if defined( EXTRA_LOGGING )
          logsmt(prcidx(prcs),"smtcbk() result: DNSABT",OUTGING);
#endif
          dnsfre(usrnum);
          usrnum=-1;
          prcs->tskstt=ENDTSK;
          return;
     }
     if (dnsptr->status >= 0) {
#if defined( EXTRA_LOGGING )
          logsmt(prcidx(prcs),"smtcbk() - dnsptr->status >= 0",OUTGING);
#endif
          if (prcs->sndstt == DIALCNAME) {
#if defined( EXTRA_LOGGING )
               logsmt(prcidx(prcs),
                      spr("smtcbk() calling dnsn2ex(TYPEMX) with %s",
                          dnsptr->name),OUTGING);
#endif
               prcs->sndstt=DIALMX;
               dnsn2ex(dnsptr,TYPEMX,&prcs->dnshstex);
          }
          else {
#if defined( EXTRA_LOGGING )
               logsmt(prcidx(prcs),"smtcbk() - TRYCONN",OUTGING);
#endif
               prcs->tskstt=TRYCONN;
          }
     }
     else {
#if defined( EXTRA_LOGGING )
          logsmt(prcidx(prcs),"smtcbk() - dnsptr->status < 0",OUTGING);
#endif
          if (smtsmrt[0] != '\0') {
               namptr=smtsmrt;
               namlen=strlen(namptr);
          }
          else if ((namptr=strchr(prcs->rinfo.to,'@')) == NULL) {
               if ((namptr=strchr(prcs->rinfo.to,'!')) == NULL) {
                    namptr=prcs->rinfo.to;       /* impossible contingency */
                    namlen=strlen(namptr);
               }
               else {
                    namlen=(INT)(namptr-prcs->rinfo.to);
                    namptr=prcs->rinfo.to;
               }
          }
          else {
               namptr++;
               namlen=strlen(namptr);
          }
          stlcpy(dnsptr->name,namptr,namlen+1);
          if (prcs->sndstt == DIALCNAME) {
#if defined( EXTRA_LOGGING )
               logsmt(prcidx(prcs),
                      spr("smtcbk() calling dnsn2ex(TYPEMX) with %s",
                          dnsptr->name),OUTGING);
#endif
               prcs->sndstt=DIALMX;
               dnsn2ex(dnsptr,TYPEMX,&prcs->dnshstex);
          }
          else if (prcs->sndstt == DIALMX) {
#if defined( EXTRA_LOGGING )
               logsmt(prcidx(prcs),
                      spr("smtcbk() calling dnsn2at(TYPEA) with %s",
                          dnsptr->name),OUTGING);
#endif
               prcs->sndstt=DIALADR;
               dnsn2at(dnsptr,TYPEA);
          }
          else {
               if (smtaude) {
                    sholog(prcidx(prcs),"SMTP SEND INVALID HOST",
                      "%s (%s)",dnsptr->name,dnsemg);
               }
               if (dnsptr->status == DNSUNK
                || dnsptr->status == DNSNOX
                || dnsptr->status == DNSSTX) {
#if defined( EXTRA_LOGGING )
                    logsmt(prcidx(prcs),"smtcbk() calling ret2snd()",OUTGING);
#endif
                    ret2snd(prcs,dnsemg,SMTDNE,dnsptr->name,prcs->rinfo.to);
                    prcs->rinfo.flags|=MSGSNT;
               }
#if defined( EXTRA_LOGGING )
               logsmt(prcidx(prcs),"smtcbk() calling wrtrinf()",OUTGING);
#endif
               wrtrinf(prcs);
          }
     }
     usrnum=-1;
}

static VOID
tryconn(                           /* try to connect to IP address         */
struct sndprc *prcs)
{
     INT savunm;

#if defined( EXTRA_LOGGING )
     logsmt(prcidx(prcs),
            spr("tryconn(), numaddr=%d, currip=%d, numofhst=%d, currname=%d",
                prcs->dns.numaddr,prcs->currip,prcs->dnshstex.numofhst,
                prcs->currname),OUTGING);
#endif

     if (prcs->rinfo.flags&MSGSNT) {
          /* this happens when we failed to send "QUIT" or did not get a   */
          /* "221" back.  At that point, e-mail was already accepted by    */
          /* the receiver, so just go to the next recipient.               */
          wrtrinf(prcs);
          return;
     }
     distmo(prcs);
     if (prcs->sockfd >= 0) {
          clsskt(prcs->sockfd);
          prcs->sockfd=-1;
     }
     if (prcs->currip < prcs->dns.numaddr) {
          if (smtcall(&prcs->dns.inaddr[prcs->currip++],prcs)) {
#if defined( EXTRA_LOGGING )
               logsmt(prcidx(prcs),"Setting up bgnsmt() callback in tryconn()",
                      OUTGING);
#endif
               sktnfy(TNFCONN,prcs->sockfd,bgnsmt,prcs,-1);
               prcs->sndstt=CONNING;
               prcs->tskstt=TCPDRV;
               enatmo(prcs);
          }
     }
     else if (prcs->currname < prcs->dnshstex.numofhst) {
          stzcpy(prcs->dns.name,
                 prcs->dnshstex.host[prcs->currname++].hostname,DNSNSZ);
          prcs->currip=0;
          prcs->sndstt=DIALADR;
          prcs->tskstt=TCPDRV;
          savunm=usrnum;
          usrnum=prcs->usrnum;
#if defined( EXTRA_LOGGING )
          logsmt(prcidx(prcs),spr("tryconn() calling dnsn2at(TYPEA) with %s",
                                  prcs->dns.name),OUTGING);
#endif
          dnsn2at(&prcs->dns,TYPEA);
          usrnum=savunm;
     }
     else {
          if (smtaude) {
               sholog(prcidx(prcs),prcs->logmsg0,"%s",prcs->logmsg1);
          }
          wrtrinf(prcs);
     }
}

static GBOOL
smtcall(                           /* try to connect to SMTP server        */
struct in_addr *inaddr,
struct sndprc *prcs)
{
     INT rc;

#if defined( EXTRA_LOGGING )
     logsmt(prcidx(prcs),spr("trying to dial into server %s",
                             inet_ntoa(*inaddr)),OUTGING);
#endif
     strcpy(prcs->logmsg0,"SMTP SEND FAILED");
     strcpy(prcs->logmsg1,"(Dial) Failed to connect to ");
     stlcat(prcs->logmsg1,inet_ntoa(*inaddr),sizeof(prcs->logmsg1));
     rc=tcpdial(*inaddr,htons(SMTPORT),0,&prcs->sockfd);
     switch (rc) {
     case DLCNOW:
     case DLCING:
#if defined( EXTRA_LOGGING )
          logsmt(prcidx(prcs),
                 spr("tcpdial() returned %s",rc == DLCNOW ? "DLCNOW" : "DLCING"),
                 OUTGING);
#endif
          return(TRUE);
     default:
          return(FALSE);
     }
}

static VOID
bgnsmt(                            /* connection has begun                */
struct sndprc *prcs)               /*  process handling connection        */
{
#if defined( EXTRA_LOGGING )
     logsmt(prcidx(prcs),"Connection has begun",INCMING);
#endif
     enatmo(prcs);
     prcs->sndstt=TRANSFR;
     prcs->smtstt=WAITRDY;
     if (prcs->sockfd != -1) {
          sktcnc(TNFCONN,prcs->sockfd);
#if defined( EXTRA_LOGGING )
          logsmt(prcidx(prcs),"sktnfy(smtrecv) called",INCMING);
#endif
          sktnfy(TNFRECV,prcs->sockfd,smtrecv,prcs,-1);
     }
}

static VOID
smtrecv(                           /* SMTP client receive-from-server      */
struct sndprc *prcs)               /*   process handling connection        */
{
     INT nroom,nactual;
     CHAR *ptr,fname[GCMAXFNM];

     nroom=RCVBSIZ-prcs->rcvcnt-1;
#if defined( EXTRA_LOGGING )
     logsmt(prcidx(prcs),spr("smtrecv(), nroom=%d",nroom),INCMING);
#endif
     if (nroom <= 0) {
#if defined( EXTRA_LOGGING )
          sholog(prcidx(prcs),"nroom <= problem","nroom <=0 conditions, buffer follows:");
#endif
          nactual=(INT)prcs->rcvcnt;
          while (nactual > 0) {
#if defined( EXTRA_LOGGING )
               logsmt(prcidx(prcs),prcs->rcvbuf,INCMING);
#endif
               setmem(prcs->rcvbuf,RCVBSIZ,0);
               if ((nactual=recv(prcs->sockfd,prcs->rcvbuf,RCVBSIZ-1,0)) > 0) {
                    prcs->rcvbuf[nactual]='\0';
               }
          }
#if defined( EXTRA_LOGGING )
          sholog(prcidx(prcs),"nroom <=0 problem","nroom <= 0 conditions finished.");
#endif
          //catastro("SMTRECV(): nroom <= 0, Everything logged");
          prcs->rcvcnt=0;
          nroom=RCVBSIZ-prcs->rcvcnt-1;
     }
     if ((nactual=recv(prcs->sockfd,prcs->rcvbuf+prcs->rcvcnt,nroom,0)) < 0) {
          if (prcs->sockfd != -1) {
               clsskt(prcs->sockfd);
               prcs->sockfd=-1;
          }
          strcpy(prcs->logmsg0,"SMTP SEND FAILED");
          sprintf(prcs->logmsg1,
                  "(Recv) %d,%d - Error: %d - File: %s - To: ",
                  prcs->sndstt,prcs->smtstt,tcpip_errno,
                  fileparts(GCPART_FNAM,prcs->msgfil,fname,GCMAXFNM));
          stlcat(prcs->logmsg1,prcs->rinfo.to,sizeof(prcs->logmsg1));
          prcs->tskstt=TRYCONN;
     }
     else if (nactual == 0) {
          if (prcs->sockfd != -1) {
               clsskt(prcs->sockfd);
               prcs->sockfd=-1;
          }
          strcpy(prcs->logmsg0,"SMTP SEND FAILED");
          strcpy(prcs->logmsg1,"(Recv) SMTP server closed connection");
          prcs->tskstt=TRYCONN;
     }
     else {
          prcs->byttfc+=nactual;
          prcs->rcvcnt+=nactual;
          prcs->rcvcnt=memstp(prcs->rcvbuf,prcs->rcvcnt,'\0');
          prcs->rcvbuf[prcs->rcvcnt]='\0';
          while ((ptr=strstr(prcs->rcvbuf,"\r\n")) != NULL) {
               *ptr++='\0';
               *ptr++='\0';
               if (!prclin(prcs)) {
                    prcs->rcvbuf[0]='\0';
                    prcs->rcvcnt=0;
                    break;
               }
               strcpy(prcs->rcvbuf,ptr);
               prcs->rcvcnt=strlen(prcs->rcvbuf);
          }
#if defined( EXTRA_LOGGING )
          logsmt(prcidx(prcs),"Saving Buffer, continuing",INCMING);
#endif
     }
}

static VOID
smtsend(                           /* try to send data in buffer to server */
struct sndprc *prcs)               /*   SMTP client process info           */
{
     CHAR fname[GCMAXFNM];

#if defined( EXTRA_LOGGING )
     logsmt(prcidx(prcs),"smtsend() called",OUTGING);
#endif

     switch (sndmgr(prcs->sndbuf,&prcs->sndcnt,prcs->sockfd)) {
     case -1:
          if (prcs->sockfd != -1) {
               clsskt(prcs->sockfd);
               prcs->sockfd=-1;
          }
          strcpy(prcs->logmsg0,"SMTP SEND FAILED");
          sprintf(prcs->logmsg1,
                  "(Send) %d,%d - Error: %d - File: %s - To: ",
                  prcs->sndstt,prcs->smtstt,tcpip_errno,
                  fileparts(GCPART_FNAM,prcs->msgfil,fname,GCMAXFNM));
          stlcat(prcs->logmsg1,prcs->rinfo.to,sizeof(prcs->logmsg1));
          prcs->tskstt=TRYCONN;
          break;
     case 0:
          switch (prcs->smtstt) {
          case PRCTEXT:
          case FINSHUP:
               prclin(prcs);
               break;
          default:
               sktcnc(TNFSEND,prcs->sockfd);
               break;
          }
          break;
     case 1:
          break;
     }
     prcs->byttfc+=sndact;
}

static GBOOL                       /*   return FALSE if done for recipient */
prclin(                            /* Process a line from the SMTP server  */
struct sndprc *prcs)
{
     INT lines;
     GBOOL retval=TRUE;
     LONG bdiff,len;
     static CHAR tmpbuf[SMTLSZ];

     enatmo(prcs);
     if (strlen(skptwht(prcs->rcvbuf)) > 0) {
          logsmt(prcidx(prcs),prcs->rcvbuf,INCMING);
     }
     if (anytime(prcs)
      || (strlen(prcs->rcvbuf) >= 4 && prcs->rcvbuf[3] == '-')) {
          return(retval);
     }
     switch (prcs->smtstt) {
     case WAITRDY:
          if (sameto("220",prcs->rcvbuf)) {
               sndsvr(prcs,"HELO %s\r\n",hstdom);
               prcs->smtstt=SNTHELO;
          }
          else {
               hdlbcmd(prcs);
          }
          break;
     case SNTHELO:
          if (sameto("250",prcs->rcvbuf)) {
               if (prcs->minfo.flags&LOCSNDR) {
                    cvtFrom(tmpbuf,prcs->minfo.from);
               }
               else {
                    stlcpy(tmpbuf,prcs->minfo.from,MAXADR);
               }
               sndsvr(prcs,"MAIL FROM:<%s>\r\n",tmpbuf);
               prcs->smtstt=SNTFROM;
          }
          else {
               hdlbcmd(prcs);
          }
          break;
     case SNTFROM:
          if (sameto("250",prcs->rcvbuf)) {
               sndsvr(prcs,"RCPT TO:<%s>\r\n",prcs->rinfo.to);
               prcs->smtstt=SNTRCPT;
          }
          else {
               hdlbcmd(prcs);
          }
          break;
     case SNTRCPT:
          if (sameto("250",prcs->rcvbuf) || sameto("251",prcs->rcvbuf)) {
               sndsvr(prcs,"DATA\r\n");
               prcs->smtstt=SNTDATA;
          }
          else {
               hdlbcmd(prcs);
          }
          break;
     case SNTDATA:
          if (sameto("354",prcs->rcvbuf)) {
               prcs->bsofar=0UL;
               prcs->smtstt=PRCTEXT;
               fseek(prcs->fp,prcs->minfo.hdrpos,SEEK_SET);
               sktnfy(TNFSEND,prcs->sockfd,smtsend,prcs,-1);
          }
          else {
               hdlbcmd(prcs);
          }
          break;
     case PRCTEXT:
          if (prcs->rcvcnt > 0) {
               hdlbcmd(prcs);
               break;
          }
          lines=NUMLINS;
          while (lines-- > 0 && (SNDBSIZ-prcs->sndcnt > SMTLSZ-4)) {
               if (fgets(tmpbuf,SMTLSZ-4,prcs->fp) == NULL) {
                    prcs->smtstt=FINSHUP;
                    break;
               }
               len=(LONG)strlen(tmpbuf);
               prcs->bsofar+=len;
               /* if get any stuff below 'endpos' mark, cut it off */
               bdiff=prcs->bsofar-(prcs->minfo.endpos-prcs->minfo.hdrpos+1);
               if (bdiff > 0) {
                    if (bdiff <= len) {  /* must be always true */
                         tmpbuf[(UINT)(len-bdiff)]='\0';
                    }
               }
               if (tmpbuf[0] == '.') {
                    prcs->bsofar++;
                    sndsvr(prcs,".%s",tmpbuf);
               }
               else {
                    sndsvr(prcs,"%s",tmpbuf);
               }
               if (bdiff > 0) {
                    prcs->smtstt=FINSHUP;
                    break;
               }
          }
          break;
     case FINSHUP:
          if (prcs->rcvcnt > 0) {
               hdlbcmd(prcs);
               break;
          }
          sndsvr(prcs,"\r\n.\r\n");
          prcs->smtstt=SENTDOT;
          sktnfy(TNFRECV,prcs->sockfd,smtrecv,prcs,-1);
          break;
     case SENTDOT:
          if (sameto("250",prcs->rcvbuf)) {
               if (smtaud) {
                    sholog(prcidx(prcs),"SMTP SEND MAIL DELIVERED",
                           "To: %s From: %s",prcs->rinfo.to,prcs->minfo.from);
               }
               prcs->rinfo.flags|=MSGSNT;
               sndsvr(prcs,"QUIT\r\n");
               prcs->smtstt=CLSCONN;
          }
          else {
               hdlbcmd(prcs);
          }
          break;
     case CLSCONN:
          wrtrinf(prcs);
          retval=FALSE;
          break;
     }
     return(retval);
}

static GBOOL
anytime(                           /* handle responses that occur anytime  */
struct sndprc *prcs)
{
     if (sameto("211",prcs->rcvbuf) || sameto("214",prcs->rcvbuf)) {
#if defined( EXTRA_LOGGING )
          logsmt(prcidx(prcs),"anytime() invoked",INCMING);
#endif
          return(TRUE);
     }
     return(FALSE);
}

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

#if defined( EXTRA_LOGGING )
     logsmt(prcidx(prcs),"hdlbcmd()",INCMING);
#endif

     if (smtaude) {
          sholog(prcidx(prcs),"SMTP SEND CLIENT DISCONNECT",
                 "%d %.*s (%s)",prcs->smtstt,SMTLSZ,prcs->rcvbuf,
                 fileparts(GCPART_FNAM,prcs->msgfil,fname,GCMAXFNM));
     }
     if (prcs->rcvbuf[0] == '5') {
          ret2snd(prcs,SMTPDERR,SMTRERR,prcs->rcvbuf,prcs->rinfo.to);
          prcs->rinfo.flags|=MSGSNT;
     }
     sndsvr(prcs,"QUIT\r\n");
     prcs->smtstt=CLSCONN;
}

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

     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,smtsend,prcs,-1);
     if (prcs->smtstt != SNTDATA && prcs->smtstt != PRCTEXT) {
          logsmt(prcidx(prcs),tmpbuf,OUTGING);
     }
     return(TRUE);
}

static VOID
ret2snd(                           /* return a message to sender           */
struct sndprc *prcs,               /*   message/recipient information      */
CHAR *reason,                      /*   reason for return                  */
INT msgno,
...)
{
     CHAR *tmptxt,*bdyptr;
     struct message *tmpmsg;
     INT nread,len,room;
     va_list vlst;

     setmbk(smtmb);
     tmpmsg=(struct message *)vdatmp;
     tmptxt=((CHAR *)vdatmp)+sizeof(struct message);
     va_start(vlst,msgno);
     vsprintf(tmptxt,xlttxv(stpans(rawmsg(msgno)),mxmssz),vlst);
     va_end(vlst);
     bdyptr=tmptxt+strlen(tmptxt);
     room=min((vdasiz-sizeof(struct message)),txtlen());
     room-=strlen(tmptxt)+16; /* 16 just leaves some extra room */
     if (room < 0) {
          room=0;
     }
     setmem(tmpmsg,sizeof(struct message),0);
     sprintf(tmpmsg->from,"%s%s@%s",smtfpx,maildaem,smtpHost());
     /* if e-mail was from MAILER-DAEMON or from nobody          */
     /* just submit it to our Sysop                              */
     if (prcs->minfo.from[0] == '\0'
      || sameto(maildaem,crpstr(prcs->minfo.from,':'))) {
          stlcpy(tmpmsg->to,smtdpuid,MAXADR);
     }
     else {
          stlcpy(tmpmsg->to,prcs->minfo.from,MAXADR);
          if (!(prcs->minfo.flags&LOCSNDR)) {
               if (!xltInt2GME(tmpmsg->to,NULL)) {
                    stlcpy(tmpmsg->to,smtfpx,MAXADR);
                    stlcat(tmpmsg->to,prcs->minfo.from,MAXADR);
               }
          }
     }
     stlcpy(tmpmsg->topic,reason,TPCSIZ);
     fseek(prcs->fp,prcs->minfo.hdrpos,SEEK_SET);
     len=(INT)min(prcs->minfo.endpos-prcs->minfo.hdrpos,room);
     if ((nread=fread(bdyptr,1,len,prcs->fp)) < 0) {
          nread=0;
     }
     bdyptr[nread]='\0';
     strstp(bdyptr,'\n');
     simpsnd(tmpmsg,tmptxt,NULL);
     rstmbk();
}

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

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

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

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

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

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
wrtrinf(                           /* update rcpt info & move to next rcpt */
struct sndprc *prcs)
{
     if (prcs->usrnum != 0) {
          dnsfre(prcs->usrnum);
          prcs->usrnum=0;
     }
     if (prcs->rinfo.flags&MSGSNT) {
          prcs->minfo.rmtrcpt--;
     }
     if (prcs->fp != NULL) {
          fseek(prcs->fp,prcs->rcptpos,SEEK_SET);
          if (fwrite(&prcs->rinfo,1,sizeof(struct rinfo),prcs->fp) !=
                                                      sizeof(struct rinfo)) {
               sholog(prcidx(prcs),"SMTP SEND WRITE ERROR","Error updating recipient info");
          }
     }
     prcs->rcptpos+=sizeof(struct rinfo);
     if (prcs->sockfd >= 0) {
          clsskt(prcs->sockfd);
          prcs->sockfd=-1;
     }
     prcs->sndcnt=0;
     prcs->rcvcnt=0;
     prcs->tskstt=PRSRCP;
}

static VOID
smtpmim(                           /* MIME encode a file attachment        */
struct sndprc *prcs)
{
     INT i,nread;
     INT mimipg;
     CHAR fname[GCMAXPTH];

     setmbk(smtmb);
     switch (prcs->mimstt) {
     case START:
          if ((prcs->mimfp=fopen(prcs->minfo.attfil,FOPRB)) == NULL) {
               shocst("SMTP SEND OPEN FILE ERROR",
                      "smtpmim(START): Could not open attachment file");
               prcs->mimstt=WRTBAD;
          }
          else {
               prcs->mimstt=CHK4BIN;
          }
          break;
     case CHK4BIN:
          if ((nread=fread(vdatmp,1,vdasiz,prcs->mimfp)) < 0
           || ferror(prcs->mimfp)) {
               shocst("SMTP SEND READ FILE ERROR",
                      "smtpmim(CHK4BIN): Unable to read attachment file");
               prcs->mimstt=WRTBAD;
               break;
          }
          else {
               for (i=0 ; i < nread ; i++) {
                    if (vdatmp[i] > 126
                     || (vdatmp[i] < 32
                     && (vdatmp[i] != TAB
                     && vdatmp[i] != 0x0D
                     && vdatmp[i] != 0x0A))) {
                         fseek(prcs->mimfp,0L,SEEK_SET);
                         fseek(prcs->fp,prcs->minfo.endpos,SEEK_SET);
                         prcs->mimstt=WRTHDR;
                         prcs->binatt=TRUE;
                         break;
                    }
               }
               if (nread < vdasiz && !prcs->binatt) {
                    fseek(prcs->mimfp,0L,SEEK_SET);
                    fseek(prcs->fp,prcs->minfo.endpos,SEEK_SET);
                    prcs->mimstt=WRTHDR;
                    prcs->binatt=FALSE;
               }
          }
          break;
     case WRTHDR:
          clrprf();
          sprintf(prfbuf,xlttxv(stpans(getasc(prcs->binatt ? BININC1 : ASCINC1)),
                                mxmssz),
                  prcs->minfo.mimid,
                  gmimctyp(fileparts(GCPART_EXTN,prcs->minfo.relnam,fname,
                           GCMAXPTH)),prcs->minfo.relnam,prcs->minfo.relnam);
          fwrite(prfbuf,strlen(prfbuf),1,prcs->fp);
          clrprf();
          prcs->mimstt=prcs->binatt ? MIMECD : CPYATT;
          break;
     case WRTBAD:
          prfmsg(BADATT);
          addlf(stpans(prfbuf));
          fseek(prcs->fp,prcs->minfo.endpos,SEEK_SET);
          fwrite(prfbuf,strlen(prfbuf),1,prcs->fp);
          clrprf();
          prcs->mimstt=MSGRDY;
          break;
     case CPYATT:
          if ((nread=fread(vdatmp,1,vdasiz,prcs->mimfp)) < 0
           || ferror(prcs->mimfp)) {
               shocst("SMTP SEND READ FILE ERROR",
                      "smtpmim(CPYATT): Unable to read attachment");
               prcs->mimstt=WRTBAD;
          }
          else {
               if (fwrite(vdatmp,1,nread,prcs->fp) != nread) {
                    shocst("SMTP SEND WRITE FILE ERROR",
                           "smtpmim(CPYATT): Unable to copy attachment");
                    prcs->mimstt=WRTBAD;
               }
               else if (nread != vdasiz) {
                    prcs->mimstt=MSGRDY;
               }
          }
          break;
     case MIMECD:
          curtask = prcs->taskid;  // for mimecode logging
          mimipg=mimecode(prcs->mimfp,prcs->fp,"\r\n",2);
          if (ferror(prcs->fp)) {
               shocst("SMTP SEND WRITE FILE ERROR",
                      "smtpmim(MIMECD): Unable to copy attachment");
               prcs->mimstt=WRTBAD;
          }
          else if (!mimipg) {
               prcs->mimstt=MSGRDY;
          }
          break;
     case MSGRDY:
          fprintf(prcs->fp,"\r\n--%s--\r\n",prcs->minfo.mimid);
          if (prcs->mimfp != NULL) {
               fclose(prcs->mimfp);
               prcs->mimfp=NULL;
          }
          unlink(prcs->minfo.attfil);
          prcs->minfo.flags&=~HASATT;
          prcs->minfo.endpos=ftell(prcs->fp);
          setmem(prcs->minfo.attfil,sizeof(prcs->minfo.attfil),0);
          setmem(prcs->minfo.relnam,sizeof(prcs->minfo.relnam),0);
          fseek(prcs->fp,prcs->rcptpos,SEEK_SET);
          prcs->tskstt=PRSRCP;
          break;
     }
     rstmbk();
}

#define TCPLEN 20   /* "[199.200.200.200]" */

static GBOOL
isipadr(                           /* is string a valid IP adress          */
CHAR *addr)
{
     INT i,j,len;
     CHAR *ptr,*secptr,savadr[TCPLEN];

     stzcpy(savadr,addr,TCPLEN);
     len=strlen(savadr);
     if (len < 9) {                /* "[1.1.1.1]" */
          return(FALSE);
     }
     if (savadr[0] != '[' || savadr[len-1] != ']') {
          return(FALSE);
     }
     savadr[len-1]='\0';
     for (i=0,ptr=savadr+1 ; ptr != NULL ; i++,ptr=secptr) {
          if ((secptr=strchr(ptr,'.')) != NULL) {
               *secptr++='\0';
          }
          if ((len=strlen(ptr)) > 3 || len == 0) {
               return(FALSE);
          }
          for (j=0 ; j < len ; j++) {
               if (!isdigit(ptr[j])) {
                    return(FALSE);
               }
          }
          if (atoi(ptr) > 255) {
               return(FALSE);
          }
     }
     if (i == 4) {
          return(TRUE);
     }
     return(FALSE);
}
