Package: release.debian.org
Severity: normal
Tags: bookworm
X-Debbugs-Cc: sendm...@packages.debian.org
Control: affects -1 + src:sendmail
User: release.debian....@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 <ro...@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 <ro...@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?= <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       |  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 <a...@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.

Reply via email to