diff options
Diffstat (limited to 'debian/patches/0020-CVE-2023-51764.patch')
-rw-r--r-- | debian/patches/0020-CVE-2023-51764.patch | 1335 |
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 <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 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 <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. ++</p> ++ ++<p> 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.</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 ++<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. </p> ++ ++<p> This feature is available in Postfix ≥ 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 < 3.9: no)</b></DT><DD> ++ ++<p> 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 <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 <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 <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 <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 ++$<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 <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 <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 <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 <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 <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 (<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 ≥ 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 ≥ 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 ≥ 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> < <b>3.9: no)</b> ++ Reject or restrict input lines from an SMTP client that end in ++ <LF> instead of the standard <CR><LF>. ++ ++ <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 < 3.9: no ++ ++<p> 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 <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 <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. </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 <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). </dd> ++ ++<dt> <b>no</b> (default)</dt> <dd> 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 <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 <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 ++</pre> ++</blockquote> ++ ++<p> Alternative: </p> ++ ++<blockquote> ++<pre> ++# 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 ++</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 ≥ 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 ≥ 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 ≥ 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 <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. ++</p> ++ ++<p> 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.</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 ++<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. </p> ++ ++<p> This feature is available in Postfix ≥ 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 |