On 2024-01-01 Salvatore Bonaccorso <car...@debian.org> wrote:
> On Mon, Jan 01, 2024 at 04:45:24PM +0100, Andreas Metzler wrote:
[...]
> > I have prepared updates for either DSA or stable update. (I will be on my
> > dayjob again tomorrow, so I will not be that responsive on workdays.)

> Happy new year to you. Thanks for the input in the previous message.
> Let be on the safe side, and release it through a DSA. Please upload
> to security-master.

Hello Salvatore,

thanks, happy new year!

I have uploaded with attached diffs (identical to previously sent, just
upload target changed to "bookworm/bullseye-security; urgency=high" from
UNRELEASED.

cu Andreas
diff -Nru exim4-4.96/debian/changelog exim4-4.96/debian/changelog
--- exim4-4.96/debian/changelog	2023-11-18 11:07:57.000000000 +0100
+++ exim4-4.96/debian/changelog	2024-01-01 17:58:00.000000000 +0100
@@ -1,3 +1,12 @@
+exim4 (4.96-15+deb12u4) bookworm-security; urgency=high
+
+  * 77_CVE-2023-51766_4.97.1-release.diff from 4,97.1 release: Refuse to
+    accept a line "dot, LF" as end-of-DATA unless operating in LF-only mode
+    (as detected from the first header line) to fix smtp-smuggling
+    (CVE-2023-51766). Closes: #1059387
+
+ -- Andreas Metzler <ametz...@debian.org>  Mon, 01 Jan 2024 17:58:00 +0100
+
 exim4 (4.96-15+deb12u3) bookworm; urgency=medium
 
   * Multiple bugfixes from upstream GIT master:
diff -Nru exim4-4.96/debian/patches/77_CVE-2023-51766_4.97.1-release.diff exim4-4.96/debian/patches/77_CVE-2023-51766_4.97.1-release.diff
--- exim4-4.96/debian/patches/77_CVE-2023-51766_4.97.1-release.diff	1970-01-01 01:00:00.000000000 +0100
+++ exim4-4.96/debian/patches/77_CVE-2023-51766_4.97.1-release.diff	2024-01-01 16:32:59.000000000 +0100
@@ -0,0 +1,440 @@
+Description: Fix smtp-smuggling (CVE-2023-51766)
+ Pull upstream changes from 4.97.1 security release. 
+Author: Jeremy Harris <jgh146...@wizmail.org>
+Bug-Debian: https://bugs.debian.org/1059387
+Origin: upstream
+Last-Update: 2023-12-31
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -91,10 +91,16 @@ JH/39 Bug 3023: Fix crash induced by som
+       and ${tr...}.  Found and diagnosed by Heiko Schlichting.
+  
+ JH/06 Bug 3054: Fix dnsdb lookup for a TXT record with multiple chunks.  This
+       was broken by hardening introduced for Bug 3033.
+ 
++JH/s1 Refuse to accept a line "dot, LF" as end-of-DATA unless operating in
++      LF-only mode (as detected from the first header line).  Previously we did
++      accept that in (normal) CRLF mode; this has been raised as a possible
++      attack scenario (under the name "smtp smuggling", CVE-2023-51766).
++
++
+ Exim version 4.96
+ -----------------
+ 
+ JH/01 Move the wait-for-next-tick (needed for unique message IDs) from
+       after reception to before a subsequent reception.  This should
+--- /dev/null
++++ b/doc/doc-txt/cve-2023-51766
+@@ -0,0 +1,69 @@
++CVE ID:     CVE-2023-51766
++Date:       2016-12-15
++Credits:    https://sec-consult.com/blog/detail/smtp-smuggling-spoofing-e-mails-worldwide/
++Version(s): all up to 4.97 inclusive
++Issue:      Given a buggy relay, Exim can be induced to accept a second message embedded
++            as part of the body of a first message
++
++Conditions
++==========
++
++If *all* the following conditions are met
++
++    Runtime options
++    ---------------
++
++    * Exim offers PIPELINING on incoming connections
++
++    * Exim offers CHUNKING on incoming connections
++
++    Operation
++    ---------
++
++    * DATA (as opposed to BDAT) is used for a message reception
++
++    * The relay host sends to the Exim MTA message data including
++      one of "LF . LF" or "CR LF . LF" or "LF . CR LF".
++
++    * Exim interprets the sequence as signalling the end of data for
++      the SMTP DATA command, and hence a first message.
++
++    * Exim interprets further input which the relay had as message body
++      data, as SMTP commands and data. This could include a MAIL, RCPT,
++      BDAT (etc) sequence, resulting in a further message acceptance.
++
++Impact
++======
++
++One or more messages can be accepted by Exim that have not been
++properly validated by the buggy relay.
++
++Fix
++===
++
++Install a fixed Exim version:
++
++    4.98 (once available)
++    4.97.1
++
++If you can't install one of the above versions, ask your package
++maintainer for a version containing the backported fix. On request and
++depending on our resources we will support you in backporting the fix.
++(Please note, that Exim project officially doesn't support versions
++prior the current stable version.)
++
++
++Workaround
++==========
++
++  Disable CHUNKING advertisement for incoming connections.
++
++  An attempt to "smuggle" a DATA command will trip a syncronisation
++  check.
++
++*or*
++
++  Disable PIPELINING advertisement for incoming connections.
++
++  The "smuggled" MAIL FROM command will then trip a syncronisation
++  check.
+--- a/src/receive.c
++++ b/src/receive.c
+@@ -826,104 +826,118 @@ we make the CRs optional in all cases.
+ 
+ July 2003: Bare CRs cause trouble. We now treat them as line terminators as
+ well, so that there are no CRs in spooled messages. However, the message
+ terminating dot is not recognized between two bare CRs.
+ 
++Dec 2023: getting a site to send a body including an "LF . LF" sequence
++followed by SMTP commands is a possible "smtp smuggling" attack.  If
++the first (header) line for the message has a proper CRLF then enforce
++that for the body: convert bare LF to a space.
++
+ Arguments:
+-  fout      a FILE to which to write the message; NULL if skipping
++  fout		a FILE to which to write the message; NULL if skipping
++  strict_crlf	require full CRLF sequence as a line ending
+ 
+ Returns:    One of the END_xxx values indicating why it stopped reading
+ */
+ 
+ static int
+-read_message_data_smtp(FILE *fout)
++read_message_data_smtp(FILE * fout, BOOL strict_crlf)
+ {
+-int ch_state = 0;
+-int ch;
+-int linelength = 0;
++enum { s_linestart, s_normal, s_had_cr, s_had_nl_dot, s_had_dot_cr } ch_state =
++	      s_linestart;
++int linelength = 0, ch;
+ 
+ while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF)
+   {
+   if (ch == 0) body_zerocount++;
+   switch (ch_state)
+     {
+-    case 0:                             /* After LF or CRLF */
+-    if (ch == '.')
+-      {
+-      ch_state = 3;
+-      continue;                         /* Don't ever write . after LF */
+-      }
+-    ch_state = 1;
++    case s_linestart:			/* After LF or CRLF */
++      if (ch == '.')
++	{
++	ch_state = s_had_nl_dot;
++	continue;			/* Don't ever write . after LF */
++	}
++      ch_state = s_normal;
+ 
+-    /* Else fall through to handle as normal uschar. */
++      /* Else fall through to handle as normal uschar. */
+ 
+-    case 1:                             /* Normal state */
+-    if (ch == '\n')
+-      {
+-      ch_state = 0;
+-      body_linecount++;
++    case s_normal:			/* Normal state */
++      if (ch == '\r')
++	{
++	ch_state = s_had_cr;
++	continue;			/* Don't write the CR */
++	}
++      if (ch == '\n')			/* Bare LF at end of line */
++	if (strict_crlf)
++	  ch = ' ';			/* replace LF with space */
++	else
++	  {				/* treat as line ending */
++	  ch_state = s_linestart;
++	  body_linecount++;
++	  if (linelength > max_received_linelength)
++	    max_received_linelength = linelength;
++	  linelength = -1;
++	  }
++      break;
++
++    case s_had_cr:			/* After (unwritten) CR */
++      body_linecount++;			/* Any char ends line */
+       if (linelength > max_received_linelength)
+-        max_received_linelength = linelength;
++	max_received_linelength = linelength;
+       linelength = -1;
+-      }
+-    else if (ch == '\r')
+-      {
+-      ch_state = 2;
+-      continue;
+-      }
+-    break;
++      if (ch == '\n')			/* proper CRLF */
++	ch_state = s_linestart;
++      else
++	{
++	message_size++;		/* convert the dropped CR to a stored NL */
++	if (fout && fputc('\n', fout) == EOF) return END_WERROR;
++	cutthrough_data_put_nl();
++	if (ch == '\r')			/* CR; do not write */
++	  continue;
++	ch_state = s_normal;		/* not LF or CR; process as standard */
++	}
++      break;
+ 
+-    case 2:                             /* After (unwritten) CR */
+-    body_linecount++;
+-    if (linelength > max_received_linelength)
+-      max_received_linelength = linelength;
+-    linelength = -1;
+-    if (ch == '\n')
+-      {
+-      ch_state = 0;
+-      }
+-    else
+-      {
+-      message_size++;
+-      if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
+-      cutthrough_data_put_nl();
+-      if (ch != '\r') ch_state = 1; else continue;
+-      }
+-    break;
++    case s_had_nl_dot:			/* After [CR] LF . */
++      if (ch == '\n')			/* [CR] LF . LF */
++	if (strict_crlf)
++	  ch = ' ';			/* replace LF with space */
++	else
++	  return END_DOT;
++      else if (ch == '\r')		/* [CR] LF . CR */
++	{
++	ch_state = s_had_dot_cr;
++	continue;			/* Don't write the CR */
++	}
++      /* The dot was removed on reaching s_had_nl_dot. For a doubled dot, here,
++      reinstate it to cutthrough. The current ch, dot or not, is passed both to
++      cutthrough and to file below. */
++      else if (ch == '.')
++	{
++	uschar c = ch;
++	cutthrough_data_puts(&c, 1);
++	}
++      ch_state = s_normal;
++      break;
+ 
+-    case 3:                             /* After [CR] LF . */
+-    if (ch == '\n')
+-      return END_DOT;
+-    if (ch == '\r')
+-      {
+-      ch_state = 4;
+-      continue;
+-      }
+-    /* The dot was removed at state 3. For a doubled dot, here, reinstate
+-    it to cutthrough. The current ch, dot or not, is passed both to cutthrough
+-    and to file below. */
+-    if (ch == '.')
+-      {
+-      uschar c= ch;
+-      cutthrough_data_puts(&c, 1);
+-      }
+-    ch_state = 1;
+-    break;
++    case s_had_dot_cr:			/* After [CR] LF . CR */
++      if (ch == '\n')
++	return END_DOT;			/* Preferred termination */
+ 
+-    case 4:                             /* After [CR] LF . CR */
+-    if (ch == '\n') return END_DOT;
+-    message_size++;
+-    body_linecount++;
+-    if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
+-    cutthrough_data_put_nl();
+-    if (ch == '\r')
+-      {
+-      ch_state = 2;
+-      continue;
+-      }
+-    ch_state = 1;
+-    break;
++      message_size++;		/* convert the dropped CR to a stored NL */
++      body_linecount++;
++      if (fout && fputc('\n', fout) == EOF) return END_WERROR;
++      cutthrough_data_put_nl();
++      if (ch == '\r')
++	{
++	ch_state = s_had_cr;
++	continue;			/* CR; do not write */
++	}
++      ch_state = s_normal;
++      break;
+     }
+ 
+   /* Add the character to the spool file, unless skipping; then loop for the
+   next. */
+ 
+@@ -1135,11 +1149,11 @@ Returns:     nothing
+ void
+ receive_swallow_smtp(void)
+ {
+ if (message_ended >= END_NOTENDED)
+   message_ended = chunking_state <= CHUNKING_OFFERED
+-     ? read_message_data_smtp(NULL)
++     ? read_message_data_smtp(NULL, FALSE)
+      : read_message_bdat_smtp_wire(NULL);
+ }
+ 
+ 
+ 
+@@ -1946,28 +1960,36 @@ for (;;)
+   LF specially by inserting a white space after it to ensure that the header
+   line is not terminated. */
+ 
+   if (ch == '\n')
+     {
+-    if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = FALSE;
+-      else if (first_line_ended_crlf) receive_ungetc(' ');
++    if (first_line_ended_crlf == TRUE_UNSET)
++      first_line_ended_crlf = FALSE;
++    else if (first_line_ended_crlf)
++      receive_ungetc(' ');
+     goto EOL;
+     }
+ 
+   /* This is not the end of the line. If this is SMTP input and this is
+   the first character in the line and it is a "." character, ignore it.
+   This implements the dot-doubling rule, though header lines starting with
+   dots aren't exactly common. They are legal in RFC 822, though. If the
+   following is CRLF or LF, this is the line that that terminates the
++
+   entire message. We set message_ended to indicate this has happened (to
+   prevent further reading), and break out of the loop, having freed the
+   empty header, and set next = NULL to indicate no data line. */
+ 
+   if (f.dot_ends && ptr == 0 && ch == '.')
+     {
++    /* leading dot while in headers-read mode */
+     ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
+-    if (ch == '\r')
++    if (ch == '\n' && first_line_ended_crlf == TRUE /* and not TRUE_UNSET */ )
++    		/* dot, LF  but we are in CRLF mode.  Attack? */
++      ch = ' ';	/* replace the LF with a space */
++
++    else if (ch == '\r')
+       {
+       ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
+       if (ch != '\n')
+         {
+ 	if (ch >= 0) receive_ungetc(ch);
+@@ -1999,11 +2021,12 @@ for (;;)
+   if (ch == '\r')
+     {
+     ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
+     if (ch == '\n')
+       {
+-      if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = TRUE;
++      if (first_line_ended_crlf == TRUE_UNSET)
++	first_line_ended_crlf = TRUE;
+       goto EOL;
+       }
+ 
+     /* Otherwise, put back the character after CR, and turn the bare CR
+     into LF SP. */
+@@ -3144,11 +3167,11 @@ if (cutthrough.cctx.sock >= 0 && cutthro
+   (void) cutthrough_headers_send();
+   }
+ 
+ 
+ /* Open a new spool file for the data portion of the message. We need
+-to access it both via a file descriptor and a stream. Try to make the
++to access it both via a file descriptor and a stdio stream. Try to make the
+ directory if it isn't there. */
+ 
+ spool_name = spool_fname(US"input", message_subdir, message_id, US"-D");
+ DEBUG(D_receive) debug_printf("Data file name: %s\n", spool_name);
+ 
+@@ -3213,11 +3236,11 @@ message id or "next" line. */
+ if (!ferror(spool_data_file) && !(receive_feof)() && message_ended != END_DOT)
+   {
+   if (smtp_input)
+     {
+     message_ended = chunking_state <= CHUNKING_OFFERED
+-      ? read_message_data_smtp(spool_data_file)
++      ? read_message_data_smtp(spool_data_file, first_line_ended_crlf)
+       : spool_wireformat
+       ? read_message_bdat_smtp_wire(spool_data_file)
+       : read_message_bdat_smtp(spool_data_file);
+     receive_linecount++;                /* The terminating "." line */
+     }
+--- a/src/smtp_in.c
++++ b/src/smtp_in.c
+@@ -5447,16 +5447,16 @@ while (done <= 0)
+ 	  }
+ 	break;
+ 	}
+ 
+       if (chunking_state > CHUNKING_OFFERED)
+-	rc = OK;			/* No predata ACL or go-ahead output for BDAT */
++	rc = OK;	/* There is no predata ACL or go-ahead output for BDAT */
+       else
+ 	{
+-	/* If there is an ACL, re-check the synchronization afterwards, since the
+-	ACL may have delayed.  To handle cutthrough delivery enforce a dummy call
+-	to get the DATA command sent. */
++	/* If there is a predata-ACL, re-check the synchronization afterwards,
++	since the ACL may have delayed.  To handle cutthrough delivery enforce a
++	dummy call to get the DATA command sent. */
+ 
+ 	if (!acl_smtp_predata && cutthrough.cctx.sock < 0)
+ 	  rc = OK;
+ 	else
+ 	  {
+--- a/doc/spec.txt
++++ b/doc/spec.txt
+@@ -33555,12 +33555,10 @@ MTA within an operating system would use
+ has shown that this is not the case; for example, there are Unix applications
+ that use CRLF in this circumstance. For this reason, and for compatibility with
+ other MTAs, the way Exim handles line endings for all messages is now as
+ follows:
+ 
+-  * LF not preceded by CR is treated as a line ending.
+-
+   * CR is treated as a line ending; if it is immediately followed by LF, the LF
+     is ignored.
+ 
+   * The sequence "CR, dot, CR" does not terminate an incoming SMTP message, nor
+     a local message in the state where a line containing only a dot is a
+@@ -33571,11 +33569,14 @@ follows:
+     behind this is that bare CRs in header lines are most likely either to be
+     mistakes, or people trying to play silly games.
+ 
+   * If the first header line received in a message ends with CRLF, a subsequent
+     bare LF in a header line is treated in the same way as a bare CR in a
+-    header line.
++    header line and a bare LF in a body line is replaced with a space.
++
++  * If the first header line received in a message does not end with CRLF, a
++    subsequent LF not preceded by CR is treated as a line ending.
+ 
+ 
+ 48.3 Unqualified addresses
+ --------------------------
+ 
diff -Nru exim4-4.96/debian/patches/series exim4-4.96/debian/patches/series
--- exim4-4.96/debian/patches/series	2023-11-18 11:07:57.000000000 +0100
+++ exim4-4.96/debian/patches/series	2024-01-01 16:32:59.000000000 +0100
@@ -49,4 +49,5 @@
 76-10-Fix-tr.-and-empty-strings.-Bug-3023.patch
 76-12-DNS-more-hardening-against-crafted-responses.patch
 76-14-Lookups-Fix-dnsdb-lookup-of-multi-chunk-TXT.-Bug-305.patch
+77_CVE-2023-51766_4.97.1-release.diff
 90_localscan_dlopen.dpatch
diff -Nru exim4-4.94.2/debian/changelog exim4-4.94.2/debian/changelog
--- exim4-4.94.2/debian/changelog	2023-09-29 23:23:04.000000000 +0200
+++ exim4-4.94.2/debian/changelog	2024-01-01 18:00:11.000000000 +0100
@@ -1,3 +1,12 @@
+exim4 (4.94.2-7+deb11u2) bullseye-security; urgency=high
+
+  * 79_CVE-2023-51766_4.97.1-release.diff from 4,97.1 release: Refuse to
+    accept a line "dot, LF" as end-of-DATA unless operating in LF-only mode
+    (as detected from the first header line) to fix smtp-smuggling
+    (CVE-2023-51766). Closes: #1059387
+
+ -- Andreas Metzler <ametz...@debian.org>  Mon, 01 Jan 2024 18:00:11 +0100
+
 exim4 (4.94.2-7+deb11u1) bullseye-security; urgency=high
 
   * Non-maintainer upload by the Security Team.
diff -Nru exim4-4.94.2/debian/patches/79_CVE-2023-51766_4.97.1-release.diff exim4-4.94.2/debian/patches/79_CVE-2023-51766_4.97.1-release.diff
--- exim4-4.94.2/debian/patches/79_CVE-2023-51766_4.97.1-release.diff	1970-01-01 01:00:00.000000000 +0100
+++ exim4-4.94.2/debian/patches/79_CVE-2023-51766_4.97.1-release.diff	2024-01-01 18:00:05.000000000 +0100
@@ -0,0 +1,435 @@
+Description: Fix smtp-smuggling (CVE-2023-51766)
+ Pull upstream changes from 4.97.1 security release. 
+Author: Jeremy Harris <jgh146...@wizmail.org>
+Bug-Debian: https://bugs.debian.org/1059387
+Origin: upstream
+Last-Update: 2023-12-31
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -229,10 +229,15 @@ JH/53 Bug 2743: fix immediate-delivery v
+ 
+ JH/57 Fix control=fakreject for a custom message containing tainted data.
+       Previously this resulted in a log complaint, due to a re-expansion present
+       since fakereject was originally introduced.
+ 
++JH/s1 Refuse to accept a line "dot, LF" as end-of-DATA unless operating in
++      LF-only mode (as detected from the first header line).  Previously we did
++      accept that in (normal) CRLF mode; this has been raised as a possible
++      attack scenario (under the name "smtp smuggling", CVE-2023-51766).
++
+ 
+ Exim version 4.94
+ -----------------
+ 
+ JH/01 Avoid costly startup code when not strictly needed.  This reduces time
+--- /dev/null
++++ b/doc/doc-txt/cve-2023-51766
+@@ -0,0 +1,69 @@
++CVE ID:     CVE-2023-51766
++Date:       2016-12-15
++Credits:    https://sec-consult.com/blog/detail/smtp-smuggling-spoofing-e-mails-worldwide/
++Version(s): all up to 4.97 inclusive
++Issue:      Given a buggy relay, Exim can be induced to accept a second message embedded
++            as part of the body of a first message
++
++Conditions
++==========
++
++If *all* the following conditions are met
++
++    Runtime options
++    ---------------
++
++    * Exim offers PIPELINING on incoming connections
++
++    * Exim offers CHUNKING on incoming connections
++
++    Operation
++    ---------
++
++    * DATA (as opposed to BDAT) is used for a message reception
++
++    * The relay host sends to the Exim MTA message data including
++      one of "LF . LF" or "CR LF . LF" or "LF . CR LF".
++
++    * Exim interprets the sequence as signalling the end of data for
++      the SMTP DATA command, and hence a first message.
++
++    * Exim interprets further input which the relay had as message body
++      data, as SMTP commands and data. This could include a MAIL, RCPT,
++      BDAT (etc) sequence, resulting in a further message acceptance.
++
++Impact
++======
++
++One or more messages can be accepted by Exim that have not been
++properly validated by the buggy relay.
++
++Fix
++===
++
++Install a fixed Exim version:
++
++    4.98 (once available)
++    4.97.1
++
++If you can't install one of the above versions, ask your package
++maintainer for a version containing the backported fix. On request and
++depending on our resources we will support you in backporting the fix.
++(Please note, that Exim project officially doesn't support versions
++prior the current stable version.)
++
++
++Workaround
++==========
++
++  Disable CHUNKING advertisement for incoming connections.
++
++  An attempt to "smuggle" a DATA command will trip a syncronisation
++  check.
++
++*or*
++
++  Disable PIPELINING advertisement for incoming connections.
++
++  The "smuggled" MAIL FROM command will then trip a syncronisation
++  check.
+--- a/src/receive.c
++++ b/src/receive.c
+@@ -805,104 +805,118 @@ we make the CRs optional in all cases.
+ 
+ July 2003: Bare CRs cause trouble. We now treat them as line terminators as
+ well, so that there are no CRs in spooled messages. However, the message
+ terminating dot is not recognized between two bare CRs.
+ 
++Dec 2023: getting a site to send a body including an "LF . LF" sequence
++followed by SMTP commands is a possible "smtp smuggling" attack.  If
++the first (header) line for the message has a proper CRLF then enforce
++that for the body: convert bare LF to a space.
++
+ Arguments:
+-  fout      a FILE to which to write the message; NULL if skipping
++  fout		a FILE to which to write the message; NULL if skipping
++  strict_crlf	require full CRLF sequence as a line ending
+ 
+ Returns:    One of the END_xxx values indicating why it stopped reading
+ */
+ 
+ static int
+-read_message_data_smtp(FILE *fout)
++read_message_data_smtp(FILE * fout, BOOL strict_crlf)
+ {
+-int ch_state = 0;
+-int ch;
+-int linelength = 0;
++enum { s_linestart, s_normal, s_had_cr, s_had_nl_dot, s_had_dot_cr } ch_state =
++	      s_linestart;
++int linelength = 0, ch;
+ 
+ while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF)
+   {
+   if (ch == 0) body_zerocount++;
+   switch (ch_state)
+     {
+-    case 0:                             /* After LF or CRLF */
+-    if (ch == '.')
+-      {
+-      ch_state = 3;
+-      continue;                         /* Don't ever write . after LF */
+-      }
+-    ch_state = 1;
++    case s_linestart:			/* After LF or CRLF */
++      if (ch == '.')
++	{
++	ch_state = s_had_nl_dot;
++	continue;			/* Don't ever write . after LF */
++	}
++      ch_state = s_normal;
+ 
+-    /* Else fall through to handle as normal uschar. */
++      /* Else fall through to handle as normal uschar. */
+ 
+-    case 1:                             /* Normal state */
+-    if (ch == '\n')
+-      {
+-      ch_state = 0;
+-      body_linecount++;
++    case s_normal:			/* Normal state */
++      if (ch == '\r')
++	{
++	ch_state = s_had_cr;
++	continue;			/* Don't write the CR */
++	}
++      if (ch == '\n')			/* Bare LF at end of line */
++	if (strict_crlf)
++	  ch = ' ';			/* replace LF with space */
++	else
++	  {				/* treat as line ending */
++	  ch_state = s_linestart;
++	  body_linecount++;
++	  if (linelength > max_received_linelength)
++	    max_received_linelength = linelength;
++	  linelength = -1;
++	  }
++      break;
++
++    case s_had_cr:			/* After (unwritten) CR */
++      body_linecount++;			/* Any char ends line */
+       if (linelength > max_received_linelength)
+-        max_received_linelength = linelength;
++	max_received_linelength = linelength;
+       linelength = -1;
+-      }
+-    else if (ch == '\r')
+-      {
+-      ch_state = 2;
+-      continue;
+-      }
+-    break;
++      if (ch == '\n')			/* proper CRLF */
++	ch_state = s_linestart;
++      else
++	{
++	message_size++;		/* convert the dropped CR to a stored NL */
++	if (fout && fputc('\n', fout) == EOF) return END_WERROR;
++	cutthrough_data_put_nl();
++	if (ch == '\r')			/* CR; do not write */
++	  continue;
++	ch_state = s_normal;		/* not LF or CR; process as standard */
++	}
++      break;
+ 
+-    case 2:                             /* After (unwritten) CR */
+-    body_linecount++;
+-    if (linelength > max_received_linelength)
+-      max_received_linelength = linelength;
+-    linelength = -1;
+-    if (ch == '\n')
+-      {
+-      ch_state = 0;
+-      }
+-    else
+-      {
+-      message_size++;
+-      if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
+-      cutthrough_data_put_nl();
+-      if (ch != '\r') ch_state = 1; else continue;
+-      }
+-    break;
++    case s_had_nl_dot:			/* After [CR] LF . */
++      if (ch == '\n')			/* [CR] LF . LF */
++	if (strict_crlf)
++	  ch = ' ';			/* replace LF with space */
++	else
++	  return END_DOT;
++      else if (ch == '\r')		/* [CR] LF . CR */
++	{
++	ch_state = s_had_dot_cr;
++	continue;			/* Don't write the CR */
++	}
++      /* The dot was removed on reaching s_had_nl_dot. For a doubled dot, here,
++      reinstate it to cutthrough. The current ch, dot or not, is passed both to
++      cutthrough and to file below. */
++      else if (ch == '.')
++	{
++	uschar c = ch;
++	cutthrough_data_puts(&c, 1);
++	}
++      ch_state = s_normal;
++      break;
+ 
+-    case 3:                             /* After [CR] LF . */
+-    if (ch == '\n')
+-      return END_DOT;
+-    if (ch == '\r')
+-      {
+-      ch_state = 4;
+-      continue;
+-      }
+-    /* The dot was removed at state 3. For a doubled dot, here, reinstate
+-    it to cutthrough. The current ch, dot or not, is passed both to cutthrough
+-    and to file below. */
+-    if (ch == '.')
+-      {
+-      uschar c= ch;
+-      cutthrough_data_puts(&c, 1);
+-      }
+-    ch_state = 1;
+-    break;
++    case s_had_dot_cr:			/* After [CR] LF . CR */
++      if (ch == '\n')
++	return END_DOT;			/* Preferred termination */
+ 
+-    case 4:                             /* After [CR] LF . CR */
+-    if (ch == '\n') return END_DOT;
+-    message_size++;
+-    body_linecount++;
+-    if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
+-    cutthrough_data_put_nl();
+-    if (ch == '\r')
+-      {
+-      ch_state = 2;
+-      continue;
+-      }
+-    ch_state = 1;
+-    break;
++      message_size++;		/* convert the dropped CR to a stored NL */
++      body_linecount++;
++      if (fout && fputc('\n', fout) == EOF) return END_WERROR;
++      cutthrough_data_put_nl();
++      if (ch == '\r')
++	{
++	ch_state = s_had_cr;
++	continue;			/* CR; do not write */
++	}
++      ch_state = s_normal;
++      break;
+     }
+ 
+   /* Add the character to the spool file, unless skipping; then loop for the
+   next. */
+ 
+@@ -1114,11 +1128,11 @@ Returns:     nothing
+ void
+ receive_swallow_smtp(void)
+ {
+ if (message_ended >= END_NOTENDED)
+   message_ended = chunking_state <= CHUNKING_OFFERED
+-     ? read_message_data_smtp(NULL)
++     ? read_message_data_smtp(NULL, FALSE)
+      : read_message_bdat_smtp_wire(NULL);
+ }
+ 
+ 
+ 
+@@ -1899,12 +1913,14 @@ for (;;)
+   LF specially by inserting a white space after it to ensure that the header
+   line is not terminated. */
+ 
+   if (ch == '\n')
+     {
+-    if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = FALSE;
+-      else if (first_line_ended_crlf) receive_ungetc(' ');
++    if (first_line_ended_crlf == TRUE_UNSET)
++      first_line_ended_crlf = FALSE;
++    else if (first_line_ended_crlf)
++      receive_ungetc(' ');
+     goto EOL;
+     }
+ 
+   /* This is not the end of the line. If this is SMTP input and this is
+   the first character in the line and it is a "." character, ignore it.
+@@ -1915,12 +1931,17 @@ for (;;)
+   prevent further reading), and break out of the loop, having freed the
+   empty header, and set next = NULL to indicate no data line. */
+ 
+   if (ptr == 0 && ch == '.' && f.dot_ends)
+     {
++    /* leading dot while in headers-read mode */
+     ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
+-    if (ch == '\r')
++    if (ch == '\n' && first_line_ended_crlf == TRUE /* and not TRUE_UNSET */ )
++    		/* dot, LF  but we are in CRLF mode.  Attack? */
++      ch = ' ';	/* replace the LF with a space */
++
++    else if (ch == '\r')
+       {
+       ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
+       if (ch != '\n')
+         {
+         receive_ungetc(ch);
+@@ -1952,11 +1973,12 @@ for (;;)
+   if (ch == '\r')
+     {
+     ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
+     if (ch == '\n')
+       {
+-      if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = TRUE;
++      if (first_line_ended_crlf == TRUE_UNSET)
++	first_line_ended_crlf = TRUE;
+       goto EOL;
+       }
+ 
+     /* Otherwise, put back the character after CR, and turn the bare CR
+     into LF SP. */
+@@ -3084,11 +3106,11 @@ if (cutthrough.cctx.sock >= 0 && cutthro
+   (void) cutthrough_headers_send();
+   }
+ 
+ 
+ /* Open a new spool file for the data portion of the message. We need
+-to access it both via a file descriptor and a stream. Try to make the
++to access it both via a file descriptor and a stdio stream. Try to make the
+ directory if it isn't there. */
+ 
+ spool_name = spool_fname(US"input", message_subdir, message_id, US"-D");
+ DEBUG(D_receive) debug_printf("Data file name: %s\n", spool_name);
+ 
+@@ -3153,11 +3175,11 @@ message id or "next" line. */
+ if (!ferror(spool_data_file) && !(receive_feof)() && message_ended != END_DOT)
+   {
+   if (smtp_input)
+     {
+     message_ended = chunking_state <= CHUNKING_OFFERED
+-      ? read_message_data_smtp(spool_data_file)
++      ? read_message_data_smtp(spool_data_file, first_line_ended_crlf)
+       : spool_wireformat
+       ? read_message_bdat_smtp_wire(spool_data_file)
+       : read_message_bdat_smtp(spool_data_file);
+     receive_linecount++;                /* The terminating "." line */
+     }
+--- a/src/smtp_in.c
++++ b/src/smtp_in.c
+@@ -5393,16 +5393,16 @@ while (done <= 0)
+ 	  }
+ 	break;
+ 	}
+ 
+       if (chunking_state > CHUNKING_OFFERED)
+-	rc = OK;			/* No predata ACL or go-ahead output for BDAT */
++	rc = OK;	/* There is no predata ACL or go-ahead output for BDAT */
+       else
+ 	{
+-	/* If there is an ACL, re-check the synchronization afterwards, since the
+-	ACL may have delayed.  To handle cutthrough delivery enforce a dummy call
+-	to get the DATA command sent. */
++	/* If there is a predata-ACL, re-check the synchronization afterwards,
++	since the ACL may have delayed.  To handle cutthrough delivery enforce a
++	dummy call to get the DATA command sent. */
+ 
+ 	if (acl_smtp_predata == NULL && cutthrough.cctx.sock < 0)
+ 	  rc = OK;
+ 	else
+ 	  {
+--- a/doc/spec.txt
++++ b/doc/spec.txt
+@@ -32960,12 +32960,10 @@ MTA within an operating system would use
+ has shown that this is not the case; for example, there are Unix applications
+ that use CRLF in this circumstance. For this reason, and for compatibility with
+ other MTAs, the way Exim handles line endings for all messages is now as
+ follows:
+ 
+-  * LF not preceded by CR is treated as a line ending.
+-
+   * CR is treated as a line ending; if it is immediately followed by LF, the LF
+     is ignored.
+ 
+   * The sequence "CR, dot, CR" does not terminate an incoming SMTP message, nor
+     a local message in the state where a line containing only a dot is a
+@@ -32976,11 +32974,14 @@ follows:
+     behind this is that bare CRs in header lines are most likely either to be
+     mistakes, or people trying to play silly games.
+ 
+   * If the first header line received in a message ends with CRLF, a subsequent
+     bare LF in a header line is treated in the same way as a bare CR in a
+-    header line.
++    header line and a bare LF in a body line is replaced with a space.
++
++  * If the first header line received in a message does not end with CRLF, a
++    subsequent LF not preceded by CR is treated as a line ending.
+ 
+ 
+ 48.3 Unqualified addresses
+ --------------------------
+ 
diff -Nru exim4-4.94.2/debian/patches/series exim4-4.94.2/debian/patches/series
--- exim4-4.94.2/debian/patches/series	2023-09-29 23:23:04.000000000 +0200
+++ exim4-4.94.2/debian/patches/series	2024-01-01 18:00:05.000000000 +0100
@@ -42,4 +42,5 @@
 75_31-Auths-fix-possible-OOB-write-in-SPA-authenticator.-B.patch
 75_32-Auths-fix-possible-OOB-read-in-SPA-authenticator.-Bu.patch
 78_01-Command-line-option-for-no-notifier-socket.-Bug-2616.patch
+79_CVE-2023-51766_4.97.1-release.diff
 90_localscan_dlopen.dpatch

Attachment: signature.asc
Description: PGP signature

Reply via email to