Package: release.debian.org Severity: normal Tags: bullseye X-Debbugs-Cc: sendm...@packages.debian.org Control: affects -1 + src:sendmail User: release.debian....@packages.debian.org Usertags: pu
[ Reason ] Fix CVE-2023-51765 (smtp smugling) [ Impact ] SMTP smugling [ Tests ] Manual test using virtual machine [ Risks ] Low [ Checklist ] [X] *all* changes are documented in the d/changelog [X] I reviewed all changes and I approve them [X] attach debdiff against the package in (old)stable [X] the issue is verified as fixed in unstable [ Changes ] * QA-upload * Fix CVE-2023-51765 (Closes: #1059386): sendmail allowed SMTP smuggling in certain configurations. Remote attackers can use a published exploitation technique to inject e-mail messages with a spoofed MAIL FROM address, allowing bypass of an SPF protection mechanism. This occurs because sendmail supports <LF>.<CR><LF> but some other popular e-mail servers do not. This is resolved with 'o' in srv_features. * Enable _FFR_REJECT_NUL_BYTE for rejecting mail that include NUL byte * By default enable rejecting mail that include NUL byte. set confREJECT_NUL to 'true' by default . User could disable by setting confREJECT_NUL to false. (Closes: #1070190). Close a variant of CVE-2023-51765 aka SMTP smuggling.
diff -Nru sendmail-8.15.2/debian/changelog sendmail-8.15.2/debian/changelog --- sendmail-8.15.2/debian/changelog 2021-03-16 15:04:16.000000000 +0000 +++ sendmail-8.15.2/debian/changelog 2024-05-13 18:44:56.000000000 +0000 @@ -1,3 +1,24 @@ +sendmail (8.15.2-22+deb11u1) bullseye-security; urgency=medium + + * QA-upload + * Fix CVE-2023-51765 (Closes: #1059386): + sendmail allowed SMTP smuggling in certain configurations. + Remote attackers can use a published exploitation + technique to inject e-mail messages with a spoofed + MAIL FROM address, allowing bypass of an SPF protection + mechanism. This occurs because sendmail supports + <LF>.<CR><LF> but some other popular e-mail servers + do not. This is resolved with 'o' in srv_features. + * Enable _FFR_REJECT_NUL_BYTE for rejecting mail that + include NUL byte + * By default enable rejecting mail that include NUL byte. + set confREJECT_NUL to 'true' by default . + User could disable by setting confREJECT_NUL to false. + (Closes: #1070190). Close a variant of CVE-2023-51765 + aka SMTP smuggling. + + -- Bastien Roucari??s <ro...@debian.org> Mon, 13 May 2024 18:44:56 +0000 + sendmail (8.15.2-22) unstable; urgency=medium * QA upload. diff -Nru sendmail-8.15.2/debian/configure.ac sendmail-8.15.2/debian/configure.ac --- sendmail-8.15.2/debian/configure.ac 2021-03-16 15:04:16.000000000 +0000 +++ sendmail-8.15.2/debian/configure.ac 2024-05-13 18:44:56.000000000 +0000 @@ -468,6 +468,7 @@ sm_envdef="$sm_envdef -DHASFLOCK=0"; sm_libsm_envdef="$sm_libsm_envdef -DHAVE_NANOSLEEP=1"; sm_ffr="$sm_ffr -D_FFR_QUEUE_SCHED_DBG"; # %%%%%% TESTING %%%%%%%% +sm_ffr="$sm_ffr -D_FFR_REJECT_NUL_BYTE"; # # version specific setup if test "$sm_version_major" = "8.16"; then diff -Nru sendmail-8.15.2/debian/NEWS.Debian sendmail-8.15.2/debian/NEWS.Debian --- sendmail-8.15.2/debian/NEWS.Debian 1970-01-01 00:00:00.000000000 +0000 +++ sendmail-8.15.2/debian/NEWS.Debian 2024-05-13 18:44:56.000000000 +0000 @@ -0,0 +1,19 @@ +sendmail (8.18.1-3) unstable; urgency=medium + + Sendmail was affected by SMTP smurgling (CVE-2023-51765). + Remote attackers can use a published exploitation technique + to inject e-mail messages with a spoofed MAIL FROM address, + allowing bypass of an SPF protection mechanism. + This occurs because sendmail supports some combinaison of + <CR><LF><NUL>. + . + This particular injection vulnerability has been closed, + unfortunatly full closure need to reject mail that + contain NUL. + . + This is slighly non conformant with RFC and could + be opt-out by setting confREJECT_NUL to 'false' + in sendmail.mc file. + + -- Bastien Roucari??s <ro...@debian.org> Sun, 12 May 2024 19:38:09 +0000 + diff -Nru sendmail-8.15.2/debian/patches/0024-CVE-2023-51765.patch sendmail-8.15.2/debian/patches/0024-CVE-2023-51765.patch --- sendmail-8.15.2/debian/patches/0024-CVE-2023-51765.patch 1970-01-01 00:00:00.000000000 +0000 +++ sendmail-8.15.2/debian/patches/0024-CVE-2023-51765.patch 2024-05-13 18:44:56.000000000 +0000 @@ -0,0 +1,1242 @@ +From: =?utf-8?q?Bastien_Roucari=C3=A8s?= <ro...@debian.org> +Date: Thu, 15 Feb 2024 07:59:27 +0000 +Subject: CVE-2023-51765 + +sendmail allowed SMTP smuggling in certain configurations. + +Remote attackers can use a published exploitation technique +to inject e-mail messages with a spoofed MAIL FROM address, +allowing bypass of an SPF protection mechanism. + +This occurs because sendmail supports <LF>.<CR><LF> but some other popular +e-mail servers do not. This is resolved in 8.18 and later versions with 'o' in srv_features. +--- + RELEASE_NOTES | 24 ++++- + libsm/lowercase.c | 168 +++++++++++++++++++++++++++++++++ + sendmail/collect.c | 204 ++++++++++++++++++++++++++++++---------- + sendmail/main.c | 5 +- + sendmail/mime.c | 8 +- + sendmail/sendmail.h | 19 +++- + sendmail/srvrsmtp.c | 265 ++++++++++++++++++++++++++++++++++++---------------- + sendmail/usersmtp.c | 11 ++- + sendmail/util.c | 2 +- + 9 files changed, 563 insertions(+), 143 deletions(-) + create mode 100644 libsm/lowercase.c + +diff --git a/RELEASE_NOTES b/RELEASE_NOTES +index 18a7cae..e1aeca9 100644 +--- a/RELEASE_NOTES ++++ b/RELEASE_NOTES +@@ -5,6 +5,28 @@ This listing shows the version of the sendmail binary, the version + of the sendmail configuration files, the date of release, and a + summary of the changes in that release. + ++Backport 8.18.1/8.18.1 2024/01/31 ++ sendmail is now stricter in following the RFCs and rejects ++ some invalid input with respect to line endings ++ and pipelining: ++ - Prevent transaction stuffing by ensuring SMTP clients ++ wait for the HELO/EHLO and DATA response before sending ++ further SMTP commands. This can be disabled using ++ the new srv_features option 'F'. Issue reported by ++ Yepeng Pan and Christian Rossow from CISPA Helmholtz ++ Center for Information Security. ++ - Accept only CRLF . CRLF as end of an SMTP message ++ as required by the RFCs, which can disabled by the ++ new srv_features option 'O'. ++ - Do not accept a CR or LF except in the combination ++ CRLF (as required by the RFCs). These checks can ++ be disabled by the new srv_features options ++ 'U' and 'G', respectively. In this case it is ++ suggested to use 'u2' and 'g2' instead so the server ++ replaces offending bare CR or bare LF with a space. ++ It is recommended to only turn these protections off ++ for trusted networks due to the potential for abuse. ++ + 8.15.2/8.15.2 2015/07/03 + If FEATURE(`nopercenthack') is used then some bogus input triggered + a recursion which was caught and logged as +@@ -8173,7 +8195,7 @@ summary of the changes in that release. + should show the pathname rather than hex bytes. + Restore ``-ba'' mode -- this reads a file from stdin and parses + the header for envelope sender information and uses +- CR-LF as message terminators. It was thought to be ++ CRLF as message terminators. It was thought to be + obsolete (used only for Arpanet NCP protocols), but it + turns out that the UK ``Grey Book'' protocols require + that functionality. +diff --git a/libsm/lowercase.c b/libsm/lowercase.c +new file mode 100644 +index 0000000..f980d2f +--- /dev/null ++++ b/libsm/lowercase.c +@@ -0,0 +1,168 @@ ++/* ++ * Copyright (c) 2020 Proofpoint, Inc. and its suppliers. ++ * 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 <sm/gen.h> ++#include <sm/sendmail.h> ++ ++#include <ctype.h> ++#include <sm/string.h> ++#include <sm/heap.h> ++#if USE_EAI ++# include <sm/ixlen.h> ++# include <unicode/ucasemap.h> ++# include <unicode/ustring.h> ++# include <unicode/uchar.h> ++ ++/* ++** ASCIISTR -- check whether a string is printable ASCII ++** ++** Parameters: ++** str -- string ++** ++** Returns: ++** TRUE iff printable ASCII ++*/ ++ ++bool ++asciistr(str) ++ const char *str; ++{ ++ unsigned char ch; ++ ++ if (str == NULL) ++ return true; ++ ++ SM_REQUIRE(len < INT_MAX); ++ n = 0; ++ while (n < len && (ch = (unsigned char)*str) != '\0' ++ && ch >= 32 && ch < 127) ++ { ++ n++; ++ str++; ++ return ch == '\0'; ++} ++#endif /* USE_EAI */ ++ ++/* ++** MAKELOWER -- Translate a line into lower case ++** ++** Parameters: ++** p -- string to translate (modified in place if possible). [A] ++** ++** Returns: ++** lower cased string ++** ++** Side Effects: ++** String p is translated to lower case if possible. ++*/ ++ ++char * ++makelower(p) ++ char *p; ++{ ++ char c; ++ char *orig; ++ ++ if (p == NULL) ++ return p; ++ orig = p; ++#if USE_EAI ++ if (!asciistr(p)) ++ return (char *)sm_lowercase(p); ++#endif ++ for (; (c = *p) != '\0'; p++) ++ if (isascii(c) && isupper(c)) ++ *p = tolower(c); ++ return orig; ++} ++ ++#if USE_EAI ++/* ++** SM_LOWERCASE -- lower case a UTF-8 string ++** Note: this should ONLY be applied to a UTF-8 string, ++** i.e., the caller should check first if it isn't an ASCII string. ++** ++** Parameters: ++** str -- original string ++** ++** Returns: ++** lower case version of string [S] ++** ++** How to return an error description due to failed unicode calls? ++** However, is that even relevant? ++*/ ++ ++char * ++sm_lowercase(str) ++ const char *str; ++{ ++ int olen, ilen; ++ UErrorCode error; ++ ssize_t req; ++ int n; ++ static UCaseMap *csm = NULL; ++ static char *out = NULL; ++ static int outlen = 0; ++ ++# if SM_CHECK_REQUIRE ++ if (sm_debug_active(&SmExpensiveRequire, 3)) ++ SM_REQUIRE(!asciistr(str)); ++# endif ++ /* an empty string is always ASCII */ ++ SM_REQUIRE(NULL != str && '\0' != *str); ++ ++ if (NULL == csm) ++ { ++ error = U_ZERO_ERROR; ++ csm = ucasemap_open("en_US", U_FOLD_CASE_DEFAULT, &error); ++ if (U_SUCCESS(error) == 0) ++ { ++ /* syserr("ucasemap_open error: %s", u_errorName(error)); */ ++ return NULL; ++ } ++ } ++ ++ ilen = strlen(str); ++ olen = ilen + 1; ++ if (olen > outlen) ++ { ++ outlen = olen; ++ out = sm_realloc_x(out, outlen); ++ } ++ ++ for (n = 0; n < 3; n++) ++ { ++ error = U_ZERO_ERROR; ++ req = ucasemap_utf8FoldCase(csm, out, olen, str, ilen, &error); ++ if (U_SUCCESS(error)) ++ { ++ if (req >= olen) ++ { ++ outlen = req + 1; ++ out = sm_realloc_x(out, outlen); ++ out[req] = '\0'; ++ } ++ break; ++ } ++ else if (error == U_BUFFER_OVERFLOW_ERROR) ++ { ++ outlen = req + 1; ++ out = sm_realloc_x(out, outlen); ++ olen = outlen; ++ } ++ else ++ { ++ /* syserr("conversion error for \"%s\": %s", str, u_errorName(error)); */ ++ return NULL; ++ } ++ } ++ return out; ++} ++#endif /* USE_EAI */ +diff --git a/sendmail/collect.c b/sendmail/collect.c +index 5f090b2..ef3be9c 100644 +--- a/sendmail/collect.c ++++ b/sendmail/collect.c +@@ -230,6 +230,36 @@ collect_dfopen(e) + return df; + } + ++#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. + ** +@@ -265,20 +295,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; +@@ -304,12 +340,26 @@ collect(fp, smtpmode, hdrp, e, rsetsize) + #if _FFR_REJECT_NUL_BYTE + bool hasNUL; /* has at least one NUL input byte */ + #endif /* _FFR_REJECT_NUL_BYTE */ ++ 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); +@@ -332,15 +382,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. +@@ -356,7 +406,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 (;;) + { +@@ -377,7 +427,7 @@ collect(fp, smtpmode, hdrp, e, rsetsize) + + /* timeout? */ + if (c == SM_IO_EOF && errno == EAGAIN +- && smtpmode) ++ && SMTPMODE) + { + /* + ** Override e_message in +@@ -415,15 +465,32 @@ collect(fp, smtpmode, hdrp, e, rsetsize) + hasNUL = true; + #endif /* _FFR_REJECT_NUL_BYTE */ + 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: +@@ -435,11 +502,9 @@ collect(fp, smtpmode, hdrp, e, rsetsize) + 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)) ++ if (c == '\n' && !ignrdot && !SMTPMODE_STRICT) ++ goto readdone; ++ else if (c == '\r') + { + istate = IS_DOTCR; + continue; +@@ -460,7 +525,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 +548,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,8 +582,7 @@ collect(fp, smtpmode, hdrp, e, rsetsize) + istate = IS_CR; + continue; + } +- else if (c == '\n' && !bitset(EF_NL_NOT_EOL, +- e->e_flags)) ++ else if (c == '\n' && !SMTPMODE_STRICT) + istate = IS_BOL; + else + istate = IS_NORM; +@@ -525,7 +607,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: +@@ -593,8 +676,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: +@@ -625,7 +708,7 @@ nextstate: + + /* timeout? */ + if (c == SM_IO_EOF && errno == EAGAIN +- && smtpmode) ++ && SMTPMODE) + { + /* + ** Override e_message in +@@ -654,7 +737,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'; +@@ -674,7 +757,7 @@ nextstate: + sm_dprintf("EOH\n"); + + if (headeronly) +- goto readerr; ++ goto readdone; + + df = collect_eoh(e, numhdrs, hdrslen); + if (df == NULL) +@@ -703,8 +786,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; + +@@ -744,7 +827,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 */ +@@ -809,33 +892,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 */ +@@ -867,6 +960,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)) + { +diff --git a/sendmail/main.c b/sendmail/main.c +index df74288..c9bc158 100644 +--- a/sendmail/main.c ++++ b/sendmail/main.c +@@ -2801,7 +2801,8 @@ main(argc, argv, envp) + + /* collect body for UUCP return */ + if (OpMode != MD_VERIFY) +- collect(InChannel, false, NULL, &MainEnvelope, true); ++ collect(InChannel, SMTPMODE_NO, NULL, &MainEnvelope, ++ true); + finis(true, true, EX_USAGE); + /* NOTREACHED */ + } +@@ -2861,7 +2862,7 @@ main(argc, argv, envp) + MainEnvelope.e_flags &= ~EF_FATALERRS; + Errors = 0; + buffer_errors(); +- collect(InChannel, false, NULL, &MainEnvelope, true); ++ collect(InChannel, SMTPMODE_NO, NULL, &MainEnvelope, true); + + /* header checks failed */ + if (Errors > 0) +diff --git a/sendmail/mime.c b/sendmail/mime.c +index ecfc761..69171e0 100644 +--- a/sendmail/mime.c ++++ b/sendmail/mime.c +@@ -346,7 +346,7 @@ mime8to7(mci, header, e, boundaries, flags, level) + goto writeerr; + if (tTd(43, 35)) + sm_dprintf(" ...%s\n", buf); +- collect(e->e_dfp, false, &hdr, e, false); ++ collect(e->e_dfp, SMTPMODE_NO, &hdr, e, false); + if (tTd(43, 101)) + putline("+++after collect", mci); + if (!putheader(mci, hdr, e, flags)) +@@ -408,7 +408,7 @@ mime8to7(mci, header, e, boundaries, flags, level) + goto writeerr; + + mci->mci_flags |= MCIF_INMIME; +- collect(e->e_dfp, false, &hdr, e, false); ++ collect(e->e_dfp, SMTPMODE_NO, &hdr, e, false); + if (tTd(43, 101)) + putline("+++after collect", mci); + if (!putheader(mci, hdr, e, flags)) +@@ -482,7 +482,7 @@ mime8to7(mci, header, e, boundaries, flags, level) + ** If more than 1/8 of the total characters have the + ** eighth bit set, use base64; else use quoted-printable. + ** However, only encode binary encoded data as base64, +- ** since otherwise the NL=>CRLF mapping will be a problem. ++ ** since otherwise the LF=>CRLF mapping will be a problem. + */ + + if (tTd(43, 8)) +@@ -836,7 +836,7 @@ mime_getchar(fp, boundaries, btp) + return *bp++; + } + /* +-** MIME_GETCHAR_CRLF -- do mime_getchar, but translate NL => CRLF ++** MIME_GETCHAR_CRLF -- do mime_getchar, but translate LF => CRLF + ** + ** Parameters: + ** fp -- the input file. +diff --git a/sendmail/sendmail.h b/sendmail/sendmail.h +index b2d0211..28e76d4 100644 +--- a/sendmail/sendmail.h ++++ b/sendmail/sendmail.h +@@ -983,7 +983,7 @@ struct envelope + long e_deliver_by; /* deliver by */ + int e_dlvr_flag; /* deliver by flag */ + SM_RPOOL_T *e_rpool; /* resource pool for this envelope */ +- unsigned int e_features; /* server features */ ++ unsigned long e_features; /* server features */ + #define ENHSC_LEN 11 + #if _FFR_MILTER_ENHSC + char e_enhsc[ENHSC_LEN]; /* enhanced status code */ +@@ -1028,6 +1028,7 @@ struct envelope + #define EF_SPLIT 0x04000000L /* envelope has been split */ + #define EF_UNSAFE 0x08000000L /* unsafe: read from untrusted source */ + #define EF_TOODEEP 0x10000000L /* message is nested too deep */ ++#define EF_7BITBODY 0x40000000L /* strip body to 7bit on input */ + + #define DLVR_NOTIFY 0x01 + #define DLVR_RETURN 0x02 +@@ -2216,6 +2217,11 @@ extern void inittimeouts __P((char *, bool)); + # define tTd(flag, level) (tTdvect[flag] >= (unsigned char)level) + #else + # define tTd(flag, level) (tTdvect[flag] >= (unsigned char)level && !IntSig) ++# if _FFR_TESTS ++# define TTD(flag, level) (tTdvect[flag] >= (unsigned char)level && !IntSig) ++# else ++# define TTD(flag, level) false ++# endif + #endif + #define tTdlevel(flag) (tTdvect[flag]) + +@@ -2672,7 +2678,7 @@ extern void cleanup_shm __P((bool)); + #endif /* SM_CONF_SHM */ + extern void close_sendmail_pid __P((void)); + extern void clrdaemon __P((void)); +-extern void collect __P((SM_FILE_T *, bool, HDR **, ENVELOPE *, bool)); ++extern void collect __P((SM_FILE_T *, int, HDR **, ENVELOPE *, bool)); + extern bool connection_rate_check __P((SOCKADDR *, ENVELOPE *)); + extern time_t convtime __P((char *, int)); + extern char **copyplist __P((char **, bool, SM_RPOOL_T *)); +@@ -2855,6 +2861,15 @@ extern bool xtextok __P((char *)); + extern int xunlink __P((char *)); + extern char *xuntextify __P((char *)); + ++/* flags for collect() */ ++#define SMTPMODE_NO 0 ++#define SMTPMODE_LAX 0x01 ++#define SMTPMODE_CRLF 0x02 /* CRLF.CRLF required for EOM */ ++#define SMTPMODE_LF_421 0x04 /* bare LF: drop connection */ ++#define SMTPMODE_CR_421 0x08 /* bare CR: drop connection */ ++#define SMTPMODE_LF_SP 0x10 /* bare LF: replace with space */ ++#define SMTPMODE_CR_SP 0x20 /* bare CR: replace with space */ ++ + #if _FFR_RCPTFLAGS + extern bool newmodmailer __P((ADDRESS *, int)); + #endif +diff --git a/sendmail/srvrsmtp.c b/sendmail/srvrsmtp.c +index db056ae..b5f6d43 100644 +--- a/sendmail/srvrsmtp.c ++++ b/sendmail/srvrsmtp.c +@@ -47,26 +47,32 @@ static bool NotFirstDelivery = false; + #endif /* _FFR_DM_ONE */ + + /* server features */ +-#define SRV_NONE 0x0000 /* none... */ +-#define SRV_OFFER_TLS 0x0001 /* offer STARTTLS */ +-#define SRV_VRFY_CLT 0x0002 /* request a cert */ +-#define SRV_OFFER_AUTH 0x0004 /* offer AUTH */ +-#define SRV_OFFER_ETRN 0x0008 /* offer ETRN */ +-#define SRV_OFFER_VRFY 0x0010 /* offer VRFY (not yet used) */ +-#define SRV_OFFER_EXPN 0x0020 /* offer EXPN */ +-#define SRV_OFFER_VERB 0x0040 /* offer VERB */ +-#define SRV_OFFER_DSN 0x0080 /* offer DSN */ ++#define SRV_NONE 0x00000000 /* none... */ ++#define SRV_OFFER_TLS 0x00000001 /* offer STARTTLS */ ++#define SRV_VRFY_CLT 0x00000002 /* request a cert */ ++#define SRV_OFFER_AUTH 0x00000004 /* offer AUTH */ ++#define SRV_OFFER_ETRN 0x00000008 /* offer ETRN */ ++#define SRV_OFFER_VRFY 0x00000010 /* offer VRFY (not yet used) */ ++#define SRV_OFFER_EXPN 0x00000020 /* offer EXPN */ ++#define SRV_OFFER_VERB 0x00000040 /* offer VERB */ ++#define SRV_OFFER_DSN 0x00000080 /* offer DSN */ + #if PIPELINING +-# define SRV_OFFER_PIPE 0x0100 /* offer PIPELINING */ ++# define SRV_OFFER_PIPE 0x00000100 /* offer PIPELINING */ + # if _FFR_NO_PIPE +-# define SRV_NO_PIPE 0x0200 /* disable PIPELINING, sleep if used */ ++# define SRV_NO_PIPE 0x00000200 /* disable PIPELINING, sleep if used */ + # endif /* _FFR_NO_PIPE */ + #endif /* PIPELINING */ +-#define SRV_REQ_AUTH 0x0400 /* require AUTH */ +-#define SRV_REQ_SEC 0x0800 /* require security - equiv to AuthOptions=p */ +-#define SRV_TMP_FAIL 0x1000 /* ruleset caused a temporary failure */ +- +-static unsigned int srvfeatures __P((ENVELOPE *, char *, unsigned int)); ++#define SRV_REQ_AUTH 0x00000400 /* require AUTH */ ++#define SRV_REQ_SEC 0x00000800 /* require security - equiv to AuthOptions=p */ ++#define SRV_TMP_FAIL 0x00001000 /* ruleset caused a temporary failure */ ++#define SRV_BAD_PIPELINE 0x00008000 /* reject bad pipelining (see comment below) */ ++#define SRV_REQ_CRLF 0x00010000 /* require CRLF as EOL */ ++#define SRV_BARE_LF_421 0x00020000 /* bare LF - drop connection */ ++#define SRV_BARE_CR_421 0x00040000 /* bare CR - drop connection */ ++#define SRV_BARE_LF_SP 0x00080000 ++#define SRV_BARE_CR_SP 0x00100000 ++ ++static unsigned long srvfeatures __P((ENVELOPE *, char *, unsigned long)); + + #define STOP_ATTACK ((time_t) -1) + static time_t checksmtpattack __P((volatile unsigned int *, unsigned int, +@@ -74,6 +80,7 @@ static time_t checksmtpattack __P((volatile unsigned int *, unsigned int, + static void printvrfyaddr __P((ADDRESS *, bool, bool)); + static char *skipword __P((char *volatile, char *)); + static void setup_smtpd_io __P((void)); ++static struct timeval *channel_readable __P((SM_FILE_T *, int)); + + #if SASL + # if SASL >= 20000 +@@ -392,6 +399,39 @@ rcptmods(rcpt, e) + # define rcptmods(a, e) + #endif /* _FFR_RCPTFLAGS */ + ++/* ++** CHANNEL_READBLE -- determine if data is readable from the SMTP channel ++** ++** Parameters: ++** channel -- connect channel for reading ++** timeout -- how long to pause for data in milliseconds ++** ++** Returns: ++** timeval contained how long we waited if data detected, ++** NULL otherwise ++*/ ++ ++static struct timeval * ++channel_readable(channel, timeout) ++ SM_FILE_T *channel; ++ int timeout; ++{ ++ struct timeval bp, ep; /* {begin,end} pause */ ++ static struct timeval tp; /* total pause */ ++ int eoftest; ++ ++ /* check if data is on the channel during the pause */ ++ gettimeofday(&bp, NULL); ++ if ((eoftest = sm_io_getc(channel, timeout)) != SM_IO_EOF) ++ { ++ gettimeofday(&ep, NULL); ++ sm_io_ungetc(channel, SM_TIME_DEFAULT, eoftest); ++ timersub(&ep, &bp, &tp); ++ return &tp; ++ } ++ return NULL; ++} ++ + /* + ** SMTP -- run the SMTP protocol. + ** +@@ -552,7 +592,7 @@ typedef struct + char *sm_quarmsg; /* carry quarantining across messages */ + } SMTP_T; + +-static bool smtp_data __P((SMTP_T *, ENVELOPE *)); ++static bool smtp_data __P((SMTP_T *, ENVELOPE *, bool)); + + #define MSG_TEMPFAIL "451 4.3.2 Please try again later" + +@@ -832,12 +872,10 @@ smtp(nullserver, d_flags, e) + bool saveSuprErrs; + time_t tlsstart; + #endif /* STARTTLS */ +- volatile unsigned int features; +-#if PIPELINING +-# if _FFR_NO_PIPE ++ volatile unsigned long features; ++#if PIPELINING && _FFR_NO_PIPE + int np_log = 0; +-# endif /* _FFR_NO_PIPE */ +-#endif /* PIPELINING */ ++# endif + volatile time_t log_delay = (time_t) 0; + #if MILTER + volatile bool milter_cmd_done, milter_cmd_safe; +@@ -894,8 +932,12 @@ smtp(nullserver, d_flags, e) + #endif /* PIPELINING */ + + sm_setproctitle(true, e, "server %s startup", CurSmtpClient); +- +- /* Set default features for server. */ ++ /* ++ ** Set default features for server. ++ ** ++ ** Changing SRV_BARE_LF_421 | SRV_BARE_CR_421 below also ++ ** requires changing srvfeatures() variant code. ++ */ + features = ((bitset(PRIV_NOETRN, PrivacyFlags) || + bitnset(D_NOETRN, d_flags)) ? SRV_NONE : SRV_OFFER_ETRN) + | (bitnset(D_AUTHREQ, d_flags) ? SRV_REQ_AUTH : SRV_NONE) +@@ -913,6 +955,7 @@ smtp(nullserver, d_flags, e) + #if PIPELINING + | SRV_OFFER_PIPE + #endif /* PIPELINING */ ++ | SRV_BAD_PIPELINE + #if STARTTLS + | (bitnset(D_NOTLS, d_flags) ? SRV_NONE : SRV_OFFER_TLS) + | (bitset(TLS_I_NO_VRFY, TLS_Srv_Opts) ? SRV_NONE +@@ -932,15 +975,13 @@ smtp(nullserver, d_flags, e) + } + else + { +-#if PIPELINING +-# if _FFR_NO_PIPE ++#if PIPELINING && _FFR_NO_PIPE + if (bitset(SRV_NO_PIPE, features)) + { + /* for consistency */ + features &= ~SRV_OFFER_PIPE; + } +-# endif /* _FFR_NO_PIPE */ +-#endif /* PIPELINING */ ++#endif /* PIPELINING && _FFR_NO_PIPE */ + #if SASL + if (bitset(SRV_REQ_SEC, features)) + SASLOpts |= SASL_SEC_NOPLAINTEXT; +@@ -1307,46 +1348,23 @@ smtp(nullserver, d_flags, e) + + if (msecs > 0) + { +- int fd; +- fd_set readfds; +- struct timeval timeout; +- struct timeval bp, ep, tp; /* {begin,end,total}pause */ +- int eoftest; +- +- /* pause for a moment */ +- timeout.tv_sec = msecs / 1000; +- timeout.tv_usec = (msecs % 1000) * 1000; +- +- /* Obey RFC 2821: 4.3.5.2: 220 timeout of 5 minutes */ +- if (timeout.tv_sec >= 300) +- { +- timeout.tv_sec = 300; +- timeout.tv_usec = 0; +- } ++ struct timeval *tp; /* total pause */ ++ ++ /* Obey RFC 2821: 4.5.3.2: 220 timeout of 5 minutes (300 seconds) */ ++ if (msecs >= 300000) ++ msecs = 300000; + + /* check if data is on the socket during the pause */ +- fd = sm_io_getinfo(InChannel, SM_IO_WHAT_FD, NULL); +- FD_ZERO(&readfds); +- SM_FD_SET(fd, &readfds); +- gettimeofday(&bp, NULL); +- if (select(fd + 1, FDSET_CAST &readfds, +- NULL, NULL, &timeout) > 0 && +- FD_ISSET(fd, &readfds) && +- (eoftest = sm_io_getc(InChannel, SM_TIME_DEFAULT)) +- != SM_IO_EOF) ++ if ((tp = channel_readable(InChannel, msecs)) != NULL) + { +- sm_io_ungetc(InChannel, SM_TIME_DEFAULT, +- eoftest); +- gettimeofday(&ep, NULL); +- timersub(&ep, &bp, &tp); + greetcode = "554"; + nullserver = "Command rejected"; + sm_syslog(LOG_INFO, e->e_id, + "rejecting commands from %s [%s] due to pre-greeting traffic after %d seconds", + peerhostname, + anynet_ntoa(&RealHostAddr), +- (int) tp.tv_sec + +- (tp.tv_usec >= 500000 ? 1 : 0) ++ (int) tp->tv_sec + ++ (tp->tv_usec >= 500000 ? 1 : 0) + ); + } + } +@@ -2343,6 +2361,30 @@ smtp(nullserver, d_flags, e) + STOP_IF_ATTACK(checksmtpattack(&n_helo, MAXHELOCOMMANDS, + true, "HELO/EHLO", e)); + ++ /* ++ ** Despite the fact that the name indicates this ++ ** a PIPELINE related feature, do not enclose ++ ** it in #if PIPELINING so we can protect SMTP ++ ** servers not compiled with PIPELINE support ++ ** from transaction stuffing. ++ */ ++ ++ /* check if data is on the socket before the EHLO reply */ ++ if (bitset(SRV_BAD_PIPELINE, features) && ++ sm_io_getinfo(InChannel, SM_IO_IS_READABLE, NULL) > 0) ++ { ++ sm_syslog(LOG_INFO, e->e_id, ++ "rejecting %s from %s [%s] due to traffic before response", ++ SmtpPhase, CurHostName, ++ anynet_ntoa(&RealHostAddr)); ++ usrerr("554 5.5.0 SMTP protocol error"); ++ nullserver = "Command rejected"; ++#if MILTER ++ smtp.sm_milterize = false; ++#endif ++ break; ++ } ++ + #if 0 + /* RFC2821 4.1.4 allows duplicate HELO/EHLO */ + /* check for duplicate HELO/EHLO per RFC 1651 4.2 */ +@@ -3181,7 +3223,8 @@ smtp(nullserver, d_flags, e) + + case CMDDATA: /* data -- text of mail */ + DELAY_CONN("DATA"); +- if (!smtp_data(&smtp, e)) ++ if (!smtp_data(&smtp, e, ++ bitset(SRV_BAD_PIPELINE, features))) + goto doquit; + break; + +@@ -3612,6 +3655,7 @@ doquit: + ** Parameters: + ** smtp -- status of SMTP connection. + ** e -- envelope. ++** check_stuffing -- check for transaction stuffing. + ** + ** Returns: + ** true iff SMTP session can continue. +@@ -3621,9 +3665,10 @@ doquit: + */ + + static bool +-smtp_data(smtp, e) ++smtp_data(smtp, e, check_stuffing) + SMTP_T *smtp; + ENVELOPE *e; ++ bool check_stuffing; + { + #if MILTER + bool milteraccept; +@@ -3635,7 +3680,7 @@ smtp_data(smtp, e) + ENVELOPE *ee; + char *id; + char *oldid; +- unsigned int features; ++ unsigned long features; + char buf[32]; + + SmtpPhase = "server DATA"; +@@ -3649,6 +3694,18 @@ smtp_data(smtp, e) + usrerr("503 5.0.0 Need RCPT (recipient)"); + return true; + } ++ ++ /* check if data is on the socket before the DATA reply */ ++ if (check_stuffing && ++ sm_io_getinfo(InChannel, SM_IO_IS_READABLE, NULL) > 0) ++ { ++ sm_syslog(LOG_INFO, e->e_id, ++ "rejecting %s from %s [%s] due to traffic before response", ++ SmtpPhase, CurHostName, anynet_ntoa(&RealHostAddr)); ++ usrerr("554 5.5.0 SMTP protocol error"); ++ return false; ++ } ++ + (void) sm_snprintf(buf, sizeof(buf), "%u", smtp->sm_nrcpts); + if (rscheck("check_data", buf, NULL, e, + RSF_RMCOMM|RSF_UNSTRUCTURED|RSF_COUNT, 3, NULL, +@@ -3767,7 +3824,13 @@ smtp_data(smtp, e) + SmtpPhase = "collect"; + buffer_errors(); + +- collect(InChannel, true, NULL, e, true); ++ collect(InChannel, SMTPMODE_LAX ++ | (bitset(SRV_BARE_LF_421, e->e_features) ? SMTPMODE_LF_421 : 0) ++ | (bitset(SRV_BARE_CR_421, e->e_features) ? SMTPMODE_CR_421 : 0) ++ | (bitset(SRV_BARE_LF_SP, e->e_features) ? SMTPMODE_LF_SP : 0) ++ | (bitset(SRV_BARE_CR_SP, e->e_features) ? SMTPMODE_CR_SP : 0) ++ | (bitset(SRV_REQ_CRLF, e->e_features) ? SMTPMODE_CRLF : 0), ++ NULL, e, true); + + /* redefine message size */ + (void) sm_snprintf(buf, sizeof(buf), "%ld", PRT_NONNEGL(e->e_msgsize)); +@@ -5186,35 +5249,40 @@ initsrvtls(tls_ok) + static struct + { + char srvf_opt; +- unsigned int srvf_flag; ++ unsigned long srvf_flag; ++ unsigned long srvf_flag2; + } srv_feat_table[] = + { +- { 'A', SRV_OFFER_AUTH }, +- { 'B', SRV_OFFER_VERB }, +- { 'C', SRV_REQ_SEC }, +- { 'D', SRV_OFFER_DSN }, +- { 'E', SRV_OFFER_ETRN }, +- { 'L', SRV_REQ_AUTH }, ++ { 'A', SRV_OFFER_AUTH, 0 }, ++ { 'B', SRV_OFFER_VERB, 0 }, ++ { 'C', SRV_REQ_SEC, 0 }, ++ { 'D', SRV_OFFER_DSN, 0 }, ++ { 'E', SRV_OFFER_ETRN, 0 }, ++ { 'F', SRV_BAD_PIPELINE , 0 }, ++ { 'G', SRV_BARE_LF_421 , SRV_BARE_LF_SP }, ++ { 'L', SRV_REQ_AUTH, 0 }, + #if PIPELINING + # if _FFR_NO_PIPE +- { 'N', SRV_NO_PIPE }, ++ { 'N', SRV_NO_PIPE, 0 }, + # endif /* _FFR_NO_PIPE */ +- { 'P', SRV_OFFER_PIPE }, ++ { 'P', SRV_OFFER_PIPE, 0 }, + #endif /* PIPELINING */ +- { 'R', SRV_VRFY_CLT }, /* same as V; not documented */ +- { 'S', SRV_OFFER_TLS }, +-/* { 'T', SRV_TMP_FAIL }, */ +- { 'V', SRV_VRFY_CLT }, +- { 'X', SRV_OFFER_EXPN }, +-/* { 'Y', SRV_OFFER_VRFY }, */ +- { '\0', SRV_NONE } ++ { 'O', SRV_REQ_CRLF , 0 }, /* eOl */ ++ { 'R', SRV_VRFY_CLT, 0 }, /* same as V; not documented */ ++ { 'S', SRV_OFFER_TLS, 0 }, ++/* { 'T', SRV_TMP_FAIL,0 }, */ ++ { 'U', SRV_BARE_CR_421 , SRV_BARE_CR_SP }, ++ { 'V', SRV_VRFY_CLT,0 }, ++ { 'X', SRV_OFFER_EXPN,0 }, ++/* { 'Y', SRV_OFFER_VRFY,0 }, */ ++ { '\0', SRV_NONE,0 } + }; + +-static unsigned int ++static unsigned long + srvfeatures(e, clientname, features) + ENVELOPE *e; + char *clientname; +- unsigned int features; ++ unsigned long features; + { + int r, i, j; + char **pvp, c, opt; +@@ -5248,7 +5316,7 @@ srvfeatures(e, clientname, features) + { + if (LogLevel > 9) + sm_syslog(LOG_WARNING, e->e_id, +- "srvfeatures: unknown feature %s", ++ "srv_features: unknown feature %s", + pvp[i]); + break; + } +@@ -5257,9 +5325,40 @@ srvfeatures(e, clientname, features) + features &= ~(srv_feat_table[j].srvf_flag); + break; + } ++ ++ /* ++ ** Note: the "noflag" code below works ONLY for ++ ** the current situation: ++ ** - _flag itself is set by default ++ ** (drop session if bare CR or LF is found) ++ ** - _flag2 is only "effective" if _flag is not set, ++ ** hence using it turns off _flag. ++ ** If that situation changes, the code must be changed! ++ */ ++ + if (c == tolower(opt)) + { +- features |= srv_feat_table[j].srvf_flag; ++ unsigned long flag, noflag; ++ ++ c = pvp[i][1]; ++ flag = noflag = 0; ++ if ('2' == c) ++ { ++ flag = srv_feat_table[j].srvf_flag2; ++ noflag = srv_feat_table[j].srvf_flag; ++ } ++ else if ('\0' == c) ++ flag = srv_feat_table[j].srvf_flag; ++ if (0 != flag) ++ { ++ features |= flag; ++ if (0 != noflag) ++ features &= ~noflag; ++ } ++ else if (LogLevel > 9) ++ sm_syslog(LOG_WARNING, e->e_id, ++ "srv_features: unknown variant %s", ++ pvp[i]); + break; + } + ++j; +diff --git a/sendmail/usersmtp.c b/sendmail/usersmtp.c +index 24d38ee..25516a5 100644 +--- a/sendmail/usersmtp.c ++++ b/sendmail/usersmtp.c +@@ -2275,6 +2275,9 @@ smtprcpt(to, m, mci, e, ctladdr, xstart) + { + char *bufp; + char optbuf[MAXLINE]; ++#if PIPELINING ++ char *oldto; ++#endif + + #if PIPELINING + /* +@@ -2282,20 +2285,24 @@ smtprcpt(to, m, mci, e, ctladdr, xstart) + ** This should normally happen because of SMTP pipelining. + */ + ++ oldto = e->e_to; + while (mci->mci_nextaddr != NULL && + sm_io_getinfo(mci->mci_in, SM_IO_IS_READABLE, NULL) > 0) + { + int r; + ++ e->e_to = mci->mci_nextaddr->q_paddr; + r = smtprcptstat(mci->mci_nextaddr, m, mci, e); + if (r != EX_OK) + { + markfailure(e, mci->mci_nextaddr, mci, r, false); +- giveresponse(r, mci->mci_nextaddr->q_status, m, mci, +- ctladdr, xstart, e, to); ++ giveresponse(r, mci->mci_nextaddr->q_status, m, mci, ++ ctladdr, xstart, e, mci->mci_nextaddr); + } + mci->mci_nextaddr = mci->mci_nextaddr->q_pchain; ++ e->e_to = oldto; + } ++ e->e_to = oldto; + #endif /* PIPELINING */ + + /* +diff --git a/sendmail/util.c b/sendmail/util.c +index 9775915..5f5000a 100644 +--- a/sendmail/util.c ++++ b/sendmail/util.c +@@ -944,7 +944,7 @@ makelower(p) + } + + /* +-** FIXCRLF -- fix <CR><LF> in line. ++** FIXCRLF -- fix CRLF in line. + ** + ** Looks for the <CR><LF> combination and turns it into the + ** UNIX canonical <NL> character. It only takes one line, diff -Nru sendmail-8.15.2/debian/patches/series sendmail-8.15.2/debian/patches/series --- sendmail-8.15.2/debian/patches/series 2021-03-16 15:04:16.000000000 +0000 +++ sendmail-8.15.2/debian/patches/series 2024-05-13 18:44:56.000000000 +0000 @@ -25,3 +25,4 @@ connect-from-null.patch log-stop-at-debug-level.patch glibc-2.30.patch +0024-CVE-2023-51765.patch
signature.asc
Description: This is a digitally signed message part.