summaryrefslogtreecommitdiffstats
path: root/debian/patches/0020-CVE-2023-51764.patch
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-07 05:42:00 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-07 05:44:58 +0000
commit91ca5e856ca2bb560251d14ba5ab1fa8f40020d7 (patch)
tree740871517073c72a8c5fea35604eb1f5cc6f311a /debian/patches/0020-CVE-2023-51764.patch
parentReleasing progress-linux version 3.4.23-0+deb10u1progress5u1. (diff)
downloadpostfix-91ca5e856ca2bb560251d14ba5ab1fa8f40020d7.tar.xz
postfix-91ca5e856ca2bb560251d14ba5ab1fa8f40020d7.zip
Merging debian version 3.4.23-0+deb10u2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'debian/patches/0020-CVE-2023-51764.patch')
-rw-r--r--debian/patches/0020-CVE-2023-51764.patch1335
1 files changed, 1335 insertions, 0 deletions
diff --git a/debian/patches/0020-CVE-2023-51764.patch b/debian/patches/0020-CVE-2023-51764.patch
new file mode 100644
index 0000000..b5a0152
--- /dev/null
+++ b/debian/patches/0020-CVE-2023-51764.patch
@@ -0,0 +1,1335 @@
+From: Wietse Venema <wietse@porcupine.org>
+Date: Sun, 28 Jan 2024 13:15:22 +0000
+Subject: CVE-2023-51764
+
+Postfix through allowed SMTP smuggling unless configured with
+smtpd_data_restrictions=reject_unauth_pipelining and
+smtpd_discard_ehlo_keywords=chunking (or certain other
+options that exist in recent versions).
+
+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 Postfix supports <LF>.<CR><LF> but some
+other popular e-mail servers do not. To prevent attack variants
+(by always disallowing <LF> without <CR>), a different solution
+is required, such as the smtpd_forbid_bare_newline=yes option
+with a Postfix minimum version
+
+origin: http://ftp.porcupine.org/mirrors/postfix-release/official/postfix-3.4.29-smuggling-patch.gz
+bug-freexian-security: https://deb.freexian.com/extended-lts/tracker/source-package/postfix
+bug-debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1059230
+bug-debian-security: https://security-tracker.debian.org/tracker/CVE-2023-51764
+---
+ HISTORY | 34 ++++++++
+ RELEASE_NOTES | 98 +++++++++++++++++++++++
+ html/cleanup.8.html | 10 +++
+ html/postconf.5.html | 181 ++++++++++++++++++++++++++++++++++++++++++
+ html/smtpd.8.html | 16 ++++
+ man/man5/postconf.5 | 174 ++++++++++++++++++++++++++++++++++++++++
+ man/man8/cleanup.8 | 8 ++
+ man/man8/smtpd.8 | 14 ++++
+ mantools/postlink | 4 +
+ proto/postconf.proto | 166 ++++++++++++++++++++++++++++++++++++++
+ src/cleanup/cleanup.c | 8 ++
+ src/cleanup/cleanup_init.c | 2 +
+ src/cleanup/cleanup_message.c | 17 ++++
+ src/global/cleanup_strerror.c | 1 +
+ src/global/cleanup_user.h | 6 ++
+ src/global/mail_params.h | 18 ++++-
+ src/global/smtp_stream.c | 24 +++++-
+ src/global/smtp_stream.h | 2 +
+ src/smtpd/smtpd.c | 106 ++++++++++++++++++++++++-
+ src/smtpd/smtpd_check.c | 14 +++-
+ src/smtpd/smtpd_check.h | 1 +
+ 21 files changed, 895 insertions(+), 9 deletions(-)
+
+diff --git a/HISTORY b/HISTORY
+index b34bda8..ff40691 100644
+--- a/HISTORY
++++ b/HISTORY
+@@ -24722,3 +24722,37 @@ Apologies for any names omitted.
+ previously occupied by the original message body.
+
+ Problem report by BenoƮt Panizzon.
++
++20240109
++
++ Security (outbound SMTP smuggling): with the default setting
++ "cleanup_replace_stray_cr_lf = yes" Postfix will replace
++ stray <CR> or <LF> characters in message content with a
++ space character. This prevents Postfix from enabling
++ outbound (remote) SMTP smuggling, and it also makes evaluation
++ of Postfix-added DKIM etc. signatures independent from how
++ a remote mail server handles stray <CR> or <LF> characters.
++ Files: global/mail_params.h, cleanup/cleanup.c,
++ cleanup/cleanup_message.c, mantools/postlink, proto/postconf.proto.
++
++20240112
++
++ Security (inbound SMTP smuggling): with "smtpd_forbid_bare_newline
++ = normalize" (default "no" for Postfix < 3.9), the Postfix
++ SMTP server requires the standard End-of-DATA sequence
++ <CR><LF>.<CR><LF>, and otherwise allows command or message
++ content lines ending in the non-standard <LF>, processing
++ them as if the client sent the standard <CR><LF>.
++
++ The alternative setting, "smtpd_forbid_bare_newline = reject"
++ will reject any command or message that contains a bare
++ <LF>, and is more likely to cause problems with legitimate
++ clients.
++
++ For backwards compatibility, local clients are excluded by
++ default with "smtpd_forbid_bare_newline_exclusions =
++ $mynetworks".
++
++ Files: mantools/postlink, proto/postconf.proto,
++ global/mail_params.h, global/smtp_stream.c, global/smtp_stream.h,
++ smtpd/smtpd.c, smtpd/smtpd_check.[hc].
+diff --git a/RELEASE_NOTES b/RELEASE_NOTES
+index af0566f..7866d45 100644
+--- a/RELEASE_NOTES
++++ b/RELEASE_NOTES
+@@ -16,6 +16,104 @@ specifies the release date of a stable release or snapshot release.
+ If you upgrade from Postfix 3.2 or earlier, read RELEASE_NOTES-3.3
+ before proceeding.
+
++Incompatibility with Postfix 3.8.5, 3.7.10, 3.6.14, and 3.5.24
++==============================================================
++
++Improvements for outbound SMTP smuggling defense:
++
++- With "cleanup_replace_stray_cr_lf = yes" (the default), the cleanup
++ daemon replaces each stray <CR> or <LF> character in message
++ content with a space character. The replacement happens before
++ any other content management (header/body_checks, Milters, etc).
++
++ This prevents outbound SMTP smuggling, where an attacker uses
++ Postfix to send email containing a non-standard End-of-DATA
++ sequence, to exploit inbound SMTP smuggling at a vulnerable remote
++ SMTP server.
++
++ This also improves the remote evaluation of Postfix-added DKIM
++ and other signatures, as the evaluation result will not depend
++ on how a remote email server handles stray <CR> or <LF> characters.
++
++This feature applies to all email that Postfix locally or remotely
++sends out. It is not allowlisted based on client identity.
++
++Major changes with Postfix 3.8.5, 3.7.10, 3.6.14, and 3.5.24
++============================================================
++
++Improvements for inbound SMTP smuggling defense:
++
++- Better compatibility: the recommended setting "smtpd_forbid_bare_newline
++ = normalize" requires the standard End-of-DATA sequence
++ <CR><LF>.<CR><LF>, but allows bare newlines from SMTP clients,
++ maintaining more compatibility with existing infrastructure.
++
++- Improved logging for rejected input (it now includes queue ID,
++ helo, mail, and rcpt, if available).
++
++- The setting "smtpd_forbid_bare_newline = reject" requires
++ that input lines end in <CR><LF>, requires the standard End-of-DATA
++ sequence <CR><LF>.<CR><LF>, and rejects a command or message that
++ contains a bare newline. To disconnect the client, specify
++ "smtpd_forbid_bare_newline_reject_code = 521".
++
++- The Postfix SMTP server no longer strips extra <CR> as in
++ <CR><LF>.<CR><CR><LF>, to silence false alarms from test tools
++ that send attack sequences that real mail servers cannot send.
++ Details at https://www.postfix.org/false-smuggling-claims.html
++
++- The old setting "yes" has become an alias for "normalize".
++
++- The old setting "no" has not changed, and allows SMTP smuggling.
++
++The recommended settings are now:
++
++ # Require the standard End-of-DATA sequence <CR><LF>.<CR><LF>.
++ # Otherwise, allow bare <LF> and process it as if the client sent
++ # <CR><LF>.
++ #
++ # This maintains compatibility with many legitimate SMTP client
++ # applications that send a mix of standard and non-standard line
++ # endings, but will fail to receive email from client implementations
++ # that do not terminate DATA content with the standard End-of-DATA
++ # sequence <CR><LF>.<CR><LF>.
++ #
++ # Such clients can be allowlisted with smtpd_forbid_bare_newline_exclusions.
++ # The example below allowlists SMTP clients in trusted networks.
++ #
++ smtpd_forbid_bare_newline = normalize
++ smtpd_forbid_bare_newline_exclusions = $mynetworks
++
++Alternative settings:
++
++ # Reject input lines that contain <LF> and log a "bare <LF> received"
++ # error. Require that input lines end in <CR><LF>, and require the
++ # standard End-of-DATA sequence <CR><LF>.<CR><LF>.
++ #
++ # This will reject email from SMTP clients that send any non-standard
++ # line endings such as web applications, netcat, or load balancer
++ # health checks.
++ #
++ # This will also reject email from services that use BDAT to send
++ # MIME text containing a bare newline (RFC 3030 Section 3 requires
++ # canonical MIME format for text message types, defined in RFC 2045
++ # Sections 2.7 and 2.8).
++ #
++ # Such clients can be allowlisted with smtpd_forbid_bare_newline_exclusions.
++ # The example below allowlists SMTP clients in trusted networks.
++ #
++ smtpd_forbid_bare_newline = reject
++ smtpd_forbid_bare_newline_exclusions = $mynetworks
++ #
++ # Alternatively, in the case of BDAT violations, BDAT can be selectively
++ # disabled with smtpd_discard_ehlo_keyword_address_maps, or globally
++ # disabled with smtpd_discard_ehlo_keywords.
++ #
++ # smtpd_discard_ehlo_keyword_address_maps = cidr:/path/to/file
++ # /path/to/file:
++ # 10.0.0.0/24 chunking, silent-discard
++ # smtpd_discard_ehlo_keywords = chunking, silent-discard
++
+ Runtime detection of DNSSEC support
+ -----------------------------------
+
+diff --git a/html/cleanup.8.html b/html/cleanup.8.html
+index 866fe89..6b53fbf 100644
+--- a/html/cleanup.8.html
++++ b/html/cleanup.8.html
+@@ -146,6 +146,16 @@ CLEANUP(8) CLEANUP(8)
+ The set of characters that Postfix will remove from message con-
+ tent.
+
++ Available in Postfix version 3.9, 3.8.5, 3.7.10, 3.6.14, 3.5.24, and
++ later:
++
++ <b><a href="postconf.5.html#cleanup_replace_stray_cr_lf">cleanup_replace_stray_cr_lf</a> (yes)</b>
++ Replace each stray &lt;CR&gt; or &lt;LF&gt; character in message content
++ with a space character, to prevent outbound SMTP smuggling, and
++ to make the evaluation of Postfix-added DKIM or other signatures
++ independent from how a remote mail server handles such charac-
++ ters.
++
+ <b>BEFORE QUEUE MILTER CONTROLS</b>
+ As of version 2.3, Postfix supports the Sendmail version 8 Milter (mail
+ filter) protocol. When mail is not received via the <a href="smtpd.8.html">smtpd(8)</a> server,
+diff --git a/html/postconf.5.html b/html/postconf.5.html
+index 683cdb7..53c4c7c 100644
+--- a/html/postconf.5.html
++++ b/html/postconf.5.html
+@@ -1442,6 +1442,40 @@ Examples:
+ </pre>
+
+
++</DD>
++
++<DT><b><a name="cleanup_replace_stray_cr_lf">cleanup_replace_stray_cr_lf</a>
++(default: yes)</b></DT><DD>
++
++<p> Replace each stray &lt;CR&gt; or &lt;LF&gt; character in message
++content with a space character, to prevent outbound SMTP smuggling,
++and to make the evaluation of Postfix-added DKIM or other signatures
++independent from how a remote mail server handles such characters.
++</p>
++
++<p> SMTP does not allow such characters unless they are part of a
++&lt;CR&gt;&lt;LF&gt; sequence, and different mail systems handle
++such stray characters in an implementation-dependent manner. Stray
++&lt;CR&gt; or &lt;LF&gt; characters could be used for outbound
++SMTP smuggling, where an attacker uses a Postfix server to send
++message content with a non-standard End-of-DATA sequence that
++triggers inbound SMTP smuggling at a remote SMTP server.</p>
++
++<p> The replacement happens before all other content management,
++and before Postfix may add a DKIM etc. signature; if the signature
++were created first, the replacement could invalidate the signature.
++</p>
++
++<p> In addition to preventing SMTP smuggling, replacing stray
++&lt;CR&gt; or &lt;LF&gt; characters ensures that the result of
++signature validation by later mail system will not depend on how
++that mail system handles those stray characters in an
++implementation-dependent manner. </p>
++
++<p> This feature is available in Postfix &ge; 3.9, 3.8.5, 3.7.10,
++3.6.14, and 3.5.24. </p>
++
++
+ </DD>
+
+ <DT><b><a name="cleanup_service_name">cleanup_service_name</a>
+@@ -14982,6 +15016,153 @@ This feature is available in Postfix 2.0 and later.
+ </p>
+
+
++</DD>
++
++<DT><b><a name="smtpd_forbid_bare_newline">smtpd_forbid_bare_newline</a>
++(default: Postfix &lt; 3.9: no)</b></DT><DD>
++
++<p> Reject or restrict input lines from an SMTP client that end in
++&lt;LF&gt; instead of the standard &lt;CR&gt;&lt;LF&gt;. Such line
++endings are commonly allowed with UNIX-based SMTP servers, but they
++violate <a href="http://tools.ietf.org/html/rfc5321">RFC 5321</a>, and allowing such line endings can make a server
++vulnerable to <a href="https://www.postfix.org/smtp-smuggling.html">
++SMTP smuggling</a>. </p>
++
++<p> Specify one of the following values (case does not matter): </p>
++
++<dl compact>
++
++<dt> <b>normalize</b></dt> <dd> Require the standard
++End-of-DATA sequence &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;.
++Otherwise, allow command or message content lines ending in the
++non-standard &lt;LF&gt;, and process them as if the client sent the
++standard &lt;CR&gt;&lt;LF&gt;. <br> <br> This maintains compatibility
++with many legitimate SMTP client applications that send a mix of
++standard and non-standard line endings, but will fail to receive
++email from client implementations that do not terminate DATA content
++with the standard End-of-DATA sequence
++&lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;. <br> <br> Such clients
++can be excluded with <a href="postconf.5.html#smtpd_forbid_bare_newline_exclusions">smtpd_forbid_bare_newline_exclusions</a>. </dd>
++
++<dt> <b>yes</b> </dt> <dd> Compatibility alias for <b>normalize</b>. </dd>
++
++<dt> <b>reject</b> </dt> <dd> Require the standard End-of-DATA
++sequence &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;. Reject a command
++or message content when a line contains bare &lt;LF&gt;, log a "bare
++&lt;LF&gt; received" error, and reply with the SMTP status code in
++$<a href="postconf.5.html#smtpd_forbid_bare_newline_reject_code">smtpd_forbid_bare_newline_reject_code</a>. <br> <br> This will reject
++email from SMTP clients that send any non-standard line endings
++such as web applications, netcat, or load balancer health checks.
++<br> <br> This will also reject email from services that use BDAT
++to send MIME text containing a bare newline (<a href="http://tools.ietf.org/html/rfc3030">RFC 3030</a> Section 3
++requires canonical MIME format for text message types, defined in
++<a href="http://tools.ietf.org/html/rfc2045">RFC 2045</a> Sections 2.7 and 2.8). <br> <br> Such clients can be
++excluded with <a href="postconf.5.html#smtpd_forbid_bare_newline_exclusions">smtpd_forbid_bare_newline_exclusions</a> (or, in the case
++of BDAT violations, BDAT can be selectively disabled with
++<a href="postconf.5.html#smtpd_discard_ehlo_keyword_address_maps">smtpd_discard_ehlo_keyword_address_maps</a>, or globally disabled with
++<a href="postconf.5.html#smtpd_discard_ehlo_keywords">smtpd_discard_ehlo_keywords</a>). </dd>
++
++<dt> <b>no</b> (default)</dt> <dd> Do not require the standard
++End-of-DATA
++sequence &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;. Always process
++a bare &lt;LF&gt; as if the client sent &lt;CR&gt;&lt;LF&gt;. This
++option is fully backwards compatible, but is not recommended for
++an Internet-facing SMTP server, because it is vulnerable to <a
++href="https://www.postfix.org/smtp-smuggling.html"> SMTP smuggling</a>.
++</dd>
++
++</dl>
++
++<p> Recommended settings: </p>
++
++<blockquote>
++<pre>
++# Require the standard End-of-DATA sequence &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;.
++# Otherwise, allow bare &lt;LF&gt; and process it as if the client sent
++# &lt;CR&gt;&lt;LF&gt;.
++#
++# This maintains compatibility with many legitimate SMTP client
++# applications that send a mix of standard and non-standard line
++# endings, but will fail to receive email from client implementations
++# that do not terminate DATA content with the standard End-of-DATA
++# sequence &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;.
++#
++# Such clients can be allowlisted with <a href="postconf.5.html#smtpd_forbid_bare_newline_exclusions">smtpd_forbid_bare_newline_exclusions</a>.
++# The example below allowlists SMTP clients in trusted networks.
++#
++<a href="postconf.5.html#smtpd_forbid_bare_newline">smtpd_forbid_bare_newline</a> = normalize
++<a href="postconf.5.html#smtpd_forbid_bare_newline_exclusions">smtpd_forbid_bare_newline_exclusions</a> = $<a href="postconf.5.html#mynetworks">mynetworks</a>
++</pre>
++</blockquote>
++
++<p> Alternative: </p>
++
++<blockquote>
++<pre>
++# Reject input lines that contain &lt;LF&gt; and log a "bare &lt;LF&gt; received"
++# error. Require that input lines end in &lt;CR&gt;&lt;LF&gt;, and require the
++# standard End-of-DATA sequence &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;.
++#
++# This will reject email from SMTP clients that send any non-standard
++# line endings such as web applications, netcat, or load balancer
++# health checks.
++#
++# This will also reject email from services that use BDAT to send
++# MIME text containing a bare newline (<a href="http://tools.ietf.org/html/rfc3030">RFC 3030</a> Section 3 requires
++# canonical MIME format for text message types, defined in <a href="http://tools.ietf.org/html/rfc2045">RFC 2045</a>
++# Sections 2.7 and 2.8).
++#
++# Such clients can be allowlisted with <a href="postconf.5.html#smtpd_forbid_bare_newline_exclusions">smtpd_forbid_bare_newline_exclusions</a>.
++# The example below allowlists SMTP clients in trusted networks.
++#
++<a href="postconf.5.html#smtpd_forbid_bare_newline">smtpd_forbid_bare_newline</a> = reject
++<a href="postconf.5.html#smtpd_forbid_bare_newline_exclusions">smtpd_forbid_bare_newline_exclusions</a> = $<a href="postconf.5.html#mynetworks">mynetworks</a>
++#
++# Alternatively, in the case of BDAT violations, BDAT can be selectively
++# disabled with <a href="postconf.5.html#smtpd_discard_ehlo_keyword_address_maps">smtpd_discard_ehlo_keyword_address_maps</a>, or globally
++# disabled with <a href="postconf.5.html#smtpd_discard_ehlo_keywords">smtpd_discard_ehlo_keywords</a>.
++#
++# <a href="postconf.5.html#smtpd_discard_ehlo_keyword_address_maps">smtpd_discard_ehlo_keyword_address_maps</a> = <a href="cidr_table.5.html">cidr</a>:/path/to/file
++# /path/to/file:
++# 10.0.0.0/24 chunking, silent-discard
++# <a href="postconf.5.html#smtpd_discard_ehlo_keywords">smtpd_discard_ehlo_keywords</a> = chunking, silent-discard
++</pre>
++</blockquote>
++
++<p> This feature with settings <b>yes</b> and <b>no</b> is available
++in Postfix 3.8.4, 3.7.9, 3.6.13, and 3.5.23. Additionally, the
++settings <b>reject</b>, and <b>normalize</b> are available with
++Postfix &ge; 3.9, 3.8.5, 3.7.10, 3.6.14, and 3.5.24. </p>
++
++
++</DD>
++
++<DT><b><a name="smtpd_forbid_bare_newline_exclusions">smtpd_forbid_bare_newline_exclusions</a>
++(default: $<a href="postconf.5.html#mynetworks">mynetworks</a>)</b></DT><DD>
++
++<p> Exclude the specified clients from <a href="postconf.5.html#smtpd_forbid_bare_newline">smtpd_forbid_bare_newline</a>
++enforcement. This setting uses the same syntax and parent-domain
++matching behavior as <a href="postconf.5.html#mynetworks">mynetworks</a>. </p>
++
++<p> This feature is available in Postfix &ge; 3.9, 3.8.4, 3.7.9,
++3.6.13, and 3.5.23. </p>
++
++
++</DD>
++
++<DT><b><a name="smtpd_forbid_bare_newline_reject_code">smtpd_forbid_bare_newline_reject_code</a>
++(default: 550)</b></DT><DD>
++
++<p>
++The numerical Postfix SMTP server response code when rejecting a
++request with "<a href="postconf.5.html#smtpd_forbid_bare_newline">smtpd_forbid_bare_newline</a> = reject".
++Specify a 5XX status code (521 to disconnect).
++</p>
++
++<p> This feature is available in Postfix &ge; 3.9, 3.8.5, 3.7.10,
++3.6.14, and 3.5.24. </p>
++
++
+ </DD>
+
+ <DT><b><a name="smtpd_forbidden_commands">smtpd_forbidden_commands</a>
+diff --git a/html/smtpd.8.html b/html/smtpd.8.html
+index 311c9b6..c23c361 100644
+--- a/html/smtpd.8.html
++++ b/html/smtpd.8.html
+@@ -895,6 +895,22 @@ SMTPD(8) SMTPD(8)
+ to send to this service per time unit, regardless of whether or
+ not Postfix actually accepts those commands.
+
++ Available in Postfix 3.9, 3.8.4, 3.7.9, 3.6.13, 3.5.23 and later:
++
++ <b><a href="postconf.5.html#smtpd_forbid_bare_newline">smtpd_forbid_bare_newline</a> (Postfix</b> &lt; <b>3.9: no)</b>
++ Reject or restrict input lines from an SMTP client that end in
++ &lt;LF&gt; instead of the standard &lt;CR&gt;&lt;LF&gt;.
++
++ <b><a href="postconf.5.html#smtpd_forbid_bare_newline_exclusions">smtpd_forbid_bare_newline_exclusions</a> ($<a href="postconf.5.html#mynetworks">mynetworks</a>)</b>
++ Exclude the specified clients from <a href="postconf.5.html#smtpd_forbid_bare_newline">smtpd_forbid_bare_newline</a>
++ enforcement.
++
++ Available in Postfix 3.9, 3.8.5, 3.7.10, 3.6.14, 3.5.24 and later:
++
++ <b><a href="postconf.5.html#smtpd_forbid_bare_newline_reject_code">smtpd_forbid_bare_newline_reject_code</a> (550)</b>
++ The numerical Postfix SMTP server response code when rejecting a
++ request with "<a href="postconf.5.html#smtpd_forbid_bare_newline">smtpd_forbid_bare_newline</a> = reject".
++
+ <b>TARPIT CONTROLS</b>
+ When a remote SMTP client makes errors, the Postfix SMTP server can
+ insert delays before responding. This can help to slow down run-away
+diff --git a/man/man5/postconf.5 b/man/man5/postconf.5
+index d5d06b2..b525cc1 100644
+--- a/man/man5/postconf.5
++++ b/man/man5/postconf.5
+@@ -845,6 +845,32 @@ canonical_maps = hash:/etc/postfix/canonical
+ .fi
+ .ad
+ .ft R
++.SH cleanup_replace_stray_cr_lf (default: yes)
++Replace each stray <CR> or <LF> character in message
++content with a space character, to prevent outbound SMTP smuggling,
++and to make the evaluation of Postfix\-added DKIM or other signatures
++independent from how a remote mail server handles such characters.
++.PP
++SMTP does not allow such characters unless they are part of a
++<CR><LF> sequence, and different mail systems handle
++such stray characters in an implementation\-dependent manner. Stray
++<CR> or <LF> characters could be used for outbound
++SMTP smuggling, where an attacker uses a Postfix server to send
++message content with a non\-standard End\-of\-DATA sequence that
++triggers inbound SMTP smuggling at a remote SMTP server.
++.PP
++The replacement happens before all other content management,
++and before Postfix may add a DKIM etc. signature; if the signature
++were created first, the replacement could invalidate the signature.
++.PP
++In addition to preventing SMTP smuggling, replacing stray
++<CR> or <LF> characters ensures that the result of
++signature validation by later mail system will not depend on how
++that mail system handles those stray characters in an
++implementation\-dependent manner.
++.PP
++This feature is available in Postfix >= 3.9, 3.8.5, 3.7.10,
++3.6.14, and 3.5.24.
+ .SH cleanup_service_name (default: cleanup)
+ The name of the \fBcleanup\fR(8) service. This service rewrites addresses
+ into the standard form, and performs \fBcanonical\fR(5) address mapping
+@@ -10140,6 +10166,154 @@ The smtpd_expansion_filter value is not subject to Postfix configuration
+ parameter $name expansion.
+ .PP
+ This feature is available in Postfix 2.0 and later.
++.SH smtpd_forbid_bare_newline (default: Postfix < 3.9: no)
++Reject or restrict input lines from an SMTP client that end in
++<LF> instead of the standard <CR><LF>. Such line
++endings are commonly allowed with UNIX\-based SMTP servers, but they
++violate RFC 5321, and allowing such line endings can make a server
++vulnerable to
++SMTP smuggling.
++.PP
++Specify one of the following values (case does not matter):
++.IP "\fBnormalize\fR"
++Require the standard
++End\-of\-DATA sequence <CR><LF>.<CR><LF>.
++Otherwise, allow command or message content lines ending in the
++non\-standard <LF>, and process them as if the client sent the
++standard <CR><LF>.
++.br
++.br
++This maintains compatibility
++with many legitimate SMTP client applications that send a mix of
++standard and non\-standard line endings, but will fail to receive
++email from client implementations that do not terminate DATA content
++with the standard End\-of\-DATA sequence
++<CR><LF>.<CR><LF>.
++.br
++.br
++Such clients
++can be excluded with smtpd_forbid_bare_newline_exclusions.
++.br
++.IP "\fByes\fR"
++Compatibility alias for \fBnormalize\fR.
++.br
++.IP "\fBreject\fR"
++Require the standard End\-of\-DATA
++sequence <CR><LF>.<CR><LF>. Reject a command
++or message content when a line contains bare <LF>, log a "bare
++<LF> received" error, and reply with the SMTP status code in
++$smtpd_forbid_bare_newline_reject_code.
++.br
++.br
++This will reject
++email from SMTP clients that send any non\-standard line endings
++such as web applications, netcat, or load balancer health checks.
++.br
++.br
++This will also reject email from services that use BDAT
++to send MIME text containing a bare newline (RFC 3030 Section 3
++requires canonical MIME format for text message types, defined in
++RFC 2045 Sections 2.7 and 2.8).
++.br
++.br
++Such clients can be
++excluded with smtpd_forbid_bare_newline_exclusions (or, in the case
++of BDAT violations, BDAT can be selectively disabled with
++smtpd_discard_ehlo_keyword_address_maps, or globally disabled with
++smtpd_discard_ehlo_keywords).
++.br
++.IP "\fBno\fR (default)"
++Do not require the standard
++End\-of\-DATA
++sequence <CR><LF>.<CR><LF>. Always process
++a bare <LF> as if the client sent <CR><LF>. This
++option is fully backwards compatible, but is not recommended for
++an Internet\-facing SMTP server, because it is vulnerable to SMTP smuggling.
++.br
++.br
++.PP
++Recommended settings:
++.sp
++.in +4
++.nf
++.na
++.ft C
++# Require the standard End\-of\-DATA sequence <CR><LF>.<CR><LF>.
++# Otherwise, allow bare <LF> and process it as if the client sent
++# <CR><LF>.
++#
++# This maintains compatibility with many legitimate SMTP client
++# applications that send a mix of standard and non\-standard line
++# endings, but will fail to receive email from client implementations
++# that do not terminate DATA content with the standard End\-of\-DATA
++# sequence <CR><LF>.<CR><LF>.
++#
++# Such clients can be allowlisted with smtpd_forbid_bare_newline_exclusions.
++# The example below allowlists SMTP clients in trusted networks.
++#
++smtpd_forbid_bare_newline = normalize
++smtpd_forbid_bare_newline_exclusions = $mynetworks
++.fi
++.ad
++.ft R
++.in -4
++.PP
++Alternative:
++.sp
++.in +4
++.nf
++.na
++.ft C
++# Reject input lines that contain <LF> and log a "bare <LF> received"
++# error. Require that input lines end in <CR><LF>, and require the
++# standard End\-of\-DATA sequence <CR><LF>.<CR><LF>.
++#
++# This will reject email from SMTP clients that send any non\-standard
++# line endings such as web applications, netcat, or load balancer
++# health checks.
++#
++# This will also reject email from services that use BDAT to send
++# MIME text containing a bare newline (RFC 3030 Section 3 requires
++# canonical MIME format for text message types, defined in RFC 2045
++# Sections 2.7 and 2.8).
++#
++# Such clients can be allowlisted with smtpd_forbid_bare_newline_exclusions.
++# The example below allowlists SMTP clients in trusted networks.
++#
++smtpd_forbid_bare_newline = reject
++smtpd_forbid_bare_newline_exclusions = $mynetworks
++#
++# Alternatively, in the case of BDAT violations, BDAT can be selectively
++# disabled with smtpd_discard_ehlo_keyword_address_maps, or globally
++# disabled with smtpd_discard_ehlo_keywords.
++#
++# smtpd_discard_ehlo_keyword_address_maps = cidr:/path/to/file
++# /path/to/file:
++# 10.0.0.0/24 chunking, silent\-discard
++# smtpd_discard_ehlo_keywords = chunking, silent\-discard
++.fi
++.ad
++.ft R
++.in -4
++.PP
++This feature with settings \fByes\fR and \fBno\fR is available
++in Postfix 3.8.4, 3.7.9, 3.6.13, and 3.5.23. Additionally, the
++settings \fBreject\fR, and \fBnormalize\fR are available with
++Postfix >= 3.9, 3.8.5, 3.7.10, 3.6.14, and 3.5.24.
++.SH smtpd_forbid_bare_newline_exclusions (default: $mynetworks)
++Exclude the specified clients from smtpd_forbid_bare_newline
++enforcement. This setting uses the same syntax and parent\-domain
++matching behavior as mynetworks.
++.PP
++This feature is available in Postfix >= 3.9, 3.8.4, 3.7.9,
++3.6.13, and 3.5.23.
++.SH smtpd_forbid_bare_newline_reject_code (default: 550)
++The numerical Postfix SMTP server response code when rejecting a
++request with "smtpd_forbid_bare_newline = reject".
++Specify a 5XX status code (521 to disconnect).
++.PP
++This feature is available in Postfix >= 3.9, 3.8.5, 3.7.10,
++3.6.14, and 3.5.24.
+ .SH smtpd_forbidden_commands (default: CONNECT, GET, POST)
+ List of commands that cause the Postfix SMTP server to immediately
+ terminate the session with a 221 code. This can be used to disconnect
+diff --git a/man/man8/cleanup.8 b/man/man8/cleanup.8
+index d3df1f0..4af76b3 100644
+--- a/man/man8/cleanup.8
++++ b/man/man8/cleanup.8
+@@ -146,6 +146,14 @@ content.
+ .IP "\fBmessage_strip_characters (empty)\fR"
+ The set of characters that Postfix will remove from message
+ content.
++.PP
++Available in Postfix version 3.9, 3.8.5, 3.7.10, 3.6.14,
++3.5.24, and later:
++.IP "\fBcleanup_replace_stray_cr_lf (yes)\fR"
++Replace each stray <CR> or <LF> character in message
++content with a space character, to prevent outbound SMTP smuggling,
++and to make the evaluation of Postfix\-added DKIM or other signatures
++independent from how a remote mail server handles such characters.
+ .SH "BEFORE QUEUE MILTER CONTROLS"
+ .na
+ .nf
+diff --git a/man/man8/smtpd.8 b/man/man8/smtpd.8
+index 49798dd..e0d19d4 100644
+--- a/man/man8/smtpd.8
++++ b/man/man8/smtpd.8
+@@ -791,6 +791,20 @@ Available in Postfix version 3.1 and later:
+ The maximal number of AUTH commands that any client is allowed to
+ send to this service per time unit, regardless of whether or not
+ Postfix actually accepts those commands.
++.PP
++Available in Postfix 3.9, 3.8.4, 3.7.9, 3.6.13, 3.5.23 and later:
++.IP "\fBsmtpd_forbid_bare_newline (Postfix < 3.9: no)\fR"
++Reject or restrict input lines from an SMTP client that end in
++<LF> instead of the standard <CR><LF>.
++.IP "\fBsmtpd_forbid_bare_newline_exclusions ($mynetworks)\fR"
++Exclude the specified clients from smtpd_forbid_bare_newline
++enforcement.
++.PP
++Available in Postfix 3.9, 3.8.5, 3.7.10, 3.6.14, 3.5.24 and
++later:
++.IP "\fBsmtpd_forbid_bare_newline_reject_code (550)\fR"
++The numerical Postfix SMTP server response code when rejecting a
++request with "smtpd_forbid_bare_newline = reject".
+ .SH "TARPIT CONTROLS"
+ .na
+ .nf
+diff --git a/mantools/postlink b/mantools/postlink
+index b94e0f4..f2e53f7 100755
+--- a/mantools/postlink
++++ b/mantools/postlink
+@@ -548,6 +548,10 @@ while (<>) {
+ s;\bsmtpd_etrn_restrictions\b;<a href="postconf.5.html#smtpd_etrn_restrictions">$&</a>;g;
+ s;\bsmtpd_expansion_filter\b;<a href="postconf.5.html#smtpd_expansion_filter">$&</a>;g;
+ s;\bsmtpd_for[-</bB>]*\n*[ <bB>]*bidden_commands\b;<a href="postconf.5.html#smtpd_forbidden_commands">$&</a>;g;
++ s;\bsmtpd_for[-</bB>]*\n*[ <bB>]*bid_bare_new[-</bB>]*\n*[ <bB>]*line\b;<a href="postconf.5.html#smtpd_forbid_bare_newline">$&</a>;g;
++ s;\bsmtpd_for[-</bB>]*\n*[ <bB>]*bid_bare_new[-</bB>]*\n*[ <bB>]*line_reject_code\b;<a href="postconf.5.html#smtpd_forbid_bare_newline_reject_code">$&</a>;g;
++ s;\bsmtpd_for[-</bB>]*\n*[ <bB>]*bid_bare_new[-</bB>]*\n*[ <bB>]*line_exclusions\b;<a href="postconf.5.html#smtpd_forbid_bare_newline_exclusions">$&</a>;g;
++ s;\bcleanup_replace_stray_cr_lf\b;<a href="postconf.5.html#cleanup_replace_stray_cr_lf">$&</a>;g;
+ s;\bsmtpd_hard_error_limit\b;<a href="postconf.5.html#smtpd_hard_error_limit">$&</a>;g;
+ s;\bsmtpd_helo_required\b;<a href="postconf.5.html#smtpd_helo_required">$&</a>;g;
+ s;\bsmtpd_helo_restrictions\b;<a href="postconf.5.html#smtpd_helo_restrictions">$&</a>;g;
+diff --git a/proto/postconf.proto b/proto/postconf.proto
+index 02bbfcb..78db7c8 100644
+--- a/proto/postconf.proto
++++ b/proto/postconf.proto
+@@ -17681,3 +17681,169 @@ value to disable the feature. </p>
+
+ <p> This feature was backported from Postfix 3.6 to Postfix versions
+ 3.5.9, 3.4.19, 3.3.16. 3.2.21. </p>
++
++%PARAM smtpd_forbid_bare_newline Postfix &lt; 3.9: no
++
++<p> Reject or restrict input lines from an SMTP client that end in
++&lt;LF&gt; instead of the standard &lt;CR&gt;&lt;LF&gt;. Such line
++endings are commonly allowed with UNIX-based SMTP servers, but they
++violate RFC 5321, and allowing such line endings can make a server
++vulnerable to <a href="https://www.postfix.org/smtp-smuggling.html">
++SMTP smuggling</a>. </p>
++
++<p> Specify one of the following values (case does not matter): </p>
++
++<dl compact>
++
++<dt> <b>normalize</b></dt> <dd> Require the standard
++End-of-DATA sequence &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;.
++Otherwise, allow command or message content lines ending in the
++non-standard &lt;LF&gt;, and process them as if the client sent the
++standard &lt;CR&gt;&lt;LF&gt;. <br> <br> This maintains compatibility
++with many legitimate SMTP client applications that send a mix of
++standard and non-standard line endings, but will fail to receive
++email from client implementations that do not terminate DATA content
++with the standard End-of-DATA sequence
++&lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;. <br> <br> Such clients
++can be excluded with smtpd_forbid_bare_newline_exclusions. </dd>
++
++<dt> <b>yes</b> </dt> <dd> Compatibility alias for <b>normalize</b>. </dd>
++
++<dt> <b>reject</b> </dt> <dd> Require the standard End-of-DATA
++sequence &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;. Reject a command
++or message content when a line contains bare &lt;LF&gt;, log a "bare
++&lt;LF&gt; received" error, and reply with the SMTP status code in
++$smtpd_forbid_bare_newline_reject_code. <br> <br> This will reject
++email from SMTP clients that send any non-standard line endings
++such as web applications, netcat, or load balancer health checks.
++<br> <br> This will also reject email from services that use BDAT
++to send MIME text containing a bare newline (RFC 3030 Section 3
++requires canonical MIME format for text message types, defined in
++RFC 2045 Sections 2.7 and 2.8). <br> <br> Such clients can be
++excluded with smtpd_forbid_bare_newline_exclusions (or, in the case
++of BDAT violations, BDAT can be selectively disabled with
++smtpd_discard_ehlo_keyword_address_maps, or globally disabled with
++smtpd_discard_ehlo_keywords). </dd>
++
++<dt> <b>no</b> (default)</dt> <dd> Do not require the standard
++End-of-DATA
++sequence &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;. Always process
++a bare &lt;LF&gt; as if the client sent &lt;CR&gt;&lt;LF&gt;. This
++option is fully backwards compatible, but is not recommended for
++an Internet-facing SMTP server, because it is vulnerable to <a
++href="https://www.postfix.org/smtp-smuggling.html"> SMTP smuggling</a>.
++</dd>
++
++</dl>
++
++<p> Recommended settings: </p>
++
++<blockquote>
++<pre>
++# Require the standard End-of-DATA sequence &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;.
++# Otherwise, allow bare &lt;LF&gt; and process it as if the client sent
++# &lt;CR&gt;&lt;LF&gt;.
++#
++# This maintains compatibility with many legitimate SMTP client
++# applications that send a mix of standard and non-standard line
++# endings, but will fail to receive email from client implementations
++# that do not terminate DATA content with the standard End-of-DATA
++# sequence &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;.
++#
++# Such clients can be allowlisted with smtpd_forbid_bare_newline_exclusions.
++# The example below allowlists SMTP clients in trusted networks.
++#
++smtpd_forbid_bare_newline = normalize
++smtpd_forbid_bare_newline_exclusions = $mynetworks
++</pre>
++</blockquote>
++
++<p> Alternative: </p>
++
++<blockquote>
++<pre>
++# Reject input lines that contain &lt;LF&gt; and log a "bare &lt;LF&gt; received"
++# error. Require that input lines end in &lt;CR&gt;&lt;LF&gt;, and require the
++# standard End-of-DATA sequence &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;.
++#
++# This will reject email from SMTP clients that send any non-standard
++# line endings such as web applications, netcat, or load balancer
++# health checks.
++#
++# This will also reject email from services that use BDAT to send
++# MIME text containing a bare newline (RFC 3030 Section 3 requires
++# canonical MIME format for text message types, defined in RFC 2045
++# Sections 2.7 and 2.8).
++#
++# Such clients can be allowlisted with smtpd_forbid_bare_newline_exclusions.
++# The example below allowlists SMTP clients in trusted networks.
++#
++smtpd_forbid_bare_newline = reject
++smtpd_forbid_bare_newline_exclusions = $mynetworks
++#
++# Alternatively, in the case of BDAT violations, BDAT can be selectively
++# disabled with smtpd_discard_ehlo_keyword_address_maps, or globally
++# disabled with smtpd_discard_ehlo_keywords.
++#
++# smtpd_discard_ehlo_keyword_address_maps = cidr:/path/to/file
++# /path/to/file:
++# 10.0.0.0/24 chunking, silent-discard
++# smtpd_discard_ehlo_keywords = chunking, silent-discard
++</pre>
++</blockquote>
++
++<p> This feature with settings <b>yes</b> and <b>no</b> is available
++in Postfix 3.8.4, 3.7.9, 3.6.13, and 3.5.23. Additionally, the
++settings <b>reject</b>, and <b>normalize</b> are available with
++Postfix &ge; 3.9, 3.8.5, 3.7.10, 3.6.14, and 3.5.24. </p>
++
++%PARAM smtpd_forbid_bare_newline_exclusions $mynetworks
++
++<p> Exclude the specified clients from smtpd_forbid_bare_newline
++enforcement. This setting uses the same syntax and parent-domain
++matching behavior as mynetworks. </p>
++
++<p> This feature is available in Postfix &ge; 3.9, 3.8.4, 3.7.9,
++3.6.13, and 3.5.23. </p>
++
++%PARAM smtpd_forbid_bare_newline_reject_code 550
++
++<p>
++The numerical Postfix SMTP server response code when rejecting a
++request with "smtpd_forbid_bare_newline = reject".
++Specify a 5XX status code (521 to disconnect).
++</p>
++
++<p> This feature is available in Postfix &ge; 3.9, 3.8.5, 3.7.10,
++3.6.14, and 3.5.24. </p>
++
++%PARAM cleanup_replace_stray_cr_lf yes
++
++<p> Replace each stray &lt;CR&gt; or &lt;LF&gt; character in message
++content with a space character, to prevent outbound SMTP smuggling,
++and to make the evaluation of Postfix-added DKIM or other signatures
++independent from how a remote mail server handles such characters.
++</p>
++
++<p> SMTP does not allow such characters unless they are part of a
++&lt;CR&gt;&lt;LF&gt; sequence, and different mail systems handle
++such stray characters in an implementation-dependent manner. Stray
++&lt;CR&gt; or &lt;LF&gt; characters could be used for outbound
++SMTP smuggling, where an attacker uses a Postfix server to send
++message content with a non-standard End-of-DATA sequence that
++triggers inbound SMTP smuggling at a remote SMTP server.</p>
++
++<p> The replacement happens before all other content management,
++and before Postfix may add a DKIM etc. signature; if the signature
++were created first, the replacement could invalidate the signature.
++</p>
++
++<p> In addition to preventing SMTP smuggling, replacing stray
++&lt;CR&gt; or &lt;LF&gt; characters ensures that the result of
++signature validation by later mail system will not depend on how
++that mail system handles those stray characters in an
++implementation-dependent manner. </p>
++
++<p> This feature is available in Postfix &ge; 3.9, 3.8.5, 3.7.10,
++3.6.14, and 3.5.24. </p>
++
+diff --git a/src/cleanup/cleanup.c b/src/cleanup/cleanup.c
+index e934794..b9ea2cc 100644
+--- a/src/cleanup/cleanup.c
++++ b/src/cleanup/cleanup.c
+@@ -128,6 +128,14 @@
+ /* .IP "\fBmessage_strip_characters (empty)\fR"
+ /* The set of characters that Postfix will remove from message
+ /* content.
++/* .PP
++/* Available in Postfix version 3.9, 3.8.5, 3.7.10, 3.6.14,
++/* 3.5.24, and later:
++/* .IP "\fBcleanup_replace_stray_cr_lf (yes)\fR"
++/* Replace each stray <CR> or <LF> character in message
++/* content with a space character, to prevent outbound SMTP smuggling,
++/* and to make the evaluation of Postfix-added DKIM or other signatures
++/* independent from how a remote mail server handles such characters.
+ /* BEFORE QUEUE MILTER CONTROLS
+ /* .ad
+ /* .fi
+diff --git a/src/cleanup/cleanup_init.c b/src/cleanup/cleanup_init.c
+index e411992..1dff1db 100644
+--- a/src/cleanup/cleanup_init.c
++++ b/src/cleanup/cleanup_init.c
+@@ -173,6 +173,7 @@ int var_auto_8bit_enc_hdr; /* auto-detect 8bit encoding header */
+ int var_always_add_hdrs; /* always add missing headers */
+ int var_virt_addrlen_limit; /* stop exponential growth */
+ char *var_hfrom_format; /* header_from_format */
++int var_cleanup_mask_stray_cr_lf; /* replace stray CR or LF with space */
+
+ const CONFIG_INT_TABLE cleanup_int_table[] = {
+ VAR_HOPCOUNT_LIMIT, DEF_HOPCOUNT_LIMIT, &var_hopcount_limit, 1, 0,
+@@ -189,6 +190,7 @@ const CONFIG_BOOL_TABLE cleanup_bool_table[] = {
+ VAR_VERP_BOUNCE_OFF, DEF_VERP_BOUNCE_OFF, &var_verp_bounce_off,
+ VAR_AUTO_8BIT_ENC_HDR, DEF_AUTO_8BIT_ENC_HDR, &var_auto_8bit_enc_hdr,
+ VAR_ALWAYS_ADD_HDRS, DEF_ALWAYS_ADD_HDRS, &var_always_add_hdrs,
++ VAR_CLEANUP_MASK_STRAY_CR_LF, DEF_CLEANUP_MASK_STRAY_CR_LF, &var_cleanup_mask_stray_cr_lf,
+ 0,
+ };
+
+diff --git a/src/cleanup/cleanup_message.c b/src/cleanup/cleanup_message.c
+index 135a41e..411c667 100644
+--- a/src/cleanup/cleanup_message.c
++++ b/src/cleanup/cleanup_message.c
+@@ -926,6 +926,23 @@ static void cleanup_message_headerbody(CLEANUP_STATE *state, int type,
+ const char *cp;
+ char *dst;
+
++ /*
++ * Replace each stray CR or LF with one space. These are not allowed in
++ * SMTP, and can be used to enable outbound (remote) SMTP smuggling.
++ * Replacing these early ensures that our later DKIM etc. signature will
++ * not be invalidated. Besides preventing SMTP smuggling, replacing stray
++ * <CR> or <LF> ensures that the result of signature validation by a
++ * later mail system will not depend on how that mail system handles
++ * those stray characters in an implementation-dependent manner.
++ *
++ * The input length is not changed, therefore it is safe to overwrite the
++ * input.
++ */
++ if (var_cleanup_mask_stray_cr_lf)
++ for (dst = (char *) buf; dst < buf + len; dst++)
++ if (*dst == '\r' || *dst == '\n')
++ *dst = ' ';
++
+ /*
+ * Reject unwanted characters.
+ *
+diff --git a/src/global/cleanup_strerror.c b/src/global/cleanup_strerror.c
+index ce0bb40..0f11d80 100644
+--- a/src/global/cleanup_strerror.c
++++ b/src/global/cleanup_strerror.c
+@@ -67,6 +67,7 @@ static const CLEANUP_STAT_DETAIL cleanup_stat_map[] = {
+ CLEANUP_STAT_SIZE, 552, "5.3.4", "message file too big",
+ CLEANUP_STAT_CONT, 550, "5.7.1", "message content rejected",
+ CLEANUP_STAT_WRITE, 451, "4.3.0", "queue file write error",
++ CLEANUP_STAT_BARE_LF, 521, "5.5.2", "bare <LF> received",
+ };
+
+ static CLEANUP_STAT_DETAIL cleanup_stat_success = {
+diff --git a/src/global/cleanup_user.h b/src/global/cleanup_user.h
+index a4de82a..03c7569 100644
+--- a/src/global/cleanup_user.h
++++ b/src/global/cleanup_user.h
+@@ -63,6 +63,12 @@
+ #define CLEANUP_STAT_PROXY (1<<7) /* Proxy reject */
+ #define CLEANUP_STAT_DEFER (1<<8) /* Temporary reject */
+
++ /*
++ * Non-cleanup errors that live in the same bitmask space, to centralize
++ * error handling.
++ */
++#define CLEANUP_STAT_BARE_LF (1<<16) /* Bare <LF> received */
++
+ /*
+ * These are set when we can't bounce even if we were asked to.
+ */
+diff --git a/src/global/mail_params.h b/src/global/mail_params.h
+index c30316e..967c8a3 100644
+--- a/src/global/mail_params.h
++++ b/src/global/mail_params.h
+@@ -4097,7 +4097,23 @@ extern char *var_smtp_dns_re_filter;
+ extern char *var_smtpd_dns_re_filter;
+
+ /*
+- * Share TLS sessions through tlproxy(8).
++ * Backwards compatibility.
++ */
++#define VAR_SMTPD_FORBID_BARE_LF "smtpd_forbid_bare_newline"
++#define DEF_SMTPD_FORBID_BARE_LF "no"
++
++#define VAR_SMTPD_FORBID_BARE_LF_EXCL "smtpd_forbid_bare_newline_exclusions"
++#define DEF_SMTPD_FORBID_BARE_LF_EXCL "$" VAR_MYNETWORKS
++
++#define VAR_SMTPD_FORBID_BARE_LF_CODE "smtpd_forbid_bare_newline_reject_code"
++#define DEF_SMTPD_FORBID_BARE_LF_CODE 550
++
++#define VAR_CLEANUP_MASK_STRAY_CR_LF "cleanup_replace_stray_cr_lf"
++#define DEF_CLEANUP_MASK_STRAY_CR_LF 1
++extern int var_cleanup_mask_stray_cr_lf;
++
++ /*
++ * Share TLS sessions through tlsproxy(8).
+ */
+ #define VAR_SMTP_TLS_CONN_REUSE "smtp_tls_connection_reuse"
+ #define DEF_SMTP_TLS_CONN_REUSE 0
+diff --git a/src/global/smtp_stream.c b/src/global/smtp_stream.c
+index a42cdcf..68bff7b 100644
+--- a/src/global/smtp_stream.c
++++ b/src/global/smtp_stream.c
+@@ -50,6 +50,9 @@
+ /* VSTREAM *stream;
+ /* char *format;
+ /* va_list ap;
++/*
++/* int smtp_detect_bare_lf;
++/* int smtp_got_bare_lf;
+ /* AUXILIARY API
+ /* int smtp_get_noexcept(vp, stream, maxlen, flags)
+ /* VSTRING *vp;
+@@ -127,6 +130,11 @@
+ /* without timeouts and without making long jumps. Instead,
+ /* query the stream status with vstream_feof() etc.
+ /*
++/* This function assigns smtp_got_bare_lf = smtp_detect_bare_lf,
++/* if smtp_detect_bare_lf is non-zero and the last read line
++/* was terminated with a bare newline. Otherwise, this function
++/* sets smtp_got_bare_lf to zero.
++/*
+ /* smtp_timeout_setup() is a backwards-compatibility interface
+ /* for programs that don't require per-record deadline support.
+ /* DIAGNOSTICS
+@@ -201,6 +209,9 @@
+
+ #include "smtp_stream.h"
+
++int smtp_detect_bare_lf;
++int smtp_got_bare_lf;
++
+ /* smtp_timeout_reset - reset per-stream error flags, restart deadline timer */
+
+ static void smtp_timeout_reset(VSTREAM *stream)
+@@ -362,6 +373,8 @@ int smtp_get_noexcept(VSTRING *vp, VSTREAM *stream, ssize_t bound, int flags
+ int last_char;
+ int next_char;
+
++ smtp_got_bare_lf = 0;
++
+ /*
+ * It's painful to do I/O with records that may span multiple buffers.
+ * Allow for partial long lines (we will read the remainder later) and
+@@ -404,8 +417,15 @@ int smtp_get_noexcept(VSTRING *vp, VSTREAM *stream, ssize_t bound, int flags
+ */
+ case '\n':
+ vstring_truncate(vp, VSTRING_LEN(vp) - 1);
+- while (VSTRING_LEN(vp) > 0 && vstring_end(vp)[-1] == '\r')
+- vstring_truncate(vp, VSTRING_LEN(vp) - 1);
++ if (smtp_detect_bare_lf) {
++ if (VSTRING_LEN(vp) == 0 || vstring_end(vp)[-1] != '\r')
++ smtp_got_bare_lf = smtp_detect_bare_lf;
++ else
++ vstring_truncate(vp, VSTRING_LEN(vp) - 1);
++ } else {
++ while (VSTRING_LEN(vp) > 0 && vstring_end(vp)[-1] == '\r')
++ vstring_truncate(vp, VSTRING_LEN(vp) - 1);
++ }
+ VSTRING_TERMINATE(vp);
+ /* FALLTRHOUGH */
+
+diff --git a/src/global/smtp_stream.h b/src/global/smtp_stream.h
+index ec824b3..31997d3 100644
+--- a/src/global/smtp_stream.h
++++ b/src/global/smtp_stream.h
+@@ -43,6 +43,8 @@ extern void smtp_fputs(const char *, ssize_t len, VSTREAM *);
+ extern void smtp_fwrite(const char *, ssize_t len, VSTREAM *);
+ extern void smtp_fread_buf(VSTRING *, ssize_t len, VSTREAM *);
+ extern void smtp_fputc(int, VSTREAM *);
++extern int smtp_detect_bare_lf;
++extern int smtp_got_bare_lf;
+
+ extern void smtp_vprintf(VSTREAM *, const char *, va_list);
+
+diff --git a/src/smtpd/smtpd.c b/src/smtpd/smtpd.c
+index ac1adb2..d590583 100644
+--- a/src/smtpd/smtpd.c
++++ b/src/smtpd/smtpd.c
+@@ -745,6 +745,20 @@
+ /* The maximal number of AUTH commands that any client is allowed to
+ /* send to this service per time unit, regardless of whether or not
+ /* Postfix actually accepts those commands.
++/* .PP
++/* Available in Postfix 3.9, 3.8.4, 3.7.9, 3.6.13, 3.5.23 and later:
++/* .IP "\fBsmtpd_forbid_bare_newline (Postfix < 3.9: no)\fR"
++/* Reject or restrict input lines from an SMTP client that end in
++/* <LF> instead of the standard <CR><LF>.
++/* .IP "\fBsmtpd_forbid_bare_newline_exclusions ($mynetworks)\fR"
++/* Exclude the specified clients from smtpd_forbid_bare_newline
++/* enforcement.
++/* .PP
++/* Available in Postfix 3.9, 3.8.5, 3.7.10, 3.6.14, 3.5.24 and
++/* later:
++/* .IP "\fBsmtpd_forbid_bare_newline_reject_code (550)\fR"
++/* The numerical Postfix SMTP server response code when rejecting a
++/* request with "smtpd_forbid_bare_newline = reject".
+ /* TARPIT CONTROLS
+ /* .ad
+ /* .fi
+@@ -1447,6 +1461,12 @@ char *var_tlsproxy_service;
+ char *var_smtpd_uproxy_proto;
+ int var_smtpd_uproxy_tmout;
+
++char *var_smtpd_forbid_bare_lf;
++char *var_smtpd_forbid_bare_lf_excl;
++int var_smtpd_forbid_bare_lf_code;
++static int bare_lf_mask;
++static NAMADR_LIST *bare_lf_excl;
++
+ /*
+ * Silly little macros.
+ */
+@@ -1539,6 +1559,40 @@ static int ask_client_cert;
+ */
+ static DICT *smtpd_cmd_filter;
+
++ /*
++ * Bare LF and End-of-DATA controls (bare CR is handled elsewhere).
++ *
++ * At the smtp_get*() line reader level, setting any of these flags in the
++ * smtp_detect_bare_lf variable enables the detection of bare newlines. The
++ * line reader will set the same flags in the smtp_got_bare_lf variable
++ * after it detects a bare newline, otherwise it clears smtp_got_bare_lf.
++ *
++ * At the SMTP command level, the flags in smtp_got_bare_lf control whether
++ * commands ending in a bare newline are rejected.
++ *
++ * At the DATA and BDAT content level, the flags in smtp_got_bare_lf control
++ * whether the standard End-of-DATA sequence CRLF.CRLF is required, and
++ * whether lines ending in bare newlines are rejected.
++ *
++ * Postfix implements "delayed reject" after detecting a bare newline in BDAT
++ * or DATA content. The SMTP server delays a REJECT response until the
++ * command is finished, instead of replying and hanging up immediately. The
++ * End-of-DATA detection is secured with BARE_LF_FLAG_WANT_STD_EOD.
++ */
++#define BARE_LF_FLAG_WANT_STD_EOD (1<<0) /* Require CRLF.CRLF */
++#define BARE_LF_FLAG_REPLY_REJECT (1<<1) /* Reject bare newline */
++
++#define IS_BARE_LF_WANT_STD_EOD(m) ((m) & BARE_LF_FLAG_WANT_STD_EOD)
++#define IS_BARE_LF_REPLY_REJECT(m) ((m) & BARE_LF_FLAG_REPLY_REJECT)
++
++static const NAME_CODE bare_lf_mask_table[] = {
++ "normalize", BARE_LF_FLAG_WANT_STD_EOD, /* Default */
++ "yes", BARE_LF_FLAG_WANT_STD_EOD, /* Migration aid */
++ "reject", BARE_LF_FLAG_WANT_STD_EOD | BARE_LF_FLAG_REPLY_REJECT,
++ "no", 0,
++ 0, -1, /* error */
++};
++
+ #ifdef USE_SASL_AUTH
+
+ /*
+@@ -3479,6 +3533,7 @@ static void receive_data_message(SMTPD_STATE *state,
+ int curr_rec_type;
+ int prev_rec_type;
+ int first = 1;
++ int prev_got_bare_lf = 0;
+
+ /*
+ * Copy the message content. If the cleanup process has a problem, keep
+@@ -3492,12 +3547,15 @@ static void receive_data_message(SMTPD_STATE *state,
+ * XXX Deal with UNIX-style From_ lines at the start of message content
+ * because sendmail permits it.
+ */
+- for (prev_rec_type = 0; /* void */ ; prev_rec_type = curr_rec_type) {
++ for (prev_rec_type = 0; /* void */ ; prev_rec_type = curr_rec_type,
++ prev_got_bare_lf = smtp_got_bare_lf) {
+ if (smtp_get(state->buffer, state->client, var_line_limit,
+ SMTP_GET_FLAG_NONE) == '\n')
+ curr_rec_type = REC_TYPE_NORM;
+ else
+ curr_rec_type = REC_TYPE_CONT;
++ if (IS_BARE_LF_REPLY_REJECT(smtp_got_bare_lf))
++ state->err |= CLEANUP_STAT_BARE_LF;
+ start = vstring_str(state->buffer);
+ len = VSTRING_LEN(state->buffer);
+ if (first) {
+@@ -3510,9 +3568,14 @@ static void receive_data_message(SMTPD_STATE *state,
+ if (len > 0 && IS_SPACE_TAB(start[0]))
+ out_record(out_stream, REC_TYPE_NORM, "", 0);
+ }
+- if (prev_rec_type != REC_TYPE_CONT && *start == '.'
+- && (proxy == 0 ? (++start, --len) == 0 : len == 1))
+- break;
++ if (prev_rec_type != REC_TYPE_CONT && *start == '.') {
++ if (len == 1 && IS_BARE_LF_WANT_STD_EOD(smtp_detect_bare_lf)
++ && (smtp_got_bare_lf || prev_got_bare_lf))
++ /* Do not store or send to proxy filter. */
++ continue;
++ if (proxy == 0 ? (++start, --len) == 0 : len == 1)
++ break;
++ }
+ if (state->err == CLEANUP_STAT_OK) {
+ if (var_message_limit > 0 && var_message_limit - state->act_size < len + 2) {
+ state->err = CLEANUP_STAT_SIZE;
+@@ -3664,6 +3727,11 @@ static int common_post_message_handling(SMTPD_STATE *state)
+ else
+ smtpd_chat_reply(state,
+ "250 2.0.0 Ok: queued as %s", state->queue_id);
++ } else if ((state->err & CLEANUP_STAT_BARE_LF) != 0) {
++ state->error_mask |= MAIL_ERROR_PROTOCOL;
++ log_whatsup(state, "reject", "bare <LF> received");
++ smtpd_chat_reply(state, "%d 5.5.2 %s Error: bare <LF> received",
++ var_smtpd_forbid_bare_lf_code, var_myhostname);
+ } else if (why && IS_SMTP_REJECT(STR(why))) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "%s", STR(why));
+@@ -3981,6 +4049,8 @@ static int bdat_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
+ /* Skip the out_record() and VSTRING_RESET() calls below. */
+ break;
+ }
++ if (IS_BARE_LF_REPLY_REJECT(smtp_got_bare_lf))
++ state->err |= CLEANUP_STAT_BARE_LF;
+ start = vstring_str(state->bdat_get_buffer);
+ len = VSTRING_LEN(state->bdat_get_buffer);
+ if (state->err == CLEANUP_STAT_OK) {
+@@ -4633,6 +4703,9 @@ static int xclient_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
+ */
+ xclient_allowed =
+ namadr_list_match(xclient_hosts, state->name, state->addr);
++ smtp_detect_bare_lf = (SMTPD_STAND_ALONE((state)) == 0 && bare_lf_mask
++ && !namadr_list_match(bare_lf_excl, state->name, state->addr)) ?
++ bare_lf_mask : 0;
+ /* NOT: tls_reset() */
+ if (got_helo == 0)
+ helo_reset(state);
+@@ -5614,6 +5687,13 @@ static void smtpd_proto(SMTPD_STATE *state)
+ }
+ watchdog_pat();
+ smtpd_chat_query(state);
++ if (IS_BARE_LF_REPLY_REJECT(smtp_got_bare_lf)) {
++ log_whatsup(state, "reject", "bare <LF> received");
++ state->error_mask |= MAIL_ERROR_PROTOCOL;
++ smtpd_chat_reply(state, "%d 5.5.2 %s Error: bare <LF> received",
++ var_smtpd_forbid_bare_lf_code, var_myhostname);
++ break;
++ }
+ /* Safety: protect internal interfaces against malformed UTF-8. */
+ if (var_smtputf8_enable && valid_utf8_string(STR(state->buffer),
+ LEN(state->buffer)) == 0) {
+@@ -5956,6 +6036,13 @@ static void smtpd_service(VSTREAM *stream, char *service, char **argv)
+ xforward_allowed = SMTPD_STAND_ALONE((&state)) == 0 &&
+ namadr_list_match(xforward_hosts, state.name, state.addr);
+
++ /*
++ * Reject or normalize bare LF, with compatibility exclusions.
++ */
++ smtp_detect_bare_lf = (SMTPD_STAND_ALONE((&state)) == 0 && bare_lf_mask
++ && !namadr_list_match(bare_lf_excl, state.name, state.addr)) ?
++ bare_lf_mask : 0;
++
+ /*
+ * See if we need to turn on verbose logging for this client.
+ */
+@@ -6018,6 +6105,14 @@ static void pre_jail_init(char *unused_name, char **unused_argv)
+ hogger_list = namadr_list_init(VAR_SMTPD_HOGGERS, MATCH_FLAG_RETURN
+ | match_parent_style(VAR_SMTPD_HOGGERS),
+ var_smtpd_hoggers);
++ bare_lf_excl = namadr_list_init(VAR_SMTPD_FORBID_BARE_LF_EXCL,
++ MATCH_FLAG_RETURN
++ | match_parent_style(VAR_MYNETWORKS),
++ var_smtpd_forbid_bare_lf_excl);
++ if ((bare_lf_mask = name_code(bare_lf_mask_table, NAME_CODE_FLAG_NONE,
++ var_smtpd_forbid_bare_lf)) < 0)
++ msg_fatal("bad parameter value: '%s = %s'",
++ VAR_SMTPD_FORBID_BARE_LF, var_smtpd_forbid_bare_lf);
+
+ /*
+ * Open maps before dropping privileges so we can read passwords etc.
+@@ -6314,6 +6409,7 @@ int main(int argc, char **argv)
+ VAR_VIRT_MAILBOX_CODE, DEF_VIRT_MAILBOX_CODE, &var_virt_mailbox_code, 0, 0,
+ VAR_RELAY_RCPT_CODE, DEF_RELAY_RCPT_CODE, &var_relay_rcpt_code, 0, 0,
+ VAR_PLAINTEXT_CODE, DEF_PLAINTEXT_CODE, &var_plaintext_code, 0, 0,
++ VAR_SMTPD_FORBID_BARE_LF_CODE, DEF_SMTPD_FORBID_BARE_LF_CODE, &var_smtpd_forbid_bare_lf_code, 500, 599,
+ VAR_SMTPD_CRATE_LIMIT, DEF_SMTPD_CRATE_LIMIT, &var_smtpd_crate_limit, 0, 0,
+ VAR_SMTPD_CCONN_LIMIT, DEF_SMTPD_CCONN_LIMIT, &var_smtpd_cconn_limit, 0, 0,
+ VAR_SMTPD_CMAIL_LIMIT, DEF_SMTPD_CMAIL_LIMIT, &var_smtpd_cmail_limit, 0, 0,
+@@ -6485,6 +6581,8 @@ int main(int argc, char **argv)
+ VAR_SMTPD_POLICY_CONTEXT, DEF_SMTPD_POLICY_CONTEXT, &var_smtpd_policy_context, 0, 0,
+ VAR_SMTPD_DNS_RE_FILTER, DEF_SMTPD_DNS_RE_FILTER, &var_smtpd_dns_re_filter, 0, 0,
+ VAR_SMTPD_REJ_FTR_MAPS, DEF_SMTPD_REJ_FTR_MAPS, &var_smtpd_rej_ftr_maps, 0, 0,
++ VAR_SMTPD_FORBID_BARE_LF_EXCL, DEF_SMTPD_FORBID_BARE_LF_EXCL, &var_smtpd_forbid_bare_lf_excl, 0, 0,
++ VAR_SMTPD_FORBID_BARE_LF, DEF_SMTPD_FORBID_BARE_LF, &var_smtpd_forbid_bare_lf, 1, 0,
+ 0,
+ };
+ static const CONFIG_RAW_TABLE raw_table[] = {
+diff --git a/src/smtpd/smtpd_check.c b/src/smtpd/smtpd_check.c
+index 31ed00f..72f11a0 100644
+--- a/src/smtpd/smtpd_check.c
++++ b/src/smtpd/smtpd_check.c
+@@ -48,6 +48,11 @@
+ /*
+ /* char *smtpd_check_queue(state)
+ /* SMTPD_STATE *state;
++/* AUXILIARY FUNCTIONS
++/* void log_whatsup(state, action, text)
++/* SMTPD_STATE *state;
++/* const char *action;
++/* const char *text;
+ /* DESCRIPTION
+ /* This module implements additional checks on SMTP client requests.
+ /* A client request is validated in the context of the session state.
+@@ -146,6 +151,11 @@
+ /* The recipient address given with the RCPT TO or VRFY command.
+ /* .IP size
+ /* The message size given with the MAIL FROM command (zero if unknown).
++/* .PP
++/* log_whatsup() logs "<queueid>: <action>: <protocol state>
++/* from: <client-name[client-addr]>: <text>" plus the protocol
++/* (SMTP or ESMTP), and if available, EHLO, MAIL FROM, or RCPT
++/* TO.
+ /* BUGS
+ /* Policies like these should not be hard-coded in C, but should
+ /* be user-programmable instead.
+@@ -946,8 +956,8 @@ void smtpd_check_init(void)
+
+ /* log_whatsup - log as much context as we have */
+
+-static void log_whatsup(SMTPD_STATE *state, const char *whatsup,
+- const char *text)
++void log_whatsup(SMTPD_STATE *state, const char *whatsup,
++ const char *text)
+ {
+ VSTRING *buf = vstring_alloc(100);
+
+diff --git a/src/smtpd/smtpd_check.h b/src/smtpd/smtpd_check.h
+index ce24498..bf0fe00 100644
+--- a/src/smtpd/smtpd_check.h
++++ b/src/smtpd/smtpd_check.h
+@@ -25,6 +25,7 @@ extern char *smtpd_check_etrn(SMTPD_STATE *, char *);
+ extern char *smtpd_check_data(SMTPD_STATE *);
+ extern char *smtpd_check_eod(SMTPD_STATE *);
+ extern char *smtpd_check_policy(SMTPD_STATE *, char *);
++extern void log_whatsup(SMTPD_STATE *, const char *, const char *);
+
+ /* LICENSE
+ /* .ad