/* 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,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));
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
    "vrfy",
#define VRFY_CMD    9
#ifdef  LZW
    "xlzw",
#define XLZW_CMD    10
#endif
    NULLCHAR
};
  
#ifdef STATUSWIN
int SmtpUsers;
#endif
  
/* 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";
#ifdef notdef
static char DupBidMsg[] = "550 Duplicate BID in Message-Id line\n";
#endif
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";
  
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]);
  
    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,*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");
#ifdef STATUSWIN
    SmtpUsers++;
#endif
  
    if((mp = mail_create()) == NULLSMTPSV){
        tputs(Nospace);
        log(s,"close SMTP - no space");
        close_s(s);
#ifdef STATUSWIN
        SmtpUsers--;
#endif
        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){
                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);
            }
            else origaddr=strdup(cp);
  
        /* 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);
            else
            /* 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 (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:
        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)) != 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);
            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");
#ifdef STATUSWIN
    SmtpUsers--;
#endif
    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 = buf;
    int  mailed_ok=0;
    int  headers=1, flag=0;
    int  i,prevtype = NOHEADER;
    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 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) {
#ifdef notdef
/* N5KNX: this is the old (bad) way */
                    (void) usputs(mp->s,DupBidMsg);
                    mailed_ok=1;
#else
                    (void) usputs(mp->s,Sent);  /* pretend we delivered it */
                    mailed_ok=0;
#endif
                }
                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 */
                /* n5knx: lock it first. Only competition is in makecl() and Oldbidtick() */
                if(mp->bid != NULLCHAR) {
                    if(mailed_ok==0) {
                        for (i=30; i>0; i--) {  /* try to lock bid History file */
                            if(!mlock(Historyfile,NULLCHAR)) break;
                            pause(2000L);
                        }
                        if (i) {
                            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);
                            }
                            (void)rmlock(Historyfile,NULLCHAR);
                        }
                    }
                    /* Free this bid ! */
                    free(mp->bid);
                    mp->bid = NULL;
                }
#endif
                return mailed_ok;
            } else if (!(*p == '.' && *(p+1) == '\0'))
                p--;
        }
        /* 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 (*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 (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[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;
  
    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
** 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)
FILE *data;
char *from;
struct list *tolist;
{
    struct list *ap, *dlist = NULLLIST;
    FILE *fp;
    char mailbox[FILE_PATH_SIZE], *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));
  
    /* 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
  
    /* 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, either (1) save it in our smtp queue
         * and let the smtp daemon try later [yields a tight retry loop!]
         * 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)){
#ifdef tight_loop_desired
            addlist(&dlist,ap->val,0,NULLCHAR);
            fail = queuejob(data,Hostname,dlist,from);
            del_list(dlist);
            dlist = NULLLIST;
#else
            fail=1;
#endif
        } else {
            char buf[LINELEN];
            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;
                /* 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);
                                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(strstr(buf,Hostname))    /* 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 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 */
                                }
                                /* fall into CC: case */
                            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	/* 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 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 /* LINUX */
                  }
#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 */
    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,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, "%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 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 LINUX
            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 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.  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, 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.  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;
  
    }
  
#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 */
    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);
            if(inalias)
                return tp;
            else
                return addlist(head, user, LOCAL, origuser);
        }
  
        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
                    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;
  
    /* no alias found; treat as a local address */
        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);
        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. Called by mdaemon(), to implement
** the return-mail/return-receipt function.
** Return 0 if successful, 1 if permanent failure.
*/
    static 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)) != 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);
        ret = mailit(data,from,tolist);
        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;
    }
  
  
