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

Attachment: signature.asc
Description: This is a digitally signed message part.

Reply via email to