/***************************************************************************
 *                                                                         *
 *   SMTPUTIL.C                                                            *
 *                                                                         *
 *   Copyright (c) 1995-1997 Galacticomm, Inc.  All rights reserved.       *
 *                                                                         *
 *   Utility functions related to SMTP.                                    *
 *                                                                         *
 *                                6/5/95 - Bert Love,                      *
 *                                         Scott Brinker,                  *
 *                                         Charles Dunn &                  *
 *                                         Mahesh Neelakanta               *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "majorbbs.h"
#include "phasedbg.h"
#include "gme.h"
#include "tcpip.h"
#include "dns.h"
#include "alias.h"
#include "smtp.h"
#include "smtpexp.h"
#include "smnnhook.h"

#define FILREV "$Revision: 30 $"

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

struct iprange {                   /* IP range tracking structure          */
     ULONG low;                    /*   low end of range                   */
     ULONG high;                   /*   high end of range                  */
};

struct timestamp {                 /* time stamp structure                 */
     USHORT date;                  /*   DOS packed date                    */
     USHORT time;                  /*   DOS packed time                    */
};

VOID iprCheckFile(const CHAR *fileName,struct iprange **ppRange,INT *pNumber,
                  struct timestamp *pStamp);
GBOOL iprCheckIP(struct iprange *rangeArr,INT nRanges,ULONG ip);
GBOOL iprReadFile(const CHAR *fileName,struct iprange **ppRange,INT *pNumber);
GBOOL iprReadLine(CHAR *buf,size_t bufSiz,FILE *fp);
GBOOL iprProcLine(CHAR *rangeLine,struct iprange *rangeBuf);
GBOOL iprDecodeIP(ULONG *pIP,const CHAR *ipStr);

extern int curtask;

const CHAR b64tab[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
                    "0123456789+/";

INT hint2gme;                      /* handle for internet->GME translators */
INT hgme2int;                      /* handle for GME->internet translators */
CHAR *iprLocalFile=NULL;           /* file w/list of local IP ranges       */
struct iprange *iprLocal=NULL;     /* array of local IP ranges             */
INT iprLocalNum=0;                 /* number of local IP ranges in array   */
struct timestamp iprLocalStamp={0,0}; /* time local IP range file loaded   */
CHAR *iprBlockFile=NULL;           /* file w/list of blocked IP ranges     */
struct iprange *iprBlock=NULL;     /* array of blocked IP ranges           */
INT iprBlockNum=0;                 /* number of blocked IP ranges in array */
struct timestamp iprBlockStamp={0,0}; /* time blocked IP range file loaded */

VOID
initAddrXlat(VOID)                 /* initialize address translation util  */
{
     hint2gme=newarr(16,sizeof(addrXlatFunc));
     hgme2int=newarr(16,sizeof(addrXlatFunc));
}

VOID
hookAddrXlat(                      /* hook into address translation utility*/
INT xltType,                       /*   type of hook                       */
addrXlatFunc hookFunc)             /*   translation function               */
{
     ASSERT(xltType == XINT2GME || xltType == XGME2INT);
     ASSERT(hookFunc != NULL);
     switch (xltType) {
     case XINT2GME:
          add2arr(hint2gme,&hookFunc);
          break;
     case XGME2INT:
          add2arr(hgme2int,&hookFunc);
          break;
     }
}

static GBOOL                       /*   returns TRUE if did translation    */
xltHooks(                          /* call translation hooks               */
INT hdl,                           /*   handle to array of hooks           */
CHAR *adrbuf,                      /*   buffer containing address to xlate */
const CHAR *fromadr)               /*   addr mail is from (for validation) */
{
     INT i,n;
     addrXlatFunc xltrou;

     n=ninarr(hdl);
     for (i=0 ; i < n ; i++) {
          xltrou=*((addrXlatFunc *)arrelem(hdl,i));
          BEG_PHASE("IntAddrXlat",xltrou);
          if ((*xltrou)(adrbuf,fromadr)) {
               return(TRUE);
          }
     }
     return(FALSE);
}

INT                                /*   returns address translation code   */
xltInt2GME(                        /* translate internet addr to GME format*/
CHAR *adrbuf,                      /*   buffer containing address to xlate */
const CHAR *fromadr)               /*   addr mail is from (for validation) */
{                                  /*   (adrbuf size must be MAXADR)       */
     CHAR *userid;
     GBOOL locflg;
     struct addrinf addrinf;

     if (*adrbuf == '\0') {
          return(BADADDR);
     }
     parseIntAddr(adrbuf,&addrinf);
     locflg=(addrinf.domain[0] == '\0' || ourHost(addrinf.domain));
     if (locflg) {
          if (sameas(addrinf.usrals,POSTID)
           || sameas(addrinf.usrals,ROOTID)
           || sameas(addrinf.usrals,maildaem)) {
               stlcpy(adrbuf,smtdpuid,UIDSIZ);
               return(LOCADDR);
          }
          if ((userid=(*usrofals)(addrinf.usrals)) != NULL) {
               stlcpy(adrbuf,userid,UIDSIZ);
               return(LOCADDR);
          }
     }
     if (xltHooks(hint2gme,adrbuf,fromadr)) {
          return(LOCADDR);
     }
     return(locflg ? BADADDR : RMTADDR);
}

GBOOL                              /*   returns TRUE if successful         */
xltGME2Int(                        /* translate GME addr to internet format*/
CHAR *adrbuf)                      /*   buffer containing address to xlate */
{                                  /*   (adrbuf size must be MAXADR)       */
     CHAR *cp,tmpBuf[MAXADR];

     if (*adrbuf == '\0') {
          return(TRUE);
     }
     if (sameto(smtfpx,adrbuf)) {
          cp=&adrbuf[strlen(smtfpx)];
          memmove(adrbuf,cp,strlen(cp)+1);
          return(TRUE);
     }
     cp=NULL;
     if (sameas(adrbuf,POSTID)
      || sameas(adrbuf,ROOTID)
      || sameas(adrbuf,maildaem)) {
          cp=adrbuf;
     }
     else if (islocal(adrbuf)) {
          if (sameas(cp=(*alsofusr)(adrbuf),NOALCV)) {
               cp=NULL;
          }
     }
     if (cp != NULL) {
          stlcpy(tmpBuf,cp,MAXADR);
          stlcat(tmpBuf,"@",MAXADR);
          stlcat(tmpBuf,smtpHost(),MAXADR);
          stlcpy(adrbuf,tmpBuf,MAXADR);
          return(TRUE);
     }
     return(xltHooks(hgme2int,adrbuf,NULL));
}

const CHAR *                       /*   returns ptr to temp buffer         */
smtpHost(VOID)                     /* get host name used by SMTP           */
{
     return(smtihst ? hstdom : domain);
}

const CHAR *                       /*   returns ptr to static buffer       */
cvtDate(                           /* convert DOS date to rfc1036 format   */
USHORT dtim,                       /*   DOS time                           */
USHORT ddat)                       /*   DOS date                           */
{
     static CHAR mth[16][4]={"xxx","Jan","Feb","Mar","Apr","May","Jun","Jul",
                             "Aug","Sep","Oct","Nov","Dec","xxx","xxx","xxx"};

     return(spr("%d %s %d %s %s",ddday(ddat),mth[ddmon(ddat)],ddyear(ddat),
                                 nctime(dtim),smttmz));
}

GBOOL                              /*   return TRUE on successful parse    */
parseAddrLine(                     /* parse address line                   */
const CHAR *adrlin,                /*   address line ("to:...", "<...>")   */
struct addrinf *addrinf)           /*   buffer to return info              */
{
     CHAR tmpbuf[MAXADR];

     if (extractAddr(tmpbuf,adrlin)) {
          parseIntAddr(tmpbuf,addrinf);
          return(TRUE);
     }
     return(FALSE);
}

GBOOL                              /*   returns TRUE if able to extract    */
extractAddr(                       /* extract address from header line     */
CHAR *adrbuf,                      /*   buffer to place extracted address  */
const CHAR *hdrlin)                /*   header line                        */
{
     CHAR *ptr,*secptr;
     CHAR tmpbuf[MAXADR];

     strrpl(stlcpy(tmpbuf,hdrlin,MAXADR),'\t',' ');
     if ((ptr=crpstr(tmpbuf,'<')) != tmpbuf) {
          if ((secptr=strchr(ptr,'>')) != NULL) {
               *secptr='\0';
          }
          memmove(tmpbuf,ptr,strlen(ptr)+1);
     }
     else if ((ptr=crpstr(tmpbuf,':')) != tmpbuf) {
          if ((secptr=strchr(ptr,' ')) != NULL) {
               *secptr='\0';
          }
          memmove(tmpbuf,ptr,strlen(ptr)+1);
     }
     else {
          return(FALSE);
     }
     stlcpy(adrbuf,unpad(tmpbuf),MAXADR);
     return(TRUE);
}

VOID
parseIntAddr(                      /* parse internet-style address         */
const CHAR *address,               /*   address to parse                   */
struct addrinf *addrinf)           /*   buffer to return results           */
{
     CHAR *ptr,*secptr;
     CHAR tmpbuf[MAXADR];

     setmem(addrinf,sizeof(struct addrinf),0);
     stlcpy(tmpbuf,address,MAXADR);
     if ((ptr=strchr(tmpbuf,'@')) != NULL) {
          if ((secptr=strchr(ptr,' ')) != NULL) {
               *secptr='\0';
          }
          stlcpy(addrinf->domain,ptr+1,sizeof(addrinf->domain));
          *ptr='\0';
          stlcpy(addrinf->usrals,tmpbuf,INUIDSZ);
     }
     else if ((ptr=strrchr(tmpbuf,'!')) != NULL) {
          stlcpy(addrinf->usrals,ptr+1,INUIDSZ);
          *ptr='\0';
          stlcpy(addrinf->domain,tmpbuf,sizeof(addrinf->domain));
     }
     else {
          stlcpy(addrinf->usrals,tmpbuf,INUIDSZ);
          addrinf->domain[0]='\0';
     }
}

CHAR *                             /*   returns pointer to buffer          */
smtpCvtFrom(                       /* convert from address to GME format   */
CHAR *adrbuf,                      /*   buffer to accept addr (MAXADR long)*/
struct addrinf *addrinf)           /*   parsed Internet address            */
{
     CHAR *userid;

     if (smtdcvtf && ourHost(addrinf->domain)
      && (userid=(*usrofals)(addrinf->usrals)) != NULL) {
          stlcpy(adrbuf,userid,MAXADR);
     }
     else {
          stlcpy(adrbuf,smtfpx,MAXADR);
          if (addrinf->usrals[0] == '\0') {
               stlcat(adrbuf,NOUSER,MAXADR);
          }
          else {
               stlcat(adrbuf,addrinf->usrals,MAXADR);
          }
          stlcat(adrbuf,"@",MAXADR);
          if (addrinf->domain[0] == '\0') {
               stlcat(adrbuf,NOHOST,MAXADR);
          }
          else {
               stlcat(adrbuf,addrinf->domain,MAXADR);
          }
     }
     return(adrbuf);
}

GBOOL                              /*   return TRUE if we are "name"       */
ourHost(                           /* check if name is one of our aliases  */
const CHAR *name)                  /*   host name to check                 */
{
     INT len,i;
     CHAR ipBuf[sizeof("255.255.255.255")];

     len=strlen(name);
     if (len == 0) {
          return(TRUE);
     }
     /* strlen("[1.1.1.1]") == 9 */
     if (len >= 9 && name[0] == '[' && name[len-1] == ']') {
          stlcpy(ipBuf,&name[1],sizeof(ipBuf));
          if (ipBuf[len=strlen(ipBuf)-1] == ']') {
               ipBuf[len]='\0';
          }
          if (sameas(ipBuf,ipaddr)) {
               return(TRUE);
          }
     }
     if (sameas(name,ipaddr)) {
          return(TRUE);
     }
     if (sameas(name,hostnam) || sameas(name,hstdom)
      || (!smtihst && sameas(name,domain))) {
          return(TRUE);
     }
     for (i=0 ; i < numofals ; i++) {
          if (sameas(name,mailals[i])) {
               return(TRUE);
          }
     }
     return(FALSE);
}

GBOOL
isLocalIP(                         /* is this a local IP address           */
struct in_addr inaddr)             /*   IP address to evaluate             */
{
     if (iprLocalFile == NULL) {
          return(TRUE);
     }
     iprCheckFile(iprLocalFile,&iprLocal,&iprLocalNum,&iprLocalStamp);
     return(iprCheckIP(iprLocal,iprLocalNum,ntohl(inaddr.s_addr)));
}

GBOOL
isBlockedIP(                       /* is this a blocked IP address         */
struct in_addr inaddr)             /*   IP address to evaluate             */
{
     if (iprBlockFile == NULL) {
          return(FALSE);
     }
     iprCheckFile(iprBlockFile,&iprBlock,&iprBlockNum,&iprBlockStamp);
     return(iprCheckIP(iprBlock,iprBlockNum,ntohl(inaddr.s_addr)));
}

VOID
iprCheckFile(                      /* check range file, reload if necessary*/
const CHAR *fileName,              /*   IP range list file                 */
struct iprange **ppRange,          /*   ptr to ptr to array of ranges      */
INT *pNumber,                      /*   ptr to number of array elements    */
struct timestamp *pStamp)          /*   time/date stamp of current array   */
{
     struct ffblk fb;
     struct timestamp fstamp;

     if (fndfile(&fb,fileName,0)) {
          fstamp.date=fb.ff_fdate;
          fstamp.time=fb.ff_ftime;
          if (compStamp(fstamp,*pStamp) > 0
           && iprReadFile(fileName,ppRange,pNumber)) {
               pStamp->date=fb.ff_fdate;
               pStamp->time=fb.ff_ftime;
          }
     }
     else if (*ppRange != NULL) {
          free(*ppRange);
          *ppRange=NULL;
          *pNumber=0;
          pStamp->date=0;
          pStamp->time=0;
     }
}

GBOOL
iprCheckIP(                        /* check if IP is in array of ranges    */
struct iprange *rangeArr,          /*   array of IP ranges                 */
INT nRanges,                       /*   number of elements in array        */
ULONG ip)                          /*   IP address to check                */
{
     INT i;

     for (i=0 ; i < nRanges ; ++i) {
          if (ip >= rangeArr[i].low && ip <= rangeArr[i].high) {
               return(TRUE);
          }
     }
     return(FALSE);
}

GBOOL                              /*   returns FALSE if couldn't read     */
iprReadFile(                       /* read an IP range list file           */
const CHAR *fileName,              /*   IP range list file                 */
struct iprange **ppRange,          /*   ptr to ptr to array of ranges      */
INT *pNumber)                      /*   ptr to number of array elements    */
{
     FILE *fp;
     struct iprange tmpRange;
     CHAR buf[sizeof("255.255.255.255-255.255.255.255")];

     if ((fp=fopen(fileName,FOPRA)) != NULL) {
          if (*ppRange != NULL) {
               free(*ppRange);
               *ppRange=NULL;
          }
          *pNumber=0;
          while (iprReadLine(buf,sizeof(buf),fp)) {
               if (iprProcLine(buf,&tmpRange)) {
                    if (!alcarr((VOID **)ppRange,sizeof(struct iprange),
                                *pNumber,SMLBLK)) {
                         break;
                    }
                    (*ppRange)[*pNumber]=tmpRange;
                    ++(*pNumber);
               }
          }
          fclose(fp);
          return(TRUE);
     }
     return(FALSE);
}

GBOOL
iprReadLine(                       /* read a line from IP list file        */
CHAR *buf,                         /*   buffer to receive line             */
size_t bufSiz,                     /*   size of buffer                     */
FILE *fp)                          /*   file to read from                  */
{
     size_t i;
     INT c;

     if ((c=fgetc(fp)) == EOF) {
          return(FALSE);
     }
     i=0;
     do {
          if (c == ';' || c == '\n') {  /* comment or EOL */
               break;
          }
          else if (!isspace(c)) {       /* spaces ignored */
               if (i+1 < bufSiz) {
                    buf[i++]=c;
               }
               else {
                    break;
               }
          }
     } while ((c=fgetc(fp)) != EOF);
     ASSERT(i+1 <= bufSiz);
     buf[i]='\0';
     while (c != EOF && c != '\n') {
          c=fgetc(fp);
     }
     return(TRUE);
}

GBOOL
iprProcLine(                       /* process line from IP list file       */
CHAR *rangeLine,                   /*   line of text from file (modified)  */
struct iprange *rangeBuf)          /*   buffer to receive IP range         */
{
     CHAR *plo,*phi;
     ULONG iplo,iphi;

     if ((plo=strtok(rangeLine,"-")) != NULL) {
          if ((phi=strtok(NULL,"-")) == NULL) {
               phi=plo;
          }
          if (iprDecodeIP(&iplo,plo)
           && iprDecodeIP(&iphi,phi)
           && iplo <= iphi) {
               rangeBuf->low=iplo;
               rangeBuf->high=iphi;
               return(TRUE);
          }
     }
     return(FALSE);
}

GBOOL                              /*   returns TRUE if valid IP string    */
iprDecodeIP(                       /* decode an IP address string          */
ULONG *pIP,                        /*   buffer to receive IP address       */
const CHAR *ipStr)                 /*   string form of IP address          */
{
     ULONG tmpIP;

     if (sameas("255.255.255.255",ipStr)) {
          *pIP=0xFFFFFFFFUL;       /* a.k.a. INET_ADDR_ERR */
          return(TRUE);
     }
     if ((tmpIP=inet_addr(ipStr)) != INET_ADDR_ERR) {
          *pIP=ntohl(tmpIP);
          return(TRUE);
     }
     return(FALSE);
}

INT                                /*   < 0 if stamp1 < stamp2, etc.       */
compStamp(                         /* compare time/date stamps             */
struct timestamp stamp1,           /*   first time/date stamp              */
struct timestamp stamp2)           /*   second time/date stamp             */
{
     if (stamp1.date == stamp2.date) {
          if (stamp1.time == stamp2.time) {
               return(0);
          }
          if (stamp1.time < stamp2.time) {
               return(-1);
          }
          return(1);
     }
     if (stamp1.date < stamp2.date) {
          return(-1);
     }
     return(1);
}

GBOOL
smtval(                            /* is this a valid internet address?    */
const CHAR *addr)                  /*   address to check                   */
{
     CHAR *ptr;

     if ((ptr=strchr(addr,'@')) != NULL) {
          if (strchr(ptr,'.') != NULL) {
               return(TRUE);
          }
     }
     else if (strchr(addr,'!') != NULL) {
          return(TRUE);
     }
     return(FALSE);
}

VOID
gmimid(                            /* generate MIME boundary               */
CHAR *midbuf,                      /*   MIME boundary buffer               */
LONG msgid)                        /*   local message ID                   */
{
     sprintf(midbuf,"zzzz%lx%x%x%0.12szzzz",msgid,today(),now(),domain);
}

GBOOL                              /*   returns: FALSE when done           */
mimecode(                          /* MIME encode 'inf' to 'outf'          */
FILE *inf,
FILE *outf,
const CHAR *lineTerm,
INT lineTermSize)
{
     GBOOL retval=TRUE;
     INT i,j,nbytes;
     INT lt;

     setmem(vdatmp,vdasiz,0);
     if ((nbytes=fread(vdatmp,1,IBUFSZ,inf)) > 0) {
          for (i=0,j=OBOSET ; i < nbytes ; i+=3) {
               vdatmp[j++]=b64tab[(vdatmp[i]&0xFC)>>2];
               vdatmp[j++]=b64tab[((vdatmp[i]&0x03)<<4)
                                    |(vdatmp[i+1]>>4)];
               vdatmp[j++]=b64tab[((vdatmp[i+1]&0x0F)<<2)
                                    |(vdatmp[i+2]>>6)];
               vdatmp[j++]=b64tab[vdatmp[i+2]&0x3F];
               if ((i%54) == 51) {
                    for (lt=0 ; lt < lineTermSize ; lt++) {
                         vdatmp[j++]=lineTerm[lt];
                    }
               }
          }
          if (nbytes != IBUFSZ) {
               if (vdatmp[j-1] == lineTerm[lineTermSize-1]) {
                    j-=lineTermSize;
               }
               switch (nbytes%3) {
               case 1:
                    vdatmp[j-2]='=';
               case 2:
                    vdatmp[j-1]='=';
                    break;
               }
               retval=FALSE;
          }
          if (fwrite(vdatmp+OBOSET,j-OBOSET,1,outf) != 1) {
               sholog(curtask,"SMTP MIME ERROR",
                      "Error writing output file");
               retval=FALSE;
          }
     }
     else {
          retval=FALSE;
     }
     return(retval);
}

VOID
logsmt(                            /* log send transaction                 */
INT taskno,
CHAR *logstr,
INT type)
{
     FILE *logfp;
     CHAR *cp;
     CHAR fnbuf[GCMAXPTH];
     CHAR fnroot[GCMAXPTH];
     CHAR fnpath[GCMAXPTH];

     if (!smtlog) {
          return;
     }

     (VOID)fileparts(GCPART_PATH, smtlnam, fnpath, GCMAXPTH);
     (VOID)fileparts(GCPART_FILE, smtlnam, fnroot, GCMAXPTH);

     sprintf(fnbuf, "%s%s.%03i", fnpath, fnroot, taskno);

     if ((logfp=fopen(fnbuf,FOPAA)) == NULL) {
          smtlog=FALSE;
          sholog(taskno,"SMTP SEND LOG OPEN ERROR",
                 "Could not open \"%s\".  Logging disabled.",fnbuf);
          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) {
          smtlog=FALSE;
          sholog(taskno,"SMTP SEND LOG WRITE ERROR",
                 "Could not write to send log.  Logging disabled.");
     }
     fclose(logfp);
}

VOID
sholog(                            /* prints to audit trail, logs to file  */
INT taskno,
CHAR *header,
CHAR *footer,
...)
{
     va_list ftlist;
     static CHAR wrkbuf[SMTLSZ*2];
     CHAR logbuf[(AUDBRIEFSIZ-1)+2+((SMTLSZ*2)-1)+1];

     va_start(ftlist,footer);
     if (vsprintf(wrkbuf,footer,ftlist) >= (SMTLSZ*2)) {
          catastro("GALSMTP: Sending log buffer overrun error.");
     }
     va_end(ftlist);
     shocst(header,"%.*s",AUDDETSIZ-1,wrkbuf);
     sprintf(logbuf,"%.*s: %.*s",AUDBRIEFSIZ-1,header,(SMTLSZ*2)-1,wrkbuf);
     logsmt(taskno,logbuf,SHOWLOG);
}

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';
     }
}

CHAR *
stpcr(                             /* strips CR from CR/LF                 */
CHAR *textbuf)
{
     CHAR *ptr;

     while ((ptr=strstr(textbuf,"\r\n")) != NULL) {
          strmove(ptr,ptr+1);
     }
     return(textbuf);
}

CHAR *
stpbkt(                            /* strips angle brackets from string    */
CHAR *string)
{
     CHAR *ptr,*secptr;

     if ((ptr=strchr(string,'<')) == NULL) {
          ptr=string;
     }
     else {
          ptr++;
     }
     if ((secptr=strrchr(ptr,'>')) != NULL) {
          *secptr='\0';
     }
     return(ptr);
}

/*
 * Hook Management Functions
 */

GBOOL                              /*   returns TRUE if hook set           */
smtpHook(                          /* set a SMTP function hook             */
INT hooktype,                      /*   type of hook to set                */
voidfunc hookfunc,                 /*   hook function pointer              */
SHORT hookpri)                     /*   priority (0=normal >0=high <0=low) */
{
     return(HookAdd(hooktype,hookfunc,hookpri));
}

GBOOL                              /*   returns TRUE to halt processing    */
hookHandleExp(                     /* handle exported message              */
INT * pRet,                        /*   GME status code                    */
struct message * pMsg,             /*   header of message to send off      */
CHAR const ** pTo,                 /*   to field                           */
CHAR const ** pAtt,                /*   path+file name of attachment       */
CHAR ** pText,                     /*   message text buffer                */
size_t TextBufSize)                /*   current text buffer size           */
{
     INT i;
     smtphook_export hook;
     GBOOL stopped;

     i=NOIDX;
     stopped=FALSE;
     while ((i=HookNext(SMTPHOOK_EXPORT,i)) != NOIDX && !stopped) {
          hook=(smtphook_export)(HookArr[i].hookfunc);
          *pRet=GMEAGAIN;
          stopped=(*hook)(pRet,pMsg,pTo,pAtt,pText,TextBufSize);
     }
     return(stopped && *pRet != GMEAGAIN);
}
