/* 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>
#ifdef UNIX
#include <sys/types.h>
#include <fcntl.h>
#endif
#if defined(__STDC__) || defined(__TURBOC__)
#include <stdarg.h>
#endif
#include <dir.h>
#include <ctype.h>
#include <setjmp.h>
#include "global.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
  
static struct list *expandalias __ARGS((struct list **head,char *user));
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));
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));
static int mailuser __ARGS((FILE *data,char *from,char *to));
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
#ifdef  LZW
    "xlzw",
#define XLZW_CMD    9
#endif
    NULLCHAR
};
  
int SmtpUsers;
  
/* 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 XLZW\n214 End\n";
#else
static char Help[] = "214-Commands:\n214-HELO NOOP MAIL QUIT RCPT HELP DATA RSET EXPN\n214 End\n";
#endif
static char Enter[] = "354 Enter mail, end with .\n";
static char DupBidMsg[] = "550 Duplicate BID in Message-Id line\n";
static char Ioerr[] = "452 Temp file write error\n";
static char Badcmd[] = "500 Command unrecognized\n";
static char Syntax[] = "501 Syntax error\n";
static char Needrcpt[] = "503 Need RCPT (recipient)\n";
static char Unknown[] = "550 <%s> address unknown\n";
static char Noalias[] = "550 No alias for <%s>\n";
  
extern int SmtpServTdisc;
extern int SmtpBidCheck;
  
/* 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]);
  
    return start_tcp(port,"SMTP Server",smtpserv,2048);
}
  
/* 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;
    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");
    SmtpUsers++;
  
    if((mp = mail_create()) == NULLSMTPSV){
        tputs(Nospace);
        log(s,"close SMTP - no space");
        close_s(s);
        SmtpUsers--;
        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);
    if(cnt < 4){
        /* Can't be a legal command */
        usputs(mp->s,Badcmd);
        goto loop;
    }
    rip(buf);
    cmd = buf;
  
    /* Translate entire buffer to lower case */
    for(cp = cmd;*cp != '\0';cp++)
        *cp = tolower(*cp);
  
    /* Find command in table; if not present, return syntax error */
    for(cmdp = commands;*cmdp != NULLCHAR;cmdp++)
        if(strncmp(*cmdp,cmd,strlen(*cmdp)) == 0)
            break;
    if(*cmdp == NULLCHAR){
        (void) usputs(mp->s,Badcmd);
        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) {
                usputs(mp->s,Badcmd);
            } else {
                lzwmode = lzwbits = 0;
                sscanf(arg,"%d %d",&lzwbits,&lzwmode);
                if(!((lzwmode == 0 || lzwmode == 1)
                && (lzwbits > 8 && lzwbits < 17))) {
                    lzwmode = LZWCOMPACT;
                    lzwbits = LZWBITS;
                    usputs(mp->s,Badcmd);
                } 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);
            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 */
            if((newaddr = rewrite_address(cp)) != NULLCHAR){
                strcpy(buf,newaddr);
                cp = buf;
                free(newaddr);
            }
  
        /* check if address is ok */
            if ((address_type = validate_address(cp)) == BADADDR){
                (void) usprintf(mp->s,Unknown,cp);
                break;
            }
        /* if a local address check for an alias */
            if (address_type == LOCAL)
                expandalias(&mp->to, cp);
            else
            /* a remote address is added to the list */
                addlist(&mp->to, cp, address_type);
  
            (void) usputs(mp->s,Ok);
            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 (getmsgtxt(mp) == 2) ++delay_retry;
            break;
        case RSET_CMD:
            del_list(mp->to);
            mp->to = NULLLIST;
            (void) usputs(mp->s,Reset);
            break;
        case EXPN_CMD:
            if (*arg == '\0'){
                (void) usputs(mp->s,Syntax);
                break;
            }
  
            list = NULLLIST;
        /* rewrite address if possible */
            if((newaddr = rewrite_address(arg)) != NULLCHAR)
                if(strcmp(newaddr,arg) == 0){
                    free(newaddr);
                    newaddr = NULLCHAR;
                } else {
                    strcpy(buf,newaddr);
                    arg = buf;
                }
            list = NULLLIST;
            expandalias(&list,arg);
            if (strcmp(list->val,arg) == 0 && list->next == NULLLIST)
                if(newaddr == NULLCHAR){
                    (void) usprintf(mp->s,Noalias,arg);
                    del_list(list);
                    break;
                }
            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);
            free(newaddr);
            break;
    }
    goto loop;
  
    quit:
    log(mp->s,"close SMTP");
    SmtpUsers--;
    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 smtpkick() 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
 * Returns: 0 => no errors, 1 => permanent error, 2 => temporary error (Ioerr)
 */
static int
getmsgtxt(mp)
struct smtpsv *mp;
{
    char buf[LINELEN];
    char *p = buf;
    int  mailed_ok=0;
    long t;
    char *cp;
#ifdef MBFWD
    int idnotfound;
    time_t now;
    char bid[LINELEN];
    FILE *fp;
#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,sizeof(buf)) == -1){
            return 1;
        }
        alarm(0L);
        rip(p);
        /* check for end of message ie a . or escaped .. */
        if (*p == '.'){
            if (*++p == '\0'){
#ifdef MBFWD
                /* Also sends appropriate response */
                /* if duplicate BID, do not send - WG7J/N9HKM */
                if(SmtpBidCheck && mp->dupbid) {
                    (void) usputs(mp->s,DupBidMsg);
                    mailed_ok=1;
                }
                else
#endif
                    if(mailit(mp->data,mp->from,mp->to) != 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((fp = fopen(Historyfile,APPEND_TEXT)) != NULL) {
                        /* Timestamp added to allow automatic expiry
                         * of bid file - WG7J
                         */
                        time(&now);
                        fprintf(fp,"%s %ld\n",mp->bid,now); /* Save BID */
                        fclose(fp);
                        /* Free this bid ! */
                        free(mp->bid);
                        mp->bid = NULL;
                    }
#endif
                return mailed_ok;
            } else if (!(*p == '.' && *(p+1) == '\0'))
                p--;
        }
        /* for UNIX mail compatiblity */
        if (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(fprintf(mp->data,"%s\n",p) < 0){
            (void) usputs(mp->s,Ioerr);
            return 2;
        }
#ifdef MBFWD
        /* Check for Message-Id string - WG7J */
        if(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[13] = '\0'; /* BIDs should be no longer than 13 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;
  
    mp = (struct smtpsv *)callocw(1,sizeof(struct smtpsv));
    mp->from = strdup("");  /* Default to null From address */
    return mp;
}
  
/* Free resources, delete control block */
static void
mail_clean(mp)
register struct smtpsv *mp;
{
    if (mp == NULLSMTPSV)
        return;
    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
*/
/* 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)
FILE *data;
char *from;
struct list *tolist;
{
    struct list *ap, *dlist = NULLLIST;
    FILE *fp;
    char mailbox[50], *cp0, *cp, *host, *qhost;
    int c, fail = 0;
    time_t t;
    extern int Smtpquiet;
    int index,type;
    char *s;
    long start;
    struct mailindex ind;
    char buf[128];
  
    if ((Smtpmode & QUEUE) != 0)
        return(router_queue(data,from,tolist));
  
    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);
                }
            }
        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
  
    /* 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);
                mkdir(buf);
                *cp = '/';
            }
  
        /* if mail file is busy save it in our smtp queue
         * and let the smtp daemon try later.
         */
        if (mlock(Mailspool,ap->val)){
            addlist(&dlist,ap->val,0);
            fail = queuejob(data,Hostname,dlist,from);
            del_list(dlist);
            dlist = NULLLIST;
        } else {
            char buf[LINELEN];
            int tocnt = 0;
            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;
                /* Read the SMTP header, but first
                 * read the 'Received...' and 'ID... lines'
                 * to get the msgid - WG7J
                 */
                    fgets(buf,sizeof(buf),data);
                    fputs(buf,fp);
                    fgets(buf,sizeof(buf),data);
                    fputs(buf,fp);
                    if((cp=strstr(buf,"AA")) != NULLCHAR)
                    /*what follows is the message-number*/
                        ind.msgid = atol(cp+2);
                    if((cp=strchr(buf,';')) != NULL)
                        ind.mydate = mydate(cp+2);
  
                    while(fgets(buf,sizeof(buf),data) != NULLCHAR){
                        if(buf[0] == '\n'){
                        /* End of headers */
                            if(tocnt == 0) {
                                fprintf(fp,"%s%s\n",
                                Hdrs[APPARTO],
                                ap->val);
                                if(ind.to)
                                    free(ind.to);
                                ind.to = strdup(ap->val);
                            }
                            fputc('\n',fp);
                            break;
                        }
                        type = htype(buf);
                    /* get a unique, new id for all messages - from KO4KS */
                        if(index && (type == MSGID)) {
                        /* KD4CIM */
                            if(strstr(buf,Hostname))    /* Is it our id ? */
                            /* Yes, give it a new one */
                                sprintf(buf,"%s<%ld@%s>\n",Hdrs[MSGID],get_msgid(),Hostname);
                        }
                        fputs(buf,fp);
                        rip(buf);
                        set_index(buf,&ind);
                        switch(type){
                            case TO:
                            case CC:
                                ++tocnt;
                                break;
                            case RRECEIPT:
                                if((cp = getaddress(buf,0)) != NULLCHAR){
                                    free(host);
                                    host = strdup(cp);
                                }
                                break;
                        }
                    }
                /* Now the remaining data */
                    while((c = fread(buf,1,sizeof(buf),data)) > 0)
                        if(fwrite(buf,1,c,fp) != c)
                            break;
                    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(linux)
/* need some linux code here...*/
#endif
#endif	/* PRINTOK */
                    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 LINUX
/* true, but we defeat that when using the trace interface anyway.  KF8NH */
                    CmdOverride = 1;
                    tcmdprintf(stdout,"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
                    if(host != NULLCHAR){
                        rewind(data); /* Send return receipt */
                        mdaemon(data,host,NULLLIST,0);
                        free(host);
                    }
                  }
#endif
                } 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 */
    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 tz[4];
        static char str[40];
        char *p;
    /* Read the system time */
        ltm = localtime(t);
  
        if (*tz == '\0')
            if ((p = getenv("TZ")) == NULL)
                strcpy(tz,"UTC");
            else if (ltm->tm_isdst) {
           /* I hope your runtime DST changeover dates match your government's ! */
                while (*p && (*p < '0' || *p > '9')) p++;  /* skip past std time name and sign offset */
                while (*p >= '0' && *p <= '9') p++; /* and GMT offset */
                strncpy(tz,p,3);
            }
            else strncpy(tz,p,3);
  
    /* rfc 822 format */
        sprintf(str,"%s, %.2d %s %02d %02d:%02d:%02d %.3s\n",
        Days[ltm->tm_wday],
        ltm->tm_mday,
        Months[ltm->tm_mon],
        ltm->tm_year,
        ltm->tm_hour,
        ltm->tm_min,
        ltm->tm_sec,
        tz);
        return(str);
    }
  
    long
    get_msgid()
    {
        char sfilename[LINELEN];
        char s[20];
        register long sequence = 0;
        FILE *sfile;
  
#ifdef LINUX
        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);
            }
            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, "%10ld\n", getpid());
        write(lfd, sfilename, strlen(sfilename));
        close(lfd);
#endif /* LINUX */
  
        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 a 5 digit number to use for dos name prefix. */
#ifdef LINUX
        /* The bbs spec states that msg#'s on the R: line should be 0-99999
         * inclusive; this violates that 'non-standard' 8-) - WG7J
         */
            if (sequence < 1L || sequence > 999999999L)
#else
                if (sequence < 1L || sequence > 99999L )
#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 LINUX
        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;
  
        if(*s == '!'){
#ifdef  NNTPS
            if((cp = strpbrk(s,"%@.,/")) != NULLCHAR)
                *cp = '\0';
            return NNTP_GATE;
#else
            return BADADDR;
#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 know site or one of our aliases.
        */
            if(stricmp(cp,Hostname) != 0){
                if ((addr = mailroute(cp)) == 0
                    && (Smtpmode & QUEUE) == 0)
                    return BADADDR;
                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 % seperator */
            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
        return LOCAL;
    }
  
/* place a mail job in the outbound queue */
    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, cnt, fp) != cnt)
                break;
        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 */
    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)
    struct list **head;
    char *val;
    int type;
    {
        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->type = type;
  
    /* add entry to front of existing list */
        if (*head == NULLLIST)
            *head = tp;
        else {
            tp->next = *head;
            *head = tp;
        }
        return tp;
  
    }
  
#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 and alias and expand alias into a address list */
    static struct list *
    expandalias(head, user)
    struct list **head;
    char *user;
    {
        FILE *fp;
        register char *s,*p;
        struct rr *rrp, *rrlp;
        int inalias = 0;
        struct list *tp;
        char buf[LINELEN];
  
    /* no alias file found */
        if ((fp = fopen(Alias, READ_TEXT)) == NULLFILE){
        /* 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);
                    else
                        tp = addlist(head,rrp->rdata.name,
                        LOCAL);
                    ++inalias;
                }
                rrp = rrp->next;
            }
            free_rr(rrlp);
            if(inalias)
                return tp;
            else
                return addlist(head, user, LOCAL);
        }
  
        while (fgets(buf,LINELEN,fp) != NULLCHAR){
            p = buf;
            if ( *p == '#' || *p == '\0')
                continue;
            rip(p);
  
        /* if not in an 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);
                else
#endif
                    if (strchr(s,'@') != NULLCHAR)
                        tp = addlist(head,s,DOMAIN);
                    else
                        tp = addlist(head,s,LOCAL);
                SKIPSPACE(p);
            }
        }
        (void) fclose(fp);
  
        if (inalias)    /* found and processed and alias. */
            return tp;
  
    /* no alias found treat as a local address */
        return addlist(head, user, LOCAL);
    }
  
#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);
        rip(cp);
        fprintf(fp,"%s ",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);
        rip(cp);
        fprintf(fp,"%s ",cp);
        fprintf(fp,fmt,arg1,arg2,arg3,arg4);
        fprintf(fp,"\n");
        fclose(fp);
    }
#endif
  
/* send mail to a single user. Can be called from the ax25 mailbox or
** from the return mail function in the smtp client
*/
    static int
    mailuser(data,from,to)
    FILE *data;
    char *from;
    char *to;
    {
  
        int address_type, ret;
        struct list *tolist = NULLLIST;
  
        /* check if address is ok */
        if ((address_type = validate_address(to)) == BADADDR){
            return 1;
        }
        /* if a local address check for an alias */
        if (address_type == LOCAL)
            expandalias(&tolist, to);
        else
            /* a remote address is added to the list */
            addlist(&tolist, to, address_type);
        ret = mailit(data,from,tolist);
        del_list(tolist);
        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){
            while(fgets(buf,sizeof(buf),data) != NULLCHAR){
                if(buf[0] == '\n')
                    break;
            /* Look for Errors-To: */
                if(htype(buf) == ERRORSTO &&
                (cp = getaddress(buf,0)) != 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;
    }
  
  
