root/usr/src/cmd/sendmail/src/collect.c
/*
 * Copyright (c) 1998-2006, 2008 Sendmail, Inc. and its suppliers.
 *      All rights reserved.
 * Copyright (c) 1983, 1995-1997 Eric P. Allman.  All rights reserved.
 * Copyright (c) 1988, 1993
 *      The Regents of the University of California.  All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 *
 */

#include <sendmail.h>

SM_RCSID("@(#)$Id: collect.c,v 8.284 2008/08/06 05:26:24 ca Exp $")

static void     eatfrom __P((char *volatile, ENVELOPE *));
static void     collect_doheader __P((ENVELOPE *));
static SM_FILE_T *collect_dfopen __P((ENVELOPE *));
static SM_FILE_T *collect_eoh __P((ENVELOPE *, int, int));

/*
**  COLLECT_EOH -- end-of-header processing in collect()
**
**      Called by collect() when it encounters the blank line
**      separating the header from the message body, or when it
**      encounters EOF in a message that contains only a header.
**
**      Parameters:
**              e -- envelope
**              numhdrs -- number of headers
**              hdrslen -- length of headers
**
**      Results:
**              NULL, or handle to open data file
**
**      Side Effects:
**              end-of-header check ruleset is invoked.
**              envelope state is updated.
**              headers may be added and deleted.
**              selects the queue.
**              opens the data file.
*/

static SM_FILE_T *
collect_eoh(e, numhdrs, hdrslen)
        ENVELOPE *e;
        int numhdrs;
        int hdrslen;
{
        char hnum[16];
        char hsize[16];

        /* call the end-of-header check ruleset */
        (void) sm_snprintf(hnum, sizeof(hnum), "%d", numhdrs);
        (void) sm_snprintf(hsize, sizeof(hsize), "%d", hdrslen);
        if (tTd(30, 10))
                sm_dprintf("collect: rscheck(\"check_eoh\", \"%s $| %s\")\n",
                           hnum, hsize);
        (void) rscheck("check_eoh", hnum, hsize, e, RSF_UNSTRUCTURED|RSF_COUNT,
                        3, NULL, e->e_id, NULL);

        /*
        **  Process the header,
        **  select the queue, open the data file.
        */

        collect_doheader(e);
        return collect_dfopen(e);
}

/*
**  COLLECT_DOHEADER -- process header in collect()
**
**      Called by collect() after it has finished parsing the header,
**      but before it selects the queue and creates the data file.
**      The results of processing the header will affect queue selection.
**
**      Parameters:
**              e -- envelope
**
**      Results:
**              none.
**
**      Side Effects:
**              envelope state is updated.
**              headers may be added and deleted.
*/

static void
collect_doheader(e)
        ENVELOPE *e;
{
        /*
        **  Find out some information from the headers.
        **      Examples are who is the from person & the date.
        */

        eatheader(e, true, false);

        if (GrabTo && e->e_sendqueue == NULL)
                usrerr("No recipient addresses found in header");

        /*
        **  If we have a Return-Receipt-To:, turn it into a DSN.
        */

        if (RrtImpliesDsn && hvalue("return-receipt-to", e->e_header) != NULL)
        {
                ADDRESS *q;

                for (q = e->e_sendqueue; q != NULL; q = q->q_next)
                        if (!bitset(QHASNOTIFY, q->q_flags))
                                q->q_flags |= QHASNOTIFY|QPINGONSUCCESS;
        }

        /*
        **  Add an appropriate recipient line if we have none.
        */

        if (hvalue("to", e->e_header) != NULL ||
            hvalue("cc", e->e_header) != NULL ||
            hvalue("apparently-to", e->e_header) != NULL)
        {
                /* have a valid recipient header -- delete Bcc: headers */
                e->e_flags |= EF_DELETE_BCC;
        }
        else if (hvalue("bcc", e->e_header) == NULL)
        {
                /* no valid recipient headers */
                register ADDRESS *q;
                char *hdr = NULL;

                /* create a recipient field */
                switch (NoRecipientAction)
                {
                  case NRA_ADD_APPARENTLY_TO:
                        hdr = "Apparently-To";
                        break;

                  case NRA_ADD_TO:
                        hdr = "To";
                        break;

                  case NRA_ADD_BCC:
                        addheader("Bcc", " ", 0, e, true);
                        break;

                  case NRA_ADD_TO_UNDISCLOSED:
                        addheader("To", "undisclosed-recipients:;", 0, e, true);
                        break;
                }

                if (hdr != NULL)
                {
                        for (q = e->e_sendqueue; q != NULL; q = q->q_next)
                        {
                                if (q->q_alias != NULL)
                                        continue;
                                if (tTd(30, 3))
                                        sm_dprintf("Adding %s: %s\n",
                                                hdr, q->q_paddr);
                                addheader(hdr, q->q_paddr, 0, e, true);
                        }
                }
        }
}

/*
**  COLLECT_DFOPEN -- open the message data file
**
**      Called by collect() after it has finished processing the header.
**      Queue selection occurs at this point, possibly based on the
**      envelope's recipient list and on header information.
**
**      Parameters:
**              e -- envelope
**
**      Results:
**              NULL, or a pointer to an open data file,
**              into which the message body will be written by collect().
**
**      Side Effects:
**              Calls syserr, sets EF_FATALERRS and returns NULL
**              if there is insufficient disk space.
**              Aborts process if data file could not be opened.
**              Otherwise, the queue is selected,
**              e->e_{dfino,dfdev,msgsize,flags} are updated,
**              and a pointer to an open data file is returned.
*/

static SM_FILE_T *
collect_dfopen(e)
        ENVELOPE *e;
{
        MODE_T oldumask = 0;
        int dfd;
        struct stat stbuf;
        SM_FILE_T *df;
        char *dfname;

        if (!setnewqueue(e))
                return NULL;

        dfname = queuename(e, DATAFL_LETTER);
        if (bitset(S_IWGRP, QueueFileMode))
                oldumask = umask(002);
        df = bfopen(dfname, QueueFileMode, DataFileBufferSize,
                    SFF_OPENASROOT);
        if (bitset(S_IWGRP, QueueFileMode))
                (void) umask(oldumask);
        if (df == NULL)
        {
                syserr("@Cannot create %s", dfname);
                e->e_flags |= EF_NO_BODY_RETN;
                flush_errors(true);
                finis(false, true, ExitStat);
                /* NOTREACHED */
        }
        dfd = sm_io_getinfo(df, SM_IO_WHAT_FD, NULL);
        if (dfd < 0 || fstat(dfd, &stbuf) < 0)
                e->e_dfino = -1;
        else
        {
                e->e_dfdev = stbuf.st_dev;
                e->e_dfino = stbuf.st_ino;
        }
        e->e_flags |= EF_HAS_DF;
        return df;
}

/*
**  COLLECT -- read & parse message header & make temp file.
**
**      Creates a temporary file name and copies the standard
**      input to that file.  Leading UNIX-style "From" lines are
**      stripped off (after important information is extracted).
**
**      Parameters:
**              fp -- file to read.
**              smtpmode -- if set, we are running SMTP: give an RFC821
**                      style message to say we are ready to collect
**                      input, and never ignore a single dot to mean
**                      end of message.
**              hdrp -- the location to stash the header.
**              e -- the current envelope.
**              rsetsize -- reset e_msgsize?
**
**      Returns:
**              none.
**
**      Side Effects:
**              If successful,
**              - Data file is created and filled, and e->e_dfp is set.
**              - The from person may be set.
**              If the "enough disk space" check fails,
**              - syserr is called.
**              - e->e_dfp is NULL.
**              - e->e_flags & EF_FATALERRS is set.
**              - collect() returns.
**              If data file cannot be created, the process is terminated.
*/

/* values for input state machine */
#define IS_NORM         0       /* middle of line */
#define IS_BOL          1       /* beginning of line */
#define IS_DOT          2       /* read a dot at beginning of line */
#define IS_DOTCR        3       /* read ".\r" at beginning of line */
#define IS_CR           4       /* read a carriage return */

/* values for message state machine */
#define MS_UFROM        0       /* reading Unix from line */
#define MS_HEADER       1       /* reading message header */
#define MS_BODY         2       /* reading message body */
#define MS_DISCARD      3       /* discarding rest of message */

void
collect(fp, smtpmode, hdrp, e, rsetsize)
        SM_FILE_T *fp;
        bool smtpmode;
        HDR **hdrp;
        register ENVELOPE *e;
        bool rsetsize;
{
        register SM_FILE_T *df;
        bool ignrdot;
        int dbto;
        register char *bp;
        int c;
        bool inputerr;
        bool headeronly;
        char *buf;
        int buflen;
        int istate;
        int mstate;
        int hdrslen;
        int numhdrs;
        int afd;
        unsigned char *pbp;
        unsigned char peekbuf[8];
        char bufbuf[MAXLINE];

        df = NULL;
        ignrdot = smtpmode ? false : IgnrDot;

        /* timeout for I/O functions is in milliseconds */
        dbto = smtpmode ? ((int) TimeOuts.to_datablock * 1000)
                        : SM_TIME_FOREVER;
        sm_io_setinfo(fp, SM_IO_WHAT_TIMEOUT, &dbto);
        set_tls_rd_tmo(TimeOuts.to_datablock);
        c = SM_IO_EOF;
        inputerr = false;
        headeronly = hdrp != NULL;
        hdrslen = 0;
        numhdrs = 0;
        HasEightBits = false;
        buf = bp = bufbuf;
        buflen = sizeof(bufbuf);
        pbp = peekbuf;
        istate = IS_BOL;
        mstate = SaveFrom ? MS_HEADER : MS_UFROM;

        /*
        **  Tell ARPANET to go ahead.
        */

        if (smtpmode)
                message("354 Enter mail, end with \".\" on a line by itself");

        /* simulate an I/O timeout when used as sink */
        if (tTd(83, 101))
                sleep(319);

        if (tTd(30, 2))
                sm_dprintf("collect\n");

        /*
        **  Read the message.
        **
        **      This is done using two interleaved state machines.
        **      The input state machine is looking for things like
        **      hidden dots; the message state machine is handling
        **      the larger picture (e.g., header versus body).
        */

        if (rsetsize)
                e->e_msgsize = 0;
        for (;;)
        {
                if (tTd(30, 35))
                        sm_dprintf("top, istate=%d, mstate=%d\n", istate,
                                   mstate);
                for (;;)
                {
                        if (pbp > peekbuf)
                                c = *--pbp;
                        else
                        {
                                while (!sm_io_eof(fp) && !sm_io_error(fp))
                                {
                                        errno = 0;
                                        c = sm_io_getc(fp, SM_TIME_DEFAULT);
                                        if (c == SM_IO_EOF && errno == EINTR)
                                        {
                                                /* Interrupted, retry */
                                                sm_io_clearerr(fp);
                                                continue;
                                        }

                                        /* timeout? */
                                        if (c == SM_IO_EOF && errno == EAGAIN
                                            && smtpmode)
                                        {
                                                /*
                                                **  Override e_message in
                                                **  usrerr() as this is the
                                                **  reason for failure that
                                                **  should be logged for
                                                **  undelivered recipients.
                                                */

                                                e->e_message = NULL;
                                                errno = 0;
                                                inputerr = true;
                                                goto readabort;
                                        }
                                        break;
                                }
                                if (TrafficLogFile != NULL && !headeronly)
                                {
                                        if (istate == IS_BOL)
                                                (void) sm_io_fprintf(TrafficLogFile,
                                                        SM_TIME_DEFAULT,
                                                        "%05d <<< ",
                                                        (int) CurrentPid);
                                        if (c == SM_IO_EOF)
                                                (void) sm_io_fprintf(TrafficLogFile,
                                                        SM_TIME_DEFAULT,
                                                        "[EOF]\n");
                                        else
                                                (void) sm_io_putc(TrafficLogFile,
                                                        SM_TIME_DEFAULT,
                                                        c);
                                }
                                if (c == SM_IO_EOF)
                                        goto readerr;
                                if (SevenBitInput)
                                        c &= 0x7f;
                                else
                                        HasEightBits |= bitset(0x80, c);
                        }
                        if (tTd(30, 94))
                                sm_dprintf("istate=%d, c=%c (0x%x)\n",
                                        istate, (char) c, c);
                        switch (istate)
                        {
                          case IS_BOL:
                                if (c == '.')
                                {
                                        istate = IS_DOT;
                                        continue;
                                }
                                break;

                          case IS_DOT:
                                if (c == '\n' && !ignrdot &&
                                    !bitset(EF_NL_NOT_EOL, e->e_flags))
                                        goto readerr;
                                else if (c == '\r' &&
                                         !bitset(EF_CRLF_NOT_EOL, e->e_flags))
                                {
                                        istate = IS_DOTCR;
                                        continue;
                                }
                                else if (ignrdot ||
                                         (c != '.' &&
                                          OpMode != MD_SMTP &&
                                          OpMode != MD_DAEMON &&
                                          OpMode != MD_ARPAFTP))

                                {
                                        SM_ASSERT(pbp < peekbuf +
                                                        sizeof(peekbuf));
                                        *pbp++ = c;
                                        c = '.';
                                }
                                break;

                          case IS_DOTCR:
                                if (c == '\n' && !ignrdot)
                                        goto readerr;
                                else
                                {
                                        /* push back the ".\rx" */
                                        SM_ASSERT(pbp < peekbuf +
                                                        sizeof(peekbuf));
                                        *pbp++ = c;
                                        if (OpMode != MD_SMTP &&
                                            OpMode != MD_DAEMON &&
                                            OpMode != MD_ARPAFTP)
                                        {
                                                SM_ASSERT(pbp < peekbuf +
                                                         sizeof(peekbuf));
                                                *pbp++ = '\r';
                                                c = '.';
                                        }
                                        else
                                                c = '\r';
                                }
                                break;

                          case IS_CR:
                                if (c == '\n')
                                        istate = IS_BOL;
                                else
                                {
                                        (void) sm_io_ungetc(fp, SM_TIME_DEFAULT,
                                                            c);
                                        c = '\r';
                                        istate = IS_NORM;
                                }
                                goto bufferchar;
                        }

                        if (c == '\r' && !bitset(EF_CRLF_NOT_EOL, e->e_flags))
                        {
                                istate = IS_CR;
                                continue;
                        }
                        else if (c == '\n' && !bitset(EF_NL_NOT_EOL,
                                                      e->e_flags))
                                istate = IS_BOL;
                        else
                                istate = IS_NORM;

bufferchar:
                        if (!headeronly)
                        {
                                /* no overflow? */
                                if (e->e_msgsize >= 0)
                                {
                                        e->e_msgsize++;
                                        if (MaxMessageSize > 0 &&
                                            !bitset(EF_TOOBIG, e->e_flags) &&
                                            e->e_msgsize > MaxMessageSize)
                                                 e->e_flags |= EF_TOOBIG;
                                }
                        }
                        switch (mstate)
                        {
                          case MS_BODY:
                                /* just put the character out */
                                if (!bitset(EF_TOOBIG, e->e_flags))
                                        (void) sm_io_putc(df, SM_TIME_DEFAULT,
                                                          c);

                                /* FALLTHROUGH */

                          case MS_DISCARD:
                                continue;
                        }

                        SM_ASSERT(mstate == MS_UFROM || mstate == MS_HEADER);

                        /* header -- buffer up */
                        if (bp >= &buf[buflen - 2])
                        {
                                char *obuf;

                                /* out of space for header */
                                obuf = buf;
                                if (buflen < MEMCHUNKSIZE)
                                        buflen *= 2;
                                else
                                        buflen += MEMCHUNKSIZE;
                                if (buflen <= 0)
                                {
                                        sm_syslog(LOG_NOTICE, e->e_id,
                                                  "header overflow from %s during message collect",
                                                  CURHOSTNAME);
                                        errno = 0;
                                        e->e_flags |= EF_CLRQUEUE;
                                        e->e_status = "5.6.0";
                                        usrerrenh(e->e_status,
                                                  "552 Headers too large");
                                        goto discard;
                                }
                                buf = xalloc(buflen);
                                memmove(buf, obuf, bp - obuf);
                                bp = &buf[bp - obuf];
                                if (obuf != bufbuf)
                                        sm_free(obuf);  /* XXX */
                        }

                        if (c != '\0')
                        {
                                *bp++ = c;
                                ++hdrslen;
                                if (!headeronly &&
                                    MaxHeadersLength > 0 &&
                                    hdrslen > MaxHeadersLength)
                                {
                                        sm_syslog(LOG_NOTICE, e->e_id,
                                                  "headers too large (%d max) from %s during message collect",
                                                  MaxHeadersLength,
                                                  CURHOSTNAME);
                                        errno = 0;
                                        e->e_flags |= EF_CLRQUEUE;
                                        e->e_status = "5.6.0";
                                        usrerrenh(e->e_status,
                                                  "552 Headers too large (%d max)",
                                                  MaxHeadersLength);
  discard:
                                        mstate = MS_DISCARD;
                                }
                        }
                        if (istate == IS_BOL)
                                break;
                }
                *bp = '\0';

nextstate:
                if (tTd(30, 35))
                        sm_dprintf("nextstate, istate=%d, mstate=%d, line=\"%s\"\n",
                                istate, mstate, buf);
                switch (mstate)
                {
                  case MS_UFROM:
                        mstate = MS_HEADER;
#ifndef NOTUNIX
                        if (strncmp(buf, "From ", 5) == 0)
                        {
                                bp = buf;
                                eatfrom(buf, e);
                                continue;
                        }
#endif /* ! NOTUNIX */
                        /* FALLTHROUGH */

                  case MS_HEADER:
                        if (!isheader(buf))
                        {
                                mstate = MS_BODY;
                                goto nextstate;
                        }

                        /* check for possible continuation line */
                        do
                        {
                                sm_io_clearerr(fp);
                                errno = 0;
                                c = sm_io_getc(fp, SM_TIME_DEFAULT);

                                /* timeout? */
                                if (c == SM_IO_EOF && errno == EAGAIN
                                    && smtpmode)
                                {
                                        /*
                                        **  Override e_message in
                                        **  usrerr() as this is the
                                        **  reason for failure that
                                        **  should be logged for
                                        **  undelivered recipients.
                                        */

                                        e->e_message = NULL;
                                        errno = 0;
                                        inputerr = true;
                                        goto readabort;
                                }
                        } while (c == SM_IO_EOF && errno == EINTR);
                        if (c != SM_IO_EOF)
                                (void) sm_io_ungetc(fp, SM_TIME_DEFAULT, c);
                        if (c == ' ' || c == '\t')
                        {
                                /* yep -- defer this */
                                continue;
                        }

                        SM_ASSERT(bp > buf);

                        /* guaranteed by isheader(buf) */
                        SM_ASSERT(*(bp - 1) != '\n' || bp > buf + 1);

                        /* trim off trailing CRLF or NL */
                        if (*--bp != '\n' || *--bp != '\r')
                                bp++;
                        *bp = '\0';

                        if (bitset(H_EOH, chompheader(buf,
                                                      CHHDR_CHECK | CHHDR_USER,
                                                      hdrp, e)))
                        {
                                mstate = MS_BODY;
                                goto nextstate;
                        }
                        numhdrs++;
                        break;

                  case MS_BODY:
                        if (tTd(30, 1))
                                sm_dprintf("EOH\n");

                        if (headeronly)
                                goto readerr;

                        df = collect_eoh(e, numhdrs, hdrslen);
                        if (df == NULL)
                                e->e_flags |= EF_TOOBIG;

                        bp = buf;

                        /* toss blank line */
                        if ((!bitset(EF_CRLF_NOT_EOL, e->e_flags) &&
                                bp[0] == '\r' && bp[1] == '\n') ||
                            (!bitset(EF_NL_NOT_EOL, e->e_flags) &&
                                bp[0] == '\n'))
                        {
                                break;
                        }

                        /* if not a blank separator, write it out */
                        if (!bitset(EF_TOOBIG, e->e_flags))
                        {
                                while (*bp != '\0')
                                        (void) sm_io_putc(df, SM_TIME_DEFAULT,
                                                          *bp++);
                        }
                        break;
                }
                bp = buf;
        }

readerr:
        if ((sm_io_eof(fp) && smtpmode) || sm_io_error(fp))
        {
                const char *errmsg;

                if (sm_io_eof(fp))
                        errmsg = "unexpected close";
                else
                        errmsg = sm_errstring(errno);
                if (tTd(30, 1))
                        sm_dprintf("collect: premature EOM: %s\n", errmsg);
                if (LogLevel > 1)
                        sm_syslog(LOG_WARNING, e->e_id,
                                "collect: premature EOM: %s", errmsg);
                inputerr = true;
        }

        if (headeronly)
                return;

        if (mstate != MS_BODY)
        {
                /* no body or discard, so we never opened the data file */
                SM_ASSERT(df == NULL);
                df = collect_eoh(e, numhdrs, hdrslen);
        }

        if (df == NULL)
        {
                /* skip next few clauses */
                /* EMPTY */
        }
        else if (sm_io_flush(df, SM_TIME_DEFAULT) != 0 || sm_io_error(df))
        {
                dferror(df, "sm_io_flush||sm_io_error", e);
                flush_errors(true);
                finis(true, true, ExitStat);
                /* NOTREACHED */
        }
        else if (SuperSafe == SAFE_NO ||
                 SuperSafe == SAFE_INTERACTIVE ||
                 (SuperSafe == SAFE_REALLY_POSTMILTER && smtpmode))
        {
                /* skip next few clauses */
                /* EMPTY */
                /* Note: updfs() is not called in this case! */
        }
        else if (sm_io_setinfo(df, SM_BF_COMMIT, NULL) < 0 && errno != EINVAL)
        {
                int save_errno = errno;

                if (save_errno == EEXIST)
                {
                        char *dfile;
                        struct stat st;
                        int dfd;

                        dfile = queuename(e, DATAFL_LETTER);
                        if (stat(dfile, &st) < 0)
                                st.st_size = -1;
                        errno = EEXIST;
                        syserr("@collect: bfcommit(%s): already on disk, size=%ld",
                               dfile, (long) st.st_size);
                        dfd = sm_io_getinfo(df, SM_IO_WHAT_FD, NULL);
                        if (dfd >= 0)
                                dumpfd(dfd, true, true);
                }
                errno = save_errno;
                dferror(df, "bfcommit", e);
                flush_errors(true);
                finis(save_errno != EEXIST, true, ExitStat);
        }
        else if ((afd = sm_io_getinfo(df, SM_IO_WHAT_FD, NULL)) < 0)
        {
                dferror(df, "sm_io_getinfo", e);
                flush_errors(true);
                finis(true, true, ExitStat);
                /* NOTREACHED */
        }
        else if (fsync(afd) < 0)
        {
                dferror(df, "fsync", e);
                flush_errors(true);
                finis(true, true, ExitStat);
                /* NOTREACHED */
        }
        else if (sm_io_close(df, SM_TIME_DEFAULT) < 0)
        {
                dferror(df, "sm_io_close", e);
                flush_errors(true);
                finis(true, true, ExitStat);
                /* NOTREACHED */
        }
        else
        {
                /* everything is happily flushed to disk */
                df = NULL;

                /* remove from available space in filesystem */
                updfs(e, 0, 1, "collect");
        }

        /* An EOF when running SMTP is an error */
  readabort:
        if (inputerr && (OpMode == MD_SMTP || OpMode == MD_DAEMON))
        {
                char *host;
                char *problem;
                ADDRESS *q;

                host = RealHostName;
                if (host == NULL)
                        host = "localhost";

                if (sm_io_eof(fp))
                        problem = "unexpected close";
                else if (sm_io_error(fp))
                        problem = "I/O error";
                else
                        problem = "read timeout";
                if (LogLevel > 0 && sm_io_eof(fp))
                        sm_syslog(LOG_NOTICE, e->e_id,
                                "collect: %s on connection from %.100s, sender=%s",
                                problem, host,
                                shortenstring(e->e_from.q_paddr, MAXSHORTSTR));
                if (sm_io_eof(fp))
                        usrerr("421 4.4.1 collect: %s on connection from %s, from=%s",
                                problem, host,
                                shortenstring(e->e_from.q_paddr, MAXSHORTSTR));
                else
                        syserr("421 4.4.1 collect: %s on connection from %s, from=%s",
                                problem, host,
                                shortenstring(e->e_from.q_paddr, MAXSHORTSTR));
                flush_errors(true);

                /* don't return an error indication */
                e->e_to = NULL;
                e->e_flags &= ~EF_FATALERRS;
                e->e_flags |= EF_CLRQUEUE;

                /* Don't send any message notification to sender */
                for (q = e->e_sendqueue; q != NULL; q = q->q_next)
                {
                        if (QS_IS_DEAD(q->q_state))
                                continue;
                        q->q_state = QS_FATALERR;
                }

                (void) sm_io_close(df, SM_TIME_DEFAULT);
                df = NULL;
                finis(true, true, ExitStat);
                /* NOTREACHED */
        }

        /* Log collection information. */
        if (tTd(92, 2))
                sm_dprintf("collect: e_id=%s, EF_LOGSENDER=%d, LogLevel=%d\n",
                        e->e_id, bitset(EF_LOGSENDER, e->e_flags), LogLevel);
        if (bitset(EF_LOGSENDER, e->e_flags) && LogLevel > 4)
        {
                logsender(e, e->e_msgid);
                e->e_flags &= ~EF_LOGSENDER;
        }

        /* check for message too large */
        if (bitset(EF_TOOBIG, e->e_flags))
        {
                e->e_flags |= EF_NO_BODY_RETN|EF_CLRQUEUE;
                if (!bitset(EF_FATALERRS, e->e_flags))
                {
                        e->e_status = "5.2.3";
                        usrerrenh(e->e_status,
                                "552 Message exceeds maximum fixed size (%ld)",
                                MaxMessageSize);
                        if (LogLevel > 6)
                                sm_syslog(LOG_NOTICE, e->e_id,
                                        "message size (%ld) exceeds maximum (%ld)",
                                        e->e_msgsize, MaxMessageSize);
                }
        }

        /* check for illegal 8-bit data */
        if (HasEightBits)
        {
                e->e_flags |= EF_HAS8BIT;
                if (!bitset(MM_PASS8BIT|MM_MIME8BIT, MimeMode) &&
                    !bitset(EF_IS_MIME, e->e_flags))
                {
                        e->e_status = "5.6.1";
                        usrerrenh(e->e_status, "554 Eight bit data not allowed");
                }
        }
        else
        {
                /* if it claimed to be 8 bits, well, it lied.... */
                if (e->e_bodytype != NULL &&
                    sm_strcasecmp(e->e_bodytype, "8BITMIME") == 0)
                        e->e_bodytype = "7BIT";
        }

        if (SuperSafe == SAFE_REALLY && !bitset(EF_FATALERRS, e->e_flags))
        {
                char *dfname = queuename(e, DATAFL_LETTER);
                if ((e->e_dfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, dfname,
                                           SM_IO_RDONLY_B, NULL)) == NULL)
                {
                        /* we haven't acked receipt yet, so just chuck this */
                        syserr("@Cannot reopen %s", dfname);
                        finis(true, true, ExitStat);
                        /* NOTREACHED */
                }
        }
        else
                e->e_dfp = df;

        /* collect statistics */
        if (OpMode != MD_VERIFY)
        {
                /*
                **  Recalculate e_msgpriority, it is done at in eatheader()
                **  which is called (in 8.12) after the header is collected,
                **  hence e_msgsize is (most likely) incorrect.
                */

                e->e_msgpriority = e->e_msgsize
                                 - e->e_class * WkClassFact
                                 + e->e_nrcpts * WkRecipFact;
                markstats(e, (ADDRESS *) NULL, STATS_NORMAL);
        }
}

/*
**  DFERROR -- signal error on writing the data file.
**
**      Called by collect().  Collect() always terminates the process
**      immediately after calling dferror(), which means that the SMTP
**      session will be terminated, which means that any error message
**      issued by dferror must be a 421 error, as per RFC 821.
**
**      Parameters:
**              df -- the file pointer for the data file.
**              msg -- detailed message.
**              e -- the current envelope.
**
**      Returns:
**              none.
**
**      Side Effects:
**              Gives an error message.
**              Arranges for following output to go elsewhere.
*/

void
dferror(df, msg, e)
        SM_FILE_T *volatile df;
        char *msg;
        register ENVELOPE *e;
{
        char *dfname;

        dfname = queuename(e, DATAFL_LETTER);
        setstat(EX_IOERR);
        if (errno == ENOSPC)
        {
#if STAT64 > 0
                struct stat64 st;
#else /* STAT64 > 0 */
                struct stat st;
#endif /* STAT64 > 0 */
                long avail;
                long bsize;

                e->e_flags |= EF_NO_BODY_RETN;

                if (
#if STAT64 > 0
                    fstat64(sm_io_getinfo(df, SM_IO_WHAT_FD, NULL), &st)
#else /* STAT64 > 0 */
                    fstat(sm_io_getinfo(df, SM_IO_WHAT_FD, NULL), &st)
#endif /* STAT64 > 0 */
                    < 0)
                  st.st_size = 0;
                (void) sm_io_reopen(SmFtStdio, SM_TIME_DEFAULT, dfname,
                                    SM_IO_WRONLY_B, NULL, df);
                if (st.st_size <= 0)
                        (void) sm_io_fprintf(df, SM_TIME_DEFAULT,
                                "\n*** Mail could not be accepted");
                else
                        (void) sm_io_fprintf(df, SM_TIME_DEFAULT,
                                "\n*** Mail of at least %llu bytes could not be accepted\n",
                                (ULONGLONG_T) st.st_size);
                (void) sm_io_fprintf(df, SM_TIME_DEFAULT,
                        "*** at %s due to lack of disk space for temp file.\n",
                        MyHostName);
                avail = freediskspace(qid_printqueue(e->e_qgrp, e->e_qdir),
                                      &bsize);
                if (avail > 0)
                {
                        if (bsize > 1024)
                                avail *= bsize / 1024;
                        else if (bsize < 1024)
                                avail /= 1024 / bsize;
                        (void) sm_io_fprintf(df, SM_TIME_DEFAULT,
                                "*** Currently, %ld kilobytes are available for mail temp files.\n",
                                avail);
                }
#if 0
                /* Wrong response code; should be 421. */
                e->e_status = "4.3.1";
                usrerrenh(e->e_status, "452 Out of disk space for temp file");
#else /* 0 */
                syserr("421 4.3.1 Out of disk space for temp file");
#endif /* 0 */
        }
        else
                syserr("421 4.3.0 collect: Cannot write %s (%s, uid=%d, gid=%d)",
                        dfname, msg, (int) geteuid(), (int) getegid());
        if (sm_io_reopen(SmFtStdio, SM_TIME_DEFAULT, SM_PATH_DEVNULL,
                         SM_IO_WRONLY, NULL, df) == NULL)
                sm_syslog(LOG_ERR, e->e_id,
                          "dferror: sm_io_reopen(\"/dev/null\") failed: %s",
                          sm_errstring(errno));
}
/*
**  EATFROM -- chew up a UNIX style from line and process
**
**      This does indeed make some assumptions about the format
**      of UNIX messages.
**
**      Parameters:
**              fm -- the from line.
**              e -- envelope
**
**      Returns:
**              none.
**
**      Side Effects:
**              extracts what information it can from the header,
**              such as the date.
*/

#ifndef NOTUNIX

static char     *DowList[] =
{
        "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL
};

static char     *MonthList[] =
{
        "Jan", "Feb", "Mar", "Apr", "May", "Jun",
        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
        NULL
};

static void
eatfrom(fm, e)
        char *volatile fm;
        register ENVELOPE *e;
{
        register char *p;
        register char **dt;

        if (tTd(30, 2))
                sm_dprintf("eatfrom(%s)\n", fm);

        /* find the date part */
        p = fm;
        while (*p != '\0')
        {
                /* skip a word */
                while (*p != '\0' && *p != ' ')
                        p++;
                while (*p == ' ')
                        p++;
                if (strlen(p) < 17)
                {
                        /* no room for the date */
                        return;
                }
                if (!(isascii(*p) && isupper(*p)) ||
                    p[3] != ' ' || p[13] != ':' || p[16] != ':')
                        continue;

                /* we have a possible date */
                for (dt = DowList; *dt != NULL; dt++)
                        if (strncmp(*dt, p, 3) == 0)
                                break;
                if (*dt == NULL)
                        continue;

                for (dt = MonthList; *dt != NULL; dt++)
                {
                        if (strncmp(*dt, &p[4], 3) == 0)
                                break;
                }
                if (*dt != NULL)
                        break;
        }

        if (*p != '\0')
        {
                char *q, buf[25];

                /* we have found a date */
                (void) sm_strlcpy(buf, p, sizeof(buf));
                q = arpadate(buf);
                macdefine(&e->e_macro, A_TEMP, 'a', q);
        }
}
#endif /* ! NOTUNIX */