/* SMTP Server state machine - see RFC 821
 *  enhanced 4/88 Dave Trulli nn2z
 *
 * Mods by G1EMM and PA0GRI
 *
 * Index file support by WG7J
 */
#include <time.h>
#include "global.h"
#ifdef UNIX
#include <sys/types.h>
#include <fcntl.h>
#ifdef HOLD_PARSER
#include <sys/wait.h>
#endif
#else
#include <dir.h>
#endif
#if defined(__STDC__) || defined(__TURBOC__)
#include <stdarg.h>
#endif
#include <ctype.h>
#include <setjmp.h>
#include "mbuf.h"
#include "cmdparse.h"
#include "socket.h"
#ifdef  LZW
#include "lzw.h"
#endif
#include "iface.h"
#include "proc.h"
#include "smtp.h"
#include "commands.h"
#include "dirutil.h"
#include "mailbox.h"
#include "mailutil.h"
#include "bm.h"
#include "domain.h"
#include "session.h"
#include "files.h"
#include "index.h"
#ifdef  NNTPS
#include "nntp.h"
#endif
  
char *Days[7] = {  "Sun","Mon","Tue","Wed","Thu","Fri","Sat" };
extern char *Months[];
#ifdef SMTPTRACE
extern unsigned short Smtptrace;
#endif
#ifdef HOLD_LOCAL_MSGS
extern int MbHoldLocal;
#endif
  
static struct list *expandalias __ARGS((struct list **head,char *user,char *origuser));
static int  getmsgtxt __ARGS((struct smtpsv *mp));
static struct smtpsv *mail_create __ARGS((void));
static void mail_clean __ARGS((struct smtpsv *mp));
static int mailit __ARGS((FILE *data,char *from,struct list *tolist,int requeue_OK));
static int router_queue __ARGS((FILE *data,char *from,struct list *to));
static void smtplog __ARGS((char *fmt,...));
static void smtpserv __ARGS((int s,void *unused,void *p));
extern int msgidcheck __ARGS((char *string));
  
#ifdef SMTPSERVER
  
/* Command table */
static char *commands[] = {
    "helo",
#define HELO_CMD    0
    "noop",
#define NOOP_CMD    1
    "mail from:",
#define MAIL_CMD    2
    "quit",
#define QUIT_CMD    3
    "rcpt to:",
#define RCPT_CMD    4
    "help",
#define HELP_CMD    5
    "data",
#define DATA_CMD    6
    "rset",
#define RSET_CMD    7
    "expn",
#define EXPN_CMD    8
    "vrfy",
#define VRFY_CMD    9
#ifdef  LZW
    "xlzw",
#define XLZW_CMD    10
#endif
    NULLCHAR
};
  
#ifdef STATUSWIN
int SmtpUsers;
#endif
  
int Smtpservcnt;
int Smtpmaxserv;

/* Reply messages */
static char Banner[] = "220 %s SMTP ready\n";
static char Closing[] = "221 Closing\n";
static char Ok[] = "250 Ok\n";
static char Reset[] = "250 Reset state\n";
static char Sent[] = "250 Sent\n";
static char Ourname[] = "250 %s, Share and Enjoy!\n";
#ifdef  LZW
static char LZWOk[] = "250 %d %d LZW Ok\n";
static char Help[] = "214-Commands:\n214-HELO NOOP MAIL QUIT RCPT HELP DATA RSET EXPN VRFY XLZW\n214 End\n";
#else
static char Help[] = "214-Commands:\n214-HELO NOOP MAIL QUIT RCPT HELP DATA RSET EXPN VRFY\n214 End\n";
#endif
static char Enter[] = "354 Enter mail, end with .\n";
static char Ioerr[] = "452 Temp file write error\n";
#ifdef DEBUG500
/*static char Badcmd[] = "500 Command unrecognized: %s";*/
static char BadcmdNL[] = "500 Command unrecognized: %s\n";
#else
static char Badcmd[] = "500 Command unrecognized\n";
#endif
static char Syntax[] = "501 Syntax error\n";
static char Needrcpt[] = "503 Need RCPT (recipient)\n";
static char Unknown[] = "550 <%s> address unknown\n";
  
extern int SmtpServTdisc;
  
/* Start up SMTP receiver service */
int
smtp1(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    int16 port;
  
    if(argc < 2)
        port = IPPORT_SMTP;
    else
        port = atoi(argv[1]);
  
#define SMTPSTK 2048

#if defined(NNTPS) && defined(NEWS_TO_MAIL)
#ifdef UNIX
#define SMTPSTK2 1000
#else
#define SMTPSTK2 80
#endif /* UNIX */
#else
#define SMTPSTK2 0
#endif /* defined(NNTPS) && defined(NEWS_TO_MAIL) */

#ifdef SMTP_VALIDATE_LOCAL_USERS
#define SMTPSTK3 150
#else
#define SMTPSTK3 0
#endif /* SMTP_VALIDATE_LOCAL_USERS */

#if defined(MAILCMDS) && defined(HOLD_LOCAL_MSGS)
#define SMTPSTK4 150
#else
#define SMTPSTK4 0
#endif /* defined(MAILCMDS) && defined(HOLD_LOCAL_MSGS) */

#define SMTPSTKALL	(SMTPSTK+SMTPSTK2+SMTPSTK3+SMTPSTK4)
    return start_tcp(port,"SMTP Server",smtpserv,SMTPSTKALL);
}
  
/* Shutdown SMTP service (existing connections are allowed to finish) */
int
smtp0(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    int16 port;
  
    if(argc < 2)
        port = IPPORT_SMTP;
    else
        port = atoi(argv[1]);
    return stop_tcp(port);
}
  
static void
smtpserv(s,unused,p)
int s;
void *unused;
void *p;
{
    struct smtpsv *mp;
    char **cmdp,buf[LINELEN],*arg,*cp,*cmd,*newaddr,*origaddr;
    struct list *ap,*list;
    int cnt, delay_retry=0;
    char address_type;
#ifdef  LZW
    extern int Smtpslzw;
    int lzwbits, lzwmode;
#endif
  
    sockmode(s,SOCK_ASCII);
    sockowner(s,Curproc);       /* We own it now */
    log(s,"open SMTP");
  
    if((mp = mail_create()) == NULLSMTPSV){
        tputs(Nospace);
        log(s,"close SMTP - no space or toomany");
        close_s(s);
        return;
    }
    mp->s = s;
  
    (void) usprintf(s,Banner,Hostname);
  
    loop:
    /* Time-out after some inactivity time - WG7J */
    alarm((long)(SmtpServTdisc*1000L));
    if ((cnt = recvline(s,buf,sizeof(buf))) == -1){
        /* He closed on us */
        goto quit;
    }
    alarm(0L);
#ifdef notdef
    if(cnt < 4){
        /* Can't be a legal command */
#ifdef DEBUG500
        usprintf(mp->s,Badcmd,buf);
#else
        usputs(mp->s,Badcmd);
#endif
        goto loop;
    }
#endif
    rip(buf);
    cmd = buf;
  
#ifndef SMTP_ALLOW_MIXEDCASE
    /* Translate entire buffer to lower case */
#ifdef __TURBOC__
    (void)strlwr(cmd);
#else
    for(cp = cmd;*cp != '\0';cp++)
        *cp = tolower(*cp);
#endif
#endif /* SMTP_ALLOW_MIXEDCASE */
  
    /* Find command in table; if not present, return syntax error */
    for(cmdp = commands;*cmdp != NULLCHAR;cmdp++)
        if(strnicmp(*cmdp,cmd,strlen(*cmdp)) == 0)
            break;
    if(*cmdp == NULLCHAR){
#ifdef DEBUG500
        usprintf(mp->s,BadcmdNL,cmd);
#else
        (void) usputs(mp->s,Badcmd);
#endif
        goto loop;
    }
    arg = &cmd[strlen(*cmdp)];
    /* Skip spaces after command */
    while(*arg == ' ')
        arg++;
    /* Execute specific command */
    switch(cmdp-commands){
#ifdef  LZW
        case XLZW_CMD:
            if(!Smtpslzw) {
#ifdef DEBUG500
                usprintf(mp->s,BadcmdNL,cmd);
#else
                usputs(mp->s,Badcmd);
#endif
            } else {
                lzwmode = lzwbits = 0;
                sscanf(arg,"%d %d",&lzwbits,&lzwmode);
                if(!((lzwmode == 0 || lzwmode == 1)
                && (lzwbits > 8 && lzwbits < 17))) {
                    lzwmode = LZWCOMPACT;
                    lzwbits = LZWBITS;
#ifdef DEBUG500
                    usprintf(mp->s,BadcmdNL,cmd);
#else
                    usputs(mp->s,Badcmd);
#endif
                } else {
                    usprintf(mp->s,LZWOk,lzwbits,lzwmode);
                    lzwinit(mp->s,lzwbits,lzwmode);
                }
            }
            break;
#endif
        case HELO_CMD:
            free(mp->system);
            mp->system = strdup(arg);
            (void) usprintf(mp->s,Ourname,Hostname);
            break;
        case NOOP_CMD:
            (void) usputs(mp->s,Ok);
            break;
        case MAIL_CMD:
            if((cp = getname(arg)) == NULLCHAR){
                (void) usputs(mp->s,Syntax);
                break;
            }
            free(mp->from);
#ifdef TRANSLATEFROM
            /* rewrite FROM address if possible */
            if((mp->from = rewrite_address(cp,REWRITE_FROM)) != NULLCHAR){
                if(!strcmp(mp->from,"refuse")){  /*n5knx: refusal needed? */
                    (void)usprintf(mp->s, Unknown, cp);
                    break;
                }
            }
            else
#endif
                mp->from = strdup(cp);
            (void) usputs(mp->s,Ok);
            break;
        case QUIT_CMD:
            (void) usputs(mp->s,Closing);
            goto quit;
        case RCPT_CMD:  /* Specify recipient */
            if((cp = getname(arg)) == NULLCHAR || !*cp){
                (void) usputs(mp->s,Syntax);
                break;
            }
        /* rewrite address if possible */
#ifdef SMTP_REFILE
/* rewrite to: address based on <from>|<to> mapping in Refilefile */
            cmd=mallocw(strlen(mp->from)+strlen(cp)+2);
            sprintf(cmd,"%s|%s",mp->from,cp);
            if((newaddr = rewrite_address(cmd,REWRITE_FROM_TO)) == NULLCHAR
            &&
#else
            if(
#endif

               (newaddr = rewrite_address(cp,REWRITE_TO)) == NULLCHAR){
#if SMTP_REFILE
                free(cmd);
#endif
                origaddr=strdup(cp);
            } else {  /* one of the rewrites was successful */
#if SMTP_REFILE
                free(cmd);
#endif
                if(!strcmp(newaddr,"refuse")){  /*n5knx: refusal needed? */
                    (void)usprintf(mp->s, Unknown, cp);
                    free(newaddr);
                    break;
                }
                origaddr=strdup(cp);
                strcpy(buf,newaddr);
                cp = buf;
                free(newaddr);
            }
  
        /* check if address is ok (and short enough for us to handle) */
            if ((address_type = validate_address(cp)) == BADADDR ||
              (address_type==LOCAL && strlen(cp) > FILE_PATH_SIZE - strlen(Mailspool) - 6)){
                (void) usprintf(mp->s,Unknown,cp);
                free(origaddr);
                break;
            }
        /* if a local address check for an alias */
            if (address_type == LOCAL) {
                expandalias(&mp->to, cp, origaddr);
#ifdef SMTP_VALIDATE_LOCAL_USERS
                if(mp->to == NULLLIST) {  /* id was invalid */
                    (void) usprintf(mp->s,Unknown,cp);
                    free(origaddr);
                    break;
                }
#endif
            } else {
#ifdef SMTP_DENY_RELAY
                /* refuse to send off-host unless connecting host is in our net*/
                struct iface *ifp;
                struct sockaddr_in fsock;

                cnt = sizeof(fsock);
                if(getpeername(mp->s,(char *)&fsock,&cnt) == 0 && cnt != 0 && fsock.sin_family==AF_INET) {
                    for (ifp=Ifaces; ifp != NULLIF; ifp=ifp->next) {
                        if((ifp->addr & ifp->netmask) == (fsock.sin_addr.s_addr & ifp->netmask))
                            break;  /* same subnet as our interface */
                    }
                    if (ifp == NULLIF) {
                        (void) usprintf(mp->s,Unknown,cp);
                        free(origaddr);
                        break;
                    }
                }
#endif
                /* a remote address is added to the list */
                addlist(&mp->to, cp, address_type, NULLCHAR);
            }
  
            (void) usputs(mp->s,Ok);
            free (origaddr);
            break;
        case HELP_CMD:
            (void) usputs(mp->s,Help);
            break;
        case DATA_CMD:
            if(mp->to == NULLLIST)
                (void) usputs(mp->s,Needrcpt);
            else if ((mp->data = tmpfile()) == NULLFILE)
                (void) usputs(mp->s,Ioerr), ++delay_retry;
            else
                if ((cnt=getmsgtxt(mp)) == 2) ++delay_retry;
                else if (cnt == 1) {
                    log(mp->s, "Early disconnect or timeout");
                    goto quit;
                }
            break;
        case RSET_CMD:
            del_list(mp->to);
            mp->to = NULLLIST;
            (void) usputs(mp->s,Reset);
            break;
        case EXPN_CMD:
        case VRFY_CMD:    /* treat same as expn (sendmail does this too) */
            if (*arg == '\0'){
                (void) usputs(mp->s,Syntax);
                break;
            }
  
            list = NULLLIST;
        /* rewrite address if possible */
            if((newaddr = rewrite_address(arg,REWRITE_TO)) != NULLCHAR){
                if(!strcmp(newaddr,"refuse")){  /*n5knx: refusal needed? */
                    (void)usprintf(mp->s, Unknown, arg);
                    free(newaddr);
                    break;
                }
                strcpy(buf,newaddr);
                arg = buf;
                free(newaddr);
            }
            list = NULLLIST;
            expandalias(&list,arg,NULLCHAR);
#ifdef SMTP_VALIDATE_LOCAL_USERS
            if (list == NULLLIST) {
                (void)usprintf(mp->s, Unknown, arg);
                break;
            }
#endif
            ap = list;
            while (ap->next != NULLLIST){
                (void) usprintf(mp->s,"250-%s\n",ap->val);
                ap = ap->next;
            }
            usprintf(mp->s,"250 %s\n",ap->val);
            del_list(list);
            break;
    }
    goto loop;
  
quit:
    log(mp->s,"close SMTP");
    close_s(mp->s);
    mail_clean(mp);

    /* N5KNX: If we tried to deliver to an offline/unready printer, or to a
       locked mailbox, we sent back code 452/Ioerr and the job remains in the
       queue.  If we invoke smtptick() now to reprocess the queue, we'll have
       a tight loop awaiting a successful delivery.
       Instead, we'll just process the queue later, in due course [provided
       smtp timer was set].  The down side is we are less responsive processing
       non-local email if sent in the same session that incurred an Ioerr.
    */
    if (!delay_retry) smtptick(NULL);         /* start SMTP daemon immediately */
}
  
extern char shortversion[];
extern char *Mbfwdinfo;
  
/* read the message text
 * This now checks for Message-Id's that could be bids
 * and deletes the message if so. - WG7J
 * Also adds Message-Id and Date headers if not already present - G8FSL
 * Returns: 0 => no errors, 1 => permanent error, 2 => temporary error (Ioerr)
 */
static int
getmsgtxt(mp)
struct smtpsv *mp;
{
    char buf[LINELEN];
    char *p;
    int  mailed_ok=0;
    int  headers=1, flag=0;
    int  prevtype = NOHEADER;
    int  continuation, continued=FALSE;
    long t;
    char *cp;
#ifdef MBFWD
    int idnotfound;
    char bid[LINELEN];
#endif
  
    /* Add timestamp; ptime adds newline */
    time(&t);
    fprintf(mp->data,Hdrs[RECEIVED]);
    if(mp->system != NULLCHAR)
        fprintf(mp->data,"from %s ",mp->system);
#ifdef MBFWD
    fprintf(mp->data,"by %s (%s) with SMTP\n\tid AA%ld ; %s",
    Hostname, (Mbfwdinfo != NULLCHAR) ? Mbfwdinfo : shortversion, \
    get_msgid(), ptime(&t));
#else
    fprintf(mp->data,"by %s (%s) with SMTP\n\tid AA%ld ; %s",
    Hostname, shortversion, get_msgid(), ptime(&t));
#endif
    if(ferror(mp->data)){
        (void) usputs(mp->s,Ioerr);
        return 2;  /* retryable */
    } else {
        (void) usputs(mp->s,Enter);
    }
#ifdef MBFWD
    idnotfound = 1;
#endif
    while(1){
        /* Timeout after inactivity - WG7J */
        alarm((long)(SmtpServTdisc*1000L));
        if(recvline(mp->s,(p=buf),sizeof(buf)) == -1){
            return 1;
        }
        alarm(0L);
        continuation = continued;  /*  handle lines longer than buf */
        cp = strchr(p, '\n');
        if(cp) { *cp = '\0'; continued = FALSE; }
        else continued = TRUE;
        /* check for end of message ie a . or escaped .. */
        if (*p == '.' && !continuation){
            if (*++p == '\0'){
#ifdef MBFWD
                /* Also send appropriate response */
                /* if duplicate BID, we just ignore the dup message.  We should NOT
                   trouble the sender when it's OUR problem, not theirs!  We get dups
                   when the same bulletin is queued 2+ times before smtpserv runs to
                   process the queue.  Since we store bids after processing, in just
                   this process, we couldn't refuse duplicates of the msg when first
                   proposed in the ax.25 forwarding process. -- N5KNX
                */
                if(mp->dupbid) {
                    (void) usputs(mp->s,Sent);  /* pretend we delivered it */
                    mailed_ok=0;
                }
                else
#endif
#ifndef RELIABLE_SMTP_BUT_SLOW_ACK
                    if(strcmp(mp->system, Hostname)) {  /* Processing our queue? */
                        (void) usputs(mp->s,Sent); /* no, send immediate positive reply */
                        if (mailit(mp->data,mp->from,mp->to, TRUE) != 0) /* then store or requeue if error */
                            log(-1,"smtpserv: msg from %s to %s+ lost; queueing error", mp->from, mp->to->val);
                        mailed_ok=0;
                    }
                    else  /* yes, we work with our own queue, so never requeue */
#endif
                    {
                        if(mailit(mp->data,mp->from,mp->to, FALSE) != 0) {
                            (void) usputs(mp->s,Ioerr);
                            mailed_ok=2;
                        }
                        else
                            (void) usputs(mp->s,Sent);
                    }
                fclose(mp->data);
                mp->data = NULLFILE;
                del_list(mp->to);
                mp->to = NULLLIST;
#ifdef MBFWD
                /* If there is a BID set, save it in the history file - WG7J */
                if(mp->bid != NULLCHAR) {
                    if(mailed_ok==0) {
                        storebid(mp->bid);
                    }
                    /* Free this bid ! */
                    free(mp->bid);
                    mp->bid = NULL;
                }
#endif
                return mailed_ok;
            }
            else if (*p != '.')
                p--;  /* bad syntax, as ALL leading periods should be doubled */
        }
        /* examine headers for Date and Message-ID lines, very important! -- G8FSL */
        if (headers) {
            int h  =  htype(p, &prevtype);

            if (h == DATE)
                flag |= 1;
            else if (h == MSGID)
                flag |= 2;
            else if (!continuation && *p == 0) {  /* end of headers? */
                if (!(flag & 1)) {
                    time(&t);
                    fprintf(mp->data,"%s%s",Hdrs[DATE],ptime(&t)); /* ptime() adds NL */
                }
                if (!(flag & 2)) {
                    fprintf(mp->data,"%s<%ld@%s>\n",Hdrs[MSGID],get_msgid(),Hostname);
                }
                headers = 0;
            }
        }
        /* for UNIX mail compatiblity */
        if (!continuation && strncmp(p,"From ",5) == 0)
            (void) putc('>',mp->data);
        /* Append to data file */
#ifdef MSDOS
        /* Get rid of Control-Z's in the line */
        cp = p;
        while(*cp) {
            if(*cp == CTLZ)
                *cp = '\n';
            cp++;
        }
#endif
        if(fputs(p,mp->data) == EOF){
            (void) usputs(mp->s,Ioerr);
            return 2;
        }
        if (!continued) putc('\n',mp->data);
        pwait(NULL);  /* let other processes run after each write to tmpfile */
#ifdef MBFWD
        /* Check for Message-Id string - WG7J */
        if(headers && idnotfound && !strnicmp(p,Hdrs[MSGID],11)) {
            if((cp = getname(p)) == NULLCHAR)
                continue;
            idnotfound = 0;
            strcpy(bid,cp);
            if((cp = strchr(bid,'@')) == NULLCHAR)
                continue;
            /* A trailing ".bbs" indicates that the Message-ID was generated
             * from a BBS style message, and not a RFC-822 message.
             */
            if(stricmp(&bid[strlen(bid) - 4], ".bbs") == 0) {
                *cp = '\0'; /*retain the bid given by user*/
                bid[12] = '\0'; /* BIDs should be no longer than 12 bytes */
                /* now check it, and save if not duplicate - WG7J */
                if((mp->dupbid = msgidcheck(bid)) == 0)
                    mp->bid = strdup(bid);
            }
        }
#endif
    }
}
  
/* Create control block, initialize */
static struct smtpsv *
mail_create()
{
    register struct smtpsv *mp;
  
    if (Smtpmaxserv && Smtpservcnt >= Smtpmaxserv)
        return NULLSMTPSV;
    mp = (struct smtpsv *)calloc(1,sizeof(struct smtpsv));
    if (mp) {
        mp->from = strdup("");  /* Default to null From address */
        Smtpservcnt++;
#ifdef STATUSWIN
        SmtpUsers++;
#endif
    }
    return mp;
}
  
/* Free resources, delete control block */
static void
mail_clean(mp)
register struct smtpsv *mp;
{
    if (mp == NULLSMTPSV)
        return;
    Smtpservcnt--;
#ifdef STATUSWIN
    SmtpUsers--;
#endif
    free(mp->system);
    free(mp->from);
    free(mp->bid);
    if(mp->data != NULLFILE)
        fclose(mp->data);
    del_list(mp->to);
    free((char *)mp);
}
#endif /* SMTPSERVER */
  
/* General mailit function. It takes a list of addresses which have already
** been verified and expanded for aliases. Base on the current mode the message
** is place in an mbox, the outbound smtp queue or the rqueue interface
** Return 0 if successful, 1 if failure.
*/
/* Modified to touch the timestamp file for new-message tracking on local
 * deliveries... - WG7J.
 */
/* Supports mail index files - WG7J */
static int
mailit(data,from,tolist,requeue_OK)
FILE *data;
char *from;
struct list *tolist;
int requeue_OK;   /* TRUE when client host is another system */
{
    struct list *ap, *dlist = NULLLIST;
    FILE *fp;
    char mailbox[FILE_PATH_SIZE], *cp, *host, *qhost;
    int c, fail = 0;
    time_t t;
    extern int Smtpquiet;
    int index,type,continued,continuation;
    long start;
    struct mailindex ind;
    char buf[LINELEN];
#if defined(MAILCMDS) && defined(HOLD_LOCAL_MSGS) && defined(UNIX) && defined(HOLD_PARSER)
    int external_hold = 0;
#endif
  
    if ((Smtpmode & QUEUE) != 0)
        return(router_queue(data,from,tolist));
  
    /* scan destinations, and requeue those going to the same off-site host */
    do {
        qhost = NULLCHAR;
        for(ap = tolist;ap != NULLLIST;ap = ap->next)
            if (ap->type == DOMAIN){
                if ((host = strrchr(ap->val,'@')) != NULLCHAR)
                    host++;
                else
                    host = Hostname;
                if(qhost == NULLCHAR)
                    qhost = host;
                if(stricmp(qhost,host) == 0){
                    ap->type = BADADDR;
                    addlist(&dlist,ap->val,0,NULLCHAR);
                }
            }
        if(qhost != NULLCHAR){
            rewind(data);
            queuejob(data,qhost,dlist,from);
            del_list(dlist);
            dlist = NULLLIST;
        }
    } while(qhost != NULLCHAR);
  
#ifdef  NNTPS
    for(ap = tolist;ap != NULLLIST;ap = ap->next){
        if(ap->type != NNTP_GATE)
            continue;
        nnGpost(data,from,ap);
        ap->type = BADADDR;
    }
#endif
  
#if defined(MAILCMDS) && defined(HOLD_LOCAL_MSGS) && defined(UNIX) && defined(HOLD_PARSER)
    if (MbHoldLocal) {
        int pid, ac;
    
        rewind(data);
        switch(pid=fork()) {
        case -1:
            break;   /* fork failed */
        case 0:   /* child ... invoke HOLD_PARSER */
            if (dup2(fileno(data), 0) != -1)  /* make stdin==fileno(data) */
                execl(HOLD_PARSER, HOLD_PARSER);
            _exit(0);  /* pretend nothing found worth holding this msg */
            /*break;*/
        default:  /* parent */
            while (!waitpid (pid, &ac, WNOHANG)) {
                pause(100L);  /* let other Jnos processes run */
            }
            if (WIFEXITED(ac)) {   /* normal exit? */
                if (WEXITSTATUS(ac))  /* non-zero exit code? */
                    external_hold=BM_HOLD;
            }
        }  /* end switch */
    }
#endif

    /* Clear the index to start */
    memset(&ind,0,sizeof(ind));
  
    index = 0;
    for(ap = tolist;ap != NULLLIST;ap = ap->next,index++){
        if(ap->type != LOCAL){
            ap->type = DOMAIN;
            continue;
        }
        rewind(data);
        /* strip off host name of LOCAL addresses */
        if ((cp = strchr(ap->val,'@')) != NULLCHAR)
            *cp = '\0';
  
        /* replace '\' and '.' with '/', and create subdirs.
         * this allows mailing into subdirs of spool/mail - WG7J
         */
        for(cp=ap->val;*cp != '\0'; cp++)
            if((*cp == '.') || (*cp == '\\') || *cp == '/') {
                /* Now create sub directories in the message name - WG7J */
                *cp = '\0';
                sprintf(buf,"%s/%s",Mailspool,ap->val);
#ifdef UNIX
                mkdir(buf, (mode_t)0755);
#else
                mkdir(buf);
#endif
                *cp = '/';
            }
  
        /* if mail file is busy, either (1) save it in our smtp queue
         * and let the smtp daemon try later [yields a tight retry loop
         * if we were processing our own queue, hence the requeue_OK flag]
         * or (2) mark it as failed, so sender will retry later on.  This
         * could yield dupes for msgs with multiple destinations.
         */
        if (mlock(Mailspool,ap->val)){
            if (requeue_OK) {
                addlist(&dlist,ap->val,0,NULLCHAR);
                fail = queuejob(data,Hostname,dlist,from);
                del_list(dlist);
                dlist = NULLLIST;
            }
            else
                fail=1;
        } else {
            int tocnt = 0, prevtype = NOHEADER;

            SyncIndex(ap->val); /* ensure index file is current */
            sprintf(mailbox,"%s/%s.txt",Mailspool,ap->val);
#ifndef AMIGA
            if((fp = fopen(mailbox,APPEND_TEXT)) != NULLFILE){
#else
                if((fp = fopen(mailbox,"r+")) != NULLFILE){
#endif
  
                    default_index(ap->val,&ind);
                    time(&t);
                    fseek(fp,0,SEEK_END);
                    start = ftell(fp);
                    fprintf(fp,"From %s %s",from,ctime(&t));
                    host = NULLCHAR;
  
                    c = continued = 0;
                    while(fgets(buf,sizeof(buf),data) != NULLCHAR){
                        continuation = continued;
                        continued = (strchr(buf,'\n') == NULLCHAR);
                        c++;   /* count header lines (roughly) */
                        if(!continuation && buf[0] == '\n'){
                        /* End of headers */
                            if(tocnt == 0) {
                                fprintf(fp,"%s%s\n", Hdrs[APPARTO], ap->val);
                                free(ind.to);
                                ind.to = strdup(ap->val);
                            }
                            fputc('\n',fp);
                            break;
                        }
                        type = htype(buf, &prevtype);
                    /* assure a unique Message-id for each To: addr - from KO4KS/KD4CIM */
                        if(index && (type == MSGID)) {  /* since we may use it for bids */
                            if((cp=strstr(buf,Hostname))!=NULLCHAR && *--cp=='@') /* if it came from us */
                                sprintf(buf,"%s<%ld@%s>\n",Hdrs[MSGID],get_msgid(),Hostname);
                        }
                        fputs(buf,fp);
                        set_index(buf,&ind,type);
                        switch(type){
                            case RECEIVED:
                                if (c==2 && buf[0]=='\t') {  /* only want our Received: line, first continuation */
                                    if((cp=strstr(buf,"AA")) != NULLCHAR)
                                        ind.msgid = atol(cp+2); /* message-number follows AA */
                                    if((cp=strchr(buf,';')) != NULLCHAR)
                                        ind.mydate = mydate(cp+2);
                                }
                                break;
                            case TO:
                                /* For multiple-addressees (to/cc) we should not use the first To:
                                 * address in the index.  But ap->val is an area, so see if we kept
                                 * the original value before rewrite().
                                 */
                                if (ap->aux) { /* n5knx: original destination preferred here */
                                    free(ind.to);
                                    ind.to = strdup(ap->aux);
                                    /* we ought to fudge To: and Cc: lines in case sysop re-indexes
                                       but for now let's ignore that little problem */
                                }
                                if ((cp=strchr(ind.to,'@')) != NULLCHAR && !stricmp(cp+1,Hostname)) {
                                    *cp='\0'; /* but strip @our_hostname */
                                    if ((cp = strrchr(ind.to,'%')) != NULLCHAR) *cp = '@';
                                }
                                /* fall into CC: case */
                            case CC:
                                ++tocnt;
                                break;
#ifdef TRANSLATEFROM
                            case FROM:
                                if ((cp = getaddress(from,1)) != NULLCHAR) {
                                    free(ind.from);
                                    ind.from = strdup(cp);
                                }
                                break;
#endif
                            case RRECEIPT:
                                if((cp = getaddress(buf,0)) != NULLCHAR){
                                    free(host);
                                    host = strdup(cp);
                                }
                                break;
#if defined(MAILCMDS) && defined(HOLD_LOCAL_MSGS)
                            case MSGID:
                                if(MbHoldLocal) {
                                    if(isheldarea(ap->val) &&
                                       (cp = getaddress(buf,0)) != NULLCHAR){
                                        if(strcmp(cp+strlen(cp)-4,".bbs")) /* not from a bbs */
                                            ind.status |= BM_HOLD;
                                       }
#if defined(UNIX) && defined(HOLD_PARSER)
                                    /* Apply possible 'external' hold regardless of msg origin */
                                    ind.status |= external_hold;
#endif  /* HOLD_PARSER */
                                }
                                break;
#endif
                        }
                        pwait(NULL);  /* let other processes run */
                    }
                /* Now the remaining data */
                    while((c = fread(buf,1,sizeof(buf),data)) > 0) {
                        if(fwrite(buf,1,(size_t)c,fp) != (size_t)c)
                            break;
                        pwait(NULL);  /* let other processes run */
                    }
                    if(ferror(fp))
                        fail = 1;
                    else
                    /* Leave a blank line between msgs */
                        fputc('\n',fp);
                    ind.size = ftell(fp) - start;
                    type = 1;   /* assume disk file, not a printer device */
#ifdef PRINTEROK
#if defined(MSDOS)
/* we now allow a printer device to be opened for output (see newfopen() in
   main.c.  If we've just now written to a printer, there's no point in
   writing the index, and in fact we should emit a FormFeed.  -- n5knx
*/
                    c = ioctl(fileno(fp), 0 /* get status */);
                    if (c != -1 && (c&0x9f) == 0x80) {  /* device, console,clock,nul flags */
                        type=0;     /* NOT a disk file, DON'T write the index */
                        fputc('\f', fp);
                    }
#elif defined(UNIX)
                    {
#include <sys/stat.h>
/*#include <unistd.h>*/
                        struct stat statbuf;


                        if (fstat(fileno(fp), &statbuf)==0 && !S_ISREG(statbuf.st_mode)) {
                            type=0;     /* NOT a disk file, DON'T write the index */
                            fputc('\f', fp);
                        }
                    }
#endif
#endif	/* PRINTEROK */
                    fclose(fp);
#ifdef SMTPTRACE
                  if (Smtptrace) {
/* If we use tprintf here, instead of printf, flowcontrol
 * in the command screen is used; if the system is unattended for
 * more then 24 messages coming in, it will lock up mail delivery.
 * Make sure this only goes to the command screen - WG7J
 */
#ifdef UNIX
/* true, but we defeat that when using the trace interface anyway.  KF8NH */
                    tcmdprintf("New mail for %s from <%s>%c\n",ap->val,from,(Smtpquiet? ' ': '\007'));
#else
                    if(Current->output == Command->output)
                        printf("New mail for %s from <%s>%c\n",ap->val,from, Smtpquiet ? ' ' : '\007');
#endif /* UNIX */
                  }
#endif /* SMTPTRACE */
                  if(host != NULLCHAR){
                      rewind(data); /* Send return receipt */
                      mdaemon(data,host,NULLLIST,0);
                      free(host);
                  }
                } else
                    fail = 1;

                if (fail) {
                    (void) rmlock(Mailspool,ap->val);
                    break;
                }
  
#ifdef USERLOG
            /* Now touch the timestamp file if it's an area - WG7J */
                if(isarea(ap->val)) {
                    sprintf(mailbox,"%s/%s.inf",Mailspool,ap->val);
                    fclose(fopen(mailbox,"w"));
                }
#endif
            /* Update the index file */
                if (type && write_index(ap->val,&ind) == -1)
                    log(-1,"smtpserv: can't update index for %s", ap->val);
  
            (void) rmlock(Mailspool,ap->val);

            /* make a log entry */
                smtplog("deliver: To: %s From: %s",ap->val,from);
            }
        }
  
    /* Free remaining data in index structure */
        default_index("",&ind);
  
        return fail;
    }
  
/* Return Date/Time in Arpanet format in passed string */
/* Result must be parsable by mydate() */
    char *
    ptime(t)
    long *t;
    {
    /* Print out the time and date field as
     *      "DAY, day MONTH year hh:mm:ss ZONE"
     */
        register struct tm *ltm;
        static char str[40];
#ifdef TIMEZONE_OFFSET_NOT_NAME
        time_t offset;
#endif
    /* Read the system time */
        ltm = localtime(t);
  
    /* I hope your runtime DST changeover dates match your government's ! */
  
    /* rfc 822 format */
#ifdef TIMEZONE_OFFSET_NOT_NAME     /* G8FSL */
        offset = (timezone - (ltm->tm_isdst * 3600L)) / ( -36L);
#ifndef MSDOS
        if (offset % 100L)  /* not integral hours? */
            offset = (100L * (offset/100L)) + (60L * (offset % 100L))/100L;
#endif
        sprintf(str,"%s, %.2d %s %04d %02d:%02d:%02d %+05ld\n",
#else
        sprintf(str,"%s, %.2d %s %04d %02d:%02d:%02d %.3s\n",
#endif
        Days[ltm->tm_wday],
        ltm->tm_mday,
        Months[ltm->tm_mon],
        ltm->tm_year+1900,  /* or just take it %100 and use %02d format */
        ltm->tm_hour,
        ltm->tm_min,
        ltm->tm_sec,
#ifdef TIMEZONE_OFFSET_NOT_NAME
        offset);
#else
        tzname[ltm->tm_isdst]);
#endif
        return(str);
    }
  
    long
    get_msgid()
    {
        char sfilename[LINELEN];
        char s[20];
        register long sequence = 0;
        FILE *sfile;
  
#ifdef UNIX
        int lfd, cnt;
        long pid;
  
    /*
     * I have a filter (u2j) which injects messages into JNOS for SMTP
     * delivery.  It's a good idea to make sure the sequence file is locked
     * while we update it, so JNOS/Linux and u2j don't get into a race for
     * the next message ID.
     */
        sprintf(sfilename, "%s/sequence.lck", Mailqdir);
        while ((lfd = open(sfilename, O_WRONLY|O_CREAT|O_EXCL, 0600)) == -1)
        {
            if (errno != EEXIST || ++cnt == 5)
            {
                log(-1, "can't lock sequence file %s: %s", sfilename,
                strerror(errno));
                where_outta_here(1,0);
            }
            if ((lfd = open(sfilename, O_RDONLY)) != -1)
            {
                sfile = fdopen(lfd, "r");
                fscanf(sfile, "%ld", &pid);
                fclose(sfile);
                if (kill(pid, 0) == -1 && errno == ESRCH)
                {
                    unlink(sfilename);
                    continue;
                }
            }
            pause(500);
        }
        sprintf(sfilename, "%10u\n", getpid());
        write(lfd, sfilename, strlen(sfilename));
        close(lfd);
#endif /* UNIX */
  
        sprintf(sfilename,"%s/sequence.seq",Mailqdir);
        sfile = fopen(sfilename,READ_TEXT);
  
    /* if sequence file exists, get the value, otherwise set it */
        if (sfile != NULL){
            (void) fgets(s,sizeof(s),sfile);
            sequence = atol(s);
        /* Keep it in range of an 8 digit number to use for dos name prefix.
         * The bbs spec states that msg#'s on the R: line should be 0-99999
         * inclusive; this is enforced by use of modulus in sendmsg() -- N5KNX 1.10i
         */
#ifdef UNIX
            if (sequence < 1L || sequence > 999999999L)
#else
            if (sequence < 1L || sequence > 99999999L )
#endif
                sequence = 1;
            fclose(sfile);
        }
  
    /* increment sequence number, and write to sequence file */
        sfile = fopen(sfilename,WRITE_TEXT);
        fprintf(sfile,"%ld",++sequence);
        fclose(sfile);
#ifdef UNIX
        sprintf(sfilename, "%s/sequence.lck", Mailqdir);
        unlink(sfilename);
#endif
        return sequence;
    }
  
/* test if mail address is valid */
    int
    validate_address(s)
    char *s;
    {
        char *cp;
        int32 addr;
  
#ifdef  NNTPS
        if(*s == '!'){
            if((cp = strpbrk(s,"%@.,/")) != NULLCHAR)
                *cp = '\0';
            return NNTP_GATE;
        }
#endif
    /* if address has @ in it then check dest address */
        if ((cp = strrchr(s,'@')) != NULLCHAR){
            cp++;
        /* 1st check if it is our hostname.
        * if not then check the hosts file and see if we can
        * resolve the address to a known site or one of our aliases.
        */
            if(stricmp(cp,Hostname) != 0){
                if ((addr = mailroute(cp)) == 0L)
                    if ((Smtpmode & QUEUE) == 0)
                        return BADADDR;
                    else
                        return LOCAL;   /* use external router */
                if (ismyaddr(addr) == NULLIF)
                    return DOMAIN;
            }
  
        /* on a local address remove the host name part */
            *--cp = '\0';
        }
  
    /* if using an external router leave address alone */
        if ((Smtpmode & QUEUE) != 0)
            return LOCAL;
  
    /* check for the user%host hack */
        if ((cp = strrchr(s,'%')) != NULLCHAR){
            *cp = '@';
            cp++;
        /* reroute based on host name following the % separator */
            if (mailroute(cp) == 0)
                return BADADDR;
            else
                return DOMAIN;
        }
  
#ifdef MSDOS    /* dos file name checks */
    /* Check for characters illegal in MS-DOS file names */
        for(cp = s;*cp != '\0';cp++){
        /* Accept '.', '/', and '\' !
         * that way we can mail into subdirs - WG7J
         */
            if(*cp == '.' || *cp == '\\' || *cp == '/')
                continue;
            if(dosfnchr(*cp) == 0){
                return BADADDR;
            }
        }
#endif
#ifndef  NNTPS
        /* disallow nntp-gate syntax in local deliveries with no nntp-gate */
        if(*s == '!')
            return BADADDR;
#endif
        return LOCAL;
    }
  
/* place a mail job in the outbound queue.  Return 0 if successful, else 1 */
    int
    queuejob(dfile,host,to,from)
    FILE *dfile;
    char *host;
    struct list *to;
    char *from;
    {
        FILE *fp;
        struct list *ap;
        char tmpstring[50], prefix[9], buf[LINELEN];
        register int cnt;
  
        sprintf(prefix,"%ld",get_msgid());
        mlock(Mailqdir,prefix);
        sprintf(tmpstring,"%s/%s.txt",Mailqdir,prefix);
        if((fp = fopen(tmpstring,WRITE_TEXT)) == NULLFILE){
            (void) rmlock(Mailqdir,prefix);
            return 1;
        }
        while((cnt = fread(buf, 1, LINELEN, dfile)) > 0) {
            if(fwrite(buf, 1, (unsigned)cnt, fp) != (unsigned)cnt)
                break;
            pwait(NULL);   /* allow other processes to run */
        }
        if(ferror(fp)){
            fclose(fp);
            (void) rmlock(Mailqdir,prefix);
            return 1;
        }
        fclose(fp);
        sprintf(tmpstring,"%s/%s.wrk",Mailqdir,prefix);
        if((fp = fopen(tmpstring,WRITE_TEXT)) == NULLFILE){
            (void) rmlock(Mailqdir,prefix);
            return 1;
        }
        fprintf(fp,"%s\n%s\n",host,from);
        for(ap = to; ap != NULLLIST; ap = ap->next){
            fprintf(fp,"%s\n",ap->val);
            smtplog("queue job %s To: %s From: %s",prefix,ap->val,from);
        }
        fclose(fp);
        (void) rmlock(Mailqdir,prefix);
        return 0;
    }
  
/* Deliver mail to the appropriate mail boxes.  Return 0 if successful, else 1 */
    static int
    router_queue(data,from,to)
    FILE *data;
    char *from;
    struct list *to;
    {
        int c;
        register struct list *ap;
        FILE *fp;
        char tmpstring[50];
        char prefix[9];
  
        sprintf(prefix,"%ld",get_msgid());
        mlock(Routeqdir,prefix);
        sprintf(tmpstring,"%s/%s.txt",Routeqdir,prefix);
        if((fp = fopen(tmpstring,WRITE_TEXT)) == NULLFILE){
            (void) rmlock(Routeqdir,prefix);
            return 1;
        }
        rewind(data);
        while((c = getc(data)) != EOF)
            if(putc(c,fp) == EOF)
                break;
        if(ferror(fp)){
            fclose(fp);
            (void) rmlock(Routeqdir,prefix);
            return 1;
        }
        fclose(fp);
        sprintf(tmpstring,"%s/%s.wrk",Routeqdir,prefix);
        if((fp = fopen(tmpstring,WRITE_TEXT)) == NULLFILE){
            (void) rmlock(Routeqdir,prefix);
            return 1;
        }
        fprintf(fp,"From: %s\n",from);
        for(ap = to;ap != NULLLIST;ap = ap->next){
            fprintf(fp,"To: %s\n",ap->val);
        }
        fclose(fp);
        (void) rmlock(Routeqdir,prefix);
        smtplog("rqueue job %s From: %s",prefix,from);
        return 0;
    }
  
/* add an element to the front of the list pointed to by head
** return NULLLIST if out of memory.
*/
    struct list *
    addlist(head,val,type,aux)
    struct list **head;
    char *val;
    int type;
    char *aux;
    {
        register struct list *tp;
  
        tp = (struct list *)callocw(1,sizeof(struct list));
  
        tp->next = NULLLIST;
  
    /* allocate storage for the char string */
        tp->val = strdup(val);
        tp->aux = (aux == NULLCHAR) ? NULLCHAR : strdup(aux);
        tp->type = type;
  
    /* add entry to front of existing list */
        if (*head != NULLLIST)
            tp->next = *head;
        *head = tp;
        return tp;
  
    }
  
#ifdef SMTP_VALIDATE_LOCAL_USERS
/* return 0 if userid is in the first field of some line in DBfile,
          1 if not in any line,
         -1 if file can't be opened
   File lines begin with # if a comment, and the first field is terminated
   by SP, HT, NL or ':'.  Case is not important.  No continuations are allowed.
*/
static int firstfield(char *userid, char *DBfile)
{
    FILE *fp;
    char buf[LINELEN], *p;
    int ret=1;
    
    if ((fp = fopen(DBfile, READ_TEXT)) == NULLFILE) return -1;
    while (fgets(buf,LINELEN,fp) != NULLCHAR){
        if ( *buf == '#' )
            continue;
        if ((p=strpbrk(buf," 	:\n")) != NULLCHAR) *p='\0';
        if ((ret=stricmp(buf,userid)) == 0) break;
    }
    fclose(fp);
    return ret?1:0;
}
#endif /* SMTP_VALIDATE_LOCAL_USERS */

#define SKIPWORD(X) while(*X && *X!=' ' && *X!='\t' && *X!='\n' && *X!= ',') X++;
#define SKIPSPACE(X) while(*X ==' ' || *X =='\t' || *X =='\n' || *X == ',') X++;
  
/* check for an alias and expand alias into an address list */
/* Note that the returned list can be NULL if SMTP_VALIDATE_LOCAL_USERS is defined */
    static struct list *
    expandalias(head, user, origuser)
    struct list **head;
    char *user;
    char *origuser;
    {
        FILE *fp;
        register char *s,*p;
        struct rr *rrp, *rrlp;
        int inalias = 0;
        struct list *tp;
        char buf[LINELEN];
  
        if ((fp = fopen(Alias, READ_TEXT)) == NULLFILE){
        /* no alias file found, so try MB, MG or MR domain name records */
            rrlp = rrp = resolve_mailb(user);
            while(rrp != NULLRR){
                if(rrp->rdlength > 0){
                /* remove the trailing dot */
                    rrp->rdata.name[rrp->rdlength-1] = '\0';
                /* replace first dot with @ if there is no @ */
                    if(strchr(rrp->rdata.name,'@') == NULLCHAR
                        && (p = strchr(rrp->rdata.name,'.')) != NULLCHAR)
                            *p = '@';
                    if(strchr(rrp->rdata.name,'@') != NULLCHAR)
                        tp = addlist(head,rrp->rdata.name,DOMAIN,NULLCHAR);
                    else
                        tp = addlist(head,rrp->rdata.name,LOCAL,NULLCHAR);
                    ++inalias;
                }
                rrp = rrp->next;
            }
            free_rr(rrlp);
        }
        else {  /* process the alias file */
            while (fgets(buf,LINELEN,fp) != NULLCHAR){
                p = buf;
                if ( *p == '#' || *p == '\0')
                    continue;
                rip(p);
  
            /* if not in a matching entry skip continuation lines */
                if (!inalias && isspace(*p))
                    continue;
  
            /* when processing an active alias check for a continuation */
                if (inalias){
                    if (!isspace(*p))
                        break;  /* done */
                } else {
                    s = p;
                    SKIPWORD(p);
                    *p++ = '\0';    /* end the alias name */
                    if (strcmp(s,user) != 0)
                        continue;   /* no match go on */
                    inalias = 1;
                }
  
            /* process the recipients on the alias line */
                SKIPSPACE(p);
                while(*p != '\0' && *p != '#'){
                    s = p;
                    SKIPWORD(p);
                    if (*p != '\0')
                        *p++ = '\0';
  
                /* find hostname */
#ifdef  NNTPS
                    if(*s == '!')
                        tp = addlist(head,s,NNTP_GATE,NULLCHAR);
                    else
#endif
#ifdef	ALIASEXTFILE
                    if (*s == '&') {    /* &xxx => replace by lines from file xxx */
                        char buf2[LINELEN], *cp2;
                        FILE *fp2;

                        if ((fp2 = fopen((cp2=rootdircat(++s)), READ_TEXT)) != NULLCHAR) {
                            while (fgets(buf2, sizeof(buf2), fp2) != NULLCHAR) {
                                rip(buf2);
                                if (*buf2 == '#') continue;
                                else if (strchr(buf2, '@') != NULLCHAR)
                                    tp = addlist(head,buf2,DOMAIN,NULLCHAR);
                                else
                                    tp = addlist(head,buf2,LOCAL,NULLCHAR);
                            }
                            fclose(fp2);
                        }
                        if (s != cp2) free(cp2);
                    }
                    else
#endif /* ALIASEXTFILE */
                        if (strchr(s,'@') != NULLCHAR)
                            tp = addlist(head,s,DOMAIN,NULLCHAR);
                        else
                            tp = addlist(head,s,LOCAL,NULLCHAR);
                    SKIPSPACE(p);
                }
            }
            (void) fclose(fp);
        }

        if (inalias)    /* found and processed an alias */
            return tp;
        else {           /* no alias found; treat as a local address */
#ifdef SMTP_VALIDATE_LOCAL_USERS
            if (firstfield(user,Userfile)  /* in ftpusers file? */
#ifdef MAILCMDS
                && firstfield(user,Arealist) /* mbox S-cmd area destination? */
#endif
#if defined(USERLOG) && !defined(SMTP_VALIDATE_FTPPOP_ONLY)
                && firstfield(user,UDefaults) /* ever connected? */
#endif
#if defined(POP2SERVER) || defined(POP3SERVER)
                && firstfield(user,Popusers) /* in popusers file? */
#endif
               ) return *head;  /* no one we know, so ignore user */
#endif /* SMTP_VALIDATE_LOCAL_USERS */
            return addlist(head, user, LOCAL, origuser);
        }
    }
  
#if defined(ANSIPROTO)
    static void
    smtplog(char *fmt, ...)
    {
        va_list ap;
        char *cp;
        long t;
        FILE *fp;
  
        if ((fp = fopen(Maillog,APPEND_TEXT)) == NULLFILE)
            return;
        time(&t);
        cp = ctime(&t);
        fprintf(fp,"%.24s ",cp);
        va_start(ap,fmt);
        vfprintf(fp,fmt,ap);
        va_end(ap);
        fprintf(fp,"\n");
        fclose(fp);
    }
  
#else
  
    static void
    smtplog(fmt,arg1,arg2,arg3,arg4)
    char *fmt;
    int arg1,arg2,arg3,arg4;
    {
        char *cp;
        long t;
        FILE *fp;
  
        if ((fp = fopen(Maillog,APPEND_TEXT)) == NULLFILE)
            return;
        time(&t);
        cp = ctime(&t);
        fprintf(fp,"%.24s ",cp);
        fprintf(fp,fmt,arg1,arg2,arg3,arg4);
        fprintf(fp,"\n");
        fclose(fp);
    }
#endif
  
/* send mail to a single user. Called by mdaemon(), to implement the
** return-mail/return-receipt function (from addr will be "").  Also called by
** domailmsg() and news2mail().  We will queue msg unless from="", so that bid
** checks are done and news2mail() can't produce a local dupe msg.  -- n5knx 11/96
** Return 0 if successful, 1 if permanent failure.
*/
    int
    mailuser(data,from,to)
    FILE *data;
    char *from;
    char *to;
    {
  
        int address_type, ret;
        struct list *tolist = NULLLIST;
        char *newaddr, *origaddr;
  
        if((newaddr = rewrite_address(to,REWRITE_TO)) != NULLCHAR){
            if(!strcmp(newaddr,"refuse")) {
                free(newaddr);
                return 1;
            }
            origaddr = to;
            to = newaddr;
        }
        else origaddr=NULLCHAR;
  
        /* check if address is ok */
        if ((address_type = validate_address(to)) == BADADDR){
            free (newaddr);
            return 1;
        }
        /* if a local address check for an alias */
        if (address_type == LOCAL)
            expandalias(&tolist, to, origaddr);
        else
            /* a remote address is added to the list */
            addlist(&tolist, to, address_type, NULLCHAR);

        /* Do direct delivery when from="", else just queue the msg */
        if (*from) {
            rewind(data);
            ret = queuejob(data,Hostname,tolist,from);
        }
        else
            ret = mailit(data,from,tolist,FALSE);
        del_list(tolist);
        free (newaddr);
        return ret;
  
    }
  
/* Mailer daemon return mail mechanism */
    int
    mdaemon(data,to,lp,bounce)
    FILE *data;     /* pointer to rewound data file */
    char *to;       /* Overridden by Errors-To: line if bounce is true */
    struct list *lp;    /* error log for failed mail */
    int bounce;     /* True for failed mail, otherwise return receipt */
    {
        time_t t;
        FILE *tfile;
        char buf[LINELEN], *cp, *newto = NULLCHAR;
        int cnt;

        if(to == NULLCHAR || (to != NULLCHAR && *to == '\0') || bounce){
            cnt = NOHEADER;
            while(fgets(buf,sizeof(buf),data) != NULLCHAR){
                if(buf[0] == '\n')
                    break;
            /* Look for Errors-To: */
                if(htype(buf,&cnt) == ERRORSTO &&
                (cp = getaddress(buf,(*buf==' ' || *buf=='\t'))) != NULLCHAR){
                    free(newto);
                    newto = strdup(cp);
                    break;
                }
            }
            if(newto == NULLCHAR && ((to != NULLCHAR && *to == '\0') ||
                to == NULLCHAR))
                return -1;
            rewind(data);
        }
        if((tfile = tmpfile()) == NULLFILE)
            return -1;
        time(&t);
        fprintf(tfile,"%s%s",Hdrs[DATE],ptime(&t));
        fprintf(tfile,"%s<%ld@%s>\n",Hdrs[MSGID],get_msgid(),Hostname);
        fprintf(tfile,"%sMAILER-DAEMON@%s (Mail Delivery Subsystem)\n",
        Hdrs[FROM],Hostname);
        fprintf(tfile,"%s%s\n",Hdrs[TO],newto != NULLCHAR ? newto : to);
        fprintf(tfile,"%s%s\n\n",Hdrs[SUBJECT],
        bounce ? "Failed mail" : "Return receipt");
        if(bounce){
            fprintf(tfile,"  ===== transcript follows =====\n\n");
            for (; lp != NULLLIST; lp = lp->next)
                fprintf(tfile,"%s\n",lp->val);
            fprintf(tfile,"\n");
        }
        fprintf(tfile,"  ===== %s follows ====\n",
        bounce ? "Unsent message" : "Message header");
  
        while(fgets(buf,sizeof(buf),data) != NULLCHAR){
            if(buf[0] == '\n')
                break;
            fputs(buf,tfile);
        }
        if(bounce){
            fputc('\n',tfile);
            while((cnt = fread(buf,1,sizeof(buf),data)) > 0)
                fwrite(buf,1,cnt,tfile);
        }
        fseek(tfile,0L,0);
    /* A null From<> so no looping replys to MAIL-DAEMONS */
        (void) mailuser(tfile,"",newto != NULLCHAR ? newto : to);
        fclose(tfile);
        free(newto);
        return 0;
    }
  
  
#ifdef MAILMSG
/* Command to email a file or msg to a user */
/* mailmsg <to> [<subject>] <msg|/filepath> (from {MSGUSER|sysop}@Hostname) */
int
domailmsg(argc,argv,p)
int argc;
char *argv[];
void *p;
{
        time_t t;
        FILE *tfile,*sfile;
        int cnt;
        char buf[LINELEN], *from, *fromid, *to=argv[1];

        if((tfile = tmpfile()) == NULLFILE)
            return -1;

        if((fromid = getenv("MSGUSER")) == NULLCHAR)   /* G6OPM */
            fromid = "sysop";

        time(&t);
        fprintf(tfile,"%sby %s with SMTP\n\tid AA%ld ; %s",
            Hdrs[RECEIVED], Hostname, get_msgid(), ptime(&t));
        fprintf(tfile,"%s%s",Hdrs[DATE],ptime(&t));
        fprintf(tfile,"%s<%ld@%s>\n",Hdrs[MSGID],get_msgid(),Hostname);
        sprintf(from=mallocw(strlen(Hostname)+strlen(fromid)+2), "%s@%s", fromid, Hostname);
        fprintf(tfile,"%s%s\n",Hdrs[FROM],from);
        fprintf(tfile,"%s%s\n",Hdrs[TO],argv[1]);
        if (argc > 3)
            fprintf(tfile,"%s%s\n\n",Hdrs[SUBJECT],argv++[2]);
        else
            fputc('\n',tfile);

#ifdef UNIX
        if (*argv[2] == '/') {
#else
        if (*argv[2] == '/' || *argv[2] == '\\' || argv[2][1] == ':') {
#endif
            if((sfile=fopen(argv[2],READ_TEXT))==NULLFILE) {
                tprintf("Can't open %s\n", argv[2]);
                fclose(tfile);
                free(from);
                return 1;
            }
            while((cnt = fread(buf,1,sizeof(buf),sfile)) > 0)
                fwrite(buf,1,cnt,tfile);
            fclose(sfile);
        }
        else
            fprintf(tfile,"%s\n",argv[2]);
        if(ferror(tfile))
            cnt=1;
        else {
            fseek(tfile,0L,0);
            cnt=mailuser(tfile,from,to);
        }
        fclose(tfile);
        free(from);
        return cnt;  /* 0 if successful, 1 if failure */
}
#endif /* MAILMSG */

#ifdef HOLD_LOCAL_MSGS
/* Returns 1 if name is listed in Holdlist, 0 otherwise. Similar to isarea(). */
int
isheldarea(name)
char *name;
{
    FILE *fp;
    char *area;
    char buf[LINELEN];
  
    if((fp = fopen(Holdlist,READ_TEXT)) == NULLFILE)
        return 0;
    area = strdup(name);
    dotformat(area);
    while(fgets(buf,sizeof(buf),fp) != NULLCHAR) {
        /* The first word on each line is all that matters */
        firsttoken(buf);
        dotformat(buf);
        if(stricmp(area,buf) == 0) {    /* found it */
            fclose(fp);
            free(area);
            return 1;
        }
    }
    fclose(fp);
    free(area);
    return 0;
}
#endif
