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       |  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,
