</para>
<para>
This means that you will not need any special configuration if
- you want to use TLS for outgoing mail. However, if your
+ you want to use TLS for outgoing mail. However, to enforce
+ TLS and successful certificate verification, a few things
+ need to be configured.
+ </para>
+ <para>
+ To enforce TLS and prevent fallback to unencrypted
+ connections, ensure that hosts_require_tls = * is in effect on
+ the respective transport. For the remote_smtp_smarthost
+ transport, this setting can be controlled via the
+ REMOTE_SMTP_SMARTHOST_HOSTS_REQUIRE_TLS macro.
+ </para>
+ <para>
+ The certificate presented by the remote host is checked
+ against the system CA certificate store
+ (<filename>/etc/ssl/certs/</filename>) and the verification
+ result is logged (CV=...). However successful certificate
+ verification is <emphasis>not enforced</emphasis> by default.
+ This can be changed by setting tls_verify_hosts = * on the
+ respective transport.
+ </para>
+ <para>
+ Another possibility would be to use DANE for certificate
+ verification. This requires support on the server side and
+ a resolver with DNSSEC support on the client side.
+ </para>
+ <para>
+ If your
server setup mandates the use of client certificates, you
need to amend your remote_smtp and/or remote_smtp_smarthost
transports with a tls_certificate option. This is not
commonly needed.
</para>
- <para>
- The certificate
- presented by the remote host is not checked unless you
- specify a tls_verify_certificate option on the transport.
- </para>
<para id="tls_client_certicate">
To make exim send a TLS certificate to the remote host set
REMOTE_SMTP_TLS_CERTIFICATE/REMOTE_SMTP_PRIVATEKEY or for
+exim4 (4.92-8+deb10u6) buster-security; urgency=high
+
+ * Fix several security vulnerabilities reported by Qualys and add related
+ robustness improvements. (Originally fixed in upstream release 4.94.3 and
+ in upstream GIT branch exim-4.92.3+fixes. (Special thanks to Heiko)
+ + CVE-2020-28025: Heap out-of-bounds read in pdkim_finish_bodyhash()
+ + CVE-2020-28018: Use-after-free in tls-openssl.c
+ + CVE-2020-28023: Out-of-bounds read in smtp_setup_msg()
+ + CVE-2020-28010: Heap out-of-bounds write in main()
+ + CVE-2020-28011: Heap buffer overflow in queue_run()
+ + CVE-2020-28013: Heap buffer overflow in parse_fix_phrase()
+ + CVE-2020-28017: Integer overflow in receive_add_recipient()
+ + CVE-2020-28022: Heap out-of-bounds read and write in extract_option()
+ + CVE-2020-28026: Line truncation and injection in spool_read_header()
+ + CVE-2020-28015 and CVE-2020-28021: New-line injection into spool header
+ file.
+ + CVE-2020-28009: Integer overflow in get_stdinput()
+ + CVE-2020-28024: Heap buffer underflow in smtp_ungetc()
+ + CVE-2020-28012: Missing close-on-exec flag for privileged pipe
+ + CVE-2020-28019: Failure to reset function pointer after BDAT error
+ + CVE-2020-28007: Link attack in Exim's log directory
+ + CVE-2020-28008: Assorted attacks in Exim's spool directory
+ + CVE-2020-28014, CVE-2021-27216: Arbitrary PID file creation, clobbering,
+ and deletion.
+
+ -- Andreas Metzler <ametzler@debian.org> Sat, 01 May 2021 11:42:39 +0200
+
+exim4 (4.92-8+deb10u5) buster; urgency=medium
+
+ * Fix use of concurrent TLS connections under GnuTLS:
+ 80_01-GnuTLS-fix-hanging-callout-connections.patch
+ 80_02-GnuTLS-tls_write-wait-after-uncorking-the-session.patch
+ 80_03-GnuTLS-Do-not-care-about-corked-data-when-uncorking.patch
+ (Thanks, Heiko Schlittermann for the backport)
+ * Pull 82_TLS-use-RFC-6125-rules-for-certifucate-name-checks-w.patch from
+ upstream git (already included in 4.94), on TLS connections to a CNAME
+ verify the certificate against the original CNAME instead of against
+ the A record. Closes: #985243
+ * In README.Debian explicitly document the limitation/extent of server
+ certificate checking (authenticity not enforced) in the default
+ configuration (Thanks, Jö Fahlke). This Closes: #985244 (improved
+ documentation and Closes: #985344 (Yes, without required cert
+ checking MitM attacks are possible, but for a stable update documenting
+ this is the best compromise.)
+
+ -- Andreas Metzler <ametzler@debian.org> Thu, 18 Mar 2021 09:10:15 +0100
+
exim4 (4.92-8+deb10u4) buster-security; urgency=high
* Fix authentication bypass in SPA authenticator due to out-of-bound buffer
--- /dev/null
+From 97c5e07c220b55d1c506a1798c9ce3ae3105adea Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Thu, 13 Feb 2020 16:45:38 +0000
+Subject: [PATCH 4/6] GnuTLS: fix hanging callout connections
+
+Broken-by: 925ac8e4f1
+(cherry picked from commit bd95ffc2ba87fbd3c752df17bc8fd9c01586d45a)
+---
+ doc/ChangeLog | 81 ++++---------------------------------------
+ src/tls-gnu.c | 24 +++++++------
+ 2 files changed, 20 insertions(+), 85 deletions(-)
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -67,6 +67,11 @@ JH/41 Bug 2571: Fix SPA authenticator.
+ being used. A malicious client could thus cause an out-of-bounds read and
+ possibly gain authentication. Fix by adding the check.
+
++JH/25 Fix use of concurrent TLS connections under GnuTLS. When a callout was
++ done during a receiving connection, and both used TLS, global info was
++ used rather than per-connection info for tracking the state of data
++ queued for transmission. This could result in a connection hang.
++
+
+ Exim version 4.92
+ -----------------
+--- a/src/tls-gnu.c
++++ b/src/tls-gnu.c
+@@ -124,10 +124,17 @@ typedef struct exim_gnutls_state {
+ enum peer_verify_requirement verify_requirement;
+ int fd_in;
+ int fd_out;
+- BOOL peer_cert_verified;
+- BOOL peer_dane_verified;
+- BOOL trigger_sni_changes;
+- BOOL have_set_peerdn;
++
++ BOOL peer_cert_verified:1;
++ BOOL peer_dane_verified:1;
++ BOOL trigger_sni_changes:1;
++ BOOL have_set_peerdn:1;
++ BOOL xfer_eof:1; /*XXX never gets set! */
++ BOOL xfer_error:1;
++#ifdef SUPPORT_CORK
++ BOOL corked:1;
++#endif
++
+ const struct host_item *host; /* NULL if server */
+ gnutls_x509_crt_t peercert;
+ uschar *peerdn;
+@@ -160,8 +167,6 @@ typedef struct exim_gnutls_state {
+ uschar *xfer_buffer;
+ int xfer_buffer_lwm;
+ int xfer_buffer_hwm;
+- BOOL xfer_eof; /*XXX never gets set! */
+- BOOL xfer_error;
+ } exim_gnutls_state_st;
+
+ static const exim_gnutls_state_st exim_gnutls_state_init = {
+@@ -2790,9 +2795,8 @@ ssize_t outbytes;
+ size_t left = len;
+ exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server;
+ #ifdef SUPPORT_CORK
+-static BOOL corked = FALSE;
+
+-if (more && !corked) gnutls_record_cork(state->session);
++if (more && !state->corked) gnutls_record_cork(state->session);
+ #endif
+
+ DEBUG(D_tls) debug_printf("%s(%p, " SIZE_T_FMT "%s)\n", __FUNCTION__,
+@@ -2833,10 +2837,10 @@ if (len > INT_MAX)
+ }
+
+ #ifdef SUPPORT_CORK
+-if (more != corked)
++if (more != state->corked)
+ {
+ if (!more) (void) gnutls_record_uncork(state->session, 0);
+- corked = more;
++ state->corked = more;
+ }
+ #endif
+
--- /dev/null
+From 783cb0301d9ceef2748956c3f91762275b7b45e5 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Tue, 18 Feb 2020 18:59:49 +0100
+Subject: [PATCH 5/6] GnuTLS: tls_write(): wait after uncorking the session
+
+(cherry picked from commit 8f9adfd36222d4e9e730734e00dffe874073e5b4)
+---
+ src/tls-gnu.c | 34 ++++++++++++++++++++++++++++------
+ 1 file changed, 28 insertions(+), 6 deletions(-)
+
+diff --git a/src/tls-gnu.c b/src/tls-gnu.c
+index 822ad89c6..94a718673 100644
+--- a/src/tls-gnu.c
++++ b/src/tls-gnu.c
+@@ -2835,9 +2835,14 @@ tls_write(void * ct_ctx, const uschar * buff, size_t len, BOOL more)
+ ssize_t outbytes;
+ size_t left = len;
+ exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server;
+-#ifdef SUPPORT_CORK
+
+-if (more && !state->corked) gnutls_record_cork(state->session);
++#ifdef SUPPORT_CORK
++if (more && !state->corked)
++ {
++ DEBUG(D_tls) debug_printf("gnutls_record_cork(session=%p)\n", state->session);
++ gnutls_record_cork(state->session);
++ state->corked = TRUE;
++ }
+ #endif
+
+ DEBUG(D_tls) debug_printf("%s(%p, " SIZE_T_FMT "%s)\n", __FUNCTION__,
+@@ -2853,6 +2858,7 @@ while (left > 0)
+ while (outbytes == GNUTLS_E_AGAIN);
+
+ DEBUG(D_tls) debug_printf("outbytes=" SSIZE_T_FMT "\n", outbytes);
++
+ if (outbytes < 0)
+ {
+ DEBUG(D_tls) debug_printf("%s: gnutls_record_send err\n", __FUNCTION__);
+@@ -2878,10 +2884,26 @@ if (len > INT_MAX)
+ }
+
+ #ifdef SUPPORT_CORK
+-if (more != state->corked)
+- {
+- if (!more) (void) gnutls_record_uncork(state->session, 0);
+- state->corked = more;
++if (!more && state->corked)
++ {
++ DEBUG(D_tls) debug_printf("gnutls_record_uncork(session=%p)\n", state->session);
++ do {
++ do
++ /* We can't use GNUTLS_RECORD_WAIT here, as it retries on
++ GNUTLS_E_AGAIN || GNUTLS_E_INTR, which would break our timeout set by alarm().
++ The GNUTLS_E_AGAIN should not happen ever, as our sockets are blocking anyway.
++ But who knows. (That all relies on the fact that GNUTLS_E_INTR and GNUTLS_E_AGAIN
++ match the EINTR and EAGAIN errno values.) */
++ outbytes = gnutls_record_uncork(state->session, 0);
++ while (outbytes == GNUTLS_E_AGAIN);
++
++ if (outbytes < 0)
++ {
++ record_io_error(state, len, US"uncork", NULL);
++ return -1;
++ }
++ } while (gnutls_record_check_corked(state->session) > 0);
++ state->corked = FALSE;
+ }
+ #endif
+
+--
+2.28.0
+
--- /dev/null
+From 3afb07f2c63fb6dc3983b28e7cdaf11fceb741d1 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Mon, 2 Mar 2020 22:56:32 +0100
+Subject: [PATCH 6/6] GnuTLS: Do not care about corked data when uncorking
+
+(cherry picked from commit d8d7e3a4162b52382daf8319f221c085c76c5b8f)
+---
+ src/tls-gnu.c | 31 +++++++++++++++----------------
+ 1 file changed, 15 insertions(+), 16 deletions(-)
+
+diff --git a/src/tls-gnu.c b/src/tls-gnu.c
+index 94a718673..2091e44db 100644
+--- a/src/tls-gnu.c
++++ b/src/tls-gnu.c
+@@ -2887,22 +2887,21 @@ if (len > INT_MAX)
+ if (!more && state->corked)
+ {
+ DEBUG(D_tls) debug_printf("gnutls_record_uncork(session=%p)\n", state->session);
+- do {
+- do
+- /* We can't use GNUTLS_RECORD_WAIT here, as it retries on
+- GNUTLS_E_AGAIN || GNUTLS_E_INTR, which would break our timeout set by alarm().
+- The GNUTLS_E_AGAIN should not happen ever, as our sockets are blocking anyway.
+- But who knows. (That all relies on the fact that GNUTLS_E_INTR and GNUTLS_E_AGAIN
+- match the EINTR and EAGAIN errno values.) */
+- outbytes = gnutls_record_uncork(state->session, 0);
+- while (outbytes == GNUTLS_E_AGAIN);
+-
+- if (outbytes < 0)
+- {
+- record_io_error(state, len, US"uncork", NULL);
+- return -1;
+- }
+- } while (gnutls_record_check_corked(state->session) > 0);
++ do
++ /* We can't use GNUTLS_RECORD_WAIT here, as it retries on
++ GNUTLS_E_AGAIN || GNUTLS_E_INTR, which would break our timeout set by alarm().
++ The GNUTLS_E_AGAIN should not happen ever, as our sockets are blocking anyway.
++ But who knows. (That all relies on the fact that GNUTLS_E_INTR and GNUTLS_E_AGAIN
++ match the EINTR and EAGAIN errno values.) */
++ outbytes = gnutls_record_uncork(state->session, 0);
++ while (outbytes == GNUTLS_E_AGAIN);
++
++ if (outbytes < 0)
++ {
++ record_io_error(state, len, US"uncork", NULL);
++ return -1;
++ }
++
+ state->corked = FALSE;
+ }
+ #endif
+--
+2.28.0
+
--- /dev/null
+Description: TLS: use RFC 6125 rules for certificate name checks when
+ CNAMES are present. Bug 2594
+Origin: upstream https://git.exim.org/exim.git/commit/0851a3bbf4667081d47f5d85b6b3a5cb33cbdba6
+Bug: https://bugs.exim.org/show_bug.cgi?id=2594
+Forwarded: not-needed
+Last-Update: 2021-03-02
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -41,10 +41,15 @@ JH/10 OpenSSL: Fix aggregation of messag
+
+ JH/11 Harden plaintext authenticator against a badly misconfigured client-send
+ string. Previously it was possible to cause undefined behaviour in a
+ library routine (usually a crash). Found by "zerons".
+
++JH/06 Bug 2594: Change the name used for certificate name checks in the smtp
++ transport. Previously it was the name on the DNS A-record; use instead
++ the head of the CNAME chain leading there (if there is one). This seems
++ to align better with RFC 6125.
++
+
+ JH/18 GnuTLS: fix $tls_out_ocsp under hosts_request_ocsp. Previously the
+ verification result was not updated unless hosts_require_ocsp applied.
+
+ JH/20 Bug 2389: fix server advertising of usable certificates, under GnuTLS in
+--- a/src/host.c
++++ b/src/host.c
+@@ -1966,10 +1966,17 @@ host_item *last = NULL;
+ BOOL temp_error = FALSE;
+ #if HAVE_IPV6
+ int af;
+ #endif
+
++#ifndef DISABLE_TLS
++/* Copy the host name at this point to the value which is used for
++TLS certificate name checking, before anything modifies it. */
++
++host->certname = host->name;
++#endif
++
+ /* Make sure DNS options are set as required. This appears to be necessary in
+ some circumstances when the get..byname() function actually calls the DNS. */
+
+ dns_init((flags & HOST_FIND_QUALIFY_SINGLE) != 0,
+ (flags & HOST_FIND_SEARCH_PARENTS) != 0,
+@@ -2132,10 +2139,13 @@ for (i = 1; i <= times;
+
+ else
+ {
+ host_item *next = store_get(sizeof(host_item));
+ next->name = host->name;
++#ifndef DISABLE_TLS
++ next->certname = host->certname;
++#endif
+ next->mx = host->mx;
+ next->address = text_address;
+ next->port = PORT_NONE;
+ next->status = hstatus_unknown;
+ next->why = hwhy_unknown;
+@@ -2150,16 +2160,16 @@ for (i = 1; i <= times;
+
+ /* If no hosts were found, the address field in the original host block will be
+ NULL. If temp_error is set, at least one of the lookups gave a temporary error,
+ so we pass that back. */
+
+-if (host->address == NULL)
++if (!host->address)
+ {
+ uschar *msg =
+ #ifndef STAND_ALONE
+- (message_id[0] == 0 && smtp_in != NULL)?
+- string_sprintf("no IP address found for host %s (during %s)", host->name,
++ message_id[0] == 0 && smtp_in
++ ? string_sprintf("no IP address found for host %s (during %s)", host->name,
+ smtp_get_connection_info()) :
+ #endif
+ string_sprintf("no IP address found for host %s", host->name);
+
+ HDEBUG(D_host_lookup) debug_printf("%s\n", msg);
+@@ -2277,10 +2287,17 @@ dns_record *rr;
+ host_item *thishostlast = NULL; /* Indicates not yet filled in anything */
+ BOOL v6_find_again = FALSE;
+ BOOL dnssec_fail = FALSE;
+ int i;
+
++#ifndef DISABLE_TLS
++/* Copy the host name at this point to the value which is used for
++TLS certificate name checking, before any CNAME-following modifies it. */
++
++host->certname = host->name;
++#endif
++
+ /* If allow_ip is set, a name which is an IP address returns that value
+ as its address. This is used for MX records when allow_mx_to_ip is set, for
+ those sites that feel they have to flaunt the RFC rules. */
+
+ if (allow_ip && string_is_ip_address(host->name, NULL) != 0)
+--- a/src/structs.h
++++ b/src/structs.h
+@@ -77,18 +77,21 @@ host addresses is done using this struct
+
+ typedef enum {DS_UNK=-1, DS_NO, DS_YES} dnssec_status_t;
+
+ typedef struct host_item {
+ struct host_item *next;
+- const uschar *name; /* Host name */
+- const uschar *address; /* IP address in text form */
+- int port; /* port value in host order (if SRV lookup) */
+- int mx; /* MX value if found via MX records */
+- int sort_key; /* MX*1000 plus random "fraction" */
+- int status; /* Usable, unusable, or unknown */
+- int why; /* Why host is unusable */
+- int last_try; /* Time of last try if known */
++ const uschar *name; /* Host name */
++#ifndef DISABLE_TLS
++ const uschar *certname; /* Name used for certificate checks */
++#endif
++ const uschar *address; /* IP address in text form */
++ int port; /* port value in host order (if SRV lookup) */
++ int mx; /* MX value if found via MX records */
++ int sort_key; /* MX*1000 plus random "fraction" */
++ int status; /* Usable, unusable, or unknown */
++ int why; /* Why host is unusable */
++ int last_try; /* Time of last try if known */
+ dnssec_status_t dnssec;
+ } host_item;
+
+ /* Chain of rewrite rules, read from the rewrite config, or parsed from the
+ rewrite_headers field of a transport. */
+--- a/src/tls-gnu.c
++++ b/src/tls-gnu.c
+@@ -2191,13 +2191,13 @@ tls_client_setup_hostname_checks(host_it
+ {
+ if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK)
+ {
+ state->exp_tls_verify_cert_hostnames =
+ #ifdef SUPPORT_I18N
+- string_domain_utf8_to_alabel(host->name, NULL);
++ string_domain_utf8_to_alabel(host->certname, NULL);
+ #else
+- host->name;
++ host->certname;
+ #endif
+ DEBUG(D_tls)
+ debug_printf("TLS: server cert verification includes hostname: \"%s\".\n",
+ state->exp_tls_verify_cert_hostnames);
+ }
+--- a/src/tls-openssl.c
++++ b/src/tls-openssl.c
+@@ -309,18 +309,18 @@ typedef struct tls_ext_ctx_cb {
+ X509_STORE *verify_store; /* non-null if status requested */
+ BOOL verify_required;
+ } client;
+ } u_ocsp;
+ #endif
+- uschar *dhparam;
++ uschar * dhparam;
+ /* these are cached from first expand */
+- uschar *server_cipher_list;
++ uschar * server_cipher_list;
+ /* only passed down to tls_error: */
+- host_item *host;
++ host_item * host;
+ const uschar * verify_cert_hostnames;
+ #ifndef DISABLE_EVENT
+- uschar * event_action;
++ uschar * event_action;
+ #endif
+ } tls_ext_ctx_cb;
+
+ /* should figure out a cleanup of API to handle state preserved per
+ implementation, for various reasons, which can be void * in the APIs.
+@@ -2359,13 +2359,13 @@ if ((rc = setup_certs(ctx, ob->tls_verif
+
+ if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK)
+ {
+ cbinfo->verify_cert_hostnames =
+ #ifdef SUPPORT_I18N
+- string_domain_utf8_to_alabel(host->name, NULL);
++ string_domain_utf8_to_alabel(host->certname, NULL);
+ #else
+- host->name;
++ host->certname;
+ #endif
+ DEBUG(D_tls) debug_printf("Cert hostname to check: \"%s\"\n",
+ cbinfo->verify_cert_hostnames);
+ }
+ return OK;
--- /dev/null
+From 9db12ffa00aa1dcbe60eec543307f405e35cfe15 Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+Date: Sun, 21 Feb 2021 18:54:16 -0800
+Subject: [PATCH 01/29] CVE-2020-28025: Heap out-of-bounds read in
+ pdkim_finish_bodyhash()
+
+---
+ src/pdkim/pdkim.c | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/src/pdkim/pdkim.c b/src/pdkim/pdkim.c
+index 594af03c5..e203311da 100644
+--- a/src/pdkim/pdkim.c
++++ b/src/pdkim/pdkim.c
+@@ -825,7 +825,7 @@ for (sig = ctx->sig; sig; sig = sig->next)
+ /* VERIFICATION --------------------------------------------------------- */
+ /* Be careful that the header sig included a bodyash */
+
+- if ( sig->bodyhash.data
++ if (sig->bodyhash.data && sig->bodyhash.len == b->bh.len
+ && memcmp(b->bh.data, sig->bodyhash.data, b->bh.len) == 0)
+ {
+ DEBUG(D_acl) debug_printf("PDKIM [%s] Body hash compared OK\n", sig->domain);
+@@ -1524,7 +1524,7 @@ for (sig = ctx->sig; sig; sig = sig->next)
+ do this hash incrementally.
+ We don't need the hash we're calculating here for the GnuTLS and OpenSSL
+ cases of RSA signing, since those library routines can do hash-and-sign.
+-
++
+ Some time in the future we could easily avoid doing the hash here for those
+ cases (which will be common for a long while. We could also change from
+ the current copy-all-the-headers-into-one-block, then call the hash-and-sign
+@@ -1779,7 +1779,7 @@ for (sig = ctx->sig; sig; sig = sig->next)
+ );
+ goto NEXT_VERIFY;
+ }
+-
++
+ /* Make sure sig uses supported DKIM version (only v1) */
+ if (sig->version != 1)
+ {
+--
+2.30.2
+
--- /dev/null
+From 86cafc842feb6223476568921c2d3e06c706cc31 Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+Date: Sun, 21 Feb 2021 19:05:56 -0800
+Subject: [PATCH 02/29] CVE-2020-28018: Use-after-free in tls-openssl.c
+
+---
+ src/tls-openssl.c | 4 ----
+ 1 file changed, 4 deletions(-)
+
+diff --git a/src/tls-openssl.c b/src/tls-openssl.c
+index e751edd9a..2a8d4cabd 100644
+--- a/src/tls-openssl.c
++++ b/src/tls-openssl.c
+@@ -2910,16 +2910,12 @@ a store reset there, so use POOL_PERM. */
+
+ if (!ct_ctx && (more || corked))
+ {
+-#ifdef EXPERIMENTAL_PIPE_CONNECT
+ int save_pool = store_pool;
+ store_pool = POOL_PERM;
+-#endif
+
+ corked = string_catn(corked, buff, len);
+
+-#ifdef EXPERIMENTAL_PIPE_CONNECT
+ store_pool = save_pool;
+-#endif
+
+ if (more)
+ {
+--
+2.30.2
+
--- /dev/null
+From 4cfadd994e5ab6e57cc43164d1e3198bb4faedbb Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+Date: Sun, 21 Feb 2021 19:11:55 -0800
+Subject: [PATCH 03/29] CVE-2020-28023: Out-of-bounds read in smtp_setup_msg()
+
+Extracted from Jeremy Harris's commit afaf5a50.
+---
+ src/acl.c | 3 ++-
+ src/macros.h | 1 +
+ src/smtp_in.c | 4 ++--
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/src/acl.c b/src/acl.c
+index f3b860e4a..49f6fe79c 100644
+--- a/src/acl.c
++++ b/src/acl.c
+@@ -4464,7 +4464,8 @@ switch (where)
+ /* Drop cutthrough conns, and drop heldopen verify conns if
+ the previous was not DATA */
+ {
+- uschar prev = smtp_connection_had[smtp_ch_index-2];
++ uschar prev =
++ smtp_connection_had[SMTP_HBUFF_PREV(SMTP_HBUFF_PREV(smtp_ch_index))];
+ BOOL dropverify = !(prev == SCH_DATA || prev == SCH_BDAT);
+
+ cancel_cutthrough_connection(dropverify, US"quit or conndrop");
+diff --git a/src/macros.h b/src/macros.h
+index 0f93543ce..b3896b736 100644
+--- a/src/macros.h
++++ b/src/macros.h
+@@ -154,6 +154,7 @@ enough to hold all the headers from a normal kind of message. */
+ /* The size of the circular buffer that remembers recent SMTP commands */
+
+ #define SMTP_HBUFF_SIZE 20
++#define SMTP_HBUFF_PREV(n) ((n) ? (n)-1 : SMTP_HBUFF_SIZE-1)
+
+ /* The initial size of a big buffer for use in various places. It gets put
+ into big_buffer_size and in some circumstances increased. It should be at least
+diff --git a/src/smtp_in.c b/src/smtp_in.c
+index 86f87eae1..4265d77b7 100644
+--- a/src/smtp_in.c
++++ b/src/smtp_in.c
+@@ -5322,10 +5322,10 @@ while (done <= 0)
+ }
+ if (f.smtp_in_pipelining_advertised && last_was_rcpt)
+ smtp_printf("503 Valid RCPT command must precede %s\r\n", FALSE,
+- smtp_names[smtp_connection_had[smtp_ch_index-1]]);
++ smtp_names[smtp_connection_had[SMTP_HBUFF_PREV(smtp_ch_index)]]);
+ else
+ done = synprot_error(L_smtp_protocol_error, 503, NULL,
+- smtp_connection_had[smtp_ch_index-1] == SCH_DATA
++ smtp_connection_had[SMTP_HBUFF_PREV(smtp_ch_index)] == SCH_DATA
+ ? US"valid RCPT command must precede DATA"
+ : US"valid RCPT command must precede BDAT");
+
+--
+2.30.2
+
--- /dev/null
+From 5987d0dfe88ee6081b72857bc8085c7d2afd53a3 Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+Date: Sun, 21 Feb 2021 19:17:32 -0800
+Subject: [PATCH 04/29] CVE-2020-28010: Heap out-of-bounds write in main()
+
+Based on Phil Pennock's commit 0f57feb4.
+---
+ src/exim.c | 11 ++++++-----
+ 1 file changed, 6 insertions(+), 5 deletions(-)
+
+diff --git a/src/exim.c b/src/exim.c
+index 83b5ef51f..a7dc48c4e 100644
+--- a/src/exim.c
++++ b/src/exim.c
+@@ -3664,6 +3664,9 @@ during readconf_main() some expansion takes place already. */
+ /* Store the initial cwd before we change directories. Can be NULL if the
+ dir has already been unlinked. */
+ initial_cwd = os_getcwd(NULL, 0);
++if (initial_cwd && strlen(CCS initial_cwd) >= BIG_BUFFER_SIZE) {
++ exim_fail("exim: initial cwd is far too long\n");
++}
+
+ /* checking:
+ -be[m] expansion test -
+@@ -3950,11 +3953,9 @@ if ( (debug_selector & D_any || LOGGING(arguments))
+ p += 13;
+ else
+ {
+- Ustrncpy(p + 4, initial_cwd, big_buffer_size-5);
+- p += 4 + Ustrlen(initial_cwd);
+- /* in case p is near the end and we don't provide enough space for
+- * string_format to be willing to write. */
+- *p = '\0';
++ p += 4;
++ snprintf(CS p, big_buffer_size - (p - big_buffer), "%s", CCS initial_cwd);
++ p += strlen(CCS p);
+ }
+
+ (void)string_format(p, big_buffer_size - (p - big_buffer), " %d args:", argc);
+--
+2.30.2
+
--- /dev/null
+From 9970ba4d8b9477d98c722221b6b7b97f03104b9f Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+Date: Sun, 21 Feb 2021 19:22:33 -0800
+Subject: [PATCH 05/29] CVE-2020-28011: Heap buffer overflow in queue_run()
+
+---
+ src/queue.c | 14 ++++++++++----
+ 1 file changed, 10 insertions(+), 4 deletions(-)
+
+diff --git a/src/queue.c b/src/queue.c
+index 92109ef92..41af5b85e 100644
+--- a/src/queue.c
++++ b/src/queue.c
+@@ -416,12 +416,18 @@ if (!recurse)
+ p += sprintf(CS p, " -q%s", extras);
+
+ if (deliver_selectstring)
+- p += sprintf(CS p, " -R%s %s", f.deliver_selectstring_regex? "r" : "",
+- deliver_selectstring);
++ {
++ snprintf(CS p, big_buffer_size - (p - big_buffer), " -R%s %s",
++ f.deliver_selectstring_regex? "r" : "", deliver_selectstring);
++ p += strlen(CCS p);
++ }
+
+ if (deliver_selectstring_sender)
+- p += sprintf(CS p, " -S%s %s", f.deliver_selectstring_sender_regex? "r" : "",
+- deliver_selectstring_sender);
++ {
++ snprintf(CS p, big_buffer_size - (p - big_buffer), " -S%s %s",
++ f.deliver_selectstring_sender_regex? "r" : "", deliver_selectstring_sender);
++ p += strlen(CCS p);
++ }
+
+ log_detail = string_copy(big_buffer);
+ if (*queue_name)
+--
+2.30.2
+
--- /dev/null
+From 0f6c3d3f7efb5d66dabf69c36e06912d89ff96fc Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+Date: Sun, 21 Feb 2021 19:28:28 -0800
+Subject: [PATCH 06/29] CVE-2020-28013: Heap buffer overflow in
+ parse_fix_phrase()
+
+Based on Phil Pennock's commit 8a50c88a.
+---
+ src/parse.c | 9 ++++++---
+ 1 file changed, 6 insertions(+), 3 deletions(-)
+
+diff --git a/src/parse.c b/src/parse.c
+index 4b0efa0e1..e1e2e7358 100644
+--- a/src/parse.c
++++ b/src/parse.c
+@@ -1149,9 +1149,12 @@ while (s < end)
+ {
+ if (ss >= end) ss--;
+ *t++ = '(';
+- Ustrncpy(t, s, ss-s);
+- t += ss-s;
+- s = ss;
++ if (ss > s)
++ {
++ Ustrncpy(t, s, ss-s);
++ t += ss-s;
++ s = ss;
++ }
+ }
+ }
+
+--
+2.30.2
+
--- /dev/null
+From 5fd6af5815401dc60e8fe4309258911aa41d3013 Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+Date: Sun, 21 Feb 2021 19:40:21 -0800
+Subject: [PATCH 07/29] Security: Refuse negative and large store allocations
+
+Based on Phil Pennock's commits b34d3046 and e6c1606a.
+---
+ src/store.c | 29 ++++++++++++++++++++++++++++-
+ 1 file changed, 28 insertions(+), 1 deletion(-)
+
+diff --git a/src/store.c b/src/store.c
+index b52799132..a2a80f631 100644
+--- a/src/store.c
++++ b/src/store.c
+@@ -128,6 +128,12 @@ Returns: pointer to store (panic on malloc failure)
+ void *
+ store_get_3(int size, const char *filename, int linenumber)
+ {
++if (size < 0 || size > INT_MAX/2)
++ {
++ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
++ "bad memory allocation requested (%d bytes)",
++ size);
++ }
+ /* Round up the size to a multiple of the alignment. Although this looks a
+ messy statement, because "alignment" is a constant expression, the compiler can
+ do a reasonable job of optimizing, especially if the value of "alignment" is a
+@@ -270,6 +276,13 @@ store_extend_3(void *ptr, int oldsize, int newsize, const char *filename,
+ int inc = newsize - oldsize;
+ int rounded_oldsize = oldsize;
+
++if (oldsize < 0 || newsize < oldsize || newsize >= INT_MAX/2)
++ {
++ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
++ "bad memory extension requested (%d -> %d bytes)",
++ oldsize, newsize);
++ }
++
+ if (rounded_oldsize % alignment != 0)
+ rounded_oldsize += alignment - (rounded_oldsize % alignment);
+
+@@ -508,7 +521,16 @@ store_newblock_3(void * block, int newsize, int len,
+ const char * filename, int linenumber)
+ {
+ BOOL release_ok = store_last_get[store_pool] == block;
+-uschar * newtext = store_get(newsize);
++uschar * newtext;
++
++if (len < 0 || len > newsize)
++ {
++ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
++ "bad memory extension requested (%d -> %d bytes)",
++ len, newsize);
++ }
++
++newtext = store_get(newsize);
+
+ memcpy(newtext, block, len);
+ if (release_ok) store_release_3(block, filename, linenumber);
+@@ -539,6 +561,11 @@ store_malloc_3(int size, const char *filename, int linenumber)
+ {
+ void *yield;
+
++if (size < 0 || size >= INT_MAX/2)
++ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
++ "bad memory allocation requested (%d bytes)",
++ size);
++
+ if (size < 16) size = 16;
+
+ if (!(yield = malloc((size_t)size)))
+--
+2.30.2
+
--- /dev/null
+From b5052a65ed1ba81269ac5a03b4505aa9d55ce084 Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+Date: Sun, 21 Feb 2021 19:46:55 -0800
+Subject: [PATCH 08/29] CVE-2020-28017: Integer overflow in
+ receive_add_recipient()
+
+Based on Phil Pennock's commit e3b441f7.
+---
+ src/receive.c | 10 ++++++++--
+ 1 file changed, 8 insertions(+), 2 deletions(-)
+
+diff --git a/src/receive.c b/src/receive.c
+index a0467e8c8..227ace084 100644
+--- a/src/receive.c
++++ b/src/receive.c
+@@ -488,6 +488,12 @@ if (recipients_count >= recipients_list_max)
+ {
+ recipient_item *oldlist = recipients_list;
+ int oldmax = recipients_list_max;
++
++ const int safe_recipients_limit = INT_MAX / 2 / sizeof(recipient_item);
++ if (recipients_list_max < 0 || recipients_list_max >= safe_recipients_limit)
++ {
++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Too many recipients: %d", recipients_list_max);
++ }
+ recipients_list_max = recipients_list_max ? 2*recipients_list_max : 50;
+ recipients_list = store_get(recipients_list_max * sizeof(recipient_item));
+ if (oldlist != NULL)
+@@ -4070,7 +4076,7 @@ if (message_logs && !blackholed_by)
+ {
+ int fd;
+ uschar * m_name = spool_fname(US"msglog", message_subdir, message_id, US"");
+-
++
+ if ( (fd = Uopen(m_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE)) < 0
+ && errno == ENOENT
+ )
+@@ -4229,7 +4235,7 @@ if(!smtp_reply)
+ if (f.deliver_freeze) log_write(0, LOG_MAIN, "frozen by %s", frozen_by);
+ if (f.queue_only_policy) log_write(L_delay_delivery, LOG_MAIN,
+ "no immediate delivery: queued%s%s by %s",
+- *queue_name ? " in " : "", *queue_name ? CS queue_name : "",
++ *queue_name ? " in " : "", *queue_name ? CS queue_name : "",
+ queued_by);
+ }
+ f.receive_call_bombout = FALSE;
+--
+2.30.2
+
--- /dev/null
+From f46455c848def70d686d7b164df75b27f8dae04d Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+Date: Sun, 21 Feb 2021 19:53:43 -0800
+Subject: [PATCH 09/29] CVE-2020-28022: Heap out-of-bounds read and write in
+ extract_option()
+
+Based on Phil Pennock's commit c5017adf.
+---
+ src/smtp_in.c | 20 +++++++++++++-------
+ 1 file changed, 13 insertions(+), 7 deletions(-)
+
+diff --git a/src/smtp_in.c b/src/smtp_in.c
+index 4265d77b7..16c3a3e33 100644
+--- a/src/smtp_in.c
++++ b/src/smtp_in.c
+@@ -1984,29 +1984,35 @@ static BOOL
+ extract_option(uschar **name, uschar **value)
+ {
+ uschar *n;
+-uschar *v = smtp_cmd_data + Ustrlen(smtp_cmd_data) - 1;
+-while (isspace(*v)) v--;
++uschar *v;
++if (Ustrlen(smtp_cmd_data) <= 0) return FALSE;
++v = smtp_cmd_data + Ustrlen(smtp_cmd_data) - 1;
++while (v > smtp_cmd_data && isspace(*v)) v--;
+ v[1] = 0;
++
+ while (v > smtp_cmd_data && *v != '=' && !isspace(*v))
+ {
+ /* Take care to not stop at a space embedded in a quoted local-part */
+-
+- if (*v == '"') do v--; while (*v != '"' && v > smtp_cmd_data+1);
++ if (*v == '"')
++ {
++ do v--; while (v > smtp_cmd_data && *v != '"');
++ if (v <= smtp_cmd_data) return FALSE;
++ }
+ v--;
+ }
++if (v <= smtp_cmd_data) return FALSE;
+
+ n = v;
+ if (*v == '=')
+ {
+- while(isalpha(n[-1])) n--;
++ while (n > smtp_cmd_data && isalpha(n[-1])) n--;
+ /* RFC says SP, but TAB seen in wild and other major MTAs accept it */
+- if (!isspace(n[-1])) return FALSE;
++ if (n <= smtp_cmd_data || !isspace(n[-1])) return FALSE;
+ n[-1] = 0;
+ }
+ else
+ {
+ n++;
+- if (v == smtp_cmd_data) return FALSE;
+ }
+ *v++ = 0;
+ *name = n;
+--
+2.30.2
+
--- /dev/null
+From 327f647a849c3974e7107b5386421b0058c15b29 Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+Date: Sun, 21 Feb 2021 21:17:31 -0800
+Subject: [PATCH 10/29] CVE-2020-28026: Line truncation and injection in
+ spool_read_header()
+
+This also fixes:
+
+2/ In src/spool_in.c:
+
+ 462 while ( (len = Ustrlen(big_buffer)) == big_buffer_size-1
+ 463 && big_buffer[len-1] != '\n'
+ 464 )
+ 465 { /* buffer not big enough for line; certs make this possible */
+ 466 uschar * buf;
+ 467 if (big_buffer_size >= BIG_BUFFER_SIZE*4) goto SPOOL_READ_ERROR;
+ 468 buf = store_get_perm(big_buffer_size *= 2, FALSE);
+ 469 memcpy(buf, big_buffer, --len);
+
+The --len in memcpy() chops off a useful byte (we know for sure that
+big_buffer[len-1] is not a '\n' because we entered the while loop).
+---
+ src/spool_in.c | 48 +++++++++++++++++++++++++++++++---------------
+ 1 file changed, 33 insertions(+), 15 deletions(-)
+
+diff --git a/src/spool_in.c b/src/spool_in.c
+index 2d349778c..dbbcf23ee 100644
+--- a/src/spool_in.c
++++ b/src/spool_in.c
+@@ -307,6 +307,36 @@ dsn_ret = 0;
+ dsn_envid = NULL;
+ }
+
++static void *
++fgets_big_buffer(FILE *fp)
++{
++int len = 0;
++
++big_buffer[0] = 0;
++if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) return NULL;
++
++while ((len = Ustrlen(big_buffer)) == big_buffer_size-1
++ && big_buffer[len-1] != '\n')
++ {
++ uschar *newbuffer;
++ int newsize;
++
++ if (big_buffer_size >= BIG_BUFFER_SIZE * 4) return NULL;
++ newsize = big_buffer_size * 2;
++ newbuffer = store_get_perm(newsize);
++ memcpy(newbuffer, big_buffer, len);
++
++ big_buffer = newbuffer;
++ big_buffer_size = newsize;
++ if (Ufgets(big_buffer + len, big_buffer_size - len, fp) == NULL) return NULL;
++ }
++
++if (len <= 0 || big_buffer[len-1] != '\n') return NULL;
++return big_buffer;
++}
++
++
++
+
+ /*************************************************
+ * Read spool header file *
+@@ -450,21 +480,9 @@ p = big_buffer + 2;
+ for (;;)
+ {
+ int len;
+- if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
++ if (fgets_big_buffer(fp) == NULL) goto SPOOL_READ_ERROR;
+ if (big_buffer[0] != '-') break;
+- while ( (len = Ustrlen(big_buffer)) == big_buffer_size-1
+- && big_buffer[len-1] != '\n'
+- )
+- { /* buffer not big enough for line; certs make this possible */
+- uschar * buf;
+- if (big_buffer_size >= BIG_BUFFER_SIZE*4) goto SPOOL_READ_ERROR;
+- buf = store_get_perm(big_buffer_size *= 2);
+- memcpy(buf, big_buffer, --len);
+- big_buffer = buf;
+- if (Ufgets(big_buffer+len, big_buffer_size-len, fp) == NULL)
+- goto SPOOL_READ_ERROR;
+- }
+- big_buffer[len-1] = 0;
++ big_buffer[Ustrlen(big_buffer)-1] = 0;
+
+ switch(big_buffer[1])
+ {
+@@ -724,7 +742,7 @@ DEBUG(D_deliver)
+ buffer. It contains the count of recipients which follow on separate lines.
+ Apply an arbitrary sanity check.*/
+
+-if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
++if (fgets_big_buffer(fp) == NULL) goto SPOOL_READ_ERROR;
+ if (sscanf(CS big_buffer, "%d", &rcount) != 1 || rcount > 16384)
+ goto SPOOL_FORMAT_ERROR;
+
+--
+2.30.2
+
--- /dev/null
+From ac8f49ef90e768a63ed3dca50e2b2c6e8d333bfd Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+Date: Sun, 21 Feb 2021 21:26:53 -0800
+Subject: [PATCH 11/29] CVE-2020-28015+28021: New-line injection into spool
+ header file
+
+---
+ src/spool_out.c | 21 +++++++++++++++++----
+ 1 file changed, 17 insertions(+), 4 deletions(-)
+
+diff --git a/src/spool_out.c b/src/spool_out.c
+index d55895202..9394393d5 100644
+--- a/src/spool_out.c
++++ b/src/spool_out.c
+@@ -108,6 +108,18 @@ return fd;
+ * Write the header spool file *
+ *************************************************/
+
++static const uschar *
++zap_newlines(const uschar *s)
++{
++uschar *z, *p;
++
++if (Ustrchr(s, '\n') == NULL) return s;
++
++p = z = string_copy(s);
++while ((p = Ustrchr(p, '\n')) != NULL) *p++ = ' ';
++return z;
++}
++
+ /* Returns the size of the file for success; zero for failure. The file is
+ written under a temporary name, and then renamed. It's done this way so that it
+ works with re-writing the file on message deferral as well as for the initial
+@@ -210,7 +222,7 @@ if (body_zerocount > 0) fprintf(fp, "-body_zerocount %d\n", body_zerocount);
+ if (authenticated_id)
+ fprintf(fp, "-auth_id %s\n", authenticated_id);
+ if (authenticated_sender)
+- fprintf(fp, "-auth_sender %s\n", authenticated_sender);
++ fprintf(fp, "-auth_sender %s\n", zap_newlines(authenticated_sender));
+
+ if (f.allow_unqualified_recipient) fprintf(fp, "-allow_unqualified_recipient\n");
+ if (f.allow_unqualified_sender) fprintf(fp, "-allow_unqualified_sender\n");
+@@ -283,19 +295,20 @@ fprintf(fp, "%d\n", recipients_count);
+ for (i = 0; i < recipients_count; i++)
+ {
+ recipient_item *r = recipients_list + i;
++ const uschar *address = zap_newlines(r->address);
+
+ DEBUG(D_deliver) debug_printf("DSN: Flags :%d\n", r->dsn_flags);
+
+ if (r->pno < 0 && r->errors_to == NULL && r->dsn_flags == 0)
+- fprintf(fp, "%s\n", r->address);
++ fprintf(fp, "%s\n", address);
+ else
+ {
+- uschar * errors_to = r->errors_to ? r->errors_to : US"";
++ const uschar * errors_to = r->errors_to ? zap_newlines(r->errors_to) : US"";
+ /* for DSN SUPPORT extend exim 4 spool in a compatible way by
+ adding new values upfront and add flag 0x02 */
+ uschar * orcpt = r->orcpt ? r->orcpt : US"";
+
+- fprintf(fp, "%s %s %d,%d %s %d,%d#3\n", r->address, orcpt, Ustrlen(orcpt),
++ fprintf(fp, "%s %s %d,%d %s %d,%d#3\n", address, orcpt, Ustrlen(orcpt),
+ r->dsn_flags, errors_to, Ustrlen(errors_to), r->pno);
+ }
+
+--
+2.30.2
+
--- /dev/null
+From 2cb94a53eb9186bd405120543301e1240b895d86 Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+Date: Sun, 21 Feb 2021 21:45:19 -0800
+Subject: [PATCH 12/29] CVE-2020-28009: Integer overflow in get_stdinput()
+
+---
+ src/string.c | 23 ++++++++++++++++++++++-
+ 1 file changed, 22 insertions(+), 1 deletion(-)
+
+diff --git a/src/string.c b/src/string.c
+index 3445f8a42..2cdbe7c75 100644
+--- a/src/string.c
++++ b/src/string.c
+@@ -1147,6 +1147,18 @@ To try to keep things reasonable, we use increments whose size depends on the
+ existing length of the string. */
+
+ unsigned inc = oldsize < 4096 ? 127 : 1023;
++
++if (g->ptr < 0 || g->ptr > g->size || g->size >= INT_MAX/2)
++ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
++ "internal error in gstring_grow (ptr %d size %d)", g->ptr, g->size);
++
++if (count <= 0) return;
++
++if (count >= INT_MAX/2 - g->ptr)
++ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
++ "internal error in gstring_grow (ptr %d count %d)", g->ptr, count);
++
++
+ g->size = ((p + count + inc) & ~inc) + 1;
+
+ /* Try to extend an existing allocation. If the result of calling
+@@ -1194,6 +1206,10 @@ string_catn(gstring * g, const uschar *s, int count)
+ {
+ int p;
+
++if (count < 0)
++ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
++ "internal error in string_catn (count %d)", count);
++
+ if (!g)
+ {
+ unsigned inc = count < 4096 ? 127 : 1023;
+@@ -1201,8 +1217,13 @@ if (!g)
+ g = string_get(size);
+ }
+
++if (g->ptr < 0 || g->ptr > g->size)
++ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
++ "internal error in string_catn (ptr %d size %d)", g->ptr, g->size);
++
+ p = g->ptr;
+-if (p + count >= g->size)
++
++if (count >= g->size - p)
+ gstring_grow(g, p, count);
+
+ /* Because we always specify the exact number of characters to copy, we can
+--
+2.30.2
+
--- /dev/null
+From 7ea481a6471cdad3a674b767f808357b3c7fc721 Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+Date: Sun, 21 Feb 2021 21:49:30 -0800
+Subject: [PATCH 13/29] CVE-2020-28024: Heap buffer underflow in smtp_ungetc()
+
+---
+ src/smtp_in.c | 3 +++
+ src/tls.c | 3 +++
+ 2 files changed, 6 insertions(+)
+
+diff --git a/src/smtp_in.c b/src/smtp_in.c
+index 16c3a3e33..bdcfde65f 100644
+--- a/src/smtp_in.c
++++ b/src/smtp_in.c
+@@ -805,6 +805,9 @@ Returns: the character
+ int
+ smtp_ungetc(int ch)
+ {
++if (smtp_inptr <= smtp_inbuffer)
++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "buffer underflow in smtp_ungetc");
++
+ *--smtp_inptr = ch;
+ return ch;
+ }
+diff --git a/src/tls.c b/src/tls.c
+index f79bc3193..2a316fe59 100644
+--- a/src/tls.c
++++ b/src/tls.c
+@@ -151,6 +151,9 @@ Returns: the character
+ int
+ tls_ungetc(int ch)
+ {
++if (ssl_xfer_buffer_lwm <= 0)
++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "buffer underflow in tls_ungetc");
++
+ ssl_xfer_buffer[--ssl_xfer_buffer_lwm] = ch;
+ return ch;
+ }
+--
+2.30.2
+
--- /dev/null
+From a1f36d86760def10138c1053eb3b1882b281fcd9 Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+Date: Sun, 21 Feb 2021 21:53:55 -0800
+Subject: [PATCH 14/29] CVE-2020-28012: Missing close-on-exec flag for
+ privileged pipe
+
+---
+ src/rda.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/src/rda.c b/src/rda.c
+index 13f570928..c27e073a3 100644
+--- a/src/rda.c
++++ b/src/rda.c
+@@ -623,9 +623,13 @@ search_tidyup();
+ if ((pid = fork()) == 0)
+ {
+ header_line *waslast = header_last; /* Save last header */
++ int fd_flags = -1;
+
+ fd = pfd[pipe_write];
+ (void)close(pfd[pipe_read]);
++
++ if ((fd_flags = fcntl(fd, F_GETFD)) == -1) goto bad;
++ if (fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC) == -1) goto bad;
+ exim_setugid(ugid->uid, ugid->gid, FALSE, rname);
+
+ /* Addresses can get rewritten in filters; if we are not root or the exim
+--
+2.30.2
+
--- /dev/null
+From 0d5d8fc918c4b999a2d5b025d94e25e43680377d Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+Date: Sun, 21 Feb 2021 22:00:31 -0800
+Subject: [PATCH 15/29] Security: Safeguard against relative names for msglog
+ files.
+
+Based on Heiko Schlittermann's commit 4f0ac4ad. This fixes:
+
+3/ In src/deliver.c:
+
+ 333 static int
+ 334 open_msglog_file(uschar *filename, int mode, uschar **error)
+ 335 {
+ 336 if (Ustrstr(filename, US"/../"))
+ 337 log_write(0, LOG_MAIN|LOG_PANIC,
+ 338 "Attempt to open msglog file path with upward-traversal: '%s'\n", filename);
+
+Should this be LOG_PANIC_DIE instead of LOG_PANIC? Right now it will log
+the /../ attempt but will open the file anyway.
+---
+ src/deliver.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/src/deliver.c b/src/deliver.c
+index d4ed8af08..279672ce0 100644
+--- a/src/deliver.c
++++ b/src/deliver.c
+@@ -331,6 +331,10 @@ open_msglog_file(uschar *filename, int mode, uschar **error)
+ {
+ int fd, i;
+
++if (Ustrstr(filename, US"/../"))
++ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
++ "Attempt to open msglog file path with upward-traversal: '%s'", filename);
++
+ for (i = 2; i > 0; i--)
+ {
+ fd = Uopen(filename,
+--
+2.30.2
+
--- /dev/null
+From 56aadff97bc4e45e6a2ce25cfb9a98a4ae4bec79 Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+Date: Sun, 21 Feb 2021 22:05:37 -0800
+Subject: [PATCH 16/29] Security: Check overrun rcpt_count integer
+
+Based on Heiko Schlittermann's commit e5cb5e61. This fixes:
+
+4/ In src/smtp_in.c:
+
+4966 case RCPT_CMD:
+4967 HAD(SCH_RCPT);
+4968 rcpt_count++;
+....
+5123 if (rcpt_count > recipients_max && recipients_max > 0)
+
+In theory this recipients_max check can be bypassed, because the int
+rcpt_count can overflow (become negative). In practice this would either
+consume too much memory or generate too much network traffic, but maybe
+it should be fixed anyway.
+---
+ src/smtp_in.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/src/smtp_in.c b/src/smtp_in.c
+index bdcfde65f..1a5fbfea3 100644
+--- a/src/smtp_in.c
++++ b/src/smtp_in.c
+@@ -4993,6 +4993,8 @@ while (done <= 0)
+
+ case RCPT_CMD:
+ HAD(SCH_RCPT);
++ if (rcpt_count < 0 || rcpt_count >= INT_MAX/2)
++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Too many recipients: %d", rcpt_count);
+ rcpt_count++;
+ was_rcpt = fl.rcpt_in_progress = TRUE;
+
+--
+2.30.2
+
--- /dev/null
+From 9b1ba71e66d18b1ae185e4d83788dc913f903a56 Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+Date: Sun, 21 Feb 2021 22:09:06 -0800
+Subject: [PATCH 17/29] Security: Always exit when LOG_PANIC_DIE is set
+
+---
+ src/log.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/src/log.c b/src/log.c
+index d08200044..c8313890e 100644
+--- a/src/log.c
++++ b/src/log.c
+@@ -894,6 +894,7 @@ if (!(flags & (LOG_MAIN|LOG_PANIC|LOG_REJECT)))
+ if (f.disable_logging)
+ {
+ DEBUG(D_any) debug_printf("log writing disabled\n");
++ if ((flags & LOG_PANIC_DIE) == LOG_PANIC_DIE) exim_exit(EXIT_FAILURE, NULL);
+ return;
+ }
+
+--
+2.30.2
+
--- /dev/null
+From 28335a4704d8d615fd61e05ea6e435a4cd24e4df Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+Date: Sun, 21 Feb 2021 22:13:18 -0800
+Subject: [PATCH 18/29] Security: Fix off-by-one in smtp transport (read
+ response)
+
+Based on Heiko Schlittermann's commit 1887a160. This fixes:
+
+1/ In src/transports/smtp.c:
+
+2281 int n = sizeof(sx->buffer);
+2282 uschar * rsp = sx->buffer;
+2283
+2284 if (sx->esmtp_sent && (n = Ustrlen(sx->buffer)) < sizeof(sx->buffer)/2)
+2285 { rsp = sx->buffer + n + 1; n = sizeof(sx->buffer) - n; }
+
+This should probably be either:
+
+rsp = sx->buffer + n + 1; n = sizeof(sx->buffer) - n - 1;
+
+or:
+
+rsp = sx->buffer + n; n = sizeof(sx->buffer) - n;
+
+(not sure which) to avoid an off-by-one.
+---
+ src/transports/smtp.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/src/transports/smtp.c b/src/transports/smtp.c
+index cc37e73f3..07b63a2aa 100644
+--- a/src/transports/smtp.c
++++ b/src/transports/smtp.c
+@@ -2328,8 +2328,8 @@ goto SEND_QUIT;
+ int n = sizeof(sx->buffer);
+ uschar * rsp = sx->buffer;
+
+- if (sx->esmtp_sent && (n = Ustrlen(sx->buffer)) < sizeof(sx->buffer)/2)
+- { rsp = sx->buffer + n + 1; n = sizeof(sx->buffer) - n; }
++ if (sx->esmtp_sent && (n = Ustrlen(sx->buffer) + 1) < sizeof(sx->buffer)/2)
++ { rsp = sx->buffer + n; n = sizeof(sx->buffer) - n; }
+
+ if (smtp_write_command(sx, SCMD_FLUSH, "HELO %s\r\n", sx->helo_data) < 0)
+ goto SEND_FAILED;
+--
+2.30.2
+
--- /dev/null
+From 031ae594f6e68511117f6d39ce238b0c5215d8d1 Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+Date: Sun, 21 Feb 2021 22:19:42 -0800
+Subject: [PATCH 19/29] Security: Avoid decrement of dkim_collect_input if
+ already at 0
+
+Based on Heiko Schlittermann's commit bf2d6e58. This fixes:
+
+5/ receive_msg() calls dkim_exim_verify_finish(), which sets
+dkim_collect_input to 0 and calls pdkim_feed_finish(), which calls
+pdkim_header_complete(), which decreases dkim_collect_input to UINT_MAX,
+which reactivates the DKIM code.
+
+As a result, pdkim_feed() is called again (through receive_getc at the
+end of receive_msg()), but functions like pdkim_finish_bodyhash() and
+exim_sha_finish() have already been called (in pdkim_feed_finish()).
+This suggests a use-after-free.
+
+But it seems that a use-after-free would happen only with
+EVP_DigestFinal() (in exim_sha_finish()), which does not seem to be
+reachable via DKIM (no SHA3). But we checked OpenSSL only, not GnuTLS.
+
+Here is a proof of concept that triggers the bug (which came very close
+to a security vulnerability):
+
+(sleep 10; echo 'EHLO test'; sleep 3; echo 'MAIL FROM:<>'; sleep 3; echo 'RCPT TO:postmaster'; sleep 3; echo 'BDAT 42 LAST'; date >&2; sleep 30; printf 'not a valid header line\r\n
+DKIM-Signature:\r\nXXX'; sleep 30) | nc -n -v 192.168.56.102 25
+
+(gdb) print &dkim_collect_input
+$2 = (unsigned int *) 0x55e180386d90 <dkim_collect_input>
+(gdb) watch *(unsigned int *) 0x55e180386d90
+
+Hardware watchpoint 1: *(unsigned int *) 0x55e180386d90
+Old value = 0
+New value = 4294967295
+#0 0x000055e18031f805 in pdkim_header_complete (ctx=ctx@entry=0x55e181b9e8e0) at pdkim.c:1006
+#1 0x000055e18032106c in pdkim_feed_finish (ctx=0x55e181b9e8e0, return_signatures=0x55e180386d78 <dkim_signatures>, err=err@entry=0x7ffe443e1d00) at pdkim.c:1490
+#2 0x000055e1802a3280 in dkim_exim_verify_finish () at dkim.c:328
+#3 0x000055e1802c9d1d in receive_msg (extract_recip=extract_recip@entry=0) at receive.c:3409
+---
+ src/pdkim/pdkim.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/pdkim/pdkim.c b/src/pdkim/pdkim.c
+index e203311da..e3233e9f0 100644
+--- a/src/pdkim/pdkim.c
++++ b/src/pdkim/pdkim.c
+@@ -1010,7 +1010,7 @@ else
+ last_sig->next = sig;
+ }
+
+- if (--dkim_collect_input == 0)
++ if (dkim_collect_input && --dkim_collect_input == 0)
+ {
+ ctx->headers = pdkim_prepend_stringlist(ctx->headers, ctx->cur_header->s);
+ ctx->cur_header->s[ctx->cur_header->ptr = 0] = '\0';
+--
+2.30.2
+
--- /dev/null
+From 6b647c508aced6961f00e139f0337e2c8aba9eb7 Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+Date: Sun, 21 Feb 2021 22:24:13 -0800
+Subject: [PATCH 20/29] Security: Leave a clean smtp_out input buffer even in
+ case of read error
+
+Based on Heiko Schlittermann's commit 54895bc3. This fixes:
+
+7/ In src/smtp_out.c, read_response_line(), inblock->ptr is not updated
+when -1 is returned. This does not seem to have bad consequences, but is
+maybe not the intended behavior.
+---
+ src/smtp_out.c | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+--- a/src/smtp_out.c
++++ b/src/smtp_out.c
+@@ -387,11 +387,11 @@ HDEBUG(D_transport|D_acl|D_v)
+ #ifdef SUPPORT_SOCKS
+ if (ob->socks_proxy)
+ {
+ int sock = socks_sock_connect(sc->host, sc->host_af, port, sc->interface,
+ sc->tblock, ob->connect_timeout);
+-
++
+ if (sock >= 0)
+ {
+ if (early_data && early_data->data && early_data->len)
+ if (send(sock, early_data->data, early_data->len, 0) < 0)
+ {
+@@ -588,11 +588,11 @@ Arguments:
+ buffer where to put the line
+ size space available for the line
+ timelimit deadline for reading the lime, seconds past epoch
+
+ Returns: length of a line that has been put in the buffer
+- -1 otherwise, with errno set
++ -1 otherwise, with errno set, and inblock->ptr adjusted
+ */
+
+ static int
+ read_response_line(smtp_inblock *inblock, uschar *buffer, int size, time_t timelimit)
+ {
+@@ -629,10 +629,11 @@ for (;;)
+ *p++ = c;
+ if (--size < 4)
+ {
+ *p = 0; /* Leave malformed line for error message */
+ errno = ERRNO_SMTPFORMAT;
++ inblock->ptr = ptr;
+ return -1;
+ }
+ }
+
+ /* Need to read a new input packet. */
+@@ -654,10 +655,11 @@ for (;;)
+ }
+
+ /* Get here if there has been some kind of recv() error; errno is set, but we
+ ensure that the result buffer is empty before returning. */
+
++inblock->ptr = inblock->ptrend = inblock->buffer;
+ *buffer = 0;
+ return -1;
+ }
+
+
--- /dev/null
+From a4e1b7755ebbdee2689d40683ba69f09e38a8d7f Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+Date: Sun, 21 Feb 2021 22:30:03 -0800
+Subject: [PATCH 21/29] Security: Avoid modification of constant data in dkim
+ handling
+
+Based on Heiko Schlittermann's commits f880c7f3 and c118c7f4. This
+fixes:
+
+6/ In src/pdkim/pdkim.c, pdkim_update_ctx_bodyhash() is sometimes called
+with a global orig_data and hence canon_data, and the following line can
+therefore modify data that should be constant:
+
+ 773 canon_data->len = b->bodylength - b->signed_body_bytes;
+
+For example, the following proof of concept sets lineending.len to 0
+(this should not be possible):
+
+(sleep 10; echo 'EHLO test'; sleep 3; echo 'MAIL FROM:<>'; sleep 3; echo 'RCPT TO:postmaster'; sleep 3; echo 'DATA'; date >&2; sleep 30; printf 'DKIM-Signature:a=rsa-sha1;c=simple/simple;l=0\r\n\r\n\r\nXXX\r\n.\r\n'; sleep 30) | nc -n -v 192.168.56.102 25
+
+(gdb) print lineending
+$1 = {data = 0x55e18035b2ad "\r\n", len = 2}
+(gdb) print &lineending.len
+$3 = (size_t *) 0x55e180385948 <lineending+8>
+(gdb) watch *(size_t *) 0x55e180385948
+
+Hardware watchpoint 1: *(size_t *) 0x55e180385948
+Old value = 2
+New value = 0
+(gdb) print lineending
+$5 = {data = 0x55e18035b2ad "\r\n", len = 0}
+---
+ src/pdkim/pdkim.c | 21 ++++++++++++---------
+ 1 file changed, 12 insertions(+), 9 deletions(-)
+
+diff --git a/src/pdkim/pdkim.c b/src/pdkim/pdkim.c
+index e3233e9f0..512a3e352 100644
+--- a/src/pdkim/pdkim.c
++++ b/src/pdkim/pdkim.c
+@@ -107,7 +107,7 @@ pdkim_combined_canon_entry pdkim_combined_canons[] = {
+ };
+
+
+-static blob lineending = {.data = US"\r\n", .len = 2};
++static const blob lineending = {.data = US"\r\n", .len = 2};
+
+ /* -------------------------------------------------------------------------- */
+ uschar *
+@@ -719,9 +719,11 @@ return NULL;
+ If we have to relax the data for this sig, return our copy of it. */
+
+ static blob *
+-pdkim_update_ctx_bodyhash(pdkim_bodyhash * b, blob * orig_data, blob * relaxed_data)
++pdkim_update_ctx_bodyhash(pdkim_bodyhash * b, const blob * orig_data, blob * relaxed_data)
+ {
+-blob * canon_data = orig_data;
++const blob * canon_data = orig_data;
++size_t left;
++
+ /* Defaults to simple canon (no further treatment necessary) */
+
+ if (b->canon_method == PDKIM_CANON_RELAXED)
+@@ -767,16 +769,17 @@ if (b->canon_method == PDKIM_CANON_RELAXED)
+ }
+
+ /* Make sure we don't exceed the to-be-signed body length */
++left = canon_data->len;
+ if ( b->bodylength >= 0
+- && b->signed_body_bytes + (unsigned long)canon_data->len > b->bodylength
++ && left > (unsigned long)b->bodylength - b->signed_body_bytes
+ )
+- canon_data->len = b->bodylength - b->signed_body_bytes;
++ left = (unsigned long)b->bodylength - b->signed_body_bytes;
+
+-if (canon_data->len > 0)
++if (left > 0)
+ {
+- exim_sha_update(&b->body_hash_ctx, CUS canon_data->data, canon_data->len);
+- b->signed_body_bytes += canon_data->len;
+- DEBUG(D_acl) pdkim_quoteprint(canon_data->data, canon_data->len);
++ exim_sha_update(&b->body_hash_ctx, CUS canon_data->data, left);
++ b->signed_body_bytes += left;
++ DEBUG(D_acl) pdkim_quoteprint(canon_data->data, left);
+ }
+
+ return relaxed_data;
+--
+2.30.2
+
--- /dev/null
+From 1663ab541b37675dec5bcf235605568ff36ac65a Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+Date: Sun, 21 Feb 2021 22:36:10 -0800
+Subject: [PATCH 22/29] CVE-2020-28019: Failure to reset function pointer after
+ BDAT error
+
+Based on Phil Pennock's commits 4715403e and 151ffd72, and Jeremy
+Harris's commits aa171254 and 9aceb5c2.
+---
+ src/globals.c | 1 +
+ src/globals.h | 1 +
+ src/smtp_in.c | 55 +++++++++++++++++++++++++++++++++++++++--------
+ 3 files changed, 48 insertions(+), 9 deletions(-)
+
+diff --git a/src/globals.c b/src/globals.c
+index b3362a34c..894b8487b 100644
+--- a/src/globals.c
++++ b/src/globals.c
+@@ -247,6 +247,7 @@ struct global_flags f =
+ .authentication_local = FALSE,
+
+ .background_daemon = TRUE,
++ .bdat_readers_wanted = FALSE,
+
+ .chunking_offered = FALSE,
+ .config_changed = FALSE,
+diff --git a/src/globals.h b/src/globals.h
+index f71f104e2..58f7ae55f 100644
+--- a/src/globals.h
++++ b/src/globals.h
+@@ -173,6 +173,7 @@ extern struct global_flags {
+ BOOL authentication_local :1; /* TRUE if non-smtp (implicit authentication) */
+
+ BOOL background_daemon :1; /* Set FALSE to keep in foreground */
++ BOOL bdat_readers_wanted :1; /* BDAT-handling to be pushed on readfunc stack */
+
+ BOOL chunking_offered :1;
+ BOOL config_changed :1; /* True if -C used */
+diff --git a/src/smtp_in.c b/src/smtp_in.c
+index 1a5fbfea3..016c44c0f 100644
+--- a/src/smtp_in.c
++++ b/src/smtp_in.c
+@@ -602,6 +602,10 @@ if (n > 0)
+ #endif
+ }
+
++/* Forward declarations */
++static inline void bdat_push_receive_functions(void);
++static inline void bdat_pop_receive_functions(void);
++
+
+ /* Get a byte from the smtp input, in CHUNKING mode. Handle ack of the
+ previous BDAT chunk and getting new ones when we run out. Uses the
+@@ -634,9 +638,7 @@ for(;;)
+ if (chunking_data_left > 0)
+ return lwr_receive_getc(chunking_data_left--);
+
+- receive_getc = lwr_receive_getc;
+- receive_getbuf = lwr_receive_getbuf;
+- receive_ungetc = lwr_receive_ungetc;
++ bdat_pop_receive_functions();
+ #ifndef DISABLE_DKIM
+ dkim_save = dkim_collect_input;
+ dkim_collect_input = 0;
+@@ -740,9 +742,7 @@ next_cmd:
+ goto repeat_until_rset;
+ }
+
+- receive_getc = bdat_getc;
+- receive_getbuf = bdat_getbuf; /* r~getbuf is never actually used */
+- receive_ungetc = bdat_ungetc;
++ bdat_push_receive_functions();
+ #ifndef DISABLE_DKIM
+ dkim_collect_input = dkim_save;
+ #endif
+@@ -775,9 +775,7 @@ while (chunking_data_left)
+ if (!bdat_getbuf(&n)) break;
+ }
+
+-receive_getc = lwr_receive_getc;
+-receive_getbuf = lwr_receive_getbuf;
+-receive_ungetc = lwr_receive_ungetc;
++bdat_pop_receive_functions();
+
+ if (chunking_state != CHUNKING_LAST)
+ {
+@@ -787,6 +785,45 @@ if (chunking_state != CHUNKING_LAST)
+ }
+
+
++static inline void
++bdat_push_receive_functions(void)
++{
++/* push the current receive_* function on the "stack", and
++replace them by bdat_getc(), which in turn will use the lwr_receive_*
++functions to do the dirty work. */
++if (lwr_receive_getc == NULL)
++ {
++ lwr_receive_getc = receive_getc;
++ lwr_receive_getbuf = receive_getbuf;
++ lwr_receive_ungetc = receive_ungetc;
++ }
++else
++ {
++ DEBUG(D_receive) debug_printf("chunking double-push receive functions\n");
++ }
++
++receive_getc = bdat_getc;
++receive_getbuf = bdat_getbuf;
++receive_ungetc = bdat_ungetc;
++}
++
++static inline void
++bdat_pop_receive_functions(void)
++{
++if (lwr_receive_getc == NULL)
++ {
++ DEBUG(D_receive) debug_printf("chunking double-pop receive functions\n");
++ return;
++ }
++
++receive_getc = lwr_receive_getc;
++receive_getbuf = lwr_receive_getbuf;
++receive_ungetc = lwr_receive_ungetc;
++
++lwr_receive_getc = NULL;
++lwr_receive_getbuf = NULL;
++lwr_receive_ungetc = NULL;
++}
+
+
+ /*************************************************
+--
+2.30.2
+
--- /dev/null
+From 99ae249e9857e80ff4d65b2388bc68c624dcb739 Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+Date: Tue, 23 Feb 2021 08:33:03 -0800
+Subject: [PATCH 23/29] CVE-2020-28007: Link attack in Exim's log directory
+
+We patch this vulnerability by opening (instead of just creating) the
+log file in an unprivileged (exim) child process, and by passing this
+file descriptor back to the privileged (root) parent process. The two
+functions log_send_fd() and log_recv_fd() are inspired by OpenSSH's
+functions mm_send_fd() and mm_receive_fd(); thanks!
+
+This patch also fixes:
+
+- a NULL-pointer dereference in usr1_handler() (this signal handler is
+ installed before process_log_path is initialized);
+
+- a file-descriptor leak in dmarc_write_history_file() (two return paths
+ did not close history_file_fd).
+
+Note: the use of log_open_as_exim() in dmarc_write_history_file() should
+be fine because the documentation explicitly states "Make sure the
+directory of this file is writable by the user exim runs as."
+
+(cherry picked from commit 2502cc41d1d92c1413eca6a4ba035c21162662bd)
+(cherry picked from commit 93e9a18fbf09deb59bd133986f4c89aeb2d2d86a)
+---
+ src/dmarc.c | 179 ++++++++++++++++++------------------
+ src/exim.c | 14 +--
+ src/functions.h | 3 +-
+ src/log.c | 214 ++++++++++++++++++++++++++++----------------
+ test/stderr/0397 | 6 +-
+ 5 files changed, 234 insertions(+), 182 deletions(-)
+
+diff --git a/src/dmarc.c b/src/dmarc.c
+index f29f7eba6..c5e01c7ee 100644
+--- a/src/dmarc.c
++++ b/src/dmarc.c
+@@ -204,6 +204,97 @@ if ( dmarc_policy == DMARC_POLICY_REJECT && action == DMARC_RESULT_REJECT
+ }
+ }
+
++
++static int
++dmarc_write_history_file()
++{
++int tmp_ans;
++u_char **rua; /* aggregate report addressees */
++uschar *history_buffer = NULL;
++
++if (!dmarc_history_file)
++ {
++ DEBUG(D_receive) debug_printf("DMARC history file not set\n");
++ return DMARC_HIST_DISABLED;
++ }
++
++/* Generate the contents of the history file */
++history_buffer = string_sprintf(
++ "job %s\nreporter %s\nreceived %ld\nipaddr %s\nfrom %s\nmfrom %s\n",
++ message_id, primary_hostname, time(NULL), sender_host_address,
++ header_from_sender, expand_string(US"$sender_address_domain"));
++
++if (spf_response)
++ history_buffer = string_sprintf("%sspf %d\n", history_buffer, dmarc_spf_ares_result);
++ /* history_buffer = string_sprintf("%sspf -1\n", history_buffer); */
++
++history_buffer = string_sprintf(
++ "%s%spdomain %s\npolicy %d\n",
++ history_buffer, dkim_history_buffer, dmarc_used_domain, dmarc_policy);
++
++if ((rua = opendmarc_policy_fetch_rua(dmarc_pctx, NULL, 0, 1)))
++ for (tmp_ans = 0; rua[tmp_ans]; tmp_ans++)
++ history_buffer = string_sprintf("%srua %s\n", history_buffer, rua[tmp_ans]);
++else
++ history_buffer = string_sprintf("%srua -\n", history_buffer);
++
++opendmarc_policy_fetch_pct(dmarc_pctx, &tmp_ans);
++history_buffer = string_sprintf("%spct %d\n", history_buffer, tmp_ans);
++
++opendmarc_policy_fetch_adkim(dmarc_pctx, &tmp_ans);
++history_buffer = string_sprintf("%sadkim %d\n", history_buffer, tmp_ans);
++
++opendmarc_policy_fetch_aspf(dmarc_pctx, &tmp_ans);
++history_buffer = string_sprintf("%saspf %d\n", history_buffer, tmp_ans);
++
++opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans);
++history_buffer = string_sprintf("%sp %d\n", history_buffer, tmp_ans);
++
++opendmarc_policy_fetch_sp(dmarc_pctx, &tmp_ans);
++history_buffer = string_sprintf("%ssp %d\n", history_buffer, tmp_ans);
++
++history_buffer = string_sprintf(
++ "%salign_dkim %d\nalign_spf %d\naction %d\n",
++ history_buffer, da, sa, action);
++
++/* Write the contents to the history file */
++DEBUG(D_receive)
++ debug_printf("DMARC logging history data for opendmarc reporting%s\n",
++ (host_checking || f.running_in_test_harness) ? " (not really)" : "");
++if (host_checking || f.running_in_test_harness)
++ {
++ DEBUG(D_receive)
++ debug_printf("DMARC history data for debugging:\n%s", history_buffer);
++ }
++else
++ {
++ ssize_t written_len;
++ const int history_file_fd = log_open_as_exim(dmarc_history_file);
++
++ if (history_file_fd < 0)
++ {
++ log_write(0, LOG_MAIN|LOG_PANIC, "failure to create DMARC history file: %s",
++ dmarc_history_file);
++ return DMARC_HIST_FILE_ERR;
++ }
++
++ written_len = write_to_fd_buf(history_file_fd,
++ history_buffer,
++ Ustrlen(history_buffer));
++
++ (void)close(history_file_fd);
++
++ if (written_len <= 0)
++ {
++ log_write(0, LOG_MAIN|LOG_PANIC, "failure to write to DMARC history file: %s",
++ dmarc_history_file);
++ return DMARC_HIST_WRITE_ERR;
++ }
++ }
++return DMARC_HIST_OK;
++}
++
++
+ /* dmarc_process adds the envelope sender address to the existing
+ context (if any), retrieves the result, sets up expansion
+ strings and evaluates the condition outcome. */
+@@ -486,94 +577,6 @@ if (!f.dmarc_disable_verify)
+ return OK;
+ }
+
+-static int
+-dmarc_write_history_file()
+-{
+-int history_file_fd;
+-ssize_t written_len;
+-int tmp_ans;
+-u_char **rua; /* aggregate report addressees */
+-uschar *history_buffer = NULL;
+-
+-if (!dmarc_history_file)
+- {
+- DEBUG(D_receive) debug_printf("DMARC history file not set\n");
+- return DMARC_HIST_DISABLED;
+- }
+-history_file_fd = log_create(dmarc_history_file);
+-
+-if (history_file_fd < 0)
+- {
+- log_write(0, LOG_MAIN|LOG_PANIC, "failure to create DMARC history file: %s",
+- dmarc_history_file);
+- return DMARC_HIST_FILE_ERR;
+- }
+-
+-/* Generate the contents of the history file */
+-history_buffer = string_sprintf(
+- "job %s\nreporter %s\nreceived %ld\nipaddr %s\nfrom %s\nmfrom %s\n",
+- message_id, primary_hostname, time(NULL), sender_host_address,
+- header_from_sender, expand_string(US"$sender_address_domain"));
+-
+-if (spf_response)
+- history_buffer = string_sprintf("%sspf %d\n", history_buffer, dmarc_spf_ares_result);
+- /* history_buffer = string_sprintf("%sspf -1\n", history_buffer); */
+-
+-history_buffer = string_sprintf(
+- "%s%spdomain %s\npolicy %d\n",
+- history_buffer, dkim_history_buffer, dmarc_used_domain, dmarc_policy);
+-
+-if ((rua = opendmarc_policy_fetch_rua(dmarc_pctx, NULL, 0, 1)))
+- for (tmp_ans = 0; rua[tmp_ans]; tmp_ans++)
+- history_buffer = string_sprintf("%srua %s\n", history_buffer, rua[tmp_ans]);
+-else
+- history_buffer = string_sprintf("%srua -\n", history_buffer);
+-
+-opendmarc_policy_fetch_pct(dmarc_pctx, &tmp_ans);
+-history_buffer = string_sprintf("%spct %d\n", history_buffer, tmp_ans);
+-
+-opendmarc_policy_fetch_adkim(dmarc_pctx, &tmp_ans);
+-history_buffer = string_sprintf("%sadkim %d\n", history_buffer, tmp_ans);
+-
+-opendmarc_policy_fetch_aspf(dmarc_pctx, &tmp_ans);
+-history_buffer = string_sprintf("%saspf %d\n", history_buffer, tmp_ans);
+-
+-opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans);
+-history_buffer = string_sprintf("%sp %d\n", history_buffer, tmp_ans);
+-
+-opendmarc_policy_fetch_sp(dmarc_pctx, &tmp_ans);
+-history_buffer = string_sprintf("%ssp %d\n", history_buffer, tmp_ans);
+-
+-history_buffer = string_sprintf(
+- "%salign_dkim %d\nalign_spf %d\naction %d\n",
+- history_buffer, da, sa, action);
+-
+-/* Write the contents to the history file */
+-DEBUG(D_receive)
+- debug_printf("DMARC logging history data for opendmarc reporting%s\n",
+- (host_checking || f.running_in_test_harness) ? " (not really)" : "");
+-if (host_checking || f.running_in_test_harness)
+- {
+- DEBUG(D_receive)
+- debug_printf("DMARC history data for debugging:\n%s", history_buffer);
+- }
+-else
+- {
+- written_len = write_to_fd_buf(history_file_fd,
+- history_buffer,
+- Ustrlen(history_buffer));
+- if (written_len == 0)
+- {
+- log_write(0, LOG_MAIN|LOG_PANIC, "failure to write to DMARC history file: %s",
+- dmarc_history_file);
+- return DMARC_HIST_WRITE_ERR;
+- }
+- (void)close(history_file_fd);
+- }
+-return DMARC_HIST_OK;
+-}
+-
+-
+ uschar *
+ dmarc_exim_expand_query(int what)
+ {
+diff --git a/src/exim.c b/src/exim.c
+index a7dc48c4e..f0a168983 100644
+--- a/src/exim.c
++++ b/src/exim.c
+@@ -227,18 +227,8 @@ int fd;
+
+ os_restarting_signal(sig, usr1_handler);
+
+-if ((fd = Uopen(process_log_path, O_APPEND|O_WRONLY, LOG_MODE)) < 0)
+- {
+- /* If we are already running as the Exim user, try to create it in the
+- current process (assuming spool_directory exists). Otherwise, if we are
+- root, do the creation in an exim:exim subprocess. */
+-
+- int euid = geteuid();
+- if (euid == exim_uid)
+- fd = Uopen(process_log_path, O_CREAT|O_APPEND|O_WRONLY, LOG_MODE);
+- else if (euid == root_uid)
+- fd = log_create_as_exim(process_log_path);
+- }
++if (!process_log_path) return;
++fd = log_open_as_exim(process_log_path);
+
+ /* If we are neither exim nor root, or if we failed to create the log file,
+ give up. There is not much useful we can do with errors, since we don't want
+diff --git a/src/functions.h b/src/functions.h
+index cab7a7363..366cb2f26 100644
+--- a/src/functions.h
++++ b/src/functions.h
+@@ -281,8 +281,7 @@ extern int ip_streamsocket(const uschar *, uschar **, int);
+ extern int ipv6_nmtoa(int *, uschar *);
+
+ extern uschar *local_part_quote(uschar *);
+-extern int log_create(uschar *);
+-extern int log_create_as_exim(uschar *);
++extern int log_open_as_exim(uschar *);
+ extern void log_close_all(void);
+
+ extern macro_item * macro_create(const uschar *, const uschar *, BOOL);
+diff --git a/src/log.c b/src/log.c
+index c8313890e..15c88c13e 100644
+--- a/src/log.c
++++ b/src/log.c
+@@ -264,14 +264,19 @@ overwrite it temporarily if it is necessary to create the directory.
+ Returns: a file descriptor, or < 0 on failure (errno set)
+ */
+
+-int
+-log_create(uschar *name)
++static int
++log_open_already_exim(uschar * const name)
+ {
+-int fd = Uopen(name,
+-#ifdef O_CLOEXEC
+- O_CLOEXEC |
+-#endif
+- O_CREAT|O_APPEND|O_WRONLY, LOG_MODE);
++int fd = -1;
++const int flags = O_WRONLY | O_APPEND | O_CREAT | O_NONBLOCK;
++
++if (geteuid() != exim_uid)
++ {
++ errno = EACCES;
++ return -1;
++ }
++
++fd = Uopen(name, flags, LOG_MODE);
+
+ /* If creation failed, attempt to build a log directory in case that is the
+ problem. */
+@@ -285,11 +290,7 @@ if (fd < 0 && errno == ENOENT)
+ DEBUG(D_any) debug_printf("%s log directory %s\n",
+ created ? "created" : "failed to create", name);
+ *lastslash = '/';
+- if (created) fd = Uopen(name,
+-#ifdef O_CLOEXEC
+- O_CLOEXEC |
+-#endif
+- O_CREAT|O_APPEND|O_WRONLY, LOG_MODE);
++ if (created) fd = Uopen(name, flags, LOG_MODE);
+ }
+
+ return fd;
+@@ -297,6 +298,81 @@ return fd;
+
+
+
++/* Inspired by OpenSSH's mm_send_fd(). Thanks! */
++
++static int
++log_send_fd(const int sock, const int fd)
++{
++struct msghdr msg;
++union {
++ struct cmsghdr hdr;
++ char buf[CMSG_SPACE(sizeof(int))];
++} cmsgbuf;
++struct cmsghdr *cmsg;
++struct iovec vec;
++char ch = 'A';
++ssize_t n;
++
++memset(&msg, 0, sizeof(msg));
++memset(&cmsgbuf, 0, sizeof(cmsgbuf));
++msg.msg_control = &cmsgbuf.buf;
++msg.msg_controllen = sizeof(cmsgbuf.buf);
++
++cmsg = CMSG_FIRSTHDR(&msg);
++cmsg->cmsg_len = CMSG_LEN(sizeof(int));
++cmsg->cmsg_level = SOL_SOCKET;
++cmsg->cmsg_type = SCM_RIGHTS;
++*(int *)CMSG_DATA(cmsg) = fd;
++
++vec.iov_base = &ch;
++vec.iov_len = 1;
++msg.msg_iov = &vec;
++msg.msg_iovlen = 1;
++
++while ((n = sendmsg(sock, &msg, 0)) == -1 && errno == EINTR);
++if (n != 1) return -1;
++return 0;
++}
++
++/* Inspired by OpenSSH's mm_receive_fd(). Thanks! */
++
++static int
++log_recv_fd(const int sock)
++{
++struct msghdr msg;
++union {
++ struct cmsghdr hdr;
++ char buf[CMSG_SPACE(sizeof(int))];
++} cmsgbuf;
++struct cmsghdr *cmsg;
++struct iovec vec;
++ssize_t n;
++char ch = '\0';
++int fd = -1;
++
++memset(&msg, 0, sizeof(msg));
++vec.iov_base = &ch;
++vec.iov_len = 1;
++msg.msg_iov = &vec;
++msg.msg_iovlen = 1;
++
++memset(&cmsgbuf, 0, sizeof(cmsgbuf));
++msg.msg_control = &cmsgbuf.buf;
++msg.msg_controllen = sizeof(cmsgbuf.buf);
++
++while ((n = recvmsg(sock, &msg, 0)) == -1 && errno == EINTR);
++if (n != 1 || ch != 'A') return -1;
++
++cmsg = CMSG_FIRSTHDR(&msg);
++if (cmsg == NULL) return -1;
++if (cmsg->cmsg_type != SCM_RIGHTS) return -1;
++fd = *(const int *)CMSG_DATA(cmsg);
++if (fd < 0) return -1;
++return fd;
++}
++
++
++
+ /*************************************************
+ * Create a log file as the exim user *
+ *************************************************/
+@@ -312,41 +388,60 @@ Returns: a file descriptor, or < 0 on failure (errno set)
+ */
+
+ int
+-log_create_as_exim(uschar *name)
++log_open_as_exim(uschar * const name)
+ {
+-pid_t pid = fork();
+-int status = 1;
+ int fd = -1;
++const uid_t euid = geteuid();
+
+-/* In the subprocess, change uid/gid and do the creation. Return 0 from the
+-subprocess on success. If we don't check for setuid failures, then the file
+-can be created as root, so vulnerabilities which cause setuid to fail mean
+-that the Exim user can use symlinks to cause a file to be opened/created as
+-root. We always open for append, so can't nuke existing content but it would
+-still be Rather Bad. */
+-
+-if (pid == 0)
++if (euid == exim_uid)
+ {
+- if (setgid(exim_gid) < 0)
+- die(US"exim: setgid for log-file creation failed, aborting",
+- US"Unexpected log failure, please try later");
+- if (setuid(exim_uid) < 0)
+- die(US"exim: setuid for log-file creation failed, aborting",
+- US"Unexpected log failure, please try later");
+- _exit((log_create(name) < 0)? 1 : 0);
++ fd = log_open_already_exim(name);
+ }
++else if (euid == root_uid)
++ {
++ int sock[2];
++ if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock) == 0)
++ {
++ const pid_t pid = fork();
++ if (pid == 0)
++ {
++ (void)close(sock[0]);
++ if (setgroups(1, &exim_gid) != 0) _exit(EXIT_FAILURE);
++ if (setgid(exim_gid) != 0) _exit(EXIT_FAILURE);
++ if (setuid(exim_uid) != 0) _exit(EXIT_FAILURE);
++
++ if (getuid() != exim_uid || geteuid() != exim_uid) _exit(EXIT_FAILURE);
++ if (getgid() != exim_gid || getegid() != exim_gid) _exit(EXIT_FAILURE);
++
++ fd = log_open_already_exim(name);
++ if (fd < 0) _exit(EXIT_FAILURE);
++ if (log_send_fd(sock[1], fd) != 0) _exit(EXIT_FAILURE);
++ (void)close(sock[1]);
++ _exit(EXIT_SUCCESS);
++ }
+
+-/* If we created a subprocess, wait for it. If it succeeded, try the open. */
+-
+-while (pid > 0 && waitpid(pid, &status, 0) != pid);
+-if (status == 0) fd = Uopen(name,
+-#ifdef O_CLOEXEC
+- O_CLOEXEC |
+-#endif
+- O_APPEND|O_WRONLY, LOG_MODE);
++ (void)close(sock[1]);
++ if (pid > 0)
++ {
++ fd = log_recv_fd(sock[0]);
++ while (waitpid(pid, NULL, 0) == -1 && errno == EINTR);
++ }
++ (void)close(sock[0]);
++ }
++ }
+
+-/* If we failed to create a subprocess, we are in a bad way. We return
+-with fd still < 0, and errno set, letting the caller handle the error. */
++if (fd >= 0)
++ {
++ int flags;
++ flags = fcntl(fd, F_GETFD);
++ if (flags != -1) (void)fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
++ flags = fcntl(fd, F_GETFL);
++ if (flags != -1) (void)fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
++ }
++else
++ {
++ errno = EACCES;
++ }
+
+ return fd;
+ }
+@@ -459,52 +554,17 @@ if (!ok)
+ die(US"exim: log file path too long: aborting",
+ US"Logging failure; please try later");
+
+-/* We now have the file name. Try to open an existing file. After a successful
+-open, arrange for automatic closure on exec(), and then return. */
++/* We now have the file name. After a successful open, return. */
+
+-*fd = Uopen(buffer,
+-#ifdef O_CLOEXEC
+- O_CLOEXEC |
+-#endif
+- O_APPEND|O_WRONLY, LOG_MODE);
++*fd = log_open_as_exim(buffer);
+
+ if (*fd >= 0)
+ {
+-#ifndef O_CLOEXEC
+- (void)fcntl(*fd, F_SETFD, fcntl(*fd, F_GETFD) | FD_CLOEXEC);
+-#endif
+ return;
+ }
+
+-/* Open was not successful: try creating the file. If this is a root process,
+-we must do the creating in a subprocess set to exim:exim in order to ensure
+-that the file is created with the right ownership. Otherwise, there can be a
+-race if another Exim process is trying to write to the log at the same time.
+-The use of SIGUSR1 by the exiwhat utility can provoke a lot of simultaneous
+-writing. */
+-
+ euid = geteuid();
+
+-/* If we are already running as the Exim user (even if that user is root),
+-we can go ahead and create in the current process. */
+-
+-if (euid == exim_uid) *fd = log_create(buffer);
+-
+-/* Otherwise, if we are root, do the creation in an exim:exim subprocess. If we
+-are neither exim nor root, creation is not attempted. */
+-
+-else if (euid == root_uid) *fd = log_create_as_exim(buffer);
+-
+-/* If we now have an open file, set the close-on-exec flag and return. */
+-
+-if (*fd >= 0)
+- {
+-#ifndef O_CLOEXEC
+- (void)fcntl(*fd, F_SETFD, fcntl(*fd, F_GETFD) | FD_CLOEXEC);
+-#endif
+- return;
+- }
+-
+ /* Creation failed. There are some circumstances in which we get here when
+ the effective uid is not root or exim, which is the problem. (For example, a
+ non-setuid binary with log_arguments set, called in certain ways.) Rather than
+--
+2.30.2
+
--- /dev/null
+From 5fec3406547fd1e46838a76f000102beb6bfe468 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Sun, 14 Mar 2021 12:16:57 +0100
+Subject: [PATCH 24/29] CVE-2020-28008: Assorted attacks in Exim's spool
+ directory
+
+We patch dbfn_open() by introducing two functions priv_drop_temp() and
+priv_restore() (inspired by OpenSSH's functions temporarily_use_uid()
+and restore_uid()), which temporarily drop and restore root privileges
+thanks to seteuid(). This goes against Exim's developers' wishes ("Exim
+(the project) doesn't trust seteuid to work reliably") but, to the best
+of our knowledge, seteuid() works everywhere and is the only way to
+securely fix dbfn_open().
+
+(cherry picked from commit 18da59151dbafa89be61c63580bdb295db36e374)
+(cherry picked from commit b05dc3573f4cd476482374b0ac0393153d344338)
+---
+ doc/ChangeLog | 6 +++
+ src/dbfn.c | 110 +++++++++++++++++++++++++-----------------
+ test/stderr/0275 | 2 +-
+ test/stderr/0278 | 2 +-
+ test/stderr/0386 | 2 +-
+ test/stderr/0388 | 2 +-
+ test/stderr/0402 | 2 +-
+ test/stderr/0403 | 2 +-
+ test/stderr/0404 | 2 +-
+ test/stderr/0408 | 2 +-
+ test/stderr/0487 | 2 +-
+ 11 files changed, 80 insertions(+), 54 deletions(-)
+
+diff --git a/doc/ChangeLog b/doc/ChangeLog
+index 5741fb212..b32347c5b 100644
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -14,6 +14,12 @@ JH/42 Bug 2545: Fix CHUNKING for all RCPT commands rejected. Previously we
+
+ Exim version 4.92.2
+ -------------------
++QS/02 PID file creation/deletion: only possible if uid=0 or uid is the Exim
++ runtime user.
++
++QS/01 Creation of (database) files in $spool_dir: only uid=0 or the euid of
++ the Exim runtime user are allowed to create files.
++
+
+ HS/01 Handle trailing backslash gracefully. (CVE-2019-15846)
+
+diff --git a/src/dbfn.c b/src/dbfn.c
+index 336cfe73e..902756508 100644
+--- a/src/dbfn.c
++++ b/src/dbfn.c
+@@ -59,6 +59,66 @@ log_write(0, LOG_MAIN, "Berkeley DB error: %s", msg);
+
+
+
++static enum {
++ PRIV_DROPPING, PRIV_DROPPED,
++ PRIV_RESTORING, PRIV_RESTORED
++} priv_state = PRIV_RESTORED;
++
++static uid_t priv_euid;
++static gid_t priv_egid;
++static gid_t priv_groups[EXIM_GROUPLIST_SIZE + 1];
++static int priv_ngroups;
++
++/* Inspired by OpenSSH's temporarily_use_uid(). Thanks! */
++
++static void
++priv_drop_temp(const uid_t temp_uid, const gid_t temp_gid)
++{
++if (priv_state != PRIV_RESTORED) _exit(EXIT_FAILURE);
++priv_state = PRIV_DROPPING;
++
++priv_euid = geteuid();
++if (priv_euid == root_uid)
++ {
++ priv_egid = getegid();
++ priv_ngroups = getgroups(nelem(priv_groups), priv_groups);
++ if (priv_ngroups < 0) _exit(EXIT_FAILURE);
++
++ if (priv_ngroups > 0 && setgroups(1, &temp_gid) != 0) _exit(EXIT_FAILURE);
++ if (setegid(temp_gid) != 0) _exit(EXIT_FAILURE);
++ if (seteuid(temp_uid) != 0) _exit(EXIT_FAILURE);
++
++ if (geteuid() != temp_uid) _exit(EXIT_FAILURE);
++ if (getegid() != temp_gid) _exit(EXIT_FAILURE);
++ }
++
++priv_state = PRIV_DROPPED;
++}
++
++/* Inspired by OpenSSH's restore_uid(). Thanks! */
++
++static void
++priv_restore(void)
++{
++if (priv_state != PRIV_DROPPED) _exit(EXIT_FAILURE);
++priv_state = PRIV_RESTORING;
++
++if (priv_euid == root_uid)
++ {
++ if (seteuid(priv_euid) != 0) _exit(EXIT_FAILURE);
++ if (setegid(priv_egid) != 0) _exit(EXIT_FAILURE);
++ if (priv_ngroups > 0 && setgroups(priv_ngroups, priv_groups) != 0) _exit(EXIT_FAILURE);
++
++ if (geteuid() != priv_euid) _exit(EXIT_FAILURE);
++ if (getegid() != priv_egid) _exit(EXIT_FAILURE);
++ }
++
++priv_state = PRIV_RESTORED;
++}
++
++
++
++
+ /*************************************************
+ * Open and lock a database file *
+ *************************************************/
+@@ -89,7 +149,6 @@ dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof)
+ {
+ int rc, save_errno;
+ BOOL read_only = flags == O_RDONLY;
+-BOOL created = FALSE;
+ flock_t lock_data;
+ uschar dirname[256], filename[256];
+
+@@ -111,12 +170,13 @@ exists, there is no error. */
+ snprintf(CS dirname, sizeof(dirname), "%s/db", spool_directory);
+ snprintf(CS filename, sizeof(filename), "%s/%s.lockfile", dirname, name);
+
++priv_drop_temp(exim_uid, exim_gid);
+ if ((dbblock->lockfd = Uopen(filename, O_RDWR, EXIMDB_LOCKFILE_MODE)) < 0)
+ {
+- created = TRUE;
+ (void)directory_make(spool_directory, US"db", EXIMDB_DIRECTORY_MODE, TRUE);
+ dbblock->lockfd = Uopen(filename, O_RDWR|O_CREAT, EXIMDB_LOCKFILE_MODE);
+ }
++priv_restore();
+
+ if (dbblock->lockfd < 0)
+ {
+@@ -165,57 +225,17 @@ it easy to pin this down, there are now debug statements on either side of the
+ open call. */
+
+ snprintf(CS filename, sizeof(filename), "%s/%s", dirname, name);
+-EXIM_DBOPEN(filename, dirname, flags, EXIMDB_MODE, &(dbblock->dbptr));
+
++priv_drop_temp(exim_uid, exim_gid);
++EXIM_DBOPEN(filename, dirname, flags, EXIMDB_MODE, &(dbblock->dbptr));
+ if (!dbblock->dbptr && errno == ENOENT && flags == O_RDWR)
+ {
+ DEBUG(D_hints_lookup)
+ debug_printf_indent("%s appears not to exist: trying to create\n", filename);
+- created = TRUE;
+ EXIM_DBOPEN(filename, dirname, flags|O_CREAT, EXIMDB_MODE, &(dbblock->dbptr));
+ }
+-
+ save_errno = errno;
+-
+-/* If we are running as root and this is the first access to the database, its
+-files will be owned by root. We want them to be owned by exim. We detect this
+-situation by noting above when we had to create the lock file or the database
+-itself. Because the different dbm libraries use different extensions for their
+-files, I don't know of any easier way of arranging this than scanning the
+-directory for files with the appropriate base name. At least this deals with
+-the lock file at the same time. Also, the directory will typically have only
+-half a dozen files, so the scan will be quick.
+-
+-This code is placed here, before the test for successful opening, because there
+-was a case when a file was created, but the DBM library still returned NULL
+-because of some problem. It also sorts out the lock file if that was created
+-but creation of the database file failed. */
+-
+-if (created && geteuid() == root_uid)
+- {
+- DIR *dd;
+- struct dirent *ent;
+- uschar *lastname = Ustrrchr(filename, '/') + 1;
+- int namelen = Ustrlen(name);
+-
+- *lastname = 0;
+- dd = opendir(CS filename);
+-
+- while ((ent = readdir(dd)))
+- if (Ustrncmp(ent->d_name, name, namelen) == 0)
+- {
+- struct stat statbuf;
+- Ustrcpy(lastname, ent->d_name);
+- if (Ustat(filename, &statbuf) >= 0 && statbuf.st_uid != exim_uid)
+- {
+- DEBUG(D_hints_lookup) debug_printf_indent("ensuring %s is owned by exim\n", filename);
+- if (Uchown(filename, exim_uid, exim_gid))
+- DEBUG(D_hints_lookup) debug_printf_indent("failed setting %s to owned by exim\n", filename);
+- }
+- }
+-
+- closedir(dd);
+- }
++priv_restore();
+
+ /* If the open has failed, return NULL, leaving errno set. If lof is TRUE,
+ log the event - also for debugging - but debug only if the file just doesn't
+--
+2.30.2
+
--- /dev/null
+From c166890023f56388cb3482cff3def04171a488c4 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Thu, 25 Mar 2021 22:48:09 +0100
+Subject: [PATCH 26/29] CVE-2020-28014, CVE-2021-27216: Arbitrary PID file
+ creation, clobbering, and deletion
+
+Arbitrary PID file creation, clobbering, and deletion.
+Patch provided by Qualys.
+
+(cherry picked from commit 974f32939a922512b27d9f0a8a1cb5dec60e7d37)
+(cherry picked from commit 43c6f0b83200b7082353c50187ef75de3704580a)
+---
+ doc/ChangeLog | 5 +
+ src/daemon.c | 212 ++++++++++++++++++++++++++++++++++++++----
+ src/exim.c | 12 ++-
+ test/stderr/0433 | 24 +++++
+ 4 files changed, 232 insertions(+), 21 deletions(-)
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -10,13 +10,18 @@ QS/02 PID file creation/deletion: only p
+ runtime user.
+
+ QS/01 Creation of (database) files in $spool_dir: only uid=0 or the euid of
+ the Exim runtime user are allowed to create files.
+
++QS/01 Creation of (database) files in $spool_dir: only uid=0 or the uid of
++ the Exim runtime user are allowed to create files.
+
+ HS/01 Handle trailing backslash gracefully. (CVE-2019-15846)
+
++QS/02 PID file creation/deletion: only possible if uid=0 or uid is the Exim
++ runtime user.
++
+
+ Since version 4.92
+ ------------------
+
+ JH/06 Fix buggy handling of autoreply bounce_return_size_limit, and a possible
+--- a/src/daemon.c
++++ b/src/daemon.c
+@@ -886,10 +886,198 @@ while ((pid = waitpid(-1, &status, WNOHA
+ }
+ }
+ }
+
+
++static void
++set_pid_file_path(void)
++{
++if (override_pid_file_path)
++ pid_file_path = override_pid_file_path;
++
++if (!*pid_file_path)
++ pid_file_path = string_sprintf("%s/exim-daemon.pid", spool_directory);
++
++if (pid_file_path[0] != '/')
++ log_write(0, LOG_PANIC_DIE, "pid file path %s must be absolute\n", pid_file_path);
++}
++
++
++enum pid_op { PID_WRITE, PID_CHECK, PID_DELETE };
++
++/* Do various pid file operations as safe as possible. Ideally we'd just
++drop the privileges for creation of the pid file and not care at all about removal of
++the file. FIXME.
++Returns: true on success, false + errno==EACCES otherwise
++*/
++static BOOL
++operate_on_pid_file(const enum pid_op operation, const pid_t pid)
++{
++char pid_line[sizeof(int) * 3 + 2];
++const int pid_len = snprintf(pid_line, sizeof(pid_line), "%d\n", (int)pid);
++BOOL lines_match = FALSE;
++
++char * path = NULL;
++char * base = NULL;
++char * dir = NULL;
++
++const int dir_flags = O_RDONLY | O_NONBLOCK;
++const int base_flags = O_NOFOLLOW | O_NONBLOCK;
++const mode_t base_mode = 0644;
++struct stat sb;
++
++int cwd_fd = -1;
++int dir_fd = -1;
++int base_fd = -1;
++
++BOOL success = FALSE;
++errno = EACCES;
++
++set_pid_file_path();
++if (!f.running_in_test_harness && real_uid != root_uid && real_uid != exim_uid) goto cleanup;
++if (pid_len < 2 || pid_len >= (int)sizeof(pid_line)) goto cleanup;
++
++path = CS string_copy(pid_file_path);
++if ((base = Ustrrchr(path, '/')) == NULL) /* should not happen, but who knows */
++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "pid file path \"%s\" does not contain a '/'", pid_file_path);
++
++dir = (base != path) ? path : "/";
++*base++ = '\0';
++
++if (!dir || !*dir || *dir != '/') goto cleanup;
++if (!base || !*base || strchr(base, '/') != NULL) goto cleanup;
++
++cwd_fd = open(".", dir_flags);
++if (cwd_fd < 0 || fstat(cwd_fd, &sb) != 0 || !S_ISDIR(sb.st_mode)) goto cleanup;
++dir_fd = open(dir, dir_flags);
++if (dir_fd < 0 || fstat(dir_fd, &sb) != 0 || !S_ISDIR(sb.st_mode)) goto cleanup;
++
++/* emulate openat */
++if (fchdir(dir_fd) != 0) goto cleanup;
++base_fd = open(base, O_RDONLY | base_flags);
++if (fchdir(cwd_fd) != 0)
++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno));
++
++if (base_fd >= 0)
++ {
++ char line[sizeof(pid_line)];
++ ssize_t len = -1;
++
++ if (fstat(base_fd, &sb) != 0 || !S_ISREG(sb.st_mode)) goto cleanup;
++ if ((sb.st_mode & 07777) != base_mode || sb.st_nlink != 1) goto cleanup;
++ if (sb.st_size < 2 || sb.st_size >= (off_t)sizeof(line)) goto cleanup;
++
++ len = read(base_fd, line, sizeof(line));
++ if (len != (ssize_t)sb.st_size) goto cleanup;
++ line[len] = '\0';
++
++ if (strspn(line, "0123456789") != (size_t)len-1) goto cleanup;
++ if (line[len-1] != '\n') goto cleanup;
++ lines_match = (len == pid_len && strcmp(line, pid_line) == 0);
++ }
++
++if (operation == PID_WRITE)
++ {
++ if (!lines_match)
++ {
++ if (base_fd >= 0)
++ {
++ int error = -1;
++ /* emulate unlinkat */
++ if (fchdir(dir_fd) != 0) goto cleanup;
++ error = unlink(base);
++ if (fchdir(cwd_fd) != 0)
++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno));
++ if (error) goto cleanup;
++ (void)close(base_fd);
++ base_fd = -1;
++ }
++ /* emulate openat */
++ if (fchdir(dir_fd) != 0) goto cleanup;
++ base_fd = open(base, O_WRONLY | O_CREAT | O_EXCL | base_flags, base_mode);
++ if (fchdir(cwd_fd) != 0)
++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno));
++ if (base_fd < 0) goto cleanup;
++ if (fchmod(base_fd, base_mode) != 0) goto cleanup;
++ if (write(base_fd, pid_line, pid_len) != pid_len) goto cleanup;
++ DEBUG(D_any) debug_printf("pid written to %s\n", pid_file_path);
++ }
++ }
++else
++ {
++ if (!lines_match) goto cleanup;
++ if (operation == PID_DELETE)
++ {
++ int error = -1;
++ /* emulate unlinkat */
++ if (fchdir(dir_fd) != 0) goto cleanup;
++ error = unlink(base);
++ if (fchdir(cwd_fd) != 0)
++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno));
++ if (error) goto cleanup;
++ }
++ }
++
++success = TRUE;
++errno = 0;
++
++cleanup:
++if (cwd_fd >= 0) (void)close(cwd_fd);
++if (dir_fd >= 0) (void)close(dir_fd);
++if (base_fd >= 0) (void)close(base_fd);
++return success;
++}
++
++
++/* Remove the daemon's pidfile. Note: runs with root privilege,
++as a direct child of the daemon. Does not return. */
++
++void
++delete_pid_file(void)
++{
++const BOOL success = operate_on_pid_file(PID_DELETE, getppid());
++
++DEBUG(D_any)
++ debug_printf("delete pid file %s %s: %s\n", pid_file_path,
++ success ? "success" : "failure", strerror(errno));
++
++exim_exit(EXIT_SUCCESS, US"");
++}
++
++
++/* Called by the daemon; exec a child to get the pid file deleted
++since we may require privs for the containing directory */
++
++static void
++daemon_die(void)
++{
++int pid;
++
++DEBUG(D_any) debug_printf("SIGTERM/SIGINT seen\n");
++#if defined(SUPPORT_TLS) && (defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT))
++tls_watch_invalidate();
++#endif
++
++if (f.running_in_test_harness || write_pid)
++ {
++ if ((pid = fork()) == 0)
++ {
++ if (override_pid_file_path)
++ (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, FALSE, 3,
++ "-oP", override_pid_file_path, "-oPX");
++ else
++ (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, FALSE, 1, "-oPX");
++
++ /* Control never returns here. */
++ }
++ if (pid > 0)
++ child_close(pid, 1);
++ }
++exim_exit(EXIT_SUCCESS, US"");
++}
++
++
+
+ /*************************************************
+ * Exim Daemon Mainline *
+ *************************************************/
+
+@@ -1538,32 +1726,18 @@ automatically. Consequently, Exim 4 writ
+
+ The variable daemon_write_pid is used to control this. */
+
+ if (f.running_in_test_harness || write_pid)
+ {
+- FILE *f;
+-
+- if (override_pid_file_path)
+- pid_file_path = override_pid_file_path;
+-
+- if (pid_file_path[0] == 0)
+- pid_file_path = string_sprintf("%s/exim-daemon.pid", spool_directory);
+-
+- if ((f = modefopen(pid_file_path, "wb", 0644)))
+- {
+- (void)fprintf(f, "%d\n", (int)getpid());
+- (void)fclose(f);
+- DEBUG(D_any) debug_printf("pid written to %s\n", pid_file_path);
+- }
+- else
+- DEBUG(D_any)
+- debug_printf("%s\n", string_open_failed(errno, "pid file %s",
+- pid_file_path));
++ const enum pid_op operation = (f.running_in_test_harness
++ || real_uid == root_uid
++ || (real_uid == exim_uid && !override_pid_file_path)) ? PID_WRITE : PID_CHECK;
++ if (!operate_on_pid_file(operation, getpid()))
++ DEBUG(D_any) debug_printf("%s pid file %s: %s\n", (operation == PID_WRITE) ? "write" : "check", pid_file_path, strerror(errno));
+ }
+
+ /* Set up the handler for SIGHUP, which causes a restart of the daemon. */
+-
+ sighup_seen = FALSE;
+ signal(SIGHUP, sighup_handler);
+
+ /* Give up root privilege at this point (assuming that exim_uid and exim_gid
+ are not root). The third argument controls the running of initgroups().
+--- a/src/exim.c
++++ b/src/exim.c
+@@ -3042,12 +3042,20 @@ for (i = 1; i < argc; i++)
+
+ else if (Ustrcmp(argrest, "o") == 0) {}
+
+ /* -oP <name>: set pid file path for daemon */
+
+- else if (Ustrcmp(argrest, "P") == 0)
+- override_pid_file_path = argv[++i];
++ else if (*argrest == 'P')
++ {
++ if (!f.running_in_test_harness && real_uid != root_uid && real_uid != exim_uid)
++ exim_fail("exim: only uid=%d or uid=%d can use -oP and -oPX "
++ "(uid=%d euid=%d | %d)\n",
++ root_uid, exim_uid, getuid(), geteuid(), real_uid);
++ if (Ustrcmp(argrest, "P") == 0) override_pid_file_path = argv[++i];
++ else if (Ustrcmp(argrest, "PX") == 0) delete_pid_file();
++ else badarg = TRUE;
++ }
+
+ /* -or <n>: set timeout for non-SMTP acceptance
+ -os <n>: set timeout for SMTP acceptance */
+
+ else if (*argrest == 'r' || *argrest == 's')
--- /dev/null
+From 47a48ed569503d8730bafcfd0f96d27cb72c9454 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Sat, 1 May 2021 11:21:22 +0200
+Subject: [PATCH 27/29] testsuite: adjustments for CVE-2020-28014,
+ CVE-2021-27216 (Arbitrary PID file creation)
+
+---
+ src/daemon.c | 32 --------------------------------
+ test/stderr/0433 | 24 ------------------------
+ 2 files changed, 56 deletions(-)
+
+diff --git a/src/daemon.c b/src/daemon.c
+index 9403472f3..7c15d148c 100644
+--- a/src/daemon.c
++++ b/src/daemon.c
+@@ -1044,38 +1044,6 @@ exim_exit(EXIT_SUCCESS, US"");
+ }
+
+
+-/* Called by the daemon; exec a child to get the pid file deleted
+-since we may require privs for the containing directory */
+-
+-static void
+-daemon_die(void)
+-{
+-int pid;
+-
+-DEBUG(D_any) debug_printf("SIGTERM/SIGINT seen\n");
+-#if defined(SUPPORT_TLS) && (defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT))
+-tls_watch_invalidate();
+-#endif
+-
+-if (f.running_in_test_harness || write_pid)
+- {
+- if ((pid = fork()) == 0)
+- {
+- if (override_pid_file_path)
+- (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, FALSE, 3,
+- "-oP", override_pid_file_path, "-oPX");
+- else
+- (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, FALSE, 1, "-oPX");
+-
+- /* Control never returns here. */
+- }
+- if (pid > 0)
+- child_close(pid, 1);
+- }
+-exim_exit(EXIT_SUCCESS, US"");
+-}
+-
+-
+
+ /*************************************************
+ * Exim Daemon Mainline *
+--
+2.30.2
+
--- /dev/null
+From 5220dc30120bd79319d465bd7a6e4b21a0881f9a Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Fri, 30 Apr 2021 10:47:45 +0200
+Subject: [PATCH 29/29] Fix BDAT issue for body w/o trailing CRLF (again Bug
+ 1974)
+
+(cherry picked from commit 919111edac911ba9c15422eafd7c5bf14d416d26)
+---
+ src/smtp_in.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/src/smtp_in.c b/src/smtp_in.c
+index 016c44c0f..76784c15f 100644
+--- a/src/smtp_in.c
++++ b/src/smtp_in.c
+@@ -854,6 +854,7 @@ int
+ bdat_ungetc(int ch)
+ {
+ chunking_data_left++;
++bdat_push_receive_functions(); /* we're not done yet, calling push is safe, because it checks the state before pushing anything */
+ return lwr_receive_ungetc(ch);
+ }
+
+--
+2.30.2
+
78_02-Fix-buffer-overflow-in-string_vformat.-Bug-2449.patch
79_01-Fix-SPA-authenticator-checking-client-supplied-data-.patch
79_02-Rework-SPA-fix-to-avoid-overflows.-Bug-2571.patch
+80_01-GnuTLS-fix-hanging-callout-connections.patch
+80_02-GnuTLS-tls_write-wait-after-uncorking-the-session.patch
+80_03-GnuTLS-Do-not-care-about-corked-data-when-uncorking.patch
+82_TLS-use-RFC-6125-rules-for-certifucate-name-checks-w.patch
+84_01-CVE-2020-28025-Heap-out-of-bounds-read-in-pdkim_fini.patch
+84_02-CVE-2020-28018-Use-after-free-in-tls-openssl.c.patch
+84_03-CVE-2020-28023-Out-of-bounds-read-in-smtp_setup_msg.patch
+84_04-CVE-2020-28010-Heap-out-of-bounds-write-in-main.patch
+84_05-CVE-2020-28011-Heap-buffer-overflow-in-queue_run.patch
+84_06-CVE-2020-28013-Heap-buffer-overflow-in-parse_fix_phr.patch
+84_07-Security-Refuse-negative-and-large-store-allocations.patch
+84_08-CVE-2020-28017-Integer-overflow-in-receive_add_recip.patch
+84_09-CVE-2020-28022-Heap-out-of-bounds-read-and-write-in-.patch
+84_10-CVE-2020-28026-Line-truncation-and-injection-in-spoo.patch
+84_11-CVE-2020-28015-28021-New-line-injection-into-spool-h.patch
+84_12-CVE-2020-28009-Integer-overflow-in-get_stdinput.patch
+84_13-CVE-2020-28024-Heap-buffer-underflow-in-smtp_ungetc.patch
+84_14-CVE-2020-28012-Missing-close-on-exec-flag-for-privil.patch
+84_15-Security-Safeguard-against-relative-names-for-msglog.patch
+84_16-Security-Check-overrun-rcpt_count-integer.patch
+84_17-Security-Always-exit-when-LOG_PANIC_DIE-is-set.patch
+84_18-Security-Fix-off-by-one-in-smtp-transport-read-respo.patch
+84_19-Security-Avoid-decrement-of-dkim_collect_input-if-al.patch
+84_20-Security-Leave-a-clean-smtp_out-input-buffer-even-in.patch
+84_21-Security-Avoid-modification-of-constant-data-in-dkim.patch
+84_22-CVE-2020-28019-Failure-to-reset-function-pointer-aft.patch
+84_23-CVE-2020-28007-Link-attack-in-Exim-s-log-directory.patch
+84_24-CVE-2020-28008-Assorted-attacks-in-Exim-s-spool-dire.patch
+84_26-CVE-2020-28014-CVE-2021-27216-Arbitrary-PID-file-cre.patch
+84_27-testsuite-adjustments-for-CVE-2020-28014-CVE-2021-27.patch
+84_29-Fix-BDAT-issue-for-body-w-o-trailing-CRLF-again-Bug-.patch
90_localscan_dlopen.dpatch