[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#1071449: marked as done (bookworm-pu: package sendmail/8.17.1.9-2+deb12u1)



Your message dated Sat, 29 Jun 2024 10:46:20 +0000
with message-id <E1sNVb2-002bhR-6K@coccia.debian.org>
and subject line Released with 12.6
has caused the Debian Bug report #1071449,
regarding bookworm-pu: package sendmail/8.17.1.9-2+deb12u1
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact owner@bugs.debian.org
immediately.)


-- 
1071449: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1071449
Debian Bug Tracking System
Contact owner@bugs.debian.org with problems
--- Begin Message ---
Package: release.debian.org
Severity: normal
Tags: bookworm
X-Debbugs-Cc: sendmail@packages.debian.org
Control: affects -1 + src:sendmail
User: release.debian.org@packages.debian.org
Usertags: pu

[ Reason ]
sendmail was affected by CVE-2023-51765

[ Impact ]
close CVE-2023-51765 and reject NUL mail

[ Tests ]
CVE-2023-51765 fix was tested manually and cross checked

[ Risks ]
Code is complex and rejecting NUL is slighly RFC non conformant

[ 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 ]
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.


[ Other info ]
No regression bugs in sid/trixie since at least two week
diff -Nru sendmail-8.17.1.9/debian/cf/ostype/debian.m4.in sendmail-8.17.1.9/debian/cf/ostype/debian.m4.in
--- sendmail-8.17.1.9/debian/cf/ostype/debian.m4.in	2023-01-11 22:26:28.000000000 +0000
+++ sendmail-8.17.1.9/debian/cf/ostype/debian.m4.in	2024-05-13 18:44:56.000000000 +0000
@@ -65,6 +65,9 @@
 dnl #
 define(`confDEF_USER_ID', `mail:mail')dnl
 dnl #
+ifelse(eval(index(sm_ffr, `-D_FFR_REJECT_NUL_BYTE') >= 0), `1',dnl
+`define(`confREJECT_NUL',`true')')dnl
+dnl #
 dnl #---------------------------------------------------------------------
 dnl # mailer paths and options
 dnl #---------------------------------------------------------------------
diff -Nru sendmail-8.17.1.9/debian/changelog sendmail-8.17.1.9/debian/changelog
--- sendmail-8.17.1.9/debian/changelog	2023-01-11 22:26:28.000000000 +0000
+++ sendmail-8.17.1.9/debian/changelog	2024-05-13 18:44:56.000000000 +0000
@@ -1,3 +1,24 @@
+sendmail (8.17.1.9-2+deb12u1) bookworm-security; urgency=high
+
+  * 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 <rouca@debian.org>  Mon, 13 May 2024 18:44:56 +0000
+
 sendmail (8.17.1.9-2) unstable; urgency=medium
 
   * QA upload.
diff -Nru sendmail-8.17.1.9/debian/configure.ac sendmail-8.17.1.9/debian/configure.ac
--- sendmail-8.17.1.9/debian/configure.ac	2023-01-11 22:26:28.000000000 +0000
+++ sendmail-8.17.1.9/debian/configure.ac	2024-05-13 18:44:56.000000000 +0000
@@ -466,6 +466,7 @@
 sm_envdef="$sm_envdef -DHASFLOCK=1";
 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.17"; then
diff -Nru sendmail-8.17.1.9/debian/NEWS.Debian sendmail-8.17.1.9/debian/NEWS.Debian
--- sendmail-8.17.1.9/debian/NEWS.Debian	1970-01-01 00:00:00.000000000 +0000
+++ sendmail-8.17.1.9/debian/NEWS.Debian	2024-05-13 18:44:56.000000000 +0000
@@ -0,0 +1,19 @@
+sendmail (8.17.1.9-2+deb12u1) bookworm-security; 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 <rouca@debian.org>  Sun, 12 May 2024 19:38:09 +0000
+
diff -Nru sendmail-8.17.1.9/debian/patches/0024-CVE-2023-51765.patch sendmail-8.17.1.9/debian/patches/0024-CVE-2023-51765.patch
--- sendmail-8.17.1.9/debian/patches/0024-CVE-2023-51765.patch	1970-01-01 00:00:00.000000000 +0000
+++ sendmail-8.17.1.9/debian/patches/0024-CVE-2023-51765.patch	2024-05-13 18:44:56.000000000 +0000
@@ -0,0 +1,1156 @@
+From: =?utf-8?q?Bastien_Roucari=C3=A8s?= <rouca@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       |  25 ++++-
+ libsm/lowercase.c   |  10 +-
+ sendmail/collect.c  | 199 ++++++++++++++++++++++++++++--------
+ sendmail/main.c     |   5 +-
+ sendmail/mime.c     |   8 +-
+ sendmail/sendmail.h |  24 ++++-
+ sendmail/srvrsmtp.c | 285 +++++++++++++++++++++++++++++++++++-----------------
+ sendmail/usersmtp.c |   9 +-
+ sendmail/util.c     |  10 +-
+ 9 files changed, 420 insertions(+), 155 deletions(-)
+
+diff --git a/RELEASE_NOTES b/RELEASE_NOTES
+index 1a747f3..56ec3d1 100644
+--- a/RELEASE_NOTES
++++ b/RELEASE_NOTES
+@@ -5,6 +5,27 @@ 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.17.2/8.17.2	202X/XX/XX
+ 	Fix a regression introduced in 8.17.1: aliases file which
+@@ -5425,7 +5446,7 @@ summary of the changes in that release.
+ 		characters (in LMTP mode), mail.local split the incoming
+ 		line up into 2046-character output lines (excluding the
+ 		newline).  If an input line was 2047 characters long
+-		(excluding CR-LF) and the last character was a '.',
++		(excluding CRLF) and the last character was a '.',
+ 		mail.local saw it as the end of input, transferred it to the
+ 		user mailbox and tried to write an `ok' back to sendmail.
+ 		If the message was much longer, both sendmail and
+@@ -8447,7 +8468,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
+index 8448eee..f980d2f 100644
+--- a/libsm/lowercase.c
++++ b/libsm/lowercase.c
+@@ -36,9 +36,15 @@ asciistr(str)
+ {
+ 	unsigned char ch;
+ 
+-	if  (str == NULL)
++	if (str == NULL)
+ 		return true;
+-	while ((ch = (unsigned char)*str) != '\0' && ch >= 32 && ch < 127)
++
++	SM_REQUIRE(len < INT_MAX);
++	n = 0;
++	while (n < len && (ch = (unsigned char)*str) != '\0'
++	       && ch >= 32 && ch < 127)
++	{
++		n++;
+ 		str++;
+ 	return ch == '\0';
+ }
+diff --git a/sendmail/collect.c b/sendmail/collect.c
+index 762c601..454d441 100644
+--- a/sendmail/collect.c
++++ b/sendmail/collect.c
+@@ -232,6 +232,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.
+ **
+@@ -267,20 +297,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 +342,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 +384,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 +408,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 +429,7 @@ collect(fp, smtpmode, hdrp, e, rsetsize)
+ 
+ 					/* timeout? */
+ 					if (c == SM_IO_EOF && errno == EAGAIN
+-					    && smtpmode)
++					    && SMTPMODE)
+ 					{
+ 						/*
+ 						**  Override e_message in
+@@ -417,15 +467,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 +504,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 +527,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 +550,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 +584,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 +609,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:
+@@ -592,8 +678,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 +710,7 @@ nextstate:
+ 
+ 				/* timeout? */
+ 				if (c == SM_IO_EOF && errno == EAGAIN
+-				    && smtpmode)
++				    && SMTPMODE)
+ 				{
+ 					/*
+ 					**  Override e_message in
+@@ -653,7 +739,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 +759,7 @@ nextstate:
+ 				sm_dprintf("EOH\n");
+ 
+ 			if (headeronly)
+-				goto readerr;
++				goto readdone;
+ 
+ 			df = collect_eoh(e, numhdrs, hdrslen);
+ 			if (df == NULL)
+@@ -700,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;
+ 
+@@ -741,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 */
+@@ -806,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 */
+@@ -863,6 +959,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 d9851a5..a76a922 100644
+--- a/sendmail/main.c
++++ b/sendmail/main.c
+@@ -2878,7 +2878,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 */
+ 	}
+@@ -2938,7 +2939,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 126304c..75cb112 100644
+--- a/sendmail/mime.c
++++ b/sendmail/mime.c
+@@ -347,7 +347,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))
+@@ -409,7 +409,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))
+@@ -483,7 +483,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))
+@@ -837,7 +837,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 9579f1e..1504100 100644
+--- a/sendmail/sendmail.h
++++ b/sendmail/sendmail.h
+@@ -1132,7 +1132,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 */
+@@ -1167,8 +1167,8 @@ struct envelope
+ #define EF_LOGSENDER	0x00008000L	/* need to log the sender */
+ #define EF_NORECEIPT	0x00010000L	/* suppress all return-receipts */
+ #define EF_HAS8BIT	0x00020000L	/* at least one 8-bit char in body */
+-/* was: EF_NL_NOT_EOL	0x00040000L	* don't accept raw NL as EOLine */
+-/* was: EF_CRLF_NOT_EOL	0x00080000L	* don't accept CR-LF as EOLine */
++/* was: EF_NL_NOT_EOL	0x00040000L	* don't accept raw LF as EOLine */
++/* was: EF_CRLF_NOT_EOL	0x00080000L	* don't accept CRLF as EOLine */
+ #define EF_RET_PARAM	0x00100000L	/* RCPT command had RET argument */
+ #define EF_HAS_DF	0x00200000L	/* set when data file is instantiated */
+ #define EF_IS_MIME	0x00400000L	/* really is a MIME message */
+@@ -1179,6 +1179,7 @@ struct envelope
+ #define EF_UNSAFE	0x08000000L	/* unsafe: read from untrusted source */
+ #define EF_TOODEEP	0x10000000L	/* message is nested too deep */
+ #define EF_SECURE	0x20000000L	/* DNSSEC for currently parsed addr */
++#define EF_7BITBODY	0x40000000L	/* strip body to 7bit on input */
+ 
+ #define DLVR_NOTIFY	0x01
+ #define DLVR_RETURN	0x02
+@@ -2336,6 +2337,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])
+ 
+@@ -2818,7 +2824,7 @@ extern void	cleanup_shm __P((bool));
+ #endif
+ 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 time_t	convtime __P((char *, int));
+ extern char	**copyplist __P((char **, bool, SM_RPOOL_T *));
+ extern void	copy_class __P((int, int));
+@@ -3001,6 +3007,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 */
++
+ #define ASSIGN_IFDIFF(old, new)		\
+ 	do				\
+ 	{				\
+@@ -3014,6 +3029,7 @@ extern char	*xuntextify __P((char *));
+ 
+ #if USE_EAI
+ extern bool	addr_is_ascii __P((const char *));
++extern bool	str_is_print __P((const char *));
+ extern const char	*hn2alabel __P((const char *));
+ #endif
+ 
+diff --git a/sendmail/srvrsmtp.c b/sendmail/srvrsmtp.c
+index e0062b7..57f26e0 100644
+--- a/sendmail/srvrsmtp.c
++++ b/sendmail/srvrsmtp.c
+@@ -52,30 +52,36 @@ static bool	NotFirstDelivery = false;
+ #endif
+ 
+ /* 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
+ #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 */
++#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 */
+ #if USE_EAI
+-# define SRV_OFFER_EAI	0x2000	/* offer SMTPUTF8 */
++# define SRV_OFFER_EAI	0x00002000	/* offer SMTPUTF8 */
+ #endif
+-#define SRV_NO_HTTP_CMD	0x4000	/* always reject HTTP commands */
++#define SRV_NO_HTTP_CMD	0x00004000	/* always reject HTTP commands */
++#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 int	srvfeatures __P((ENVELOPE *, char *, unsigned int));
++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,
+@@ -83,6 +89,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
+ # ifndef MAX_AUTH_USER_LEN
+@@ -507,6 +514,39 @@ rmargsorigp(delimptr, origp, id, p)
+ }
+ #endif /* _FFR_8BITENVADDR */
+ 
++/*
++**  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.
+ **
+@@ -667,7 +707,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"
+ 
+@@ -972,11 +1010,9 @@ smtp(nullserver, d_flags, e)
+ 	int save_errno;
+ 	extern int TLSsslidx;
+ #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
+ #endif
+ 	volatile time_t log_delay = (time_t) 0;
+ #if MILTER
+@@ -1035,8 +1070,12 @@ smtp(nullserver, d_flags, e)
+ #endif
+ 
+ 	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)
+@@ -1054,6 +1093,7 @@ smtp(nullserver, d_flags, e)
+ #if PIPELINING
+ 		| SRV_OFFER_PIPE
+ #endif
++		| 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
+@@ -1062,6 +1102,7 @@ smtp(nullserver, d_flags, e)
+ #if USE_EAI
+ 		| (SMTPUTF8 ? SRV_OFFER_EAI : 0)
+ #endif
++		| SRV_REQ_CRLF | SRV_BARE_LF_421 | SRV_BARE_CR_421
+ 		;
+ 	if (nullserver == NULL)
+ 	{
+@@ -1076,15 +1117,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;
+@@ -1452,46 +1491,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;
++			struct timeval *tp; /* total pause */
+ 
+-			/* 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;
+-			}
++			/* 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)
+-			{
+-				sm_io_ungetc(InChannel, SM_TIME_DEFAULT,
+-					     eoftest);
+-				gettimeofday(&ep, NULL);
+-				timersub(&ep, &bp, &tp);
++			if ((tp = channel_readable(InChannel, msecs)) != NULL)
++			{
+ 				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)
+ 					 );
+ 			}
+ 		}
+@@ -2520,6 +2536,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 */
+@@ -3434,7 +3474,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;
+ 
+@@ -3914,6 +3955,7 @@ doquit:
+ **	Parameters:
+ **		smtp -- status of SMTP connection.
+ **		e -- envelope.
++**		check_stuffing -- check for transaction stuffing.
+ **
+ **	Returns:
+ **		true iff SMTP session can continue.
+@@ -3923,9 +3965,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;
+@@ -3937,7 +3980,7 @@ smtp_data(smtp, e)
+ 	ENVELOPE *ee;
+ 	char *id;
+ 	char *oldid;
+-	unsigned int features;
++	unsigned long features;
+ 	char buf[32];
+ 
+ 	SmtpPhase = "server DATA";
+@@ -3951,6 +3994,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,
+@@ -4071,7 +4126,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));
+@@ -5557,39 +5618,50 @@ 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	},
+-	{ 'H',	SRV_NO_HTTP_CMD	},
++	{ '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	},
++	{ 'H',	SRV_NO_HTTP_CMD	, 0	},
+ #if USE_EAI
+-	{ 'I',	SRV_OFFER_EAI	},
++	{ 'I',	SRV_OFFER_EAI	, 0	},
++#endif
++/*	{ 'J',	0	, 0	},	*/
++/*	{ 'K',	0	, 0	},	*/
++	{ 'L',	SRV_REQ_AUTH	, 0	},
++/*	{ 'M',	0	, 0	},	*/
++#if PIPELINING && _FFR_NO_PIPE
++	{ 'N',	SRV_NO_PIPE	, 0	},
+ #endif
+-	{ 'L',	SRV_REQ_AUTH	},
++	{ 'O',	SRV_REQ_CRLF	, 0	},	/* eOl */
+ #if PIPELINING
+-# if _FFR_NO_PIPE
+-	{ 'N',	SRV_NO_PIPE	},
+-# endif
+-	{ 'P',	SRV_OFFER_PIPE	},
+-#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	}
++	{ 'P',	SRV_OFFER_PIPE	, 0	},
++#endif
++/*	{ 'Q',	0	, 0	},	*/
++	{ '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	},
++/*	{ 'W',	0	, 0	},	*/
++	{ 'X',	SRV_OFFER_EXPN	, 0	},
++/*	{ 'Y',	SRV_OFFER_VRFY	, 0	},	*/
++/*	{ 'Z',	0	, 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;
+@@ -5623,7 +5695,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;
+ 			}
+@@ -5632,9 +5704,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 b246421..85979ad 100644
+--- a/sendmail/usersmtp.c
++++ b/sendmail/usersmtp.c
+@@ -2393,6 +2393,9 @@ smtprcpt(to, m, mci, e, ctladdr, xstart)
+ 	char buf[MAXNAME + 1];	/* EAI:ok */
+ 	int len, nlen;
+ #endif
++#if PIPELINING
++	char *oldto;
++#endif
+ 
+ #if PIPELINING
+ 	/*
+@@ -2400,20 +2403,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);
++				     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 03671de..00030a7 100644
+--- a/sendmail/util.c
++++ b/sendmail/util.c
+@@ -1084,15 +1084,15 @@ makelower_buf(str, buf, buflen)
+ }
+ 
+ /*
+-**  FIXCRLF -- fix <CR><LF> in line.
++**  FIXCRLF -- fix CRLF in line.
+ **
+ **	XXX: Could this be a problem for EAI? That is, can there
+ **		be a string with \n and the previous octet is \n
+ **		but is part of a UTF8 "char"?
+ **
+-**	Looks for the <CR><LF> combination and turns it into the
+-**	UNIX canonical <NL> character.  It only takes one line,
+-**	i.e., it is assumed that the first <NL> found is the end
++**	Looks for the CRLF combination and turns it into the
++**	UNIX canonical LF character.  It only takes one line,
++**	i.e., it is assumed that the first LF found is the end
+ **	of the line.
+ **
+ **	Parameters:
+@@ -1529,7 +1529,7 @@ sfgets(buf, siz, fp, timeout, during)
+ **	Side Effects:
+ **		buf gets lines from f, with continuation lines (lines
+ **		with leading white space) appended.  CRLF's are mapped
+-**		into single newlines.  Any trailing NL is stripped.
++**		into single newlines.  Any trailing LF is stripped.
+ **		Increases LineNumber for each line.
+ */
+ 
diff -Nru sendmail-8.17.1.9/debian/patches/reject_nul.patch sendmail-8.17.1.9/debian/patches/reject_nul.patch
--- sendmail-8.17.1.9/debian/patches/reject_nul.patch	1970-01-01 00:00:00.000000000 +0000
+++ sendmail-8.17.1.9/debian/patches/reject_nul.patch	2024-05-13 18:44:56.000000000 +0000
@@ -0,0 +1,15 @@
+Author: Andreas Beckmann <anbe@debian.org>
+Description: add configurable 'O RejectNUL' to *.cf
+
+--- a/cf/m4/proto.m4
++++ b/cf/m4/proto.m4
+@@ -720,6 +720,9 @@ _OPTION(MaxNOOPCommands, `confMAX_NOOP_C
+ # Name to use for EHLO (defaults to $j)
+ _OPTION(HeloName, `confHELO_NAME')
+ 
++# Reject NUL bytes in message body, requires _FFR_REJECT_NUL_BYTE
++_OPTION(RejectNUL, `confREJECT_NUL', `false')
++
+ ifdef(`_NEED_SMTPOPMODES_', `dnl
+ # SMTP operation modes
+ C{SMTPOpModes} s d D')
diff -Nru sendmail-8.17.1.9/debian/patches/series sendmail-8.17.1.9/debian/patches/series
--- sendmail-8.17.1.9/debian/patches/series	2023-01-11 22:26:28.000000000 +0000
+++ sendmail-8.17.1.9/debian/patches/series	2024-05-13 18:44:56.000000000 +0000
@@ -21,3 +21,6 @@
 fhs.patch
 typos.patch
 log-stop-at-debug-level.patch
+0024-CVE-2023-51765.patch
+reject_nul.patch
+

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


--- End Message ---
--- Begin Message ---
Version: 12.6

The upload requested in this bug has been released as part of 12.6.

--- End Message ---

Reply to: