diff options
Diffstat (limited to 'usr.sbin/sendmail/src/collect.c')
| -rw-r--r-- | usr.sbin/sendmail/src/collect.c | 652 |
1 files changed, 408 insertions, 244 deletions
diff --git a/usr.sbin/sendmail/src/collect.c b/usr.sbin/sendmail/src/collect.c index 29546cec62c1..7e43c5f5ffa2 100644 --- a/usr.sbin/sendmail/src/collect.c +++ b/usr.sbin/sendmail/src/collect.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1983 Eric P. Allman + * Copyright (c) 1983, 1995 Eric P. Allman * Copyright (c) 1988, 1993 * The Regents of the University of California. All rights reserved. * @@ -33,7 +33,7 @@ */ #ifndef lint -static char sccsid[] = "@(#)collect.c 8.14 (Berkeley) 4/18/94"; +static char sccsid[] = "@(#)collect.c 8.49 (Berkeley) 10/29/95"; #endif /* not lint */ # include <errno.h> @@ -47,12 +47,14 @@ static char sccsid[] = "@(#)collect.c 8.14 (Berkeley) 4/18/94"; ** 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. ** requeueflag -- this message will be requeued later, so ** don't do final processing on it. +** hdrp -- the location to stash the header. ** e -- the current envelope. ** ** Returns: @@ -63,37 +65,77 @@ static char sccsid[] = "@(#)collect.c 8.14 (Berkeley) 4/18/94"; ** The from person may be set. */ -char *CollectErrorMessage; -bool CollectErrno; - -collect(smtpmode, requeueflag, e) +static jmp_buf CtxCollectTimeout; +static void collecttimeout(); +static bool CollectProgress; +static EVENT *CollectTimeout; + +/* 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 */ + +void +collect(fp, smtpmode, requeueflag, hdrp, e) + FILE *fp; bool smtpmode; bool requeueflag; + HDR **hdrp; register ENVELOPE *e; { register FILE *tf; bool ignrdot = smtpmode ? FALSE : IgnrDot; time_t dbto = smtpmode ? TimeOuts.to_datablock : 0; - char buf[MAXLINE], buf2[MAXLINE]; - register char *workbuf, *freebuf; + register char *bp; + int c = '\0'; bool inputerr = FALSE; - extern char *hvalue(); - extern bool isheader(), flusheol(); - - CollectErrorMessage = NULL; - CollectErrno = 0; + bool headeronly; + char *buf; + int buflen; + int istate; + int mstate; + char *pbp; + char peekbuf[8]; + char dfname[20]; + char bufbuf[MAXLINE]; + extern bool isheader(); + extern void eatheader(); + extern void tferror(); + + headeronly = hdrp != NULL; /* ** Create the temp file name and create the file. */ - e->e_df = queuename(e, 'd'); - e->e_df = newstr(e->e_df); - if ((tf = dfopen(e->e_df, O_WRONLY|O_CREAT|O_TRUNC, FileMode)) == NULL) + if (!headeronly) { - syserr("Cannot create %s", e->e_df); - NoReturn = TRUE; - finis(); + struct stat stbuf; + + strcpy(dfname, queuename(e, 'd')); + if ((tf = dfopen(dfname, O_WRONLY|O_CREAT|O_TRUNC, FileMode)) == NULL) + { + syserr("Cannot create %s", dfname); + e->e_flags |= EF_NO_BODY_RETN; + finis(); + } + if (fstat(fileno(tf), &stbuf) < 0) + e->e_dfino = -1; + else + { + e->e_dfdev = stbuf.st_dev; + e->e_dfino = stbuf.st_ino; + } + HasEightBits = FALSE; + e->e_msgsize = 0; + e->e_flags |= EF_HAS_DF; } /* @@ -103,223 +145,296 @@ collect(smtpmode, requeueflag, e) if (smtpmode) message("354 Enter mail, end with \".\" on a line by itself"); - /* set global timer to monitor progress */ - sfgetset(dbto); + if (tTd(30, 2)) + printf("collect\n"); /* - ** Try to read a UNIX-style From line + ** 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 (sfgets(buf, MAXLINE, InChannel, dbto, - "initial message read") == NULL) - goto readerr; - fixcrlf(buf, FALSE); -# ifndef NOTUNIX - if (!SaveFrom && strncmp(buf, "From ", 5) == 0) + buf = bp = bufbuf; + buflen = sizeof bufbuf; + pbp = peekbuf; + istate = IS_BOL; + mstate = SaveFrom ? MS_HEADER : MS_UFROM; + CollectProgress = FALSE; + + if (dbto != 0) { - if (!flusheol(buf, InChannel)) - goto readerr; - eatfrom(buf, e); - if (sfgets(buf, MAXLINE, InChannel, dbto, - "message header read") == NULL) + /* handle possible input timeout */ + if (setjmp(CtxCollectTimeout) != 0) + { +#ifdef LOG + syslog(LOG_NOTICE, + "timeout waiting for input from %s during message collect", + CurHostName ? CurHostName : "<local machine>"); +#endif + errno = 0; + usrerr("451 timeout waiting for input during message collect"); goto readerr; - fixcrlf(buf, FALSE); + } + CollectTimeout = setevent(dbto, collecttimeout, dbto); } -# endif /* NOTUNIX */ - /* - ** Copy InChannel to temp file & do message editing. - ** To keep certain mailers from getting confused, - ** and to keep the output clean, lines that look - ** like UNIX "From" lines are deleted in the header. - */ - - workbuf = buf; /* `workbuf' contains a header field */ - freebuf = buf2; /* `freebuf' can be used for read-ahead */ for (;;) { - char *curbuf; - int curbuffree; - register int curbuflen; - char *p; - - /* first, see if the header is over */ - if (!isheader(workbuf)) - { - fixcrlf(workbuf, TRUE); - break; - } - - /* if the line is too long, throw the rest away */ - if (!flusheol(workbuf, InChannel)) - goto readerr; - - /* it's okay to toss '\n' now (flusheol() needed it) */ - fixcrlf(workbuf, TRUE); - - curbuf = workbuf; - curbuflen = strlen(curbuf); - curbuffree = MAXLINE - curbuflen; - p = curbuf + curbuflen; - - /* get the rest of this field */ + if (tTd(30, 35)) + printf("top, istate=%d, mstate=%d\n", istate, mstate); for (;;) { - int clen; - - if (sfgets(freebuf, MAXLINE, InChannel, dbto, - "message header read") == NULL) + if (pbp > peekbuf) + c = *--pbp; + else { - freebuf[0] = '\0'; - break; + while (!feof(fp) && !ferror(fp)) + { + errno = 0; + c = getc(fp); + if (errno != EINTR) + break; + clearerr(fp); + } + CollectProgress = TRUE; + if (TrafficLogFile != NULL && !headeronly) + { + if (istate == IS_BOL) + fprintf(TrafficLogFile, "%05d <<< ", + getpid()); + if (c == EOF) + fprintf(TrafficLogFile, "[EOF]\n"); + else + putc(c, TrafficLogFile); + } + if (c == EOF) + goto readerr; + if (SevenBitInput) + c &= 0x7f; + else + HasEightBits |= bitset(0x80, c); + if (!headeronly) + e->e_msgsize++; } + if (tTd(30, 94)) + printf("istate=%d, c=%c (0x%x)\n", + istate, c, c); + switch (istate) + { + case IS_BOL: + if (c == '.') + { + istate = IS_DOT; + continue; + } + break; - /* is this a continuation line? */ - if (*freebuf != ' ' && *freebuf != '\t') + 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 (c != '.' || + (OpMode != MD_SMTP && + OpMode != MD_DAEMON && + OpMode != MD_ARPAFTP)) + { + *pbp++ = c; + c = '.'; + } break; - if (!flusheol(freebuf, InChannel)) - goto readerr; + case IS_DOTCR: + if (c == '\n') + goto readerr; + else + { + /* push back the ".\rx" */ + *pbp++ = c; + *pbp++ = '\r'; + c = '.'; + } + break; - fixcrlf(freebuf, TRUE); - clen = strlen(freebuf) + 1; + case IS_CR: + if (c == '\n') + istate = IS_BOL; + else + { + ungetc(c, fp); + c = '\r'; + istate = IS_NORM; + } + goto bufferchar; + } - /* if insufficient room, dynamically allocate buffer */ - if (clen >= curbuffree) + if (c == '\r' && !bitset(EF_CRLF_NOT_EOL, e->e_flags)) { - /* reallocate buffer */ - int nbuflen = ((p - curbuf) + clen) * 2; - char *nbuf = xalloc(nbuflen); - - p = nbuf + curbuflen; - curbuffree = nbuflen - curbuflen; - bcopy(curbuf, nbuf, curbuflen); - if (curbuf != buf && curbuf != buf2) - free(curbuf); - curbuf = nbuf; + istate = IS_CR; + continue; } - *p++ = '\n'; - bcopy(freebuf, p, clen - 1); - p += clen - 1; - curbuffree -= clen; - curbuflen += clen; - } - *p++ = '\0'; - - e->e_msgsize += curbuflen; - - /* - ** The working buffer now becomes the free buffer, since - ** the free buffer contains a new header field. - ** - ** This is premature, since we still havent called - ** chompheader() to process the field we just created - ** (so the call to chompheader() will use `freebuf'). - ** This convolution is necessary so that if we break out - ** of the loop due to H_EOH, `workbuf' will always be - ** the next unprocessed buffer. - */ + else if (c == '\n' && !bitset(EF_NL_NOT_EOL, e->e_flags)) + istate = IS_BOL; + else + istate = IS_NORM; - { - register char *tmp = workbuf; - workbuf = freebuf; - freebuf = tmp; - } +bufferchar: + if (mstate == MS_BODY) + { + /* just put the character out */ + if (MaxMessageSize <= 0 || + e->e_msgsize <= MaxMessageSize) + putc(c, tf); + continue; + } - /* - ** Snarf header away. - */ + /* header -- buffer up */ + if (bp >= &buf[buflen - 2]) + { + char *obuf; + + if (mstate != MS_HEADER) + break; + + /* out of space for header */ + obuf = buf; + if (buflen < MEMCHUNKSIZE) + buflen *= 2; + else + buflen += MEMCHUNKSIZE; + buf = xalloc(buflen); + bcopy(obuf, buf, bp - obuf); + bp = &buf[bp - obuf]; + if (obuf != bufbuf) + free(obuf); + } + if (c != '\0') + *bp++ = c; + if (istate == IS_BOL) + break; + } + *bp = '\0'; - if (bitset(H_EOH, chompheader(curbuf, FALSE, e))) - break; +nextstate: + if (tTd(30, 35)) + printf("nextstate, istate=%d, mstate=%d, line = \"%s\"\n", + istate, mstate, buf); + switch (mstate) + { + extern int chompheader(); - /* - ** If the buffer was dynamically allocated, free it. - */ + case MS_UFROM: + mstate = MS_HEADER; +#ifndef NOTUNIX + if (strncmp(buf, "From ", 5) == 0) + { + extern void eatfrom(); - if (curbuf != buf && curbuf != buf2) - free(curbuf); - } + bp = buf; + eatfrom(buf, e); + continue; + } +#endif + /* fall through */ - if (tTd(30, 1)) - printf("EOH\n"); + case MS_HEADER: + if (!isheader(buf)) + { + mstate = MS_BODY; + goto nextstate; + } - if (*workbuf == '\0') - { - /* throw away a blank line */ - if (sfgets(buf, MAXLINE, InChannel, dbto, - "message separator read") == NULL) - goto readerr; - } - else if (workbuf == buf2) /* guarantee `buf' contains data */ - (void) strcpy(buf, buf2); + /* check for possible continuation line */ + do + { + clearerr(fp); + errno = 0; + c = getc(fp); + } while (errno == EINTR); + if (c != EOF) + ungetc(c, fp); + if (c == ' ' || c == '\t') + { + /* yep -- defer this */ + continue; + } - /* - ** Collect the body of the message. - */ + /* trim off trailing CRLF or NL */ + if (*--bp != '\n' || *--bp != '\r') + bp++; + *bp = '\0'; + if (bitset(H_EOH, chompheader(buf, FALSE, hdrp, e))) + mstate = MS_BODY; + break; - for (;;) - { - register char *bp = buf; + case MS_BODY: + if (tTd(30, 1)) + printf("EOH\n"); + if (headeronly) + goto readerr; + bp = buf; - fixcrlf(buf, TRUE); + /* 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; + } - /* check for end-of-message */ - if (!ignrdot && buf[0] == '.' && (buf[1] == '\n' || buf[1] == '\0')) + /* if not a blank separator, write it out */ + if (MaxMessageSize <= 0 || + e->e_msgsize <= MaxMessageSize) + { + while (*bp != '\0') + putc(*bp++, tf); + } break; - - /* check for transparent dot */ - if ((OpMode == MD_SMTP || OpMode == MD_DAEMON) && - bp[0] == '.' && bp[1] == '.') - bp++; - - /* - ** Figure message length, output the line to the temp - ** file, and insert a newline if missing. - */ - - e->e_msgsize += strlen(bp) + 1; - fputs(bp, tf); - fputs("\n", tf); - if (ferror(tf)) - tferror(tf, e); - if (sfgets(buf, MAXLINE, InChannel, dbto, - "message body read") == NULL) - goto readerr; + } + bp = buf; } - if (feof(InChannel) || ferror(InChannel)) - { readerr: + if ((feof(fp) && smtpmode) || ferror(fp)) + { + const char *errmsg = errstring(errno); + if (tTd(30, 1)) - printf("collect: read error\n"); + printf("collect: premature EOM: %s\n", errmsg); +#ifdef LOG + if (LogLevel >= 2) + syslog(LOG_WARNING, "collect: premature EOM: %s", errmsg); +#endif inputerr = TRUE; } /* reset global timer */ - sfgetset((time_t) 0); + clrevent(CollectTimeout); - if (fflush(tf) != 0) - tferror(tf, e); - if (fsync(fileno(tf)) < 0 || fclose(tf) < 0) + if (headeronly) + return; + + if (tf != NULL && + (fflush(tf) != 0 || ferror(tf) || fsync(fileno(tf)) < 0 || + fclose(tf) < 0)) { tferror(tf, e); + flush_errors(TRUE); finis(); } - if (CollectErrorMessage != NULL && Errors <= 0) - { - if (CollectErrno != 0) - { - errno = CollectErrno; - syserr(CollectErrorMessage, e->e_df); - finis(); - } - usrerr(CollectErrorMessage); - } - else if (inputerr && (OpMode == MD_SMTP || OpMode == MD_DAEMON)) + /* An EOF when running SMTP is an error */ + if (inputerr && (OpMode == MD_SMTP || OpMode == MD_DAEMON)) { - /* An EOF when running SMTP is an error */ char *host; char *problem; @@ -327,24 +442,28 @@ readerr: if (host == NULL) host = "localhost"; - if (feof(InChannel)) + if (feof(fp)) problem = "unexpected close"; - else if (ferror(InChannel)) + else if (ferror(fp)) problem = "I/O error"; else problem = "read timeout"; # ifdef LOG - if (LogLevel > 0 && feof(InChannel)) + if (LogLevel > 0 && feof(fp)) syslog(LOG_NOTICE, - "collect: %s on connection from %s, sender=%s: %s\n", - problem, host, e->e_from.q_paddr, errstring(errno)); + "collect: %s on connection from %.100s, sender=%s: %s", + problem, host, + shortenstring(e->e_from.q_paddr, 203), + errstring(errno)); # endif - if (feof(InChannel)) + if (feof(fp)) usrerr("451 collect: %s on connection from %s, from=%s", - problem, host, e->e_from.q_paddr); + problem, host, + shortenstring(e->e_from.q_paddr, 203)); else syserr("451 collect: %s on connection from %s, from=%s", - problem, host, e->e_from.q_paddr); + problem, host, + shortenstring(e->e_from.q_paddr, 203)); /* don't return an error indication */ e->e_to = NULL; @@ -364,84 +483,128 @@ readerr: eatheader(e, !requeueflag); + if (GrabTo && e->e_sendqueue == NULL) + usrerr("No recipient addresses found in header"); + /* collect statistics */ if (OpMode != MD_VERIFY) + { + extern void markstats(); + markstats(e, (ADDRESS *) NULL); + } /* ** Add an Apparently-To: line if we have no recipient lines. */ - if (hvalue("to", e) == NULL && hvalue("cc", e) == NULL && - hvalue("bcc", e) == NULL && hvalue("apparently-to", e) == NULL) + 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; + extern void addheader(); /* create an Apparently-To: field */ /* that or reject the message.... */ - for (q = e->e_sendqueue; q != NULL; q = q->q_next) + switch (NoRecipientAction) { - if (q->q_alias != NULL) - continue; - if (tTd(30, 3)) - printf("Adding Apparently-To: %s\n", q->q_paddr); - addheader("Apparently-To", q->q_paddr, e); + case NRA_ADD_APPARENTLY_TO: + hdr = "Apparently-To"; + break; + + case NRA_ADD_TO: + hdr = "To"; + break; + + case NRA_ADD_BCC: + addheader("Bcc", "", &e->e_header); + break; + + case NRA_ADD_TO_UNDISCLOSED: + addheader("To", "undisclosed-recipients:;", &e->e_header); + 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)) + printf("Adding %s: %s\n", + hdr, q->q_paddr); + addheader(hdr, q->q_paddr, &e->e_header); + } } } /* check for message too large */ if (MaxMessageSize > 0 && e->e_msgsize > MaxMessageSize) { + e->e_status = "5.2.3"; usrerr("552 Message exceeds maximum fixed size (%ld)", MaxMessageSize); +# ifdef LOG + if (LogLevel > 6) + syslog(LOG_NOTICE, "%s: message size (%ld) exceeds maximum (%ld)", + e->e_id, e->e_msgsize, MaxMessageSize); +# endif } - if ((e->e_dfp = fopen(e->e_df, "r")) == NULL) + /* check for illegal 8-bit data */ + if (HasEightBits) + { + e->e_flags |= EF_HAS8BIT; + if (!bitset(MM_PASS8BIT|MM_MIME8BIT, MimeMode)) + { + e->e_status = "5.6.1"; + usrerr("554 Eight bit data not allowed"); + } + } + else + { + /* if it claimed to be 8 bits, well, it lied.... */ + if (e->e_bodytype != NULL && + strcasecmp(e->e_bodytype, "8BITMIME") == 0) + e->e_bodytype = "7BIT"; + } + + if ((e->e_dfp = fopen(dfname, "r")) == NULL) { /* we haven't acked receipt yet, so just chuck this */ - syserr("Cannot reopen %s", e->e_df); + syserr("Cannot reopen %s", dfname); finis(); } } -/* -** FLUSHEOL -- if not at EOL, throw away rest of input line. -** -** Parameters: -** buf -- last line read in (checked for '\n'), -** fp -- file to be read from. -** -** Returns: -** FALSE on error from sfgets(), TRUE otherwise. -** -** Side Effects: -** none. -*/ -bool -flusheol(buf, fp) - char *buf; - FILE *fp; -{ - register char *p = buf; - char junkbuf[MAXLINE]; - while (strchr(p, '\n') == NULL) - { - CollectErrorMessage = "553 header line too long"; - CollectErrno = 0; - if (sfgets(junkbuf, MAXLINE, fp, TimeOuts.to_datablock, - "long line flush") == NULL) - return (FALSE); - p = junkbuf; - } +static void +collecttimeout(timeout) + time_t timeout; +{ + /* if no progress was made, die now */ + if (!CollectProgress) + longjmp(CtxCollectTimeout, 1); - return (TRUE); + /* otherwise reset the timeout */ + CollectTimeout = setevent(timeout, collecttimeout, timeout); + CollectProgress = FALSE; } /* ** TFERROR -- signal error on writing the temporary file. ** ** Parameters: ** tf -- the file pointer for the temporary file. +** e -- the current envelope. ** ** Returns: ** none. @@ -451,21 +614,22 @@ flusheol(buf, fp) ** Arranges for following output to go elsewhere. */ +void tferror(tf, e) FILE *tf; register ENVELOPE *e; { - CollectErrno = errno; + setstat(EX_IOERR); if (errno == ENOSPC) { struct stat st; long avail; long bsize; - NoReturn = TRUE; + e->e_flags |= EF_NO_BODY_RETN; if (fstat(fileno(tf), &st) < 0) st.st_size = 0; - (void) freopen(e->e_df, "w", tf); + (void) freopen(queuename(e, 'd'), "w", tf); if (st.st_size <= 0) fprintf(tf, "\n*** Mail could not be accepted"); else if (sizeof st.st_size > sizeof (long)) @@ -476,7 +640,7 @@ tferror(tf, e) st.st_size); fprintf(tf, "*** at %s due to lack of disk space for temp file.\n", MyHostName); - avail = freespace(QueueDir, &bsize); + avail = freediskspace(QueueDir, &bsize); if (avail > 0) { if (bsize > 1024) @@ -486,12 +650,11 @@ tferror(tf, e) fprintf(tf, "*** Currently, %ld kilobytes are available for mail temp files.\n", avail); } - CollectErrorMessage = "452 Out of disk space for temp file"; + e->e_status = "4.3.1"; + usrerr("452 Out of disk space for temp file"); } else - { - CollectErrorMessage = "cannot write message body to disk (%s)"; - } + syserr("collect: Cannot write tf%s", e->e_id); (void) freopen("/dev/null", "w", tf); } /* @@ -525,6 +688,7 @@ char *MonthList[] = NULL }; +void eatfrom(fm, e) char *fm; register ENVELOPE *e; |
