From a6596ec37a4892e1d9c2498ecbfc4b8e6be5156a Mon Sep 17 00:00:00 2001
From: Wietse Venema <wietse@porcupine.org>
Date: Fri, 22 Dec 2023 00:00:00 -0500
Subject: [PATCH] postfix-3.6.13
---
Upstream-Status: Backport from [https://launchpad.net/ubuntu/+source/postfix/3.6.4-1ubuntu1.3]
CVE: CVE-2023-51764
Signed-off-by: Ashish Sharma <asharma@mvista.com>

 man/man5/postconf.5      |   55 +++++++++++++++++++++++++++++++++++++++++++++++
 man/man8/smtpd.8         |    9 +++++++
 mantools/postlink        |    2 +
 proto/postconf.proto     |   52 ++++++++++++++++++++++++++++++++++++++++++++
 src/global/mail_params.h |   11 ++++++++-
 src/global/smtp_stream.c |   14 +++++++++++
 src/global/smtp_stream.h |    2 +
 src/smtpd/smtpd.c        |   42 +++++++++++++++++++++++++++++++++++
 8 files changed, 185 insertions(+), 2 deletions(-)

--- a/man/man5/postconf.5
+++ b/man/man5/postconf.5
@@ -10412,6 +10412,61 @@
 parameter $name expansion.
 .PP
 This feature is available in Postfix 2.0 and later.
+.SH smtpd_forbid_bare_newline (default: Postfix < 3.9: no)
+Reply with "Error: bare <LF> received" and disconnect
+when a remote SMTP client sends a line ending in <LF>, violating
+the RFC 5321 requirement that lines must end in <CR><LF>.
+This feature is disbled by default with Postfix < 3.9. Use
+smtpd_forbid_bare_newline_exclusions to exclude non\-standard clients
+such as netcat. Specify "smtpd_forbid_bare_newline = no" to disable
+(not recommended for an Internet\-connected MTA).
+.PP
+See
+https://www.postfix.org/smtp\-smuggling.html for details.
+.PP
+Example:
+.sp
+.in +4
+.nf
+.na
+.ft C
+# Disconnect remote SMTP clients that send bare newlines, but allow
+# local clients with non\-standard SMTP implementations such as netcat,
+# fax machines, or load balancer health checks.
+#
+smtpd_forbid_bare_newline = yes
+smtpd_forbid_bare_newline_exclusions = $mynetworks
+.fi
+.ad
+.ft R
+.in -4
+.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_exclusions (default: $mynetworks)
+Exclude the specified clients from smtpd_forbid_bare_newline
+enforcement. It uses the same syntax and parent\-domain matching
+behavior as mynetworks.
+.PP
+Example:
+.sp
+.in +4
+.nf
+.na
+.ft C
+# Disconnect remote SMTP clients that send bare newlines, but allow
+# local clients with non\-standard SMTP implementations such as netcat,
+# fax machines, or load balancer health checks.
+#
+smtpd_forbid_bare_newline = yes
+smtpd_forbid_bare_newline_exclusions = $mynetworks
+.fi
+.ad
+.ft R
+.in -4
+.PP
+This feature is available in Postfix >= 3.9, 3.8.4, 3.7.9,
+3.6.13, and 3.5.23.
 .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
--- a/man/man8/smtpd.8
+++ b/man/man8/smtpd.8
@@ -808,6 +808,15 @@
 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"
+Reply with "Error: bare <LF> received" and disconnect
+when a remote SMTP client sends a line ending in <LF>, violating
+the RFC 5321 requirement that lines must end in <CR><LF>.
+.IP "\fBsmtpd_forbid_bare_newline_exclusions ($mynetworks)\fR"
+Exclude the specified clients from smtpd_forbid_bare_newline
+enforcement.
 .SH "TARPIT CONTROLS"
 .na
 .nf
--- a/mantools/postlink
+++ b/mantools/postlink
@@ -547,6 +547,8 @@
     s;\bsmtpd_error_sleep_time\b;<a href="postconf.5.html#smtpd_error_sleep_time">$&</a>;g;
     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>]*bid_bare_newline\b;<a href="postconf.5.html#smtpd_forbi    d_bare_newline">$&</a>;g;
+    s;\bsmtpd_for[-</bB>]*\n*[ <bB>]*bid_bare_newline_exclusions\b;<a href="postconf.5.html#    smtpd_forbid_bare_newline_exclusions">$&</a>;g;
     s;\bsmtpd_for[-</bB>]*\n*[ <bB>]*bidden_commands\b;<a href="postconf.5.html#smtpd_forbidden_commands">$&</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;
--- a/proto/postconf.proto
+++ b/proto/postconf.proto
@@ -18058,3 +18058,55 @@
 name or port number. </p>
 
 <p> This feature is available in Postfix 3.6 and later. </p>
+
+%PARAM smtpd_forbid_bare_newline Postfix &lt; 3.9: no
+
+<p> Reply with "Error: bare &lt;LF&gt; received" and disconnect
+when a remote SMTP client sends a line ending in &lt;LF&gt;, violating
+the RFC 5321 requirement that lines must end in &lt;CR&gt;&lt;LF&gt;.
+This feature is disbled by default with Postfix &lt; 3.9. Use
+smtpd_forbid_bare_newline_exclusions to exclude non-standard clients
+such as netcat. Specify "smtpd_forbid_bare_newline = no" to disable
+(not recommended for an Internet-connected MTA). </p>
+
+<p> See <a href="https://www.postfix.org/smtp-smuggling.html">
+https://www.postfix.org/smtp-smuggling.html</a> for details.
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+# Disconnect remote SMTP clients that send bare newlines, but allow
+# local clients with non-standard SMTP implementations such as netcat,
+# fax machines, or load balancer health checks.
+#
+smtpd_forbid_bare_newline = yes
+smtpd_forbid_bare_newline_exclusions = $mynetworks
+</pre>
+</blockquote>
+
+<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_exclusions $mynetworks
+
+<p> Exclude the specified clients from smtpd_forbid_bare_newline
+enforcement. It uses the same syntax and parent-domain matching
+behavior as mynetworks. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+# Disconnect remote SMTP clients that send bare newlines, but allow
+# local clients with non-standard SMTP implementations such as netcat,
+# fax machines, or load balancer health checks.
+#
+smtpd_forbid_bare_newline = yes
+smtpd_forbid_bare_newline_exclusions = $mynetworks
+</pre>
+</blockquote>
+
+<p> This feature is available in Postfix &ge; 3.9, 3.8.4, 3.7.9,
+3.6.13, and 3.5.23. </p>
+
--- a/src/global/mail_params.h
+++ b/src/global/mail_params.h
@@ -4170,7 +4170,16 @@
 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	0
+
+#define VAR_SMTPD_FORBID_BARE_LF_EXCL	"smtpd_forbid_bare_newline_exclusions"
+#define DEF_SMTPD_FORBID_BARE_LF_EXCL	"$" VAR_MYNETWORKS
+
+ /*
+  * Share TLS sessions through tlsproxy(8).
   */
 #define VAR_SMTP_TLS_CONN_REUSE		"smtp_tls_connection_reuse"
 #define DEF_SMTP_TLS_CONN_REUSE		0
--- a/src/global/smtp_stream.c
+++ b/src/global/smtp_stream.c
@@ -50,6 +50,8 @@
 /*	VSTREAM *stream;
 /*	char	*format;
 /*	va_list	ap;
+/*
+/*	int	smtp_forbid_bare_lf;
 /* AUXILIARY API
 /*	int	smtp_get_noexcept(vp, stream, maxlen, flags)
 /*	VSTRING	*vp;
@@ -124,11 +126,16 @@
 /*	smtp_vprintf() is the machine underneath smtp_printf().
 /*
 /*	smtp_get_noexcept() implements the subset of smtp_get()
-/*	without timeouts and without making long jumps. Instead,
+/*	without long jumps for timeout or EOF errors. Instead,
 /*	query the stream status with vstream_feof() etc.
+/*	This function will make a VSTREAM long jump (error code
+/*	SMTP_ERR_LF) when rejecting input with a bare newline byte.
 /*
 /*	smtp_timeout_setup() is a backwards-compatibility interface
 /*	for programs that don't require per-record deadline support.
+/*
+/*	smtp_forbid_bare_lf controls whether smtp_get_noexcept()
+/*	will reject input with a bare newline byte.
 /* DIAGNOSTICS
 /* .fi
 /* .ad
@@ -201,6 +208,8 @@
 
 #include "smtp_stream.h"
 
+int     smtp_forbid_bare_lf;
+
 /* smtp_timeout_reset - reset per-stream error flags, restart deadline timer */
 
 static void smtp_timeout_reset(VSTREAM *stream)
@@ -404,6 +413,9 @@
 	 */
     case '\n':
 	vstring_truncate(vp, VSTRING_LEN(vp) - 1);
+	if (smtp_forbid_bare_lf
+	    && (VSTRING_LEN(vp) == 0 || vstring_end(vp)[-1] != '\r'))
+	    vstream_longjmp(stream, SMTP_ERR_LF);
 	while (VSTRING_LEN(vp) > 0 && vstring_end(vp)[-1] == '\r')
 	    vstring_truncate(vp, VSTRING_LEN(vp) - 1);
 	VSTRING_TERMINATE(vp);
--- a/src/global/smtp_stream.h
+++ b/src/global/smtp_stream.h
@@ -32,6 +32,7 @@
 #define SMTP_ERR_QUIET	3		/* silent cleanup (application) */
 #define SMTP_ERR_NONE	4		/* non-error case */
 #define SMTP_ERR_DATA	5		/* application data error */
+#define SMTP_ERR_LF	6		/* bare <LF> protocol error */
 
 extern void smtp_stream_setup(VSTREAM *, int, int);
 extern void PRINTFLIKE(2, 3) smtp_printf(VSTREAM *, const char *,...);
@@ -43,6 +44,7 @@
 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_forbid_bare_lf;
 
 extern void smtp_vprintf(VSTREAM *, const char *, va_list);
 
--- a/src/smtpd/smtpd.c
+++ b/src/smtpd/smtpd.c
@@ -762,6 +762,15 @@
 /*	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"
+/*	Reply with "Error: bare <LF> received" and disconnect
+/*	when a remote SMTP client sends a line ending in <LF>, violating
+/*	the RFC 5321 requirement that lines must end in <CR><LF>.
+/* .IP "\fBsmtpd_forbid_bare_newline_exclusions ($mynetworks)\fR"
+/*	Exclude the specified clients from smtpd_forbid_bare_newline
+/*	enforcement.
 /* TARPIT CONTROLS
 /* .ad
 /* .fi
@@ -1467,6 +1476,10 @@
 int     var_smtpd_uproxy_tmout;
 bool    var_relay_before_rcpt_checks;
 
+bool    var_smtpd_forbid_bare_lf;
+char   *var_smtpd_forbid_bare_lf_excl;
+static NAMADR_LIST *bare_lf_excl;
+
  /*
   * Silly little macros.
   */
@@ -1541,6 +1554,7 @@
 #define REASON_TIMEOUT		"timeout"
 #define REASON_LOST_CONNECTION	"lost connection"
 #define REASON_ERROR_LIMIT	"too many errors"
+#define REASON_BARE_LF		"bare <LF> received"
 
 #ifdef USE_TLS
 
@@ -3967,6 +3981,7 @@
      */
     done = 0;
     do {
+	int     payload_err;
 
 	/*
 	 * Do not skip the smtp_fread_buf() call if read_len == 0. We still
@@ -3980,6 +3995,10 @@
 	smtp_fread_buf(state->buffer, read_len, state->client);
 	state->bdat_get_stream = vstream_memreopen(
 			   state->bdat_get_stream, state->buffer, O_RDONLY);
+	vstream_control(state->bdat_get_stream, CA_VSTREAM_CTL_EXCEPT,
+			CA_VSTREAM_CTL_END);
+	if ((payload_err = vstream_setjmp(state->bdat_get_stream)) != 0)
+	    vstream_longjmp(state->client, payload_err);
 
 	/*
 	 * Read lines from the fragment. The last line may continue in the
@@ -4655,6 +4674,9 @@
      */
     xclient_allowed =
 	namadr_list_match(xclient_hosts, state->name, state->addr);
+    smtp_forbid_bare_lf = SMTPD_STAND_ALONE((state)) == 0
+	&& var_smtpd_forbid_bare_lf
+	&& !namadr_list_match(bare_lf_excl, state->name, state->addr);
     /* NOT: tls_reset() */
     if (got_helo == 0)
 	helo_reset(state);
@@ -5446,6 +5468,13 @@
 			     var_myhostname);
 	break;
 
+    case SMTP_ERR_LF:
+	state->reason = REASON_BARE_LF;
+	if (vstream_setjmp(state->client) == 0)
+	    smtpd_chat_reply(state, "521 5.5.2 %s Error: bare <LF> received",
+			     var_myhostname);
+	break;
+
     case 0:
 
 	/*
@@ -5995,6 +6024,13 @@
 	namadr_list_match(xforward_hosts, state.name, state.addr);
 
     /*
+     * Enforce strict SMTP line endings, with compatibility exclusions.
+     */
+    smtp_forbid_bare_lf = SMTPD_STAND_ALONE((&state)) == 0
+	&& var_smtpd_forbid_bare_lf
+	&& !namadr_list_match(bare_lf_excl, state.name, state.addr);
+
+    /*
      * See if we need to turn on verbose logging for this client.
      */
     debug_peer_check(state.name, state.addr);
@@ -6055,6 +6091,10 @@
     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);
 
     /*
      * Open maps before dropping privileges so we can read passwords etc.
@@ -6412,6 +6452,7 @@
 	VAR_SMTPD_PEERNAME_LOOKUP, DEF_SMTPD_PEERNAME_LOOKUP, &var_smtpd_peername_lookup,
 	VAR_SMTPD_DELAY_OPEN, DEF_SMTPD_DELAY_OPEN, &var_smtpd_delay_open,
 	VAR_SMTPD_CLIENT_PORT_LOG, DEF_SMTPD_CLIENT_PORT_LOG, &var_smtpd_client_port_log,
+    VAR_SMTPD_FORBID_BARE_LF, DEF_SMTPD_FORBID_BARE_LF, &var_smtpd_forbid_bare_lf,
 	0,
     };
     static const CONFIG_NBOOL_TABLE nbool_table[] = {
@@ -6527,6 +6568,7 @@
 	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,
 	0,
     };
     static const CONFIG_RAW_TABLE raw_table[] = {
