diff options
Diffstat (limited to 'contrib/sendmail/src/collect.c')
-rw-r--r-- | contrib/sendmail/src/collect.c | 258 |
1 files changed, 201 insertions, 57 deletions
diff --git a/contrib/sendmail/src/collect.c b/contrib/sendmail/src/collect.c index 762c60155f9b..418517902233 100644 --- a/contrib/sendmail/src/collect.c +++ b/contrib/sendmail/src/collect.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2006, 2008 Proofpoint, Inc. and its suppliers. + * Copyright (c) 1998-2006, 2008, 2023, 2024 Proofpoint, Inc. and its suppliers. * All rights reserved. * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved. * Copyright (c) 1988, 1993 @@ -34,7 +34,7 @@ static SM_FILE_T *collect_eoh __P((ENVELOPE *, int, int)); ** numhdrs -- number of headers ** hdrslen -- length of headers ** -** Results: +** Returns: ** NULL, or handle to open data file ** ** Side Effects: @@ -82,7 +82,7 @@ collect_eoh(e, numhdrs, hdrslen) ** Parameters: ** e -- envelope ** -** Results: +** Returns: ** none. ** ** Side Effects: @@ -179,7 +179,7 @@ collect_doheader(e) ** Parameters: ** e -- envelope ** -** Results: +** Returns: ** NULL, or a pointer to an open data file, ** into which the message body will be written by collect(). ** @@ -233,6 +233,70 @@ collect_dfopen(e) } /* +** INCBUFLEN -- increase buflen for the header buffer in collect() +** +** Parameters: +** buflen -- current size of buffer +** +** Returns: +** new buflen +*/ + +static int incbuflen __P((int)); +static int +incbuflen(buflen) + int buflen; +{ + int newlen; + + /* this also handles the case of MaxMessageSize == 0 */ + if (MaxMessageSize <= MEMCHUNKSIZE) + { + if (buflen < MEMCHUNKSIZE) + return buflen * 2; + else + return buflen + MEMCHUNKSIZE; + } + + /* MaxMessageSize > MEMCHUNKSIZE */ + newlen = buflen * 2; + if (newlen > 0 && newlen < MaxMessageSize) + return newlen; + else + return MaxMessageSize; +} + +#if _FFR_TESTS +/* just for testing/debug output */ +static const char * +makeprint(c) + char c; +{ + static char prt[6]; + + prt[1] = '\0'; + prt[2] = '\0'; + if (isprint((unsigned char)c)) + prt[0] = c; + else if ('\n' == c) + { + prt[0] = 'L'; + prt[1] = 'F'; + } + else if ('\r' == c) + { + prt[0] = 'C'; + prt[1] = 'R'; + } + else + snprintf(prt, sizeof(prt), "%o", c); + return prt; +} +#else /* _FFR_TESTS */ +# define makeprint(c) "X" +#endif /* _FFR_TESTS */ + +/* ** COLLECT -- read & parse message header & make temp file. ** ** Creates a temporary file name and copies the standard @@ -241,10 +305,10 @@ collect_dfopen(e) ** ** 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. +** smtpmode -- if >= SMTPMODE_LAX 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? @@ -267,20 +331,26 @@ collect_dfopen(e) /* 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_DOT 2 /* read "." at beginning of line */ #define IS_DOTCR 3 /* read ".\r" at beginning of line */ -#define IS_CR 4 /* read a carriage return */ +#define IS_CR 4 /* read "\r" */ + +/* hack to enhance readability of debug output */ +static const char *istates[] = { "NORM", "BOL", "DOT", "DOTCR", "CR" }; +#define ISTATE istates[istate] /* 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 */ +#define BARE_LF_MSG "Bare linefeed (LF) not allowed" +#define BARE_CR_MSG "Bare carriage return (CR) not allowed" void collect(fp, smtpmode, hdrp, e, rsetsize) SM_FILE_T *fp; - bool smtpmode; + int smtpmode; HDR **hdrp; register ENVELOPE *e; bool rsetsize; @@ -306,12 +376,26 @@ collect(fp, smtpmode, hdrp, e, rsetsize) #if _FFR_REJECT_NUL_BYTE bool hasNUL; /* has at least one NUL input byte */ #endif + int bare_lf, bare_cr; + +#define SMTPMODE (smtpmode >= SMTPMODE_LAX) +#define SMTPMODE_STRICT ((smtpmode & SMTPMODE_CRLF) != 0) +#define BARE_LF_421 ((smtpmode & SMTPMODE_LF_421) != 0) +#define BARE_CR_421 ((smtpmode & SMTPMODE_CR_421) != 0) +#define BARE_LF_SP ((smtpmode & SMTPMODE_LF_SP) != 0) +#define BARE_CR_SP ((smtpmode & SMTPMODE_CR_SP) != 0) + +/* for bare_{lf,cr} */ +#define BARE_IN_HDR 0x01 +#define BARE_IN_BDY 0x02 +#define BARE_WHERE ((MS_BODY == mstate) ? BARE_IN_BDY : BARE_IN_HDR) df = NULL; - ignrdot = smtpmode ? false : IgnrDot; + ignrdot = SMTPMODE ? false : IgnrDot; + bare_lf = bare_cr = 0; /* timeout for I/O functions is in milliseconds */ - dbto = smtpmode ? ((int) TimeOuts.to_datablock * 1000) + dbto = SMTPMODE ? ((int) TimeOuts.to_datablock * 1000) : SM_TIME_FOREVER; sm_io_setinfo(fp, SM_IO_WHAT_TIMEOUT, &dbto); old_rd_tmo = set_tls_rd_tmo(TimeOuts.to_datablock); @@ -334,15 +418,15 @@ collect(fp, smtpmode, hdrp, e, rsetsize) ** Tell ARPANET to go ahead. */ - if (smtpmode) - message("354 Enter mail, end with \".\" on a line by itself"); + if (SMTPMODE) + message("354 End data with <CR><LF>.<CR><LF>"); /* simulate an I/O timeout when used as sink */ if (tTd(83, 101)) sleep(319); if (tTd(30, 2)) - sm_dprintf("collect\n"); + sm_dprintf("collect, smtpmode=%#x\n", smtpmode); /* ** Read the message. @@ -358,7 +442,7 @@ collect(fp, smtpmode, hdrp, e, rsetsize) for (;;) { if (tTd(30, 35)) - sm_dprintf("top, istate=%d, mstate=%d\n", istate, + sm_dprintf("top, istate=%s, mstate=%d\n", ISTATE, mstate); for (;;) { @@ -379,7 +463,7 @@ collect(fp, smtpmode, hdrp, e, rsetsize) /* timeout? */ if (c == SM_IO_EOF && errno == EAGAIN - && smtpmode) + && SMTPMODE) { /* ** Override e_message in @@ -417,15 +501,32 @@ collect(fp, smtpmode, hdrp, e, rsetsize) hasNUL = true; #endif if (c == SM_IO_EOF) - goto readerr; - if (SevenBitInput) + goto readdone; + if (SevenBitInput || + bitset(EF_7BITBODY, e->e_flags)) c &= 0x7f; else HasEightBits |= bitset(0x80, c); } if (tTd(30, 94)) - sm_dprintf("istate=%d, c=%c (0x%x)\n", - istate, (char) c, c); + sm_dprintf("istate=%s, c=%s (0x%x)\n", + ISTATE, makeprint((char) c), c); + if ('\n' == c && SMTPMODE && + !(IS_CR == istate || IS_DOTCR == istate)) + { + bare_lf |= BARE_WHERE; + if (BARE_LF_421) + { + inputerr = true; + goto readabort; + } + if (BARE_LF_SP) + { + if (TTD(30, 64)) + sm_dprintf("LF: c=%s %#x\n", makeprint((char) c), c); + c = ' '; + } + } switch (istate) { case IS_BOL: @@ -437,8 +538,8 @@ collect(fp, smtpmode, hdrp, e, rsetsize) break; case IS_DOT: - if (c == '\n' && !ignrdot) - goto readerr; + if (c == '\n' && !ignrdot && !SMTPMODE_STRICT) + goto readdone; else if (c == '\r') { istate = IS_DOTCR; @@ -460,7 +561,7 @@ collect(fp, smtpmode, hdrp, e, rsetsize) case IS_DOTCR: if (c == '\n' && !ignrdot) - goto readerr; + goto readdone; else { /* push back the ".\rx" */ @@ -483,12 +584,30 @@ collect(fp, smtpmode, hdrp, e, rsetsize) case IS_CR: if (c == '\n') + { + if (TTD(30, 64)) + sm_dprintf("state=CR, c=%s %#x -> BOL\n", makeprint((char) c), c); istate = IS_BOL; + } else { + if (TTD(30, 64)) + sm_dprintf("state=CR, c=%s %#x -> NORM\n", makeprint((char) c), c); + if (SMTPMODE) + { + bare_cr |= BARE_WHERE; + if (BARE_CR_421) + { + inputerr = true; + goto readabort; + } + } (void) sm_io_ungetc(fp, SM_TIME_DEFAULT, c); - c = '\r'; + if (BARE_CR_SP) + c = ' '; + else + c = '\r'; istate = IS_NORM; } goto bufferchar; @@ -499,7 +618,7 @@ collect(fp, smtpmode, hdrp, e, rsetsize) istate = IS_CR; continue; } - else if (c == '\n') + else if (c == '\n' && !SMTPMODE_STRICT) istate = IS_BOL; else istate = IS_NORM; @@ -524,7 +643,8 @@ bufferchar: if (!bitset(EF_TOOBIG, e->e_flags)) (void) sm_io_putc(df, SM_TIME_DEFAULT, c); - + if (TTD(30, 64)) + sm_dprintf("state=%s, put=%s %#x\n", ISTATE, makeprint((char) c), c); /* FALLTHROUGH */ case MS_DISCARD: @@ -540,10 +660,9 @@ bufferchar: /* out of space for header */ obuf = buf; - if (buflen < MEMCHUNKSIZE) - buflen *= 2; - else - buflen += MEMCHUNKSIZE; + buflen = incbuflen(buflen); + if (tTd(30, 32)) + sm_dprintf("buflen=%d, hdrslen=%d\n", buflen, hdrslen); if (buflen <= 0) { sm_syslog(LOG_NOTICE, e->e_id, @@ -592,8 +711,8 @@ bufferchar: nextstate: if (tTd(30, 35)) - sm_dprintf("nextstate, istate=%d, mstate=%d, line=\"%s\"\n", - istate, mstate, buf); + sm_dprintf("nextstate, istate=%s, mstate=%d, line=\"%s\"\n", + ISTATE, mstate, buf); switch (mstate) { case MS_UFROM: @@ -624,7 +743,7 @@ nextstate: /* timeout? */ if (c == SM_IO_EOF && errno == EAGAIN - && smtpmode) + && SMTPMODE) { /* ** Override e_message in @@ -653,7 +772,7 @@ nextstate: /* guaranteed by isheader(buf) */ SM_ASSERT(*(bp - 1) != '\n' || bp > buf + 1); - /* trim off trailing CRLF or NL */ + /* trim off trailing CRLF or LF */ if (*--bp != '\n' || *--bp != '\r') bp++; *bp = '\0'; @@ -673,7 +792,7 @@ nextstate: sm_dprintf("EOH\n"); if (headeronly) - goto readerr; + goto readdone; df = collect_eoh(e, numhdrs, hdrslen); if (df == NULL) @@ -700,8 +819,8 @@ nextstate: bp = buf; } -readerr: - if ((sm_io_eof(fp) && smtpmode) || sm_io_error(fp)) +readdone: + if ((sm_io_eof(fp) && SMTPMODE) || sm_io_error(fp)) { const char *errmsg; @@ -741,7 +860,7 @@ readerr: } else if (SuperSafe == SAFE_NO || SuperSafe == SAFE_INTERACTIVE || - (SuperSafe == SAFE_REALLY_POSTMILTER && smtpmode)) + (SuperSafe == SAFE_REALLY_POSTMILTER && SMTPMODE)) { /* skip next few clauses */ /* EMPTY */ @@ -806,33 +925,43 @@ readerr: 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 if (0 != bare_lf) + problem = BARE_LF_MSG; + else if (0 != bare_cr) + problem = BARE_CR_MSG; else problem = "read timeout"; - if (LogLevel > 0 && sm_io_eof(fp)) + +#define LOG_CLT ((NULL != RealHostName) ? RealHostName: "localhost") +#define CONN_ERR_TXT "collect: relay=%s, from=%s, info=%s%s%s%s" +#define CONN_ERR_CODE "421 4.4.1 " +#define CONN_LOG_FROM shortenstring(e->e_from.q_paddr, MAXSHORTSTR) +#define CONN_ERR_BARE (0 != bare_lf) ? BARE_LF_MSG : ((0 != bare_cr) ? BARE_CR_MSG : "") +#define CONN_ERR_WHERE(bare_xy) (BARE_IN_HDR==(bare_xy) ? "header" : \ + (BARE_IN_BDY==(bare_xy) ? "body" : "header+body")) + +#define HAS_BARE_XY (0 != (bare_lf | bare_cr)) +#define CONN_ERR_ARGS LOG_CLT, CONN_LOG_FROM, problem, \ + HAS_BARE_XY ? ", where=" : "", \ + HAS_BARE_XY ? CONN_ERR_WHERE(bare_lf|bare_cr) : "", \ + HAS_BARE_XY ? ", status=tempfail" : "" + + if (LogLevel > 0 && (sm_io_eof(fp) || (0 != (bare_lf | bare_cr)))) 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)); + CONN_ERR_TXT, CONN_ERR_ARGS); + if (0 != (bare_lf | bare_cr)) + usrerr("421 4.5.0 %s", CONN_ERR_BARE); + else if (sm_io_eof(fp)) + usrerr(CONN_ERR_CODE CONN_ERR_TXT, CONN_ERR_ARGS); else - syserr("421 4.4.1 collect: %s on connection from %s, from=%s", - problem, host, - shortenstring(e->e_from.q_paddr, MAXSHORTSTR)); + syserr(CONN_ERR_CODE CONN_ERR_TXT, CONN_ERR_ARGS); flush_errors(true); /* don't return an error indication */ @@ -863,6 +992,21 @@ readerr: e->e_flags &= ~EF_LOGSENDER; } +#define LOG_BARE_XY(bare_xy, bare_xy_sp, bare_xy_msg) \ + do \ + { \ + if ((0 != bare_xy) && LogLevel > 8) \ + sm_syslog(LOG_NOTICE, e->e_id, \ + "collect: relay=%s, from=%s, info=%s, where=%s%s" \ + , LOG_CLT, CONN_LOG_FROM, bare_xy_msg \ + , CONN_ERR_WHERE(bare_xy) \ + , bare_xy_sp ? ", status=replaced" : "" \ + ); \ + } while (0) + + LOG_BARE_XY(bare_lf, BARE_LF_SP, BARE_LF_MSG); + LOG_BARE_XY(bare_cr, BARE_CR_SP, BARE_CR_MSG); + /* check for message too large */ if (bitset(EF_TOOBIG, e->e_flags)) { @@ -947,7 +1091,7 @@ readerr: /* ** DFERROR -- signal error on writing the data file. ** -** Called by collect(). Collect() always terminates the process +** 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. |