summaryrefslogtreecommitdiffstats
path: root/debian/patches
diff options
context:
space:
mode:
Diffstat (limited to 'debian/patches')
-rwxr-xr-xdebian/patches/31_eximmanpage.dpatch250
-rw-r--r--debian/patches/32_exim4.dpatch106
-rwxr-xr-xdebian/patches/33_eximon.binary.dpatch18
-rwxr-xr-xdebian/patches/34_eximstatsmanpage.dpatch20
-rwxr-xr-xdebian/patches/35_install.dpatch49
-rwxr-xr-xdebian/patches/60_convert4r4.dpatch41
-rw-r--r--debian/patches/67_unnecessaryCopt.diff69
-rwxr-xr-xdebian/patches/70_remove_exim-users_references.dpatch37
-rw-r--r--debian/patches/75_01-Fix-json-extract-operator-for-unfound-case.patch69
-rw-r--r--debian/patches/75_02-Fix-transport-buffer-size-handling.patch52
-rw-r--r--debian/patches/75_03-Fix-info-on-using-local_scan-in-the-default-Makefile.patch42
-rw-r--r--debian/patches/75_04-GnuTLS-Fix-client-detection-of-server-reject-of-clie.patch420
-rw-r--r--debian/patches/75_05-Fix-expansions-for-RFC-822-addresses-having-comments.patch91
-rw-r--r--debian/patches/75_06-Docs-Add-note-on-lsearch-for-IPv4-mapped-IPv6-addres.patch48
-rw-r--r--debian/patches/75_07-Fix-crash-from-SRV-lookup-hitting-a-CNAME.patch69
-rw-r--r--debian/patches/75_08-Logging-fix-initial-listening-on-log-line.patch206
-rw-r--r--debian/patches/75_09-OpenSSL-Fix-aggregation-of-messages.patch127
-rw-r--r--debian/patches/75_10-Harden-plaintext-authenticator.patch55
-rw-r--r--debian/patches/75_11-GnuTLS-fix-tls_out_ocsp-under-hosts_request_ocsp.patch54
-rw-r--r--debian/patches/75_12-GnuTLS-fix-the-advertising-of-acceptable-certs-by-th.patch42
-rw-r--r--debian/patches/75_13-Use-dsn_from-for-success-DSN-messages.-Bug-2404.patch52
-rw-r--r--debian/patches/75_14-Fix-smtp-response-timeout.patch325
-rw-r--r--debian/patches/75_15-Fix-detection-of-32b-platform-at-build-time.-Bug-240.patch48
-rw-r--r--debian/patches/77_Avoid-re-expansion-in-sort-CVE-2019-13917-OVE-201907.patch394
-rw-r--r--debian/patches/78_01-string.c-do-not-interpret-before-0-CVE-2019-15846.patch50
-rw-r--r--debian/patches/78_02-Fix-buffer-overflow-in-string_vformat.-Bug-2449.patch36
-rw-r--r--debian/patches/79_01-Fix-SPA-authenticator-checking-client-supplied-data-.patch74
-rw-r--r--debian/patches/79_02-Rework-SPA-fix-to-avoid-overflows.-Bug-2571.patch59
-rw-r--r--debian/patches/80_01-GnuTLS-fix-hanging-callout-connections.patch83
-rw-r--r--debian/patches/80_02-GnuTLS-tls_write-wait-after-uncorking-the-session.patch73
-rw-r--r--debian/patches/80_03-GnuTLS-Do-not-care-about-corked-data-when-uncorking.patch55
-rw-r--r--debian/patches/82_TLS-use-RFC-6125-rules-for-certifucate-name-checks-w.patch188
-rw-r--r--debian/patches/84_01-CVE-2020-28025-Heap-out-of-bounds-read-in-pdkim_fini.patch44
-rw-r--r--debian/patches/84_02-CVE-2020-28018-Use-after-free-in-tls-openssl.c.patch33
-rw-r--r--debian/patches/84_03-CVE-2020-28023-Out-of-bounds-read-in-smtp_setup_msg.patch58
-rw-r--r--debian/patches/84_04-CVE-2020-28010-Heap-out-of-bounds-write-in-main.patch42
-rw-r--r--debian/patches/84_05-CVE-2020-28011-Heap-buffer-overflow-in-queue_run.patch39
-rw-r--r--debian/patches/84_06-CVE-2020-28013-Heap-buffer-overflow-in-parse_fix_phr.patch34
-rw-r--r--debian/patches/84_07-Security-Refuse-negative-and-large-store-allocations.patch74
-rw-r--r--debian/patches/84_08-CVE-2020-28017-Integer-overflow-in-receive_add_recip.patch49
-rw-r--r--debian/patches/84_09-CVE-2020-28022-Heap-out-of-bounds-read-and-write-in-.patch61
-rw-r--r--debian/patches/84_10-CVE-2020-28026-Line-truncation-and-injection-in-spoo.patch102
-rw-r--r--debian/patches/84_11-CVE-2020-28015-28021-New-line-injection-into-spool-h.patch69
-rw-r--r--debian/patches/84_12-CVE-2020-28009-Integer-overflow-in-get_stdinput.patch61
-rw-r--r--debian/patches/84_13-CVE-2020-28024-Heap-buffer-underflow-in-smtp_ungetc.patch41
-rw-r--r--debian/patches/84_14-CVE-2020-28012-Missing-close-on-exec-flag-for-privil.patch31
-rw-r--r--debian/patches/84_15-Security-Safeguard-against-relative-names-for-msglog.patch41
-rw-r--r--debian/patches/84_16-Security-Check-overrun-rcpt_count-integer.patch39
-rw-r--r--debian/patches/84_17-Security-Always-exit-when-LOG_PANIC_DIE-is-set.patch24
-rw-r--r--debian/patches/84_18-Security-Fix-off-by-one-in-smtp-transport-read-respo.patch47
-rw-r--r--debian/patches/84_19-Security-Avoid-decrement-of-dkim_collect_input-if-al.patch59
-rw-r--r--debian/patches/84_20-Security-Leave-a-clean-smtp_out-input-buffer-even-in.patch67
-rw-r--r--debian/patches/84_21-Security-Avoid-modification-of-constant-data-in-dkim.patch89
-rw-r--r--debian/patches/84_22-CVE-2020-28019-Failure-to-reset-function-pointer-aft.patch135
-rw-r--r--debian/patches/84_23-CVE-2020-28007-Link-attack-in-Exim-s-log-directory.patch542
-rw-r--r--debian/patches/84_24-CVE-2020-28008-Assorted-attacks-in-Exim-s-spool-dire.patch205
-rw-r--r--debian/patches/84_26-CVE-2020-28014-CVE-2021-27216-Arbitrary-PID-file-cre.patch303
-rw-r--r--debian/patches/84_27-testsuite-adjustments-for-CVE-2020-28014-CVE-2021-27.patch57
-rw-r--r--debian/patches/84_29-Fix-BDAT-issue-for-body-w-o-trailing-CRLF-again-Bug-.patch26
-rw-r--r--debian/patches/90_localscan_dlopen.dpatch281
-rw-r--r--debian/patches/series60
61 files changed, 6015 insertions, 0 deletions
diff --git a/debian/patches/31_eximmanpage.dpatch b/debian/patches/31_eximmanpage.dpatch
new file mode 100755
index 0000000..b8f8bf6
--- /dev/null
+++ b/debian/patches/31_eximmanpage.dpatch
@@ -0,0 +1,250 @@
+Description: We ship the binary as exim4 instead of exim, fix manpage
+ accordingly.
+Author: Marc Haber <mh+debian-packages@zugschlus.de>,
+ Andreas Metzler <ametzler@bebt.de>
+Last-Update: 2018-12-31
+Forwarded: not-needed (upstream uses the "exim" name)
+
+--- a/doc/exim.8
++++ b/doc/exim.8
+@@ -1,9 +1,9 @@
+-.TH EXIM 8
++.TH EXIM4 8
+ .SH NAME
+-exim \- a Mail Transfer Agent
++exim4 \- a Mail Transfer Agent
+ .SH SYNOPSIS
+ .nf
+-.B exim [options] arguments ...
++.B exim4 [options] arguments ...
+ .B mailq [options] arguments ...
+ .B rsmtp [options] arguments ...
+ .B rmail [options] arguments ...
+@@ -40,7 +40,7 @@ local message on the standard input, wit
+ recipients) is assumed. Thus, for example, if Exim is installed in
+ \fI/usr/sbin\fP, you can send a message from the command line like this:
+ .sp
+- /usr/sbin/exim -i <recipient-address(es)>
++ /usr/sbin/exim4 -i <recipient-address(es)>
+ <message content, including all the header lines>
+ CTRL-D
+ .sp
+@@ -125,8 +125,8 @@ ports, on multiple ports, and only on sp
+ .sp
+ When a listening daemon
+ is started without the use of \fB\-oX\fP (that is, without overriding the normal
+-configuration), it writes its process id to a file called exim\-daemon.pid
+-in Exim's spool directory. This location can be overridden by setting
++configuration), it writes its process id to a file called
++/var/run/exim4/exim.pid. This location can be overridden by setting
+ PID_FILE_PATH in Local/Makefile. The file is written while Exim is still
+ running as root.
+ .sp
+@@ -180,7 +180,7 @@ available to admin users.
+ This option operates like \fB\-be\fP except that it must be followed by the name
+ of a file. For example:
+ .sp
+- exim \-bem /tmp/testmessage
++ exim4 \-bem /tmp/testmessage
+ .sp
+ The file is read as a message (as if receiving a locally\-submitted non\-SMTP
+ message) before any of the test expansions are done. Thus, message\-specific
+@@ -206,7 +206,7 @@ If you want to test a system filter file
+ can use both \fB\-bF\fP and \fB\-bf\fP on the same command, in order to test a system
+ filter and a user filter in the same run. For example:
+ .sp
+- exim \-bF /system/filter \-bf /user/filter </test/message
++ exim4 \-bF /system/filter \-bf /user/filter </test/message
+ .sp
+ This is helpful when the system filter adds header lines or sets filter
+ variables that are used by the user filter.
+@@ -258,8 +258,8 @@ This option runs a fake SMTP session as
+ standard input and output. The IP address may include a port number at the end,
+ after a full stop. For example:
+ .sp
+- exim \-bh 10.9.8.7.1234
+- exim \-bh fe80::a00:20ff:fe86:a061.5678
++ exim4 \-bh 10.9.8.7.1234
++ exim4 \-bh fe80::a00:20ff:fe86:a061.5678
+ .sp
+ When an IPv6 address is given, it is converted into canonical form. In the case
+ of the second example above, the value of \fI$sender_host_address\fP after
+@@ -417,7 +417,7 @@ main configuration options to be written
+ of one or more specific options can be requested by giving their names as
+ arguments, for example:
+ .sp
+- exim \-bP qualify_domain hold_domains
++ exim4 \-bP qualify_domain hold_domains
+ .sp
+ However, any option setting that is preceded by the word "hide" in the
+ configuration file is not shown in full, except to an admin user. For other
+@@ -445,7 +445,7 @@ written directly into the spool director
+ .sp
+ If \fB\-bP\fP is followed by a name preceded by +, for example,
+ .sp
+- exim \-bP +local_domains
++ exim4 \-bP +local_domains
+ .sp
+ it searches for a matching named list of any type (domain, host, address, or
+ local part) and outputs what it finds.
+@@ -454,7 +454,7 @@ If one of the words \fBrouter\fP, \fBtra
+ followed by the name of an appropriate driver instance, the option settings for
+ that driver are output. For example:
+ .sp
+- exim \-bP transport local_delivery
++ exim4 \-bP transport local_delivery
+ .sp
+ The generic driver options are output first, followed by the driver's private
+ options. A list of the names of drivers of a particular type can be obtained by
+@@ -539,7 +539,7 @@ This option is for testing retry rules,
+ arguments. It causes Exim to look for a retry rule that matches the values
+ and to write it to the standard output. For example:
+ .sp
+- exim \-brt bach.comp.mus.example
++ exim4 \-brt bach.comp.mus.example
+ Retry rule: *.comp.mus.example F,2h,15m; F,4d,30m;
+ .sp
+ The first
+@@ -552,7 +552,7 @@ rule is found that matches the host, one
+ sought. Finally, an argument that is the name of a specific delivery error, as
+ used in setting up retry rules, can be given. For example:
+ .sp
+- exim \-brt haydn.comp.mus.example quota_3d
++ exim4 \-brt haydn.comp.mus.example quota_3d
+ Retry rule: *@haydn.comp.mus.example quota_3d F,1h,15m
+ .TP 10
+ \fB\-brw\fP
+@@ -655,7 +655,7 @@ doing such tests.
+ .TP 10
+ \fB\-bV\fP
+ This option causes Exim to write the current version number, compilation
+-number, and compilation date of the \fIexim\fP binary to the standard output.
++number, and compilation date of the \fIexim4\fP binary to the standard output.
+ It also lists the DBM library that is being used, the optional modules (such as
+ specific lookup types), the drivers that are included in the binary, and the
+ name of the runtime configuration file that is in use.
+@@ -683,7 +683,7 @@ If no arguments are given, Exim runs in
+ right angle bracket for addresses to be verified.
+ .sp
+ Unlike the \fB\-be\fP test option, you cannot arrange for Exim to use the
+-readline() function, because it is running as \fIexim\fP and there are
++readline() function, because it is running as \fIexim4\fP and there are
+ security issues.
+ .sp
+ Verification differs from address testing (the \fB\-bt\fP option) in that routers
+@@ -796,14 +796,14 @@ command line item. \fB\-D\fP can be used
+ string, in which case the equals sign is optional. These two commands are
+ synonymous:
+ .sp
+- exim \-DABC ...
+- exim \-DABC= ...
++ exim4 \-DABC ...
++ exim4 \-DABC= ...
+ .sp
+ To include spaces in a macro definition item, quotes must be used. If you use
+ quotes, spaces are permitted around the macro name and the equals sign. For
+ example:
+ .sp
+- exim '\-D ABC = something' ...
++ exim4 '\-D ABC = something' ...
+ .sp
+ \fB\-D\fP may be repeated up to 10 times on a command line.
+ Only macro names up to 22 letters long can be set.
+@@ -938,8 +938,8 @@ never provoke a bounce. An empty sender
+ string, or as a pair of angle brackets with nothing between them, as in these
+ examples of shell commands:
+ .sp
+- exim \-f '<>' user@domain
+- exim \-f "" user@domain
++ exim4 \-f '<>' user@domain
++ exim4 \-f "" user@domain
+ .sp
+ In addition, the use of \fB\-f\fP is not restricted when testing a filter file
+ with \fB\-bf\fP or when testing or verifying addresses using the \fB\-bt\fP or
+@@ -1315,12 +1315,12 @@ other circumstances, they are ignored un
+ The \fB\-oMa\fP option sets the sender host address. This may include a port
+ number at the end, after a full stop (period). For example:
+ .sp
+- exim \-bs \-oMa 10.9.8.7.1234
++ exim4 \-bs \-oMa 10.9.8.7.1234
+ .sp
+ An alternative syntax is to enclose the IP address in square brackets,
+ followed by a colon and the port number:
+ .sp
+- exim \-bs \-oMa [10.9.8.7]:1234
++ exim4 \-bs \-oMa [10.9.8.7]:1234
+ .sp
+ The IP address is placed in the \fI$sender_host_address\fP variable, and the
+ port, if present, in \fI$sender_host_port\fP. If both \fB\-oMa\fP and \fB\-bh\fP
+@@ -1526,22 +1526,22 @@ If other commandline options specify an
+ will specify a queue to operate on.
+ For example:
+ .sp
+- exim \-bp \-qGquarantine
++ exim4 \-bp \-qGquarantine
+ mailq \-qGquarantine
+- exim \-qGoffpeak \-Rf @special.domain.example
++ exim4 \-qGoffpeak \-Rf @special.domain.example
+ .TP 10
+ \fB\-q\fP<\fIqflags\fP> <\fIstart id\fP> <\fIend id\fP>
+ When scanning the queue, Exim can be made to skip over messages whose ids are
+ lexically less than a given value by following the \fB\-q\fP option with a
+ starting message id. For example:
+ .sp
+- exim \-q 0t5C6f\-0000c8\-00
++ exim4 \-q 0t5C6f\-0000c8\-00
+ .sp
+ Messages that arrived earlier than 0t5C6f\-0000c8\-00 are not inspected. If a
+ second message id is given, messages whose ids are lexically greater than it
+ are also skipped. If the same id is given twice, for example,
+ .sp
+- exim \-q 0t5C6f\-0000c8\-00 0t5C6f\-0000c8\-00
++ exim4 \-q 0t5C6f\-0000c8\-00 0t5C6f\-0000c8\-00
+ .sp
+ just one delivery process is started, for that message. This differs from
+ \fB\-M\fP in that retry data is respected, and it also differs from \fB\-Mc\fP in
+@@ -1557,7 +1557,7 @@ starting a queue runner process at inter
+ single daemon process handles both functions. A common way of starting up a
+ combined daemon at system boot time is to use a command such as
+ .sp
+- /usr/exim/bin/exim \-bd \-q30m
++ /usr/sbin/exim4 \-bd \-q30m
+ .sp
+ Such a daemon listens for incoming SMTP calls, and also starts a queue runner
+ process every 30 minutes.
+@@ -1588,7 +1588,7 @@ regular expression; otherwise it is a li
+ If you want to do periodic queue runs for messages with specific recipients,
+ you can combine \fB\-R\fP with \fB\-q\fP and a time value. For example:
+ .sp
+- exim \-q25m \-R @special.domain.example
++ exim4 \-q25m \-R @special.domain.example
+ .sp
+ This example does a queue run for messages with recipients in the given domain
+ every 25 minutes. Any additional flags that are specified with \fB\-q\fP are
+@@ -1704,6 +1704,26 @@ under most shells.
+ .sp
+ .
+ .SH "SEE ALSO"
++.BR exicyclog (8),
++.BR exigrep (8),
++.BR exim_checkaccess (8),
++.BR exim_convert4r4 (8),
++.BR exim_db (8),
++.BR exim_dbmbuild (8),
++.BR exim_lock (8),
++.BR eximon (8),
++.BR exinext (8),
++.BR exiqgrep (8),
++.BR exiqsumm (8),
++.BR exiwhat (8),
++.BR update\-exim4.conf (8),
++.BR update\-exim4defaults (8),
++/usr/share/doc/exim4\-base/,
++/usr/share/doc/exim4\-base/README.Debian.[gz|html].
+ .rs
+ .sp
+ The full Exim specification, the Exim book, and the Exim wiki.
++
++.SH AUTHOR
++This manual page was provided with the upstream Exim source package.
++It was enhanced for the Debian GNU/Linux system.
diff --git a/debian/patches/32_exim4.dpatch b/debian/patches/32_exim4.dpatch
new file mode 100644
index 0000000..967869d
--- /dev/null
+++ b/debian/patches/32_exim4.dpatch
@@ -0,0 +1,106 @@
+Description: Accommodate source for installing exim as exim4.
+Author: Andreas Metzler <ametzler@debian.org>
+Origin: vendor
+Forwarded: not-needed
+Last-Update: 2018-12-12
+
+--- a/OS/Makefile-Linux
++++ b/OS/Makefile-Linux
+@@ -28,9 +28,9 @@ XLFLAGS=-L$(X11)/lib
+ X11_LD_LIB=$(X11)/lib
+
+ EXIWHAT_PS_ARG=ax
+-EXIWHAT_EGREP_ARG='/exim( |$$)'
++EXIWHAT_EGREP_ARG='/exim4( |$$)'
+ EXIWHAT_MULTIKILL_CMD=killall
+-EXIWHAT_MULTIKILL_ARG=exim
++EXIWHAT_MULTIKILL_ARG=exim4
+ EXIWHAT_KILL_SIGNAL=-USR1
+
+ # End
+--- a/src/exicyclog.src
++++ b/src/exicyclog.src
+@@ -149,7 +149,7 @@ done
+
+ st=' '
+ exim_path=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"`
+-if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim; fi
++if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim4; fi
+
+ spool_directory=`$exim_path -C $config -bP spool_directory | sed 's/.*=[ ]*//'`
+
+--- a/src/exim_checkaccess.src
++++ b/src/exim_checkaccess.src
+@@ -52,7 +52,7 @@ done
+ # a tab to keep the tab in one place.
+
+ exim_path=`perl -ne 'chop;if (/^\s*exim_path\s*=\s*(.*)/){print "$1\n";last;}' $config`
+-if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim; fi
++if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim4; fi
+
+
+ #########################################################################
+--- a/src/eximon.src
++++ b/src/eximon.src
+@@ -79,7 +79,7 @@ config=${EXIMON_EXIM_CONFIG-$config}
+
+ st=' '
+ EXIM_PATH=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"`
+-if test "$EXIM_PATH" = ""; then EXIM_PATH=BIN_DIRECTORY/exim; fi
++if test "$EXIM_PATH" = ""; then EXIM_PATH=BIN_DIRECTORY/exim4; fi
+
+ SPOOL_DIRECTORY=`$EXIM_PATH -C $config -bP spool_directory | sed 's/.*=[ ]*//'`
+ LOG_FILE_PATH=`$EXIM_PATH -C $config -bP log_file_path | sed 's/.*=[ ]*//'`
+--- a/src/exinext.src
++++ b/src/exinext.src
+@@ -97,7 +97,7 @@ if [ "$exim_path" = "" ]; then
+ exim_path=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"`
+ fi
+
+-if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim; fi
++if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim4; fi
+ spool_directory=`$exim_path $eximmacdef -C $config -bP spool_directory | sed 's/.*=[ ]*//'`
+ qualify_domain=`$exim_path $eximmacdef -C $config -bP qualify_domain | sed 's/.*=[ ]*//'`
+
+@@ -181,7 +181,7 @@ perl - $exim_path "$eximmacdef" $argone
+
+ # Run exim_dumpdb to get out the retry data and pick off what we want
+
+- open(DATA, "${exim}_dumpdb $spool retry |") ||
++ open(DATA, "/usr/sbin/exim_dumpdb $spool retry |") ||
+ die "can't run exim_dumpdb";
+
+ while (<DATA>)
+--- a/src/exiqgrep.src
++++ b/src/exiqgrep.src
+@@ -24,7 +24,7 @@ use Getopt::Std;
+ use File::Basename;
+
+ # Have this variable point to your exim binary.
+-my $exim = 'BIN_DIRECTORY/exim';
++my $exim = 'BIN_DIRECTORY/exim4';
+ my $eargs = '-bpu';
+ my %id;
+ my %opt;
+--- a/src/exiwhat.src
++++ b/src/exiwhat.src
+@@ -98,7 +98,7 @@ fi
+
+ st=' '
+ exim_path=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"`
+-if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim; fi
++if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim4; fi
+ spool_directory=`$exim_path -C $config -bP spool_directory | sed "s/.*=[ ]*//"`
+ process_log_path=`$exim_path -C $config -bP process_log_path | sed "s/.*=[ ]*//"`
+
+--- a/src/globals.c
++++ b/src/globals.c
+@@ -906,7 +906,7 @@ const uschar *event_name = NULL;
+
+
+ gid_t exim_gid = EXIM_GID;
+-uschar *exim_path = US BIN_DIRECTORY "/exim"
++uschar *exim_path = US BIN_DIRECTORY "/exim4"
+ "\0<---------------Space to patch exim_path->";
+ uid_t exim_uid = EXIM_UID;
+ int expand_level = 0; /* Nesting depth, indent for debug */
diff --git a/debian/patches/33_eximon.binary.dpatch b/debian/patches/33_eximon.binary.dpatch
new file mode 100755
index 0000000..bc78cdc
--- /dev/null
+++ b/debian/patches/33_eximon.binary.dpatch
@@ -0,0 +1,18 @@
+#! /bin/sh /usr/share/dpatch/dpatch-run
+## 33_eximon.binary.dpatch by Andreas Piesk
+##
+## All lines beginning with `## DP:' are a description of the patch.
+## DP: eximon.bin is installed in /usr/lib/exim4/ and not in path.
+
+diff -NurBbp exim-4.80.orig/OS/eximon.conf-Default exim-4.80/OS/eximon.conf-Default
+--- exim-4.80.orig/OS/eximon.conf-Default 2012-05-18 05:04:36.000000000 +0200
++++ exim-4.80/OS/eximon.conf-Default 2012-05-18 19:10:59.000000000 +0200
+@@ -5,7 +5,7 @@
+ # The name of the eximon binary, usually the same as the eximon script,
+ # with .bin stuck on the end.
+
+-EXIMON_BINARY="${EXIMON_BINARY-$0.bin}"
++EXIMON_BINARY="/usr/lib/exim4/${EXIMON_BINARY-${0##*/}.bin}"
+
+ # The remaining parameters are values likely to be changed to suit the
+ # user's taste. They are documented in the EDITME file.
diff --git a/debian/patches/34_eximstatsmanpage.dpatch b/debian/patches/34_eximstatsmanpage.dpatch
new file mode 100755
index 0000000..3245965
--- /dev/null
+++ b/debian/patches/34_eximstatsmanpage.dpatch
@@ -0,0 +1,20 @@
+Description: Add note about installing perl-modules on Debian to
+ generated manpage
+Author: Andreas Metzler <ametzler@debian.org>
+Origin: vendor
+Forwarded: not-needed
+Last-Update: 2013-09-28
+
+--- exim4-4.82~rc1.orig/src/eximstats.src
++++ exim4-4.82~rc1/src/eximstats.src
+@@ -501,6 +501,10 @@ To install these, download and unpack th
+ make test
+ make install
+
++On B<Debian GNU/Linux> you can use
++C<apt-get install libgd-perl libgd-text-perl libgd-graph-perl>
++instead.
++
+ =item B<-chartdir>I <dir>
+
+ Create the charts in the directory <dir>
diff --git a/debian/patches/35_install.dpatch b/debian/patches/35_install.dpatch
new file mode 100755
index 0000000..d0ab602
--- /dev/null
+++ b/debian/patches/35_install.dpatch
@@ -0,0 +1,49 @@
+Description: Exim's installation scripts install the binary as
+ exim-<version> - disable this feature.
+Author: Andreas Metzler <ametzler@debian.org>
+Origin: vendor
+Forwarded: not-needed
+Last-Update: 2016-09-25
+
+--- a/scripts/exim_install
++++ b/scripts/exim_install
+@@ -221,6 +221,8 @@ while [ $# -gt 0 ]; do
+ version=exim-`$exim 2>/dev/null | \
+ awk '/Exim version/ { OFS=""; print $3,"-",substr($4,2,length($4)-1) }'`${EXE}
+
++ version=exim
++
+ if [ "${version}" = "exim-${EXE}" ]; then
+ echo $com ""
+ echo $com "*** Could not run $exim to find version number ***"
+@@ -370,10 +372,8 @@ done
+
+
+
+-# If there is no configuration file, install the default, modifying it to refer
+-# to the configured system aliases file. If there is no setting for
+-# SYSTEM_ALIASES_FILE, use the traditional /etc/aliases. If the file does not
+-# exist, install a default (dummy) for that too.
++# Install default configuration file
++# This is a local Debian modification.
+
+ # However, if CONFIGURE_FILE specifies a list of files, skip this code.
+
+@@ -396,7 +396,7 @@ elif [ ! -f ${CONFIGURE_FILE} ]; then
+ ${real} ${MKDIR} -p `${DIRNAME} ${CONFIGURE_FILE}`
+
+ echo sed -e '\\'
+- echo " \"/SYSTEM_ALIASES_FILE/ s'SYSTEM_ALIASES_FILE'${ACTUAL_SYSTEM_ALIASES_FILE}'\"" '\\'
++ echo " \"/SYSTEM_ALIASES_FILE/ s'SYSTEM_ALIASES_FILE'/etc/aliases'\"" '\\'
+ echo " ../src/configure.default > \${CONFIGURE_FILE}"
+
+ # I can't find a way of writing this using the ${real} feature because
+@@ -405,7 +405,7 @@ elif [ ! -f ${CONFIGURE_FILE} ]; then
+
+ if [ "$real" = "" ] ; then
+ sed -e \
+- "/SYSTEM_ALIASES_FILE/ s'SYSTEM_ALIASES_FILE'${ACTUAL_SYSTEM_ALIASES_FILE}'" \
++ "/SYSTEM_ALIASES_FILE/ s'SYSTEM_ALIASES_FILE'/etc/aliases'" \
+ ../src/configure.default > ${CONFIGURE_FILE}
+ else
+ true
diff --git a/debian/patches/60_convert4r4.dpatch b/debian/patches/60_convert4r4.dpatch
new file mode 100755
index 0000000..290b913
--- /dev/null
+++ b/debian/patches/60_convert4r4.dpatch
@@ -0,0 +1,41 @@
+Description: Add a warning message to convert4r4
+Author: Marc Haber <mh+debian-packages@zugschlus.de>
+Origin: vendor
+Forwarded: no
+Last-Update: 2013-09-28
+
+--- a/src/convert4r4.src
++++ b/src/convert4r4.src
+@@ -666,6 +666,32 @@ return defined $main{$_[0]} && $main{$_[
+
+ print STDERR "Runtime configuration file converter for Exim release 4.\n";
+
++if( !defined $ENV{"CONVERT4R4"} || $ENV{"CONVERT4R4"} ne "I understand this is an unsupported tool" ) {
++
++ print STDERR <<EOF;
++convert4r4 on Debian GNU/Linux deprecated
++
++This tool is unsupported by upstream and discouraged by the Debian Exim 4
++maintainers. It has multiple known bugs, and you need to manually
++review its output after using it anyway. Please seriously consider complete
++manual regeneration of the Exim 4 configuration, preferably by using the new
++Debconf interface to Exim 4.
++
++If you decide to ignore this advice and to use this script anyway,
++setting the environment variable CONVERT4R4 to the value
++\"I understand this is an unsupported tool\"
++will allow you to run the script. If you find bugs, you get to keep
++the pieces. Please do not file bugs against this script in the Debian
++BTS without providing a patch fixing the bugs, and please do not
++expect the upstream exim-users mailing list to answer questions.
++
++Kind regards
++the Debian Exim4 Maintainers
++EOF
++
++ exit 1;
++}
++
+ $transport_start = $director_start = $router_start = $retry_start
+ = $rewrite_start = $auth_start = 999999;
+
diff --git a/debian/patches/67_unnecessaryCopt.diff b/debian/patches/67_unnecessaryCopt.diff
new file mode 100644
index 0000000..daae1e4
--- /dev/null
+++ b/debian/patches/67_unnecessaryCopt.diff
@@ -0,0 +1,69 @@
+Description: Stop using exim's -C option in utility scripts (exiwhat
+ et al.) since this breaks with ALT_CONFIG_PREFIX.
+Author: Andreas Metzler <ametzler@bebt.de>
+Forwarded: http://bugs.exim.org/show_bug.cgi?id=1045
+Last-Update: 2018-12-31
+
+--- a/src/exicyclog.src
++++ b/src/exicyclog.src
+@@ -151,10 +151,10 @@ st=' '
+ exim_path=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"`
+ if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim4; fi
+
+-spool_directory=`$exim_path -C $config -bP spool_directory | sed 's/.*=[ ]*//'`
++spool_directory=`$exim_path -bP spool_directory | sed 's/.*=[ ]*//'`
+
+ if [ "$log_file_path" = "" ] ; then
+- log_file_path=`$exim_path -C $config -bP log_file_path | sed 's/.*=[ ]*//'`
++ log_file_path=`$exim_path -bP log_file_path | sed 's/.*=[ ]*//'`
+ fi
+
+ # If log_file_path contains only "syslog" then no Exim log files are in use.
+--- a/src/eximon.src
++++ b/src/eximon.src
+@@ -81,8 +81,8 @@ st=' '
+ EXIM_PATH=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"`
+ if test "$EXIM_PATH" = ""; then EXIM_PATH=BIN_DIRECTORY/exim4; fi
+
+-SPOOL_DIRECTORY=`$EXIM_PATH -C $config -bP spool_directory | sed 's/.*=[ ]*//'`
+-LOG_FILE_PATH=`$EXIM_PATH -C $config -bP log_file_path | sed 's/.*=[ ]*//'`
++SPOOL_DIRECTORY=`$EXIM_PATH -bP spool_directory | sed 's/.*=[ ]*//'`
++LOG_FILE_PATH=`$EXIM_PATH -bP log_file_path | sed 's/.*=[ ]*//'`
+
+ # If log_file_path is "syslog" then logging is only to syslog, and the monitor
+ # is unable to display a log tail unless EXIMON_LOG_FILE_PATH is set to tell
+--- a/src/exinext.src
++++ b/src/exinext.src
+@@ -98,8 +98,8 @@ if [ "$exim_path" = "" ]; then
+ fi
+
+ if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim4; fi
+-spool_directory=`$exim_path $eximmacdef -C $config -bP spool_directory | sed 's/.*=[ ]*//'`
+-qualify_domain=`$exim_path $eximmacdef -C $config -bP qualify_domain | sed 's/.*=[ ]*//'`
++spool_directory=`$exim_path $eximmacdef -bP spool_directory | sed 's/.*=[ ]*//'`
++qualify_domain=`$exim_path $eximmacdef -bP qualify_domain | sed 's/.*=[ ]*//'`
+
+ # Now do the job. Perl uses $ so frequently that we don't want to have to
+ # escape them all from the shell, so pass in shell variable values as
+@@ -144,7 +144,7 @@ perl - $exim_path "$eximmacdef" $argone
+ # Run Exim to get a list of hosts for the given domain; for
+ # each one construct the appropriate retry key.
+
+- open(LIST, "$exim -C $config -v -bt $address |") ||
++ open(LIST, "$exim -v -bt $address |") ||
+ die "can't run exim to route $address";
+
+ while (<LIST>)
+--- a/src/exiwhat.src
++++ b/src/exiwhat.src
+@@ -99,8 +99,8 @@ fi
+ st=' '
+ exim_path=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"`
+ if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim4; fi
+-spool_directory=`$exim_path -C $config -bP spool_directory | sed "s/.*=[ ]*//"`
+-process_log_path=`$exim_path -C $config -bP process_log_path | sed "s/.*=[ ]*//"`
++spool_directory=`$exim_path -bP spool_directory | sed "s/.*=[ ]*//"`
++process_log_path=`$exim_path -bP process_log_path | sed "s/.*=[ ]*//"`
+
+ # The file that Exim writes when sent the SIGUSR1 signal is specified by
+ # the process_log_path option. If that is not defined, Exim uses the file
diff --git a/debian/patches/70_remove_exim-users_references.dpatch b/debian/patches/70_remove_exim-users_references.dpatch
new file mode 100755
index 0000000..9efe04f
--- /dev/null
+++ b/debian/patches/70_remove_exim-users_references.dpatch
@@ -0,0 +1,37 @@
+Description: Point Debian users to Debian specific ML.
+Author: Marc Haber <mh+debian-packages@zugschlus.de>
+Last-Update: 2018-12-31
+
+--- a/README
++++ b/README
+@@ -14,8 +14,16 @@ from Exim 3, though the basic structure
+ older book may be helpful for the background, but a lot of the detail has
+ changed, so it is likely to be confusing to newcomers.
+
+-There is a website at https://www.exim.org; this contains details of the
+-mailing list exim-users@exim.org.
++Information about the way Debian has built the binary packages is
++obtainable in /usr/share/doc/exim4-base/README.Debian.gz, and there
++is a Debian-centered mailing list,
++pkg-exim4-users@lists.alioth.debian.org. Please ask Debian-specific
++questions there, and only write to the upstream exim-users mailing
++list if you are sure that your question is not Debian-specific. You
++can find the subscription web page on
++http://lists.alioth.debian.org/mailman/listinfo/pkg-exim4-users
++
++There is a website at https://www.exim.org/.
+
+ A copy of the Exim FAQ should be available from the same source that you used
+ to obtain the Exim distribution. Additional formats for the documentation
+--- a/src/eximstats.src
++++ b/src/eximstats.src
+@@ -537,8 +537,7 @@ about how to create charts from the tabl
+
+ =head1 AUTHOR
+
+-There is a website at https://www.exim.org - this contains details of the
+-mailing list exim-users@exim.org.
++There is a website at https://www.exim.org/.
+
+ =head1 TO DO
+
diff --git a/debian/patches/75_01-Fix-json-extract-operator-for-unfound-case.patch b/debian/patches/75_01-Fix-json-extract-operator-for-unfound-case.patch
new file mode 100644
index 0000000..a0978af
--- /dev/null
+++ b/debian/patches/75_01-Fix-json-extract-operator-for-unfound-case.patch
@@ -0,0 +1,69 @@
+From b2734f7b45111f9b7de790c7b334a2ece47675b5 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Sat, 9 Feb 2019 16:56:59 +0000
+Subject: [PATCH 1/7] Fix json extract operator for unfound case
+
+(cherry picked from commit e73798976812e652320f096870359ef35ed069ff)
+---
+ doc/doc-docbook/spec.xfpt | 4 ++++
+ src/expand.c | 13 ++++++++-----
+ test/scripts/0000-Basic/0002 | 3 +++
+ test/stdout/0002 | 3 +++
+ 4 files changed, 18 insertions(+), 5 deletions(-)
+
+--- a/src/expand.c
++++ b/src/expand.c
+@@ -3901,7 +3901,8 @@ return NULL;
+ /* Pull off the leading array or object element, returning
+ a copy in an allocated string. Update the list pointer.
+
+-The element may itself be an abject or array.
++The element may itself be an object or array.
++Return NULL when the list is empty.
+ */
+
+ uschar *
+@@ -3923,6 +3924,7 @@ for (item = s;
+ case '}': object_depth--; break;
+ }
+ *list = *s ? s+1 : s;
++if (item == s) return NULL;
+ item = string_copyn(item, s - item);
+ DEBUG(D_expand) debug_printf_indent(" json ele: '%s'\n", item);
+ return US item;
+@@ -5790,10 +5792,11 @@ while (*s != 0)
+ }
+ while (field_number > 0 && (item = json_nextinlist(&list)))
+ field_number--;
+- s = item;
+- lookup_value = s;
+- while (*s) s++;
+- while (--s >= lookup_value && isspace(*s)) *s = '\0';
++ if ((lookup_value = s = item))
++ {
++ while (*s) s++;
++ while (--s >= lookup_value && isspace(*s)) *s = '\0';
++ }
+ }
+ else
+ {
+--- a/doc/spec.txt
++++ b/doc/spec.txt
+@@ -8776,6 +8776,8 @@ ${extract json{<key>}{<string1>}{<string
+ The braces, commas and colons, and the quoting of the member name are
+ required; the spaces are optional. Matching of the key against the member
+ names is done case-sensitively.
++ If a returned value is a JSON string, it retains its leading and
++ trailing quotes.
+
+ The results of matching are handled as above.
+
+@@ -8813,6 +8815,8 @@ ${extract json{<number>}}{<string1>}{<st
+
+ Field selection and result handling is as above; there is no choice of
+ field separator.
++ If a returned value is a JSON string, it retains its leading and
++ trailing quotes.
+
+ ${filter{<string>}{<condition>}}
+
diff --git a/debian/patches/75_02-Fix-transport-buffer-size-handling.patch b/debian/patches/75_02-Fix-transport-buffer-size-handling.patch
new file mode 100644
index 0000000..a96350b
--- /dev/null
+++ b/debian/patches/75_02-Fix-transport-buffer-size-handling.patch
@@ -0,0 +1,52 @@
+From 1cfa7822ca8928f95160df8742af11fff888ae7e Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Tue, 12 Feb 2019 16:52:51 +0000
+Subject: [PATCH 3/7] Fix transport buffer size handling Broken-by: 59932f7dcd
+
+(cherry picked from commit 05bf16f6217e93594929c8bbbbbc852caf3ed374)
+---
+ doc/ChangeLog | 7 +++++++
+ src/transport.c | 4 ++--
+ 2 files changed, 9 insertions(+), 2 deletions(-)
+
+diff --git a/doc/ChangeLog b/doc/ChangeLog
+index 7da07ad4..66c8a7a1 100644
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -5,6 +5,13 @@ affect Exim's operation, with an unchanged configuration file. For new
+ options, and new features, see the NewStuff file next to this ChangeLog.
+
+
++Since version 4.92
++------------------
++
++JH/06 Fix buggy handling of autoreply bounce_return_size_limit, and a possible
++ buffer overrun for (non-chunking) other transports.
++
++
+ Exim version 4.92
+ -----------------
+
+diff --git a/src/transport.c b/src/transport.c
+index 8ccdd038..a069b883 100644
+--- a/src/transport.c
++++ b/src/transport.c
+@@ -1115,13 +1115,13 @@ DEBUG(D_transport)
+
+ if (!(tctx->options & topt_no_body))
+ {
+- int size = size_limit;
++ unsigned long size = size_limit > 0 ? size_limit : ULONG_MAX;
+
+ nl_check_length = abs(nl_check_length);
+ nl_partial_match = 0;
+ if (lseek(deliver_datafile, SPOOL_DATA_START_OFFSET, SEEK_SET) < 0)
+ return FALSE;
+- while ( (len = MAX(DELIVER_IN_BUFFER_SIZE, size)) > 0
++ while ( (len = MIN(DELIVER_IN_BUFFER_SIZE, size)) > 0
+ && (len = read(deliver_datafile, deliver_in_buffer, len)) > 0)
+ {
+ if (!write_chunk(tctx, deliver_in_buffer, len))
+--
+2.20.1
+
diff --git a/debian/patches/75_03-Fix-info-on-using-local_scan-in-the-default-Makefile.patch b/debian/patches/75_03-Fix-info-on-using-local_scan-in-the-default-Makefile.patch
new file mode 100644
index 0000000..6db6f83
--- /dev/null
+++ b/debian/patches/75_03-Fix-info-on-using-local_scan-in-the-default-Makefile.patch
@@ -0,0 +1,42 @@
+From cb25b75af850d664fc005d24fbad0e58bf79d4c7 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Thu, 14 Feb 2019 17:14:34 +0000
+Subject: [PATCH 5/7] Fix info on using local_scan() in the default Makefile
+
+Broken-by: 9723f96673
+(cherry picked from commit 882bc1704d33aa34873e3a0f72e657b0cc2985e5)
+---
+ OS/Makefile-Default | 10 ++++++++--
+ 1 file changed, 8 insertions(+), 2 deletions(-)
+
+diff --git a/OS/Makefile-Default b/OS/Makefile-Default
+index b3990fe8..41a4dbbd 100644
+--- a/OS/Makefile-Default
++++ b/OS/Makefile-Default
+@@ -232,6 +232,11 @@ RANLIB=ranlib
+ EXIM_CHMOD=@true
+
+
++# If you want to use local_scan() at all, the support code must be included
++# by uncommenting this line.
++
++# HAVE_LOCAL_SCAN=yes
++
+ # LOCAL_SCAN_SOURCE defines the file in which the function local_scan() is
+ # defined. This provides the administrator with a hook for including C code
+ # for scanning incoming mails. The path that is defined must be relative to
+@@ -239,8 +244,9 @@ EXIM_CHMOD=@true
+
+ # LOCAL_SCAN_SOURCE=Local/local_scan.c
+
+-# The default setting points to a template function that doesn't actually do
+-# any scanning, but just accepts the message.
++# A very simple example points to a template function that doesn't actually do
++# any scanning, but just accepts the message. A compilable file must be
++# included in the build even if HAVE_LOCAL_SCAN is not defined.
+
+ LOCAL_SCAN_SOURCE=src/local_scan.c
+
+--
+2.20.1
+
diff --git a/debian/patches/75_04-GnuTLS-Fix-client-detection-of-server-reject-of-clie.patch b/debian/patches/75_04-GnuTLS-Fix-client-detection-of-server-reject-of-clie.patch
new file mode 100644
index 0000000..c45f6aa
--- /dev/null
+++ b/debian/patches/75_04-GnuTLS-Fix-client-detection-of-server-reject-of-clie.patch
@@ -0,0 +1,420 @@
+From c15523829ba17cce5829e2976aa1ff928965d948 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Sat, 16 Feb 2019 12:59:23 +0000
+Subject: [PATCH 7/7] GnuTLS: Fix client detection of server reject of client
+ cert under TLS1.3
+
+(cherry picked from commit fc243e944ec00b59b75f41d07494116f925d58b4)
+---
+ doc/ChangeLog | 7 +++
+ src/deliver.c | 2 +-
+ src/smtp_out.c | 10 +++--
+ src/tls-gnu.c | 23 +++-------
+ src/transports/lmtp.c | 3 +-
+ src/transports/smtp.c | 81 +++++++++++++++++++++++++++--------
+ test/confs/2027 | 8 ++--
+ test/confs/5652 | 1 +
+ test/confs/5821 | 2 +-
+ test/log/2027 | 2 +-
+ test/runtest | 14 ++++++
+ test/scripts/2000-GnuTLS/2027 | 2 +
+ 12 files changed, 111 insertions(+), 44 deletions(-)
+
+diff --git a/doc/ChangeLog b/doc/ChangeLog
+index 66c8a7a1..867a1d8a 100644
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -11,6 +11,13 @@ Since version 4.92
+ JH/06 Fix buggy handling of autoreply bounce_return_size_limit, and a possible
+ buffer overrun for (non-chunking) other transports.
+
++JH/07 GnuTLS: Our use of late (post-handshake) certificate verification, under
++ TLS1.3, means that a server rejecting a client certificate is not visible
++ to the client until the first read of encrypted data (typically the
++ response to EHLO). Add detection for that case and treat it as a failed
++ TLS connection attempt, so that the normal retry-in-clear can work (if
++ suitably configured).
++
+
+ Exim version 4.92
+ -----------------
+diff --git a/src/deliver.c b/src/deliver.c
+index 664d0045..e1799411 100644
+--- a/src/deliver.c
++++ b/src/deliver.c
+@@ -7433,7 +7433,7 @@ if (addr_senddsn)
+
+ tctx.u.fd = fd;
+ tctx.options = topt_add_return_path | topt_no_body;
+- /*XXX hmm, retval ignored.
++ /*XXX hmm, FALSE(fail) retval ignored.
+ Could error for any number of reasons, and they are not handled. */
+ transport_write_message(&tctx, 0);
+ fflush(f);
+diff --git a/src/smtp_out.c b/src/smtp_out.c
+index 9bd90c77..b194e804 100644
+--- a/src/smtp_out.c
++++ b/src/smtp_out.c
+@@ -688,20 +688,22 @@ Returns: TRUE if a valid, non-error response was received; else FALSE
+ /*XXX could move to smtp transport; no other users */
+
+ BOOL
+-smtp_read_response(void * sx0, uschar *buffer, int size, int okdigit,
++smtp_read_response(void * sx0, uschar * buffer, int size, int okdigit,
+ int timeout)
+ {
+ smtp_context * sx = sx0;
+-uschar *ptr = buffer;
+-int count = 0;
++uschar * ptr = buffer;
++int count = 0, rc;
+
+ errno = 0; /* Ensure errno starts out zero */
+
+ #ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (sx->pending_BANNER || sx->pending_EHLO)
+- if (smtp_reap_early_pipe(sx, &count) != OK)
++ if ((rc = smtp_reap_early_pipe(sx, &count)) != OK)
+ {
+ DEBUG(D_transport) debug_printf("failed reaping pipelined cmd responsess\n");
++ buffer[0] = '\0';
++ if (rc == DEFER) errno = ERRNO_TLSFAILURE;
+ return FALSE;
+ }
+ #endif
+diff --git a/src/tls-gnu.c b/src/tls-gnu.c
+index c404dc29..de2d70c0 100644
+--- a/src/tls-gnu.c
++++ b/src/tls-gnu.c
+@@ -229,7 +229,7 @@ static gnutls_dh_params_t dh_server_params = NULL;
+
+ static const int ssl_session_timeout = 200;
+
+-static const char * const exim_default_gnutls_priority = "NORMAL";
++static const uschar * const exim_default_gnutls_priority = US"NORMAL";
+
+ /* Guard library core initialisation */
+
+@@ -1278,7 +1278,6 @@ int rc;
+ size_t sz;
+ const char *errpos;
+ uschar *p;
+-BOOL want_default_priorities;
+
+ if (!exim_gnutls_base_init_done)
+ {
+@@ -1387,32 +1386,24 @@ and replaces gnutls_require_kx, gnutls_require_mac & gnutls_require_protocols.
+ This was backwards incompatible, but means Exim no longer needs to track
+ all algorithms and provide string forms for them. */
+
+-want_default_priorities = TRUE;
+-
++p = NULL;
+ if (state->tls_require_ciphers && *state->tls_require_ciphers)
+ {
+ if (!expand_check_tlsvar(tls_require_ciphers, errstr))
+ return DEFER;
+ if (state->exp_tls_require_ciphers && *state->exp_tls_require_ciphers)
+ {
+- DEBUG(D_tls) debug_printf("GnuTLS session cipher/priority \"%s\"\n",
+- state->exp_tls_require_ciphers);
+-
+- rc = gnutls_priority_init(&state->priority_cache,
+- CS state->exp_tls_require_ciphers, &errpos);
+- want_default_priorities = FALSE;
+ p = state->exp_tls_require_ciphers;
++ DEBUG(D_tls) debug_printf("GnuTLS session cipher/priority \"%s\"\n", p);
+ }
+ }
+-if (want_default_priorities)
++if (!p)
+ {
++ p = exim_default_gnutls_priority;
+ DEBUG(D_tls)
+- debug_printf("GnuTLS using default session cipher/priority \"%s\"\n",
+- exim_default_gnutls_priority);
+- rc = gnutls_priority_init(&state->priority_cache,
+- exim_default_gnutls_priority, &errpos);
+- p = US exim_default_gnutls_priority;
++ debug_printf("GnuTLS using default session cipher/priority \"%s\"\n", p);
+ }
++rc = gnutls_priority_init(&state->priority_cache, CCS p, &errpos);
+
+ exim_gnutls_err_check(rc, string_sprintf(
+ "gnutls_priority_init(%s) failed at offset %ld, \"%.6s..\"",
+diff --git a/src/transports/lmtp.c b/src/transports/lmtp.c
+index 240d78b2..57b346d4 100644
+--- a/src/transports/lmtp.c
++++ b/src/transports/lmtp.c
+@@ -122,7 +122,8 @@ Arguments:
+ Returns: TRUE if a "QUIT" command should be sent, else FALSE
+ */
+
+-static BOOL check_response(int *errno_value, int more_errno, uschar *buffer,
++static BOOL
++check_response(int *errno_value, int more_errno, uschar *buffer,
+ int *yield, uschar **message)
+ {
+ *yield = '4'; /* Default setting is to give a temporary error */
+diff --git a/src/transports/smtp.c b/src/transports/smtp.c
+index a351da84..bfd6018d 100644
+--- a/src/transports/smtp.c
++++ b/src/transports/smtp.c
+@@ -594,6 +594,11 @@ switch(*errno_value)
+ pl, smtp_command, s);
+ return FALSE;
+
++ case ERRNO_TLSFAILURE: /* Handle bad first read; can happen with
++ GnuTLS and TLS1.3 */
++ *message = US"bad first read from TLS conn";
++ return TRUE;
++
+ case ERRNO_FILTER_FAIL: /* Handle a failed filter process error;
+ can't send QUIT as we mustn't end the DATA. */
+ *message = string_sprintf("transport filter process failed (%d)%s",
+@@ -942,6 +947,7 @@ Arguments:
+
+ Return:
+ OK all well
++ DEFER error on first read of TLS'd conn
+ FAIL SMTP error in response
+ */
+ int
+@@ -949,6 +955,7 @@ smtp_reap_early_pipe(smtp_context * sx, int * countp)
+ {
+ BOOL pending_BANNER = sx->pending_BANNER;
+ BOOL pending_EHLO = sx->pending_EHLO;
++int rc = FAIL;
+
+ sx->pending_BANNER = FALSE; /* clear early to avoid recursion */
+ sx->pending_EHLO = FALSE;
+@@ -960,6 +967,7 @@ if (pending_BANNER)
+ if (!smtp_reap_banner(sx))
+ {
+ DEBUG(D_transport) debug_printf("bad banner\n");
++ if (tls_out.active.sock >= 0) rc = DEFER;
+ goto fail;
+ }
+ }
+@@ -974,6 +982,7 @@ if (pending_EHLO)
+ if (!smtp_reap_ehlo(sx))
+ {
+ DEBUG(D_transport) debug_printf("bad response for EHLO\n");
++ if (tls_out.active.sock >= 0) rc = DEFER;
+ goto fail;
+ }
+
+@@ -1011,7 +1020,7 @@ return OK;
+ fail:
+ invalidate_ehlo_cache_entry(sx);
+ (void) smtp_discard_responses(sx, sx->conn_args.ob, *countp);
+- return FAIL;
++ return rc;
+ }
+ #endif
+
+@@ -1056,6 +1065,7 @@ Returns: 3 if at least one address had 2xx and one had 5xx
+ -2 I/O or other non-response error for RCPT
+ -3 DATA or MAIL failed - errno and buffer set
+ -4 banner or EHLO failed (early-pipelining)
++ -5 banner or EHLO failed (early-pipelining, TLS)
+ */
+
+ static int
+@@ -1064,10 +1074,11 @@ sync_responses(smtp_context * sx, int count, int pending_DATA)
+ address_item * addr = sx->sync_addr;
+ smtp_transport_options_block * ob = sx->conn_args.ob;
+ int yield = 0;
++int rc;
+
+ #ifdef EXPERIMENTAL_PIPE_CONNECT
+-if (smtp_reap_early_pipe(sx, &count) != OK)
+- return -4;
++if ((rc = smtp_reap_early_pipe(sx, &count)) != OK)
++ return rc == FAIL ? -4 : -5;
+ #endif
+
+ /* Handle the response for a MAIL command. On error, reinstate the original
+@@ -1083,6 +1094,8 @@ if (sx->pending_MAIL)
+ {
+ DEBUG(D_transport) debug_printf("bad response for MAIL\n");
+ Ustrcpy(big_buffer, mail_command); /* Fits, because it came from there! */
++ if (errno == ERRNO_TLSFAILURE)
++ return -5;
+ if (errno == 0 && sx->buffer[0] != 0)
+ {
+ int save_errno = 0;
+@@ -1141,6 +1154,11 @@ while (count-- > 0)
+ }
+ }
+
++ /* Error on first TLS read */
++
++ else if (errno == ERRNO_TLSFAILURE)
++ return -5;
++
+ /* Timeout while reading the response */
+
+ else if (errno == ETIMEDOUT)
+@@ -1253,6 +1271,10 @@ if (pending_DATA != 0)
+ int code;
+ uschar *msg;
+ BOOL pass_message;
++
++ if (errno == ERRNO_TLSFAILURE) /* Error on first TLS read */
++ return -5;
++
+ if (pending_DATA > 0 || (yield & 1) != 0)
+ {
+ if (errno == 0 && sx->buffer[0] == '4')
+@@ -1802,7 +1824,9 @@ Args:
+ tc_chunk_last add LAST option to SMTP BDAT command
+ tc_reap_prev reap response to previous SMTP commands
+
+-Returns: OK or ERROR
++Returns:
++ OK or ERROR
++ DEFER TLS error on first read (EHLO-resp); errno set
+ */
+
+ static int
+@@ -1859,10 +1883,12 @@ if (flags & tc_reap_prev && prev_cmd_count > 0)
+ case 2: sx->completed_addr = TRUE; /* 5xx (only) => progress made */
+ case 0: break; /* No 2xx or 5xx, but no probs */
+
+- case -1: /* Timeout on RCPT */
++ case -5: errno = ERRNO_TLSFAILURE;
++ return DEFER;
+ #ifdef EXPERIMENTAL_PIPE_CONNECT
+ case -4: /* non-2xx for pipelined banner or EHLO */
+ #endif
++ case -1: /* Timeout on RCPT */
+ default: return ERROR; /* I/O error, or any MAIL/DATA error */
+ }
+ cmd_count = 1;
+@@ -1933,6 +1959,9 @@ BOOL pass_message = FALSE;
+ uschar * message = NULL;
+ int yield = OK;
+ int rc;
++#ifdef SUPPORT_TLS
++uschar * tls_errstr;
++#endif
+
+ sx->conn_args.ob = ob;
+
+@@ -2474,27 +2503,27 @@ if ( smtp_peer_options & OPTION_TLS
+ TLS_NEGOTIATE:
+ {
+ address_item * addr;
+- uschar * errstr;
+ sx->cctx.tls_ctx = tls_client_start(sx->cctx.sock, sx->conn_args.host,
+ sx->addrlist, sx->conn_args.tblock,
+ # ifdef SUPPORT_DANE
+ sx->dane ? &tlsa_dnsa : NULL,
+ # endif
+- &tls_out, &errstr);
++ &tls_out, &tls_errstr);
+
+ if (!sx->cctx.tls_ctx)
+ {
+ /* TLS negotiation failed; give an error. From outside, this function may
+ be called again to try in clear on a new connection, if the options permit
+ it for this host. */
+- DEBUG(D_tls) debug_printf("TLS session fail: %s\n", errstr);
++GNUTLS_CONN_FAILED:
++ DEBUG(D_tls) debug_printf("TLS session fail: %s\n", tls_errstr);
+
+ # ifdef SUPPORT_DANE
+ if (sx->dane)
+ {
+ log_write(0, LOG_MAIN,
+ "DANE attempt failed; TLS connection to %s [%s]: %s",
+- sx->conn_args.host->name, sx->conn_args.host->address, errstr);
++ sx->conn_args.host->name, sx->conn_args.host->address, tls_errstr);
+ # ifndef DISABLE_EVENT
+ (void) event_raise(sx->conn_args.tblock->event_action,
+ US"dane:fail", US"validation-failure"); /* could do with better detail */
+@@ -2503,7 +2532,7 @@ if ( smtp_peer_options & OPTION_TLS
+ # endif
+
+ errno = ERRNO_TLSFAILURE;
+- message = string_sprintf("TLS session: %s", errstr);
++ message = string_sprintf("TLS session: %s", tls_errstr);
+ sx->send_quit = FALSE;
+ goto TLS_FAILED;
+ }
+@@ -2601,7 +2630,22 @@ if (tls_out.active.sock >= 0)
+ #endif
+ {
+ if (!smtp_reap_ehlo(sx))
++#ifdef USE_GNUTLS
++ {
++ /* The GnuTLS layer in Exim only spots a server-rejection of a client
++ cert late, under TLS1.3 - which means here; the first time we try to
++ receive crypted data. Treat it as if it was a connect-time failure.
++ See also the early-pipe equivalent... which will be hard; every call
++ to sync_responses will need to check the result.
++ It would be nicer to have GnuTLS check the cert during the handshake.
++ Can it do that, with all the flexibility we need? */
++
++ tls_errstr = US"error on first read";
++ goto GNUTLS_CONN_FAILED;
++ }
++#else
+ goto RESPONSE_FAILED;
++#endif
+ smtp_peer_options = 0;
+ }
+ }
+@@ -3261,6 +3305,7 @@ for (addr = sx->first_addr, address_count = 0;
+
+ #ifdef EXPERIMENTAL_PIPE_CONNECT
+ case -4: return -1; /* non-2xx for pipelined banner or EHLO */
++ case -5: return -1; /* TLS first-read error */
+ #endif
+ }
+ sx->pending_MAIL = FALSE; /* Dealt with MAIL */
+@@ -3589,11 +3634,12 @@ if ( !(sx.peer_offered & OPTION_CHUNKING)
+
+ case 1: sx.ok = TRUE; /* 2xx (only) => OK, but if LMTP, */
+ if (!sx.lmtp) sx.completed_addr = TRUE; /* can't tell about progress yet */
+- case 0: break; /* No 2xx or 5xx, but no probs */
++ case 0: break; /* No 2xx or 5xx, but no probs */
+
+- case -1: goto END_OFF; /* Timeout on RCPT */
++ case -1: goto END_OFF; /* Timeout on RCPT */
+
+ #ifdef EXPERIMENTAL_PIPE_CONNECT
++ case -5: /* TLS first-read error */
+ case -4: HDEBUG(D_transport)
+ debug_printf("failed reaping pipelined cmd responses\n");
+ #endif
+@@ -3730,19 +3776,20 @@ else
+ {
+ case 3: sx.ok = TRUE; /* 2xx & 5xx => OK & progress made */
+ case 2: sx.completed_addr = TRUE; /* 5xx (only) => progress made */
+- break;
++ break;
+
+- case 1: sx.ok = TRUE; /* 2xx (only) => OK, but if LMTP, */
++ case 1: sx.ok = TRUE; /* 2xx (only) => OK, but if LMTP, */
+ if (!sx.lmtp) sx.completed_addr = TRUE; /* can't tell about progress yet */
+- case 0: break; /* No 2xx or 5xx, but no probs */
++ case 0: break; /* No 2xx or 5xx, but no probs */
+
+- case -1: goto END_OFF; /* Timeout on RCPT */
++ case -1: goto END_OFF; /* Timeout on RCPT */
+
+ #ifdef EXPERIMENTAL_PIPE_CONNECT
++ case -5: /* TLS first-read error */
+ case -4: HDEBUG(D_transport)
+ debug_printf("failed reaping pipelined cmd responses\n");
+ #endif
+- default: goto RESPONSE_FAILED; /* I/O error, or any MAIL/DATA error */
++ default: goto RESPONSE_FAILED; /* I/O error, or any MAIL/DATA error */
+ }
+ }
+
+--
+2.20.1
+
diff --git a/debian/patches/75_05-Fix-expansions-for-RFC-822-addresses-having-comments.patch b/debian/patches/75_05-Fix-expansions-for-RFC-822-addresses-having-comments.patch
new file mode 100644
index 0000000..517eb1c
--- /dev/null
+++ b/debian/patches/75_05-Fix-expansions-for-RFC-822-addresses-having-comments.patch
@@ -0,0 +1,91 @@
+From f634b80846cc7ffcab65c9855bcb35312f0232e8 Mon Sep 17 00:00:00 2001
+From: Jasen Betts <jasen@xnet.co.nz>
+Date: Mon, 18 Feb 2019 13:52:16 +0000
+Subject: [PATCH 1/5] Fix expansions for RFC 822 addresses having comments in
+ local-part and/or domain. Bug 2375
+
+(cherry picked from commit e2ff8e24f41caca3623228b1ec66a3f3961ecad6)
+---
+ doc/ChangeLog | 3 +++
+ src/expand.c | 19 +++++++------------
+ test/scripts/0000-Basic/0002 | 7 +++++++
+ test/stdout/0002 | 7 +++++++
+ 4 files changed, 24 insertions(+), 12 deletions(-)
+
+diff --git a/doc/ChangeLog b/doc/ChangeLog
+index 867a1d8a..9659da32 100644
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -16,10 +16,13 @@ JH/07 GnuTLS: Our use of late (post-handshake) certificate verification, under
+ to the client until the first read of encrypted data (typically the
+ response to EHLO). Add detection for that case and treat it as a failed
+ TLS connection attempt, so that the normal retry-in-clear can work (if
+ suitably configured).
+
++JB/01 BZg 2375: fix expansions of 822 addresses having comments in local-part
++ and/or domain. Found and fixed by Jason Betts.
++
+
+ Exim version 4.92
+ -----------------
+
+ JH/01 Remove code calling the customisable local_scan function, unless a new
+diff --git a/src/expand.c b/src/expand.c
+index 2c290251..35ede718 100644
+--- a/src/expand.c
++++ b/src/expand.c
+@@ -7071,20 +7071,15 @@ while (*s != 0)
+ uschar * error;
+ int start, end, domain;
+ uschar * t = parse_extract_address(sub, &error, &start, &end, &domain,
+ FALSE);
+ if (t)
+- if (c != EOP_DOMAIN)
+- {
+- if (c == EOP_LOCAL_PART && domain != 0) end = start + domain - 1;
+- yield = string_catn(yield, sub+start, end-start);
+- }
+- else if (domain != 0)
+- {
+- domain += start;
+- yield = string_catn(yield, sub+domain, end-domain);
+- }
++ yield = c == EOP_DOMAIN
++ ? string_cat(yield, t + domain)
++ : c == EOP_LOCAL_PART && domain > 0
++ ? string_catn(yield, t, domain - 1 )
++ : string_cat(yield, t);
+ continue;
+ }
+
+ case EOP_ADDRESSES:
+ {
+@@ -7104,11 +7099,11 @@ while (*s != 0)
+ }
+ f.parse_allow_group = TRUE;
+
+ for (;;)
+ {
+- uschar *p = parse_find_address_end(sub, FALSE);
++ uschar * p = parse_find_address_end(sub, FALSE);
+ uschar saveend = *p;
+ *p = '\0';
+ address = parse_extract_address(sub, &error, &start, &end, &domain,
+ FALSE);
+ *p = saveend;
+@@ -7117,11 +7112,11 @@ while (*s != 0)
+ done in chunks by searching for the separator character. At the
+ start, unless we are dealing with the first address of the output
+ list, add in a space if the new address begins with the separator
+ character, or is an empty string. */
+
+- if (address != NULL)
++ if (address)
+ {
+ if (yield->ptr != save_ptr && address[0] == *outsep)
+ yield = string_catn(yield, US" ", 1);
+
+ for (;;)
+--
+2.20.1
+
diff --git a/debian/patches/75_06-Docs-Add-note-on-lsearch-for-IPv4-mapped-IPv6-addres.patch b/debian/patches/75_06-Docs-Add-note-on-lsearch-for-IPv4-mapped-IPv6-addres.patch
new file mode 100644
index 0000000..a7863ef
--- /dev/null
+++ b/debian/patches/75_06-Docs-Add-note-on-lsearch-for-IPv4-mapped-IPv6-addres.patch
@@ -0,0 +1,48 @@
+From 8dde16b89efe2138f92cbfa6c59fb31dc80ec22a Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Tue, 19 Feb 2019 14:45:27 +0000
+Subject: [PATCH 2/5] Docs: Add note on lsearch for IPv4-mapped IPv6 addresses
+
+Cherry-picked from: 52af443324, c77d3d85fe
+---
+ doc/doc-docbook/spec.xfpt | 11 ++++++++++-
+ doc/ChangeLog | 2 +-
+ 2 files changed, 11 insertions(+), 2 deletions(-)
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -18,7 +18,7 @@ JH/07 GnuTLS: Our use of late (post-hand
+ TLS connection attempt, so that the normal retry-in-clear can work (if
+ suitably configured).
+
+-JB/01 BZg 2375: fix expansions of 822 addresses having comments in local-part
++JB/01 Bug 2375: fix expansions of 822 addresses having comments in local-part
+ and/or domain. Found and fixed by Jason Betts.
+
+
+--- a/doc/spec.txt
++++ b/doc/spec.txt
+@@ -6302,6 +6302,10 @@ The following single-key lookup types ar
+ implicit key is the host's IP address rather than its name (see section
+ 10.12).
+
++ Warning 3: Do not use an IPv4-mapped IPv6 address for a key; use the
++ IPv4, in dotted-quad form. (Exim converts IPv4-mapped IPv6 addresses to
++ this notation before executing the lookup.)
++
+ * lsearch: The given file is a text file that is searched linearly for a line
+ beginning with the search key, terminated by a colon or white space or the
+ end of the line. The search is case-insensitive; that is, upper and lower
+@@ -8003,7 +8007,11 @@ quote keys was made available in lsearch
+ implemented iplsearch files do require colons in IPv6 keys (notated using the
+ quoting facility) so as to distinguish them from IPv4 keys. For this reason,
+ when the lookup type is iplsearch, IPv6 addresses are converted using colons
+-and not dots. In all cases, full, unabbreviated IPv6 addresses are always used.
++and not dots.
++
++In all cases except IPv4-mapped IPv6, full, unabbreviated IPv6 addresses
++are always used. The latter are converted to IPv4 addresses, in dotted-quad
++form.
+
+ Ideally, it would be nice to tidy up this anomalous situation by changing to
+ colons in all cases, given that quoting is now available for lsearch. However,
diff --git a/debian/patches/75_07-Fix-crash-from-SRV-lookup-hitting-a-CNAME.patch b/debian/patches/75_07-Fix-crash-from-SRV-lookup-hitting-a-CNAME.patch
new file mode 100644
index 0000000..cfdbe51
--- /dev/null
+++ b/debian/patches/75_07-Fix-crash-from-SRV-lookup-hitting-a-CNAME.patch
@@ -0,0 +1,69 @@
+From 09720dd9506176294154dad7152f5f40554046a4 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Thu, 14 Mar 2019 12:26:34 +0000
+Subject: [PATCH 3/5] Fix crash from SRV lookup hitting a CNAME
+
+(cherry picked from commit 14bc9cf085aff7bd5147881e5b7068769a29b026)
+---
+ doc/ChangeLog | 4 ++++
+ src/dns.c | 10 +++++++---
+ 2 files changed, 11 insertions(+), 3 deletions(-)
+
+diff --git a/doc/ChangeLog b/doc/ChangeLog
+index 419c1061..0f8d05b2 100644
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -19,10 +19,14 @@ JH/07 GnuTLS: Our use of late (post-handshake) certificate verification, under
+ suitably configured).
+
+ JB/01 Bug 2375: fix expansions of 822 addresses having comments in local-part
+ and/or domain. Found and fixed by Jason Betts.
+
++JH/08 Add hardening against SRV & TLSA lookups the hit CNAMEs (a nonvalid
++ configuration). If a CNAME target was not a wellformed name pattern, a
++ crash could result.
++
+
+ Exim version 4.92
+ -----------------
+
+ JH/01 Remove code calling the customisable local_scan function, unless a new
+diff --git a/src/dns.c b/src/dns.c
+index 0f0b435d..b7978c52 100644
+--- a/src/dns.c
++++ b/src/dns.c
+@@ -714,11 +714,15 @@ regex has substrings that are used - the default uses a conditional.
+ This test is omitted for PTR records. These occur only in calls from the dnsdb
+ lookup, which constructs the names itself, so they should be OK. Besides,
+ bitstring labels don't conform to normal name syntax. (But the aren't used any
+ more.)
+
+-For SRV records, we omit the initial _smtp._tcp. components at the start. */
++For SRV records, we omit the initial _smtp._tcp. components at the start.
++The check has been seen to bite on the destination of a SRV lookup that
++initiall hit a CNAME, for which the next name had only two components.
++RFC2782 makes no mention of the possibiility of CNAMES, but the Wikipedia
++article on SRV says they are not a valid configuration. */
+
+ #ifndef STAND_ALONE /* Omit this for stand-alone tests */
+
+ if (check_dns_names_pattern[0] != 0 && type != T_PTR && type != T_TXT)
+ {
+@@ -730,12 +734,12 @@ if (check_dns_names_pattern[0] != 0 && type != T_PTR && type != T_TXT)
+ /* For an SRV lookup, skip over the first two components (the service and
+ protocol names, which both start with an underscore). */
+
+ if (type == T_SRV || type == T_TLSA)
+ {
+- while (*checkname++ != '.');
+- while (*checkname++ != '.');
++ while (*checkname && *checkname++ != '.') ;
++ while (*checkname && *checkname++ != '.') ;
+ }
+
+ if (pcre_exec(regex_check_dns_names, NULL, CCS checkname, Ustrlen(checkname),
+ 0, PCRE_EOPT, ovector, nelem(ovector)) < 0)
+ {
+--
+2.20.1
+
diff --git a/debian/patches/75_08-Logging-fix-initial-listening-on-log-line.patch b/debian/patches/75_08-Logging-fix-initial-listening-on-log-line.patch
new file mode 100644
index 0000000..4af2972
--- /dev/null
+++ b/debian/patches/75_08-Logging-fix-initial-listening-on-log-line.patch
@@ -0,0 +1,206 @@
+From e5be948a65fe601024e5d4256f64efbfed3dd72e Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Mon, 18 Mar 2019 00:31:43 +0000
+Subject: [PATCH 4/5] Logging: fix initial listening-on log line
+
+(cherry picked from commit 254f38d1c5ada5e4df0bccb385dc466549620c71)
+---
+ doc/ChangeLog | 4 +++
+ src/daemon.c | 73 +++++++++++++++++++++++++++----------------
+ src/host.c | 1 +
+ src/structs.h | 1 +
+ test/confs/0282 | 2 +-
+ test/log/0282 | 2 +-
+ 6 files changed, 54 insertions(+), 29 deletions(-)
+
+diff --git a/doc/ChangeLog b/doc/ChangeLog
+index 0f8d05b2..3c0ffbf0 100644
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -23,10 +23,14 @@ JB/01 Bug 2375: fix expansions of 822 addresses having comments in local-part
+
+ JH/08 Add hardening against SRV & TLSA lookups the hit CNAMEs (a nonvalid
+ configuration). If a CNAME target was not a wellformed name pattern, a
+ crash could result.
+
++JH/09 Logging: Fix initial listening-on line for multiple ports for an IP when
++ the OS reports them interleaved with other addresses.
++
++
+
+ Exim version 4.92
+ -----------------
+
+ JH/01 Remove code calling the customisable local_scan function, unless a new
+diff --git a/src/daemon.c b/src/daemon.c
+index a852192e..01da3936 100644
+--- a/src/daemon.c
++++ b/src/daemon.c
+@@ -1625,12 +1625,12 @@ if (f.inetd_wait_mode)
+ else if (f.daemon_listen)
+ {
+ int i, j;
+ int smtp_ports = 0;
+ int smtps_ports = 0;
+- ip_address_item * ipa, * i2;
+- uschar * p = big_buffer;
++ ip_address_item * ipa;
++ uschar * p;
+ uschar * qinfo = queue_interval > 0
+ ? string_sprintf("-q%s", readconf_printtime(queue_interval))
+ : US"no queue runs";
+
+ /* Build a list of listening addresses in big_buffer, but limit it to 10
+@@ -1638,73 +1638,92 @@ else if (f.daemon_listen)
+
+ It is now possible to have some ports listening for SMTPS (the old,
+ deprecated protocol that starts TLS without using STARTTLS), and others
+ listening for standard SMTP. Keep their listings separate. */
+
+- for (j = 0; j < 2; j++)
++ for (int j = 0, i; j < 2; j++)
+ {
+ for (i = 0, ipa = addresses; i < 10 && ipa; i++, ipa = ipa->next)
+ {
+ /* First time round, look for SMTP ports; second time round, look for
+- SMTPS ports. For the first one of each, insert leading text. */
++ SMTPS ports. Build IP+port strings. */
+
+ if (host_is_tls_on_connect_port(ipa->port) == (j > 0))
+ {
+ if (j == 0)
+- {
+- if (smtp_ports++ == 0)
+- {
+- memcpy(p, "SMTP on", 8);
+- p += 7;
+- }
+- }
++ smtp_ports++;
+ else
+- if (smtps_ports++ == 0)
+- p += sprintf(CS p, "%sSMTPS on",
+- smtp_ports == 0 ? "" : " and for ");
++ smtps_ports++;
+
+ /* Now the information about the port (and sometimes interface) */
+
+ if (ipa->address[0] == ':' && ipa->address[1] == 0)
+ { /* v6 wildcard */
+ if (ipa->next && ipa->next->address[0] == 0 &&
+ ipa->next->port == ipa->port)
+ {
+- p += sprintf(CS p, " port %d (IPv6 and IPv4)", ipa->port);
+- ipa = ipa->next;
++ ipa->log = string_sprintf(" port %d (IPv6 and IPv4)", ipa->port);
++ (ipa = ipa->next)->log = NULL;
+ }
+ else if (ipa->v6_include_v4)
+- p += sprintf(CS p, " port %d (IPv6 with IPv4)", ipa->port);
++ ipa->log = string_sprintf(" port %d (IPv6 with IPv4)", ipa->port);
+ else
+- p += sprintf(CS p, " port %d (IPv6)", ipa->port);
++ ipa->log = string_sprintf(" port %d (IPv6)", ipa->port);
+ }
+ else if (ipa->address[0] == 0) /* v4 wildcard */
+- p += sprintf(CS p, " port %d (IPv4)", ipa->port);
++ ipa->log = string_sprintf(" port %d (IPv4)", ipa->port);
+ else /* check for previously-seen IP */
+ {
++ ip_address_item * i2;
+ for (i2 = addresses; i2 != ipa; i2 = i2->next)
+ if ( host_is_tls_on_connect_port(i2->port) == (j > 0)
+ && Ustrcmp(ipa->address, i2->address) == 0
+ )
+ { /* found; append port to list */
+- if (p[-1] == '}') p--;
+- while (isdigit(*--p)) ;
+- p += 1 + sprintf(CS p+1, "%s%d,%d}", *p == ',' ? "" : "{",
+- i2->port, ipa->port);
++ for (p = i2->log; *p; ) p++; /* end of existing string */
++ if (*--p == '}') *p = '\0'; /* drop EOL */
++ while (isdigit(*--p)) ; /* char before port */
++
++ i2->log = *p == ':' /* no list yet? */
++ ? string_sprintf("%.*s{%s,%d}",
++ (int)(p - i2->log + 1), i2->log, p+1, ipa->port)
++ : string_sprintf("%s,%d}", i2->log, ipa->port);
++ ipa->log = NULL;
+ break;
+ }
+ if (i2 == ipa) /* first-time IP */
+- p += sprintf(CS p, " [%s]:%d", ipa->address, ipa->port);
++ ipa->log = string_sprintf(" [%s]:%d", ipa->address, ipa->port);
+ }
+ }
+ }
++ }
+
+- if (ipa)
++ p = big_buffer;
++ for (int j = 0, i; j < 2; j++)
++ {
++ /* First time round, look for SMTP ports; second time round, look for
++ SMTPS ports. For the first one of each, insert leading text. */
++
++ if (j == 0)
+ {
+- memcpy(p, " ...", 5);
+- p += 4;
++ if (smtp_ports > 0)
++ p += sprintf(CS p, "SMTP on");
+ }
++ else
++ if (smtps_ports > 0)
++ p += sprintf(CS p, "%sSMTPS on",
++ smtp_ports == 0 ? "" : " and for ");
++
++ /* Now the information about the port (and sometimes interface) */
++
++ for (i = 0, ipa = addresses; i < 10 && ipa; i++, ipa = ipa->next)
++ if (host_is_tls_on_connect_port(ipa->port) == (j > 0))
++ if (ipa->log)
++ p += sprintf(CS p, "%s", ipa->log);
++
++ if (ipa)
++ p += sprintf(CS p, " ...");
+ }
+
+ log_write(0, LOG_MAIN,
+ "exim %s daemon started: pid=%d, %s, listening for %s",
+ version_string, getpid(), qinfo, big_buffer);
+diff --git a/src/host.c b/src/host.c
+index 29c977fe..a3b0977b 100644
+--- a/src/host.c
++++ b/src/host.c
+@@ -757,10 +757,11 @@ while ((s = string_nextinlist(&list, &sep, NULL, 0)))
+ next = store_get(sizeof(ip_address_item));
+ next->next = NULL;
+ Ustrcpy(next->address, s);
+ next->port = port;
+ next->v6_include_v4 = FALSE;
++ next->log = NULL;
+
+ if (!yield)
+ yield = last = next;
+ else
+ {
+diff --git a/src/structs.h b/src/structs.h
+index 20db0e5f..1e63d752 100644
+--- a/src/structs.h
++++ b/src/structs.h
+@@ -442,10 +442,11 @@ hold an IPv6 address. */
+ typedef struct ip_address_item {
+ struct ip_address_item *next;
+ int port;
+ BOOL v6_include_v4; /* Used in the daemon */
+ uschar address[46];
++ uschar * log; /* portion of "listening on" log line */
+ } ip_address_item;
+
+ /* Structure for chaining together arbitrary strings. */
+
+ typedef struct string_item {
+--
+2.20.1
+
diff --git a/debian/patches/75_09-OpenSSL-Fix-aggregation-of-messages.patch b/debian/patches/75_09-OpenSSL-Fix-aggregation-of-messages.patch
new file mode 100644
index 0000000..b82891d
--- /dev/null
+++ b/debian/patches/75_09-OpenSSL-Fix-aggregation-of-messages.patch
@@ -0,0 +1,127 @@
+From 332ebeaf8139b2b75f475880fc14b63c7c45c706 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Tue, 19 Mar 2019 15:33:31 +0000
+Subject: [PATCH 5/5] OpenSSL: Fix aggregation of messages.
+
+Broken-by: a5ffa9b475
+(cherry picked from commit c09dbcfb71f4b9a42cbfd8a20e0be6bfa1b12488)
+---
+ doc/ChangeLog | 5 +++
+ src/tls-openssl.c | 24 ++++++++++----
+ test/confs/2152 | 76 +++++++++++++++++++++++++++++++++++++++++++
+ test/log/2152 | 9 +++++
+ 4 files changed, 108 insertions(+), 6 deletions(-)
+ create mode 100644 test/confs/2152
+ create mode 100644 test/log/2152
+
+diff --git a/doc/ChangeLog b/doc/ChangeLog
+index 3c0ffbf0..3d63725f 100644
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -26,10 +26,15 @@ JH/08 Add hardening against SRV & TLSA lookups the hit CNAMEs (a nonvalid
+ crash could result.
+
+ JH/09 Logging: Fix initial listening-on line for multiple ports for an IP when
+ the OS reports them interleaved with other addresses.
+
++JH/10 OpenSSL: Fix aggregation of messages. Previously, when PIPELINING was
++ used both for input and for a verify callout, both encrypted, SMTP
++ responses being sent by the server could be lost. This resulted in
++ dropped connections and sometimes bounces generated by a peer sending
++ to this system.
+
+
+ Exim version 4.92
+ -----------------
+
+diff --git a/src/tls-openssl.c b/src/tls-openssl.c
+index 8f4cf4d8..cc0ead02 100644
+--- a/src/tls-openssl.c
++++ b/src/tls-openssl.c
+@@ -272,10 +272,11 @@ Server:
+ */
+
+ typedef struct {
+ SSL_CTX * ctx;
+ SSL * ssl;
++ gstring * corked;
+ } exim_openssl_client_tls_ctx;
+
+ static SSL_CTX *server_ctx = NULL;
+ static SSL *server_ssl = NULL;
+
+@@ -2471,10 +2472,11 @@ BOOL require_ocsp = FALSE;
+ #endif
+
+ rc = store_pool;
+ store_pool = POOL_PERM;
+ exim_client_ctx = store_get(sizeof(exim_openssl_client_tls_ctx));
++exim_client_ctx->corked = NULL;
+ store_pool = rc;
+
+ #ifdef SUPPORT_DANE
+ tlsp->tlsa_usage = 0;
+ #endif
+@@ -2906,22 +2908,29 @@ Used by both server-side and client-side TLS.
+
+ int
+ tls_write(void * ct_ctx, const uschar *buff, size_t len, BOOL more)
+ {
+ int outbytes, error, left;
+-SSL * ssl = ct_ctx ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl : server_ssl;
+-static gstring * corked = NULL;
++SSL * ssl = ct_ctx
++ ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl : server_ssl;
++static gstring * server_corked = NULL;
++gstring ** corkedp = ct_ctx
++ ? &((exim_openssl_client_tls_ctx *)ct_ctx)->corked : &server_corked;
++gstring * corked = *corkedp;
+
+ DEBUG(D_tls) debug_printf("%s(%p, %lu%s)\n", __FUNCTION__,
+ buff, (unsigned long)len, more ? ", more" : "");
+
+ /* Lacking a CORK or MSG_MORE facility (such as GnuTLS has) we copy data when
+ "more" is notified. This hack is only ok if small amounts are involved AND only
+ one stream does it, in one context (i.e. no store reset). Currently it is used
+-for the responses to the received SMTP MAIL , RCPT, DATA sequence, only. */
+-/*XXX + if PIPE_COMMAND, banner & ehlo-resp for smmtp-on-connect. Suspect there's
+-a store reset there. */
++for the responses to the received SMTP MAIL , RCPT, DATA sequence, only.
++We support callouts done by the server process by using a separate client
++context for the stashed information. */
++/* + if PIPE_COMMAND, banner & ehlo-resp for smmtp-on-connect. Suspect there's
++a store reset there, so use POOL_PERM. */
++/* + if CHUNKING, cmds EHLO,MAIL,RCPT(s),BDAT */
+
+ if (!ct_ctx && (more || corked))
+ {
+ #ifdef EXPERIMENTAL_PIPE_CONNECT
+ int save_pool = store_pool;
+@@ -2933,14 +2942,17 @@ if (!ct_ctx && (more || corked))
+ #ifdef EXPERIMENTAL_PIPE_CONNECT
+ store_pool = save_pool;
+ #endif
+
+ if (more)
++ {
++ *corkedp = corked;
+ return len;
++ }
+ buff = CUS corked->s;
+ len = corked->ptr;
+- corked = NULL;
++ *corkedp = NULL;
+ }
+
+ for (left = len; left > 0;)
+ {
+ DEBUG(D_tls) debug_printf("SSL_write(%p, %p, %d)\n", ssl, buff, left);
+diff --git a/test/confs/2152 b/test/confs/2152
+new file mode 100644
+index 00000000..f783192b
+diff --git a/test/log/2152 b/test/log/2152
+new file mode 100644
+index 00000000..720200be
+--
+2.20.1
+
diff --git a/debian/patches/75_10-Harden-plaintext-authenticator.patch b/debian/patches/75_10-Harden-plaintext-authenticator.patch
new file mode 100644
index 0000000..9dcfd47
--- /dev/null
+++ b/debian/patches/75_10-Harden-plaintext-authenticator.patch
@@ -0,0 +1,55 @@
+From e5b942ae007d0533fbd599c64d550f3a8355b940 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Thu, 21 Mar 2019 20:01:03 +0000
+Subject: [PATCH] Harden plaintext authenticator
+
+Cherry-picked from: f9fc942757
+---
+ doc/ChangeLog | 5 +++++
+ src/auths/plaintext.c | 6 +-----
+ 2 files changed, 6 insertions(+), 5 deletions(-)
+
+diff --git a/doc/ChangeLog b/doc/ChangeLog
+index 3d63725f..c34e60d1 100644
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -32,10 +32,15 @@ JH/10 OpenSSL: Fix aggregation of messages. Previously, when PIPELINING was
+ used both for input and for a verify callout, both encrypted, SMTP
+ responses being sent by the server could be lost. This resulted in
+ dropped connections and sometimes bounces generated by a peer sending
+ to this system.
+
++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".
++
++
+
+ Exim version 4.92
+ -----------------
+
+ JH/01 Remove code calling the customisable local_scan function, unless a new
+diff --git a/src/auths/plaintext.c b/src/auths/plaintext.c
+index 7a0f7885..fa05b0ad 100644
+--- a/src/auths/plaintext.c
++++ b/src/auths/plaintext.c
+@@ -221,15 +221,11 @@ while ((s = string_nextinlist(&text, &sep, big_buffer, big_buffer_size)))
+ for (i = 0; i < len; i++)
+ if (ss[i] == '^')
+ if (ss[i+1] != '^')
+ ss[i] = 0;
+ else
+- {
+- i++;
+- len--;
+- memmove(ss + i, ss + i + 1, len - i);
+- }
++ if (--len > ++i) memmove(ss + i, ss + i + 1, len - i);
+
+ /* The first string is attached to the AUTH command; others are sent
+ unembellished. */
+
+ if (first)
+--
+2.20.1
+
diff --git a/debian/patches/75_11-GnuTLS-fix-tls_out_ocsp-under-hosts_request_ocsp.patch b/debian/patches/75_11-GnuTLS-fix-tls_out_ocsp-under-hosts_request_ocsp.patch
new file mode 100644
index 0000000..8322d93
--- /dev/null
+++ b/debian/patches/75_11-GnuTLS-fix-tls_out_ocsp-under-hosts_request_ocsp.patch
@@ -0,0 +1,54 @@
+From 5e64b73ef7cdaf20b998b3345a588b462fd30bfb Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Tue, 7 May 2019 22:55:41 +0100
+Subject: [PATCH] GnuTLS: fix $tls_out_ocsp under hosts_request_ocsp
+
+(cherry picked from commit 7a501c874f028f689c44999ab05bb0d39da46941)
+---
+ doc/ChangeLog | 3 +++
+ src/tls-gnu.c | 12 ++++++++----
+ test/log/5651 | 2 +-
+ test/log/5730 | 8 ++++----
+ 4 files changed, 16 insertions(+), 9 deletions(-)
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -39,6 +39,9 @@ JH/11 Harden plaintext authenticator aga
+ library routine (usually a crash). Found by "zerons".
+
+
++JH/18 GnuTLS: fix $tls_out_ocsp under hosts_request_ocsp. Previously the
++ verification result was not updated unless hosts_require_ocsp applied.
++
+
+ Exim version 4.92
+ -----------------
+--- a/src/tls-gnu.c
++++ b/src/tls-gnu.c
+@@ -2450,7 +2450,7 @@ if (!verify_certificate(state, errstr))
+ }
+
+ #ifndef DISABLE_OCSP
+-if (require_ocsp)
++if (request_ocsp)
+ {
+ DEBUG(D_tls)
+ {
+@@ -2474,10 +2474,14 @@ if (require_ocsp)
+ {
+ tlsp->ocsp = OCSP_FAILED;
+ tls_error(US"certificate status check failed", NULL, state->host, errstr);
+- return NULL;
++ if (require_ocsp)
++ return FALSE;
++ }
++ else
++ {
++ DEBUG(D_tls) debug_printf("Passed OCSP checking\n");
++ tlsp->ocsp = OCSP_VFIED;
+ }
+- DEBUG(D_tls) debug_printf("Passed OCSP checking\n");
+- tlsp->ocsp = OCSP_VFIED;
+ }
+ #endif
+
diff --git a/debian/patches/75_12-GnuTLS-fix-the-advertising-of-acceptable-certs-by-th.patch b/debian/patches/75_12-GnuTLS-fix-the-advertising-of-acceptable-certs-by-th.patch
new file mode 100644
index 0000000..5b98faa
--- /dev/null
+++ b/debian/patches/75_12-GnuTLS-fix-the-advertising-of-acceptable-certs-by-th.patch
@@ -0,0 +1,42 @@
+From 44893ba5249c6c6d5a0d62a1cc57ba3fbf7185b4 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Sun, 19 May 2019 12:12:36 +0100
+Subject: [PATCH 1/2] GnuTLS: fix the advertising of acceptable certs by the
+ server. Bug 2389
+
+(cherry picked from commit 12d95aa62042377fc9f603245a17a43142972447)
+---
+ doc/ChangeLog | 4 ++++
+ src/tls-gnu.c | 8 ++++++++
+ 2 files changed, 12 insertions(+)
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -42,6 +42,10 @@ JH/11 Harden plaintext authenticator aga
+ 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
++ directory-of-certs mode. Previously they were advertised despite the
++ documentation.
++
+
+ Exim version 4.92
+ -----------------
+--- a/src/tls-gnu.c
++++ b/src/tls-gnu.c
+@@ -1133,6 +1133,14 @@ else
+ #endif
+ gnutls_certificate_set_x509_trust_file(state->x509_cred,
+ CS state->exp_tls_verify_certificates, GNUTLS_X509_FMT_PEM);
++
++#ifdef SUPPORT_CA_DIR
++ /* Mimic the behaviour with OpenSSL of not advertising a usable-cert list
++ when using the directory-of-certs config model. */
++
++ if ((statbuf.st_mode & S_IFMT) == S_IFDIR)
++ gnutls_certificate_send_x509_rdn_sequence(state->session, 1);
++#endif
+ }
+
+ if (cert_count < 0)
diff --git a/debian/patches/75_13-Use-dsn_from-for-success-DSN-messages.-Bug-2404.patch b/debian/patches/75_13-Use-dsn_from-for-success-DSN-messages.-Bug-2404.patch
new file mode 100644
index 0000000..0eb4d4b
--- /dev/null
+++ b/debian/patches/75_13-Use-dsn_from-for-success-DSN-messages.-Bug-2404.patch
@@ -0,0 +1,52 @@
+From 454bab46ae6812e29652d10c390451c962a6f806 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Tue, 4 Jun 2019 18:13:21 +0100
+Subject: [PATCH 2/2] Use dsn_from for success-DSN messages. Bug 2404
+
+(cherry picked from commit 87abcb247b4444bab5fd0bcb212ddb26d5fd9191)
+---
+ doc/ChangeLog | 4 ++++
+ src/deliver.c | 4 ++--
+ 2 files changed, 6 insertions(+), 2 deletions(-)
+
+diff --git a/doc/ChangeLog b/doc/ChangeLog
+index 5a3e453d..1a12c014 100644
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -65,6 +65,10 @@ JH/20 Bug 2389: fix server advertising of usable certificates, under GnuTLS in
+ directory-of-certs mode. Previously they were advertised despite the
+ documentation.
+
++JH/27 Bug 2404: Use the main-section configuration option "dsn_from" for
++ success-DSN messages. Previously the From: header was always the default
++ one for these; the option was ignored.
++
+
+ Exim version 4.92
+ -----------------
+diff --git a/src/deliver.c b/src/deliver.c
+index e1799411..4720f596 100644
+--- a/src/deliver.c
++++ b/src/deliver.c
+@@ -7365,8 +7365,8 @@ if (addr_senddsn)
+ if (errors_reply_to)
+ fprintf(f, "Reply-To: %s\n", errors_reply_to);
+
++ moan_write_from(f);
+ fprintf(f, "Auto-Submitted: auto-generated\n"
+- "From: Mail Delivery System <Mailer-Daemon@%s>\n"
+ "To: %s\n"
+ "Subject: Delivery Status Notification\n"
+ "Content-Type: multipart/report; report-type=delivery-status; boundary=%s\n"
+@@ -7377,7 +7377,7 @@ if (addr_senddsn)
+
+ "This message was created automatically by mail delivery software.\n"
+ " ----- The following addresses had successful delivery notifications -----\n",
+- qualify_domain_sender, sender_address, bound, bound);
++ sender_address, bound, bound);
+
+ for (addr_dsntmp = addr_senddsn; addr_dsntmp;
+ addr_dsntmp = addr_dsntmp->next)
+--
+2.20.1
+
diff --git a/debian/patches/75_14-Fix-smtp-response-timeout.patch b/debian/patches/75_14-Fix-smtp-response-timeout.patch
new file mode 100644
index 0000000..abf2da4
--- /dev/null
+++ b/debian/patches/75_14-Fix-smtp-response-timeout.patch
@@ -0,0 +1,325 @@
+From 0a5441fcd93ae4145c07b3ed138dfe0e107174e0 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Mon, 27 May 2019 23:44:31 +0100
+Subject: [PATCH 1/2] Fix smtp response timeout
+
+---
+ doc/ChangeLog | 6 ++++++
+ src/functions.h | 4 ++--
+ src/ip.c | 16 +++++++---------
+ src/malware.c | 26 +++++++++++++-------------
+ src/routers/iplookup.c | 2 +-
+ src/smtp_out.c | 9 +++++----
+ src/spam.c | 2 +-
+ src/transports/smtp_socks.c | 6 +++---
+ src/verify.c | 2 +-
+ 9 files changed, 39 insertions(+), 34 deletions(-)
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -50,6 +50,13 @@ JH/27 Bug 2404: Use the main-section con
+ success-DSN messages. Previously the From: header was always the default
+ one for these; the option was ignored.
+
++JH/28 Fix the timeout on smtp response to apply to the whole response.
++ Previously it was reset for every read, so a teergrubing peer sending
++ single bytes within the time limit could extend the connection for a
++ long time. Credit to Qualsys Security Advisory Team for the discovery.
++[from GIT master]
++
++
+
+ Exim version 4.92
+ -----------------
+--- a/src/functions.h
++++ b/src/functions.h
+@@ -225,7 +225,7 @@ extern uschar *expand_string_copy(const
+ extern int_eximarith_t expand_string_integer(uschar *, BOOL);
+ extern void modify_variable(uschar *, void *);
+
+-extern BOOL fd_ready(int, int);
++extern BOOL fd_ready(int, time_t);
+
+ extern int filter_interpret(uschar *, int, address_item **, uschar **);
+ extern BOOL filter_personal(string_item *, BOOL);
+@@ -271,7 +271,7 @@ extern int ip_connectedsocket(int, c
+ int, host_item *, uschar **, const blob *);
+ extern int ip_get_address_family(int);
+ extern void ip_keepalive(int, const uschar *, BOOL);
+-extern int ip_recv(client_conn_ctx *, uschar *, int, int);
++extern int ip_recv(client_conn_ctx *, uschar *, int, time_t);
+ extern int ip_socket(int, int);
+
+ extern int ip_tcpsocket(const uschar *, uschar **, int);
+--- a/src/ip.c
++++ b/src/ip.c
+@@ -566,16 +566,15 @@ if (setsockopt(sock, SOL_SOCKET, SO_KEEP
+ /*
+ Arguments:
+ fd the file descriptor
+- timeout the timeout, seconds
++ timelimit the timeout endpoint, seconds-since-epoch
+ Returns: TRUE => ready for i/o
+ FALSE => timed out, or other error
+ */
+ BOOL
+-fd_ready(int fd, int timeout)
++fd_ready(int fd, time_t timelimit)
+ {
+ fd_set select_inset;
+-time_t start_recv = time(NULL);
+-int time_left = timeout;
++int time_left = timelimit - time(NULL);
+ int rc;
+
+ if (time_left <= 0)
+@@ -609,8 +608,7 @@ do
+ DEBUG(D_transport) debug_printf("EINTR while waiting for socket data\n");
+
+ /* Watch out, 'continue' jumps to the condition, not to the loops top */
+- time_left = timeout - (time(NULL) - start_recv);
+- if (time_left > 0) continue;
++ if ((time_left = timelimit - time(NULL)) > 0) continue;
+ }
+
+ if (rc <= 0)
+@@ -634,18 +632,18 @@ Arguments:
+ cctx the connection context (socket fd, possibly TLS context)
+ buffer to read into
+ bufsize the buffer size
+- timeout the timeout
++ timelimit the timeout endpoint, seconds-since-epoch
+
+ Returns: > 0 => that much data read
+ <= 0 on error or EOF; errno set - zero for EOF
+ */
+
+ int
+-ip_recv(client_conn_ctx * cctx, uschar * buffer, int buffsize, int timeout)
++ip_recv(client_conn_ctx * cctx, uschar * buffer, int buffsize, time_t timelimit)
+ {
+ int rc;
+
+-if (!fd_ready(cctx->sock, timeout))
++if (!fd_ready(cctx->sock, timelimit))
+ return -1;
+
+ /* The socket is ready, read from it (via TLS if it's active). On EOF (i.e.
+--- a/src/malware.c
++++ b/src/malware.c
+@@ -349,13 +349,13 @@ return cre;
+ -2 on timeout or error
+ */
+ static int
+-recv_line(int fd, uschar * buffer, int bsize, int tmo)
++recv_line(int fd, uschar * buffer, int bsize, time_t tmo)
+ {
+ uschar * p = buffer;
+ ssize_t rcv;
+ BOOL ok = FALSE;
+
+-if (!fd_ready(fd, tmo-time(NULL)))
++if (!fd_ready(fd, tmo))
+ return -2;
+
+ /*XXX tmo handling assumes we always get a whole line */
+@@ -382,9 +382,9 @@ return p - buffer;
+
+ /* return TRUE iff size as requested */
+ static BOOL
+-recv_len(int sock, void * buf, int size, int tmo)
++recv_len(int sock, void * buf, int size, time_t tmo)
+ {
+-return fd_ready(sock, tmo-time(NULL))
++return fd_ready(sock, tmo)
+ ? recv(sock, buf, size, 0) == size
+ : FALSE;
+ }
+@@ -430,7 +430,7 @@ for (;;)
+ }
+
+ static inline int
+-mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size, int tmo)
++mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size, time_t tmo)
+ {
+ client_conn_ctx cctx = {.sock = sock};
+ int offset = 0;
+@@ -438,7 +438,7 @@ int i;
+
+ do
+ {
+- i = ip_recv(&cctx, av_buffer+offset, av_buffer_size-offset, tmo-time(NULL));
++ i = ip_recv(&cctx, av_buffer+offset, av_buffer_size-offset, tmo);
+ if (i <= 0)
+ {
+ (void) malware_panic_defer(US"unable to read from mksd UNIX socket (/var/run/mksd/socket)");
+@@ -497,7 +497,7 @@ switch (*line)
+
+ static int
+ mksd_scan_packed(struct scan * scanent, int sock, const uschar * scan_filename,
+- int tmo)
++ time_t tmo)
+ {
+ struct iovec iov[3];
+ const char *cmd = "MSQ\n";
+@@ -746,7 +746,7 @@ if (!malware_ok)
+ if (m_sock_send(malware_daemon_ctx.sock, scanrequest, Ustrlen(scanrequest), &errstr) < 0)
+ return m_panic_defer(scanent, CUS callout_address, errstr);
+
+- bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL));
++ bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo);
+
+ if (bread <= 0)
+ return m_panic_defer_3(scanent, CUS callout_address,
+@@ -1064,7 +1064,7 @@ badseek: err = errno;
+ if (m_sock_send(malware_daemon_ctx.sock, cmdopt[i], Ustrlen(cmdopt[i]), &errstr) < 0)
+ return m_panic_defer(scanent, CUS callout_address, errstr);
+
+- bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL));
++ bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo);
+ if (bread > 0) av_buffer[bread]='\0';
+ if (bread < 0)
+ return m_panic_defer_3(scanent, CUS callout_address,
+@@ -1096,7 +1096,7 @@ badseek: err = errno;
+ {
+ errno = ETIMEDOUT;
+ i = av_buffer+sizeof(av_buffer)-p;
+- if ((bread= ip_recv(&malware_daemon_ctx, p, i-1, tmo-time(NULL))) < 0)
++ if ((bread= ip_recv(&malware_daemon_ctx, p, i-1, tmo)) < 0)
+ return m_panic_defer_3(scanent, CUS callout_address,
+ string_sprintf("unable to read result (%s)", strerror(errno)),
+ malware_daemon_ctx.sock);
+@@ -1401,7 +1401,7 @@ badseek: err = errno;
+
+ /* wait for result */
+ memset(av_buffer, 0, sizeof(av_buffer));
+- if ((bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL))) <= 0)
++ if ((bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo)) <= 0)
+ return m_panic_defer_3(scanent, CUS callout_address,
+ string_sprintf("unable to read from UNIX socket (%s)", scanner_options),
+ malware_daemon_ctx.sock);
+@@ -1737,7 +1737,7 @@ b_seek: err = errno;
+
+ /* Read the result */
+ memset(av_buffer, 0, sizeof(av_buffer));
+- bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL));
++ bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo);
+ (void)close(malware_daemon_ctx.sock);
+ malware_daemon_ctx.sock = -1;
+ malware_daemon_ctx.tls_ctx = NULL;
+@@ -1895,7 +1895,7 @@ b_seek: err = errno;
+ return m_panic_defer(scanent, CUS callout_address, errstr);
+
+ /* Read the result */
+- bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL));
++ bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo);
+
+ if (bread <= 0)
+ return m_panic_defer_3(scanent, CUS callout_address,
+--- a/src/routers/iplookup.c
++++ b/src/routers/iplookup.c
+@@ -279,7 +279,7 @@ while ((hostname = string_nextinlist(&li
+ /* Read the response and close the socket. If the read fails, try the
+ next IP address. */
+
+- count = ip_recv(&query_cctx, reply, sizeof(reply) - 1, ob->timeout);
++ count = ip_recv(&query_cctx, reply, sizeof(reply) - 1, time(NULL) + ob->timeout);
+ (void)close(query_cctx.sock);
+ if (count <= 0)
+ {
+--- a/src/smtp_out.c
++++ b/src/smtp_out.c
+@@ -587,14 +587,14 @@ Arguments:
+ inblock the SMTP input block (contains holding buffer, socket, etc.)
+ buffer where to put the line
+ size space available for the line
+- timeout the timeout to use when reading a packet
++ 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
+ */
+
+ static int
+-read_response_line(smtp_inblock *inblock, uschar *buffer, int size, int timeout)
++read_response_line(smtp_inblock *inblock, uschar *buffer, int size, time_t timelimit)
+ {
+ uschar *p = buffer;
+ uschar *ptr = inblock->ptr;
+@@ -637,7 +637,7 @@ for (;;)
+
+ /* Need to read a new input packet. */
+
+- if((rc = ip_recv(cctx, inblock->buffer, inblock->buffersize, timeout)) <= 0)
++ if((rc = ip_recv(cctx, inblock->buffer, inblock->buffersize, timelimit)) <= 0)
+ {
+ DEBUG(D_deliver|D_transport|D_acl)
+ debug_printf_indent(errno ? " SMTP(%s)<<\n" : " SMTP(closed)<<\n",
+@@ -694,6 +694,7 @@ smtp_read_response(void * sx0, uschar *
+ smtp_context * sx = sx0;
+ uschar * ptr = buffer;
+ int count = 0, rc;
++time_t timelimit = time(NULL) + timeout;
+
+ errno = 0; /* Ensure errno starts out zero */
+
+@@ -713,7 +714,7 @@ response. */
+
+ for (;;)
+ {
+- if ((count = read_response_line(&sx->inblock, ptr, size, timeout)) < 0)
++ if ((count = read_response_line(&sx->inblock, ptr, size, timelimit)) < 0)
+ return FALSE;
+
+ HDEBUG(D_transport|D_acl|D_v)
+--- a/src/spam.c
++++ b/src/spam.c
+@@ -503,7 +503,7 @@ offset = 0;
+ while ((i = ip_recv(&spamd_cctx,
+ spamd_buffer + offset,
+ sizeof(spamd_buffer) - offset - 1,
+- sd->timeout - time(NULL) + start)) > 0)
++ sd->timeout + start)) > 0)
+ offset += i;
+ spamd_buffer[offset] = '\0'; /* guard byte */
+
+--- a/src/transports/smtp_socks.c
++++ b/src/transports/smtp_socks.c
+@@ -129,7 +129,7 @@ switch(method)
+ #ifdef TCP_QUICKACK
+ (void) setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
+ #endif
+- if (!fd_ready(fd, tmo-time(NULL)) || read(fd, s, 2) != 2)
++ if (!fd_ready(fd, tmo) || read(fd, s, 2) != 2)
+ return FAIL;
+ HDEBUG(D_transport|D_acl|D_v)
+ debug_printf_indent(" SOCKS<< %02x %02x\n", s[0], s[1]);
+@@ -320,7 +320,7 @@ HDEBUG(D_transport|D_acl|D_v) debug_prin
+ (void) setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
+ #endif
+
+-if ( !fd_ready(fd, tmo-time(NULL))
++if ( !fd_ready(fd, tmo)
+ || read(fd, buf, 2) != 2
+ )
+ goto rcv_err;
+@@ -370,7 +370,7 @@ if (send(fd, buf, size, 0) < 0)
+ /* expect conn-reply (success, local(ipver, addr, port))
+ of same length as conn-request, or non-success fail code */
+
+-if ( !fd_ready(fd, tmo-time(NULL))
++if ( !fd_ready(fd, tmo)
+ || (size = read(fd, buf, size)) < 2
+ )
+ goto rcv_err;
+--- a/src/verify.c
++++ b/src/verify.c
+@@ -2770,7 +2770,7 @@ for (;;)
+ int size = sizeof(buffer) - (p - buffer);
+
+ if (size <= 0) goto END_OFF; /* Buffer filled without seeing \n. */
+- count = ip_recv(&ident_conn_ctx, p, size, rfc1413_query_timeout);
++ count = ip_recv(&ident_conn_ctx, p, size, time(NULL) + rfc1413_query_timeout);
+ if (count <= 0) goto END_OFF; /* Read error or EOF */
+
+ /* Scan what we just read, to see if we have reached the terminating \r\n. Be
diff --git a/debian/patches/75_15-Fix-detection-of-32b-platform-at-build-time.-Bug-240.patch b/debian/patches/75_15-Fix-detection-of-32b-platform-at-build-time.-Bug-240.patch
new file mode 100644
index 0000000..039ed5f
--- /dev/null
+++ b/debian/patches/75_15-Fix-detection-of-32b-platform-at-build-time.-Bug-240.patch
@@ -0,0 +1,48 @@
+From 26dd3aa007b3b77969610c031f59388e0953bd00 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Fri, 7 Jun 2019 11:54:10 +0100
+Subject: [PATCH 2/2] Fix detection of 32b platform at build time. Bug 2405
+
+---
+ src/buildconfig.c | 12 +++---
+ test/scripts/0000-Basic/0002 | 72 +++++++++++++++++++-----------------
+ test/stdout/0002 | 72 +++++++++++++++++++-----------------
+ 3 files changed, 83 insertions(+), 73 deletions(-)
+
+diff --git a/src/buildconfig.c b/src/buildconfig.c
+index 71cf97b1..a680b344 100644
+--- a/src/buildconfig.c
++++ b/src/buildconfig.c
+@@ -111,6 +111,7 @@ unsigned long test_ulong_t = 0L;
+ unsigned int test_uint_t = 0;
+ #endif
+ long test_long_t = 0;
++long long test_longlong_t = 0;
+ int test_int_t = 0;
+ FILE *base;
+ FILE *new;
+@@ -155,15 +156,16 @@ This assumption is known to be OK for the common operating systems. */
+
+ fprintf(new, "#ifndef OFF_T_FMT\n");
+ if (sizeof(test_off_t) > sizeof(test_long_t))
+- {
+ fprintf(new, "# define OFF_T_FMT \"%%lld\"\n");
+- fprintf(new, "# define LONGLONG_T long long int\n");
+- }
+ else
+- {
+ fprintf(new, "# define OFF_T_FMT \"%%ld\"\n");
++fprintf(new, "#endif\n\n");
++
++fprintf(new, "#ifndef LONGLONG_T\n");
++if (sizeof(test_longlong_t) > sizeof(test_long_t))
++ fprintf(new, "# define LONGLONG_T long long int\n");
++else
+ fprintf(new, "# define LONGLONG_T long int\n");
+- }
+ fprintf(new, "#endif\n\n");
+
+ /* Now do the same thing for time_t variables. If the length is greater than
+--
+2.20.1
+
diff --git a/debian/patches/77_Avoid-re-expansion-in-sort-CVE-2019-13917-OVE-201907.patch b/debian/patches/77_Avoid-re-expansion-in-sort-CVE-2019-13917-OVE-201907.patch
new file mode 100644
index 0000000..50c63ed
--- /dev/null
+++ b/debian/patches/77_Avoid-re-expansion-in-sort-CVE-2019-13917-OVE-201907.patch
@@ -0,0 +1,394 @@
+From cf84d126bc1f04746eb7c8e8b3468f7e70add3ec Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Fri, 5 Jul 2019 15:38:15 +0100
+Subject: [PATCH] Avoid re-expansion in ${sort } CVE-2019-13917
+ OVE-20190718-0006
+
+(cherry picked from commit 5c887f836e4d8e3f79da1c15565b56b40d9bd0dd)
+---
+ doc/ChangeLog | 6 ++
+ doc/doc-txt/cve-2019-13917 | 46 ++++++++
+ src/expand.c | 214 +++++++++++++++++++++++++------------
+ 3 files changed, 199 insertions(+), 67 deletions(-)
+ create mode 100644 doc/doc-txt/cve-2019-13917
+
+--- a/src/expand.c
++++ b/src/expand.c
+@@ -2147,6 +2147,55 @@ return ret;
+
+
+
++/************************************************/
++/* Return offset in ops table, or -1 if not found.
++Repoint to just after the operator in the string.
++
++Argument:
++ ss string representation of operator
++ opname split-out operator name
++*/
++
++static int
++identify_operator(const uschar ** ss, uschar ** opname)
++{
++const uschar * s = *ss;
++uschar name[256];
++
++/* Numeric comparisons are symbolic */
++
++if (*s == '=' || *s == '>' || *s == '<')
++ {
++ int p = 0;
++ name[p++] = *s++;
++ if (*s == '=')
++ {
++ name[p++] = '=';
++ s++;
++ }
++ name[p] = 0;
++ }
++
++/* All other conditions are named */
++
++else
++ s = read_name(name, sizeof(name), s, US"_");
++*ss = s;
++
++/* If we haven't read a name, it means some non-alpha character is first. */
++
++if (!name[0])
++ {
++ expand_string_message = string_sprintf("condition name expected, "
++ "but found \"%.16s\"", s);
++ return -1;
++ }
++if (opname)
++ *opname = string_copy(name);
++
++return chop_match(name, cond_table, nelem(cond_table));
++}
++
+
+ /*************************************************
+ * Read and evaluate a condition *
+@@ -2177,6 +2226,7 @@ BOOL sub2_honour_dollar = TRUE;
+ int i, rc, cond_type, roffset;
+ int_eximarith_t num[2];
+ struct stat statbuf;
++uschar * opname;
+ uschar name[256];
+ const uschar *sub[10];
+
+@@ -2189,37 +2239,7 @@ for (;;)
+ if (*s == '!') { testfor = !testfor; s++; } else break;
+ }
+
+-/* Numeric comparisons are symbolic */
+-
+-if (*s == '=' || *s == '>' || *s == '<')
+- {
+- int p = 0;
+- name[p++] = *s++;
+- if (*s == '=')
+- {
+- name[p++] = '=';
+- s++;
+- }
+- name[p] = 0;
+- }
+-
+-/* All other conditions are named */
+-
+-else s = read_name(name, 256, s, US"_");
+-
+-/* If we haven't read a name, it means some non-alpha character is first. */
+-
+-if (name[0] == 0)
+- {
+- expand_string_message = string_sprintf("condition name expected, "
+- "but found \"%.16s\"", s);
+- return NULL;
+- }
+-
+-/* Find which condition we are dealing with, and switch on it */
+-
+-cond_type = chop_match(name, cond_table, nelem(cond_table));
+-switch(cond_type)
++switch(cond_type = identify_operator(&s, &opname))
+ {
+ /* def: tests for a non-empty variable, or for the existence of a header. If
+ yield == NULL we are in a skipping state, and don't care about the answer. */
+@@ -2538,7 +2558,7 @@ switch(cond_type)
+ {
+ if (i == 0) goto COND_FAILED_CURLY_START;
+ expand_string_message = string_sprintf("missing 2nd string in {} "
+- "after \"%s\"", name);
++ "after \"%s\"", opname);
+ return NULL;
+ }
+ if (!(sub[i] = expand_string_internal(s+1, TRUE, &s, yield == NULL,
+@@ -2553,7 +2573,7 @@ switch(cond_type)
+ conditions that compare numbers do not start with a letter. This just saves
+ checking for them individually. */
+
+- if (!isalpha(name[0]) && yield != NULL)
++ if (!isalpha(opname[0]) && yield != NULL)
+ if (sub[i][0] == 0)
+ {
+ num[i] = 0;
+@@ -2867,7 +2887,7 @@ switch(cond_type)
+ uschar *save_iterate_item = iterate_item;
+ int (*compare)(const uschar *, const uschar *);
+
+- DEBUG(D_expand) debug_printf_indent("condition: %s item: %s\n", name, sub[0]);
++ DEBUG(D_expand) debug_printf_indent("condition: %s item: %s\n", opname, sub[0]);
+
+ tempcond = FALSE;
+ compare = cond_type == ECOND_INLISTI
+@@ -2909,14 +2929,14 @@ switch(cond_type)
+ if (*s != '{') /* }-for-text-editors */
+ {
+ expand_string_message = string_sprintf("each subcondition "
+- "inside an \"%s{...}\" condition must be in its own {}", name);
++ "inside an \"%s{...}\" condition must be in its own {}", opname);
+ return NULL;
+ }
+
+ if (!(s = eval_condition(s+1, resetok, subcondptr)))
+ {
+ expand_string_message = string_sprintf("%s inside \"%s{...}\" condition",
+- expand_string_message, name);
++ expand_string_message, opname);
+ return NULL;
+ }
+ while (isspace(*s)) s++;
+@@ -2926,7 +2946,7 @@ switch(cond_type)
+ {
+ /* {-for-text-editors */
+ expand_string_message = string_sprintf("missing } at end of condition "
+- "inside \"%s\" group", name);
++ "inside \"%s\" group", opname);
+ return NULL;
+ }
+
+@@ -2958,7 +2978,7 @@ switch(cond_type)
+ int sep = 0;
+ uschar *save_iterate_item = iterate_item;
+
+- DEBUG(D_expand) debug_printf_indent("condition: %s\n", name);
++ DEBUG(D_expand) debug_printf_indent("condition: %s\n", opname);
+
+ while (isspace(*s)) s++;
+ if (*s++ != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */
+@@ -2979,7 +2999,7 @@ switch(cond_type)
+ if (!(s = eval_condition(sub[1], resetok, NULL)))
+ {
+ expand_string_message = string_sprintf("%s inside \"%s\" condition",
+- expand_string_message, name);
++ expand_string_message, opname);
+ return NULL;
+ }
+ while (isspace(*s)) s++;
+@@ -2989,7 +3009,7 @@ switch(cond_type)
+ {
+ /* {-for-text-editors */
+ expand_string_message = string_sprintf("missing } at end of condition "
+- "inside \"%s\"", name);
++ "inside \"%s\"", opname);
+ return NULL;
+ }
+
+@@ -3001,11 +3021,11 @@ switch(cond_type)
+ if (!eval_condition(sub[1], resetok, &tempcond))
+ {
+ expand_string_message = string_sprintf("%s inside \"%s\" condition",
+- expand_string_message, name);
++ expand_string_message, opname);
+ iterate_item = save_iterate_item;
+ return NULL;
+ }
+- DEBUG(D_expand) debug_printf_indent("%s: condition evaluated to %s\n", name,
++ DEBUG(D_expand) debug_printf_indent("%s: condition evaluated to %s\n", opname,
+ tempcond? "true":"false");
+
+ if (yield != NULL) *yield = (tempcond == testfor);
+@@ -3098,19 +3118,20 @@ switch(cond_type)
+ /* Unknown condition */
+
+ default:
+- expand_string_message = string_sprintf("unknown condition \"%s\"", name);
+- return NULL;
++ if (!expand_string_message || !*expand_string_message)
++ expand_string_message = string_sprintf("unknown condition \"%s\"", opname);
++ return NULL;
+ } /* End switch on condition type */
+
+ /* Missing braces at start and end of data */
+
+ COND_FAILED_CURLY_START:
+-expand_string_message = string_sprintf("missing { after \"%s\"", name);
++expand_string_message = string_sprintf("missing { after \"%s\"", opname);
+ return NULL;
+
+ COND_FAILED_CURLY_END:
+ expand_string_message = string_sprintf("missing } at end of \"%s\" condition",
+- name);
++ opname);
+ return NULL;
+
+ /* A condition requires code that is not compiled */
+@@ -3120,7 +3141,7 @@ return NULL;
+ !defined(SUPPORT_CRYPTEQ) || !defined(CYRUS_SASLAUTHD_SOCKET)
+ COND_FAILED_NOT_COMPILED:
+ expand_string_message = string_sprintf("support for \"%s\" not compiled",
+- name);
++ opname);
+ return NULL;
+ #endif
+ }
+@@ -3849,6 +3870,56 @@ return x;
+
+
+
++/************************************************/
++/* Comparison operation for sort expansion. We need to avoid
++re-expanding the fields being compared, so need a custom routine.
++
++Arguments:
++ cond_type Comparison operator code
++ leftarg, rightarg Arguments for comparison
++
++Return true iff (leftarg compare rightarg)
++*/
++
++static BOOL
++sortsbefore(int cond_type, BOOL alpha_cond,
++ const uschar * leftarg, const uschar * rightarg)
++{
++int_eximarith_t l_num, r_num;
++
++if (!alpha_cond)
++ {
++ l_num = expanded_string_integer(leftarg, FALSE);
++ if (expand_string_message) return FALSE;
++ r_num = expanded_string_integer(rightarg, FALSE);
++ if (expand_string_message) return FALSE;
++
++ switch (cond_type)
++ {
++ case ECOND_NUM_G: return l_num > r_num;
++ case ECOND_NUM_GE: return l_num >= r_num;
++ case ECOND_NUM_L: return l_num < r_num;
++ case ECOND_NUM_LE: return l_num <= r_num;
++ default: break;
++ }
++ }
++else
++ switch (cond_type)
++ {
++ case ECOND_STR_LT: return Ustrcmp (leftarg, rightarg) < 0;
++ case ECOND_STR_LTI: return strcmpic(leftarg, rightarg) < 0;
++ case ECOND_STR_LE: return Ustrcmp (leftarg, rightarg) <= 0;
++ case ECOND_STR_LEI: return strcmpic(leftarg, rightarg) <= 0;
++ case ECOND_STR_GT: return Ustrcmp (leftarg, rightarg) > 0;
++ case ECOND_STR_GTI: return strcmpic(leftarg, rightarg) > 0;
++ case ECOND_STR_GE: return Ustrcmp (leftarg, rightarg) >= 0;
++ case ECOND_STR_GEI: return strcmpic(leftarg, rightarg) >= 0;
++ default: break;
++ }
++return FALSE; /* should not happen */
++}
++
++
+ /* Return pointer to dewrapped string, with enclosing specified chars removed.
+ The given string is modified on return. Leading whitespace is skipped while
+ looking for the opening wrap character, then the rest is scanned for the trailing
+@@ -3905,7 +3976,7 @@ The element may itself be an object or a
+ Return NULL when the list is empty.
+ */
+
+-uschar *
++static uschar *
+ json_nextinlist(const uschar ** list)
+ {
+ unsigned array_depth = 0, object_depth = 0;
+@@ -6243,9 +6314,10 @@ while (*s != 0)
+
+ case EITEM_SORT:
+ {
++ int cond_type;
+ int sep = 0;
+ const uschar *srclist, *cmp, *xtract;
+- uschar *srcitem;
++ uschar * opname, * srcitem;
+ const uschar *dstlist = NULL, *dstkeylist = NULL;
+ uschar * tmp;
+ uschar *save_iterate_item = iterate_item;
+@@ -6280,6 +6352,25 @@ while (*s != 0)
+ goto EXPAND_FAILED_CURLY;
+ }
+
++ if ((cond_type = identify_operator(&cmp, &opname)) == -1)
++ {
++ if (!expand_string_message)
++ expand_string_message = string_sprintf("unknown condition \"%s\"", s);
++ goto EXPAND_FAILED;
++ }
++ switch(cond_type)
++ {
++ case ECOND_NUM_L: case ECOND_NUM_LE:
++ case ECOND_NUM_G: case ECOND_NUM_GE:
++ case ECOND_STR_GE: case ECOND_STR_GEI: case ECOND_STR_GT: case ECOND_STR_GTI:
++ case ECOND_STR_LE: case ECOND_STR_LEI: case ECOND_STR_LT: case ECOND_STR_LTI:
++ break;
++
++ default:
++ expand_string_message = US"comparator not handled for sort";
++ goto EXPAND_FAILED;
++ }
++
+ while (isspace(*s)) s++;
+ if (*s++ != '{')
+ {
+@@ -6307,11 +6398,10 @@ while (*s != 0)
+ if (skipping) continue;
+
+ while ((srcitem = string_nextinlist(&srclist, &sep, NULL, 0)))
+- {
+- uschar * dstitem;
++ {
++ uschar * srcfield, * dstitem;
+ gstring * newlist = NULL;
+ gstring * newkeylist = NULL;
+- uschar * srcfield;
+
+ DEBUG(D_expand) debug_printf_indent("%s: $item = \"%s\"\n", name, srcitem);
+
+@@ -6332,25 +6422,15 @@ while (*s != 0)
+ while ((dstitem = string_nextinlist(&dstlist, &sep, NULL, 0)))
+ {
+ uschar * dstfield;
+- uschar * expr;
+- BOOL before;
+
+ /* field for comparison */
+ if (!(dstfield = string_nextinlist(&dstkeylist, &sep, NULL, 0)))
+ goto sort_mismatch;
+
+- /* build and run condition string */
+- expr = string_sprintf("%s{%s}{%s}", cmp, srcfield, dstfield);
+-
+- DEBUG(D_expand) debug_printf_indent("%s: cond = \"%s\"\n", name, expr);
+- if (!eval_condition(expr, &resetok, &before))
+- {
+- expand_string_message = string_sprintf("comparison in sort: %s",
+- expr);
+- goto EXPAND_FAILED;
+- }
++ /* String-comparator names start with a letter; numeric names do not */
+
+- if (before)
++ if (sortsbefore(cond_type, isalpha(opname[0]),
++ srcfield, dstfield))
+ {
+ /* New-item sorts before this dst-item. Append new-item,
+ then dst-item, then remainder of dst list. */
diff --git a/debian/patches/78_01-string.c-do-not-interpret-before-0-CVE-2019-15846.patch b/debian/patches/78_01-string.c-do-not-interpret-before-0-CVE-2019-15846.patch
new file mode 100644
index 0000000..38ba939
--- /dev/null
+++ b/debian/patches/78_01-string.c-do-not-interpret-before-0-CVE-2019-15846.patch
@@ -0,0 +1,50 @@
+From 2600301ba6dbac5c9d640c87007a07ee6dcea1f4 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Mon, 19 Aug 2019 14:45:48 +0200
+Subject: [PATCH] string.c: do not interpret '\\' before '\0' (CVE-2019-15846)
+
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -4,6 +4,11 @@ This document describes *changes* to pre
+ affect Exim's operation, with an unchanged configuration file. For new
+ options, and new features, see the NewStuff file next to this ChangeLog.
+
++Exim version 4.92.2
++-------------------
++
++HS/01 Handle trailing backslash gracefully. (CVE-2019-15846)
++
+
+ Since version 4.92
+ ------------------
+--- a/src/string.c
++++ b/src/string.c
+@@ -224,6 +224,8 @@ interpreted in strings.
+ Arguments:
+ pp points a pointer to the initiating "\" in the string;
+ the pointer gets updated to point to the final character
++ If the backslash is the last character in the string, it
++ is not interpreted.
+ Returns: the value of the character escape
+ */
+
+@@ -236,6 +238,7 @@ const uschar *hex_digits= CUS"0123456789
+ int ch;
+ const uschar *p = *pp;
+ ch = *(++p);
++if (ch == '\0') return **pp;
+ if (isdigit(ch) && ch != '8' && ch != '9')
+ {
+ ch -= '0';
+@@ -1210,8 +1213,8 @@ memcpy(g->s + p, s, count);
+ g->ptr = p + count;
+ return g;
+ }
+-
+-
++
++
+ gstring *
+ string_cat(gstring *string, const uschar *s)
+ {
diff --git a/debian/patches/78_02-Fix-buffer-overflow-in-string_vformat.-Bug-2449.patch b/debian/patches/78_02-Fix-buffer-overflow-in-string_vformat.-Bug-2449.patch
new file mode 100644
index 0000000..6c27517
--- /dev/null
+++ b/debian/patches/78_02-Fix-buffer-overflow-in-string_vformat.-Bug-2449.patch
@@ -0,0 +1,36 @@
+From 478effbfd9c3cc5a627fc671d4bf94d13670d65f Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Fri, 27 Sep 2019 12:21:49 +0100
+Subject: [PATCH] Fix buffer overflow in string_vformat. Bug 2449
+
+---
+ src/string.c | 4 ++--
+ test/scripts/0000-Basic/0214 | 11 +++++++++++
+ test/stdout/0214 | 7 +++++++
+ 3 files changed, 20 insertions(+), 2 deletions(-)
+
+diff --git a/src/string.c b/src/string.c
+index c6549bf93..3445f8a42 100644
+--- a/src/string.c
++++ b/src/string.c
+@@ -1132,7 +1132,7 @@ store_reset(g->s + (g->size = g->ptr + 1));
+ Arguments:
+ g the growable-string
+ p current end of data
+- count amount to grow by
++ count amount to grow by, offset from p
+ */
+
+ static void
+@@ -1590,7 +1590,7 @@ while (*fp)
+ }
+ else if (g->ptr >= lim - width)
+ {
+- gstring_grow(g, g->ptr, width - (lim - g->ptr));
++ gstring_grow(g, g->ptr, width);
+ lim = g->size - 1;
+ gp = CS g->s + g->ptr;
+ }
+--
+2.23.0
+
diff --git a/debian/patches/79_01-Fix-SPA-authenticator-checking-client-supplied-data-.patch b/debian/patches/79_01-Fix-SPA-authenticator-checking-client-supplied-data-.patch
new file mode 100644
index 0000000..f0688bb
--- /dev/null
+++ b/debian/patches/79_01-Fix-SPA-authenticator-checking-client-supplied-data-.patch
@@ -0,0 +1,74 @@
+From 6a7edbf6608d10ef0c707c426511e667849518d7 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Tue, 5 May 2020 21:15:34 +0100
+Subject: [PATCH 1/2] Fix SPA authenticator, checking client-supplied data
+ before using it. Bug 2571
+
+(cherry picked from commit 57aa14b216432be381b6295c312065b2fd034f86)
+---
+ doc/ChangeLog | 5 +++++
+ src/auths/spa.c | 22 ++++++++++++++++------
+ 2 files changed, 21 insertions(+), 6 deletions(-)
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -62,6 +62,11 @@ JH/28 Fix the timeout on smtp response t
+ [from GIT master]
+
+
++JH/41 Bug 2571: Fix SPA authenticator. Running as a server, an offset supplied
++ by the client was not checked as pointing within response data before
++ being used. A malicious client could thus cause an out-of-bounds read and
++ possibly gain authentication. Fix by adding the check.
++
+
+ Exim version 4.92
+ -----------------
+--- a/src/auths/spa.c
++++ b/src/auths/spa.c
+@@ -139,7 +139,7 @@ SPAAuthChallenge challenge;
+ SPAAuthResponse response;
+ SPAAuthResponse *responseptr = &response;
+ uschar msgbuf[2048];
+-uschar *clearpass;
++uschar *clearpass, *s;
+
+ /* send a 334, MS Exchange style, and grab the client's request,
+ unless we already have it via an initial response. */
+@@ -197,6 +197,13 @@ that causes failure if the size of msgbu
+ char *p = ((char*)responseptr) + IVAL(&responseptr->uUser.offset,0);
+ int len = SVAL(&responseptr->uUser.len,0)/2;
+
++ if (p + len*2 >= CS (responseptr+1))
++ {
++ DEBUG(D_auth)
++ debug_printf("auth_spa_server(): bad uUser spec in response\n");
++ return FAIL;
++ }
++
+ if (len + 1 >= sizeof(msgbuf)) return FAIL;
+ for (i = 0; i < len; ++i)
+ {
+@@ -245,14 +252,17 @@ spa_smb_nt_encrypt (clearpass, challenge
+
+ /* compare NT hash (LM may not be available) */
+
+-if (memcmp(ntRespData,
+- ((unsigned char*)responseptr)+IVAL(&responseptr->ntResponse.offset,0),
+- 24) == 0)
+- /* success. we have a winner. */
++s = (US responseptr) + IVAL(&responseptr->ntResponse.offset,0);
++if (s + 24 >= US (responseptr+1))
+ {
+- return auth_check_serv_cond(ablock);
++ DEBUG(D_auth)
++ debug_printf("auth_spa_server(): bad ntRespData spec in response\n");
++ return FAIL;
+ }
+
++if (memcmp(ntRespData, s, 24) == 0)
++ return auth_check_serv_cond(ablock); /* success. we have a winner. */
++
+ /* Expand server_condition as an authorization check (PH) */
+
+ return FAIL;
diff --git a/debian/patches/79_02-Rework-SPA-fix-to-avoid-overflows.-Bug-2571.patch b/debian/patches/79_02-Rework-SPA-fix-to-avoid-overflows.-Bug-2571.patch
new file mode 100644
index 0000000..5bc18e3
--- /dev/null
+++ b/debian/patches/79_02-Rework-SPA-fix-to-avoid-overflows.-Bug-2571.patch
@@ -0,0 +1,59 @@
+From 5a41d2c2cd2b28a0d1aea21edeaea02bd6db4984 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Wed, 6 May 2020 22:31:25 +0100
+Subject: [PATCH 2/2] Rework SPA fix to avoid overflows. Bug 2571
+
+Amends: 6a7edbf660
+(cherry picked from commit a04174dc2a84ae1008c23b6a7109e7fa3fb7b8b0)
+---
+ src/auths/spa.c | 13 +++++++++----
+ 1 file changed, 9 insertions(+), 4 deletions(-)
+
+diff --git a/src/auths/spa.c b/src/auths/spa.c
+index ed9aff23b..4e3aef808 100644
+--- a/src/auths/spa.c
++++ b/src/auths/spa.c
+@@ -140,6 +140,7 @@ SPAAuthResponse response;
+ SPAAuthResponse *responseptr = &response;
+ uschar msgbuf[2048];
+ uschar *clearpass, *s;
++unsigned off;
+
+ /* send a 334, MS Exchange style, and grab the client's request,
+ unless we already have it via an initial response. */
+@@ -194,10 +195,13 @@ that causes failure if the size of msgbuf is exceeded. ****/
+
+ {
+ int i;
+- char *p = ((char*)responseptr) + IVAL(&responseptr->uUser.offset,0);
++ char * p;
+ int len = SVAL(&responseptr->uUser.len,0)/2;
+
+- if (p + len*2 >= CS (responseptr+1))
++ if ( (off = IVAL(&responseptr->uUser.offset,0)) >= sizeof(SPAAuthResponse)
++ || len >= sizeof(responseptr->buffer)/2
++ || (p = (CS responseptr) + off) + len*2 >= CS (responseptr+1)
++ )
+ {
+ DEBUG(D_auth)
+ debug_printf("auth_spa_server(): bad uUser spec in response\n");
+@@ -252,13 +256,14 @@ spa_smb_nt_encrypt (clearpass, challenge.challengeData, ntRespData);
+
+ /* compare NT hash (LM may not be available) */
+
+-s = (US responseptr) + IVAL(&responseptr->ntResponse.offset,0);
+-if (s + 24 >= US (responseptr+1))
++off = IVAL(&responseptr->ntResponse.offset,0);
++if (off >= sizeof(SPAAuthResponse) - 24)
+ {
+ DEBUG(D_auth)
+ debug_printf("auth_spa_server(): bad ntRespData spec in response\n");
+ return FAIL;
+ }
++s = (US responseptr) + off;
+
+ if (memcmp(ntRespData, s, 24) == 0)
+ return auth_check_serv_cond(ablock); /* success. we have a winner. */
+--
+2.26.2
+
diff --git a/debian/patches/80_01-GnuTLS-fix-hanging-callout-connections.patch b/debian/patches/80_01-GnuTLS-fix-hanging-callout-connections.patch
new file mode 100644
index 0000000..0f36d73
--- /dev/null
+++ b/debian/patches/80_01-GnuTLS-fix-hanging-callout-connections.patch
@@ -0,0 +1,83 @@
+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
+
diff --git a/debian/patches/80_02-GnuTLS-tls_write-wait-after-uncorking-the-session.patch b/debian/patches/80_02-GnuTLS-tls_write-wait-after-uncorking-the-session.patch
new file mode 100644
index 0000000..9998618
--- /dev/null
+++ b/debian/patches/80_02-GnuTLS-tls_write-wait-after-uncorking-the-session.patch
@@ -0,0 +1,73 @@
+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
+
diff --git a/debian/patches/80_03-GnuTLS-Do-not-care-about-corked-data-when-uncorking.patch b/debian/patches/80_03-GnuTLS-Do-not-care-about-corked-data-when-uncorking.patch
new file mode 100644
index 0000000..da8748f
--- /dev/null
+++ b/debian/patches/80_03-GnuTLS-Do-not-care-about-corked-data-when-uncorking.patch
@@ -0,0 +1,55 @@
+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
+
diff --git a/debian/patches/82_TLS-use-RFC-6125-rules-for-certifucate-name-checks-w.patch b/debian/patches/82_TLS-use-RFC-6125-rules-for-certifucate-name-checks-w.patch
new file mode 100644
index 0000000..10e54e3
--- /dev/null
+++ b/debian/patches/82_TLS-use-RFC-6125-rules-for-certifucate-name-checks-w.patch
@@ -0,0 +1,188 @@
+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;
diff --git a/debian/patches/84_01-CVE-2020-28025-Heap-out-of-bounds-read-in-pdkim_fini.patch b/debian/patches/84_01-CVE-2020-28025-Heap-out-of-bounds-read-in-pdkim_fini.patch
new file mode 100644
index 0000000..7c79753
--- /dev/null
+++ b/debian/patches/84_01-CVE-2020-28025-Heap-out-of-bounds-read-in-pdkim_fini.patch
@@ -0,0 +1,44 @@
+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
+
diff --git a/debian/patches/84_02-CVE-2020-28018-Use-after-free-in-tls-openssl.c.patch b/debian/patches/84_02-CVE-2020-28018-Use-after-free-in-tls-openssl.c.patch
new file mode 100644
index 0000000..3a488b3
--- /dev/null
+++ b/debian/patches/84_02-CVE-2020-28018-Use-after-free-in-tls-openssl.c.patch
@@ -0,0 +1,33 @@
+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
+
diff --git a/debian/patches/84_03-CVE-2020-28023-Out-of-bounds-read-in-smtp_setup_msg.patch b/debian/patches/84_03-CVE-2020-28023-Out-of-bounds-read-in-smtp_setup_msg.patch
new file mode 100644
index 0000000..28af9cc
--- /dev/null
+++ b/debian/patches/84_03-CVE-2020-28023-Out-of-bounds-read-in-smtp_setup_msg.patch
@@ -0,0 +1,58 @@
+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
+
diff --git a/debian/patches/84_04-CVE-2020-28010-Heap-out-of-bounds-write-in-main.patch b/debian/patches/84_04-CVE-2020-28010-Heap-out-of-bounds-write-in-main.patch
new file mode 100644
index 0000000..9e3d368
--- /dev/null
+++ b/debian/patches/84_04-CVE-2020-28010-Heap-out-of-bounds-write-in-main.patch
@@ -0,0 +1,42 @@
+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
+
diff --git a/debian/patches/84_05-CVE-2020-28011-Heap-buffer-overflow-in-queue_run.patch b/debian/patches/84_05-CVE-2020-28011-Heap-buffer-overflow-in-queue_run.patch
new file mode 100644
index 0000000..086644b
--- /dev/null
+++ b/debian/patches/84_05-CVE-2020-28011-Heap-buffer-overflow-in-queue_run.patch
@@ -0,0 +1,39 @@
+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
+
diff --git a/debian/patches/84_06-CVE-2020-28013-Heap-buffer-overflow-in-parse_fix_phr.patch b/debian/patches/84_06-CVE-2020-28013-Heap-buffer-overflow-in-parse_fix_phr.patch
new file mode 100644
index 0000000..6acdecc
--- /dev/null
+++ b/debian/patches/84_06-CVE-2020-28013-Heap-buffer-overflow-in-parse_fix_phr.patch
@@ -0,0 +1,34 @@
+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
+
diff --git a/debian/patches/84_07-Security-Refuse-negative-and-large-store-allocations.patch b/debian/patches/84_07-Security-Refuse-negative-and-large-store-allocations.patch
new file mode 100644
index 0000000..53b4492
--- /dev/null
+++ b/debian/patches/84_07-Security-Refuse-negative-and-large-store-allocations.patch
@@ -0,0 +1,74 @@
+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
+
diff --git a/debian/patches/84_08-CVE-2020-28017-Integer-overflow-in-receive_add_recip.patch b/debian/patches/84_08-CVE-2020-28017-Integer-overflow-in-receive_add_recip.patch
new file mode 100644
index 0000000..d621b70
--- /dev/null
+++ b/debian/patches/84_08-CVE-2020-28017-Integer-overflow-in-receive_add_recip.patch
@@ -0,0 +1,49 @@
+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
+
diff --git a/debian/patches/84_09-CVE-2020-28022-Heap-out-of-bounds-read-and-write-in-.patch b/debian/patches/84_09-CVE-2020-28022-Heap-out-of-bounds-read-and-write-in-.patch
new file mode 100644
index 0000000..1ace416
--- /dev/null
+++ b/debian/patches/84_09-CVE-2020-28022-Heap-out-of-bounds-read-and-write-in-.patch
@@ -0,0 +1,61 @@
+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
+
diff --git a/debian/patches/84_10-CVE-2020-28026-Line-truncation-and-injection-in-spoo.patch b/debian/patches/84_10-CVE-2020-28026-Line-truncation-and-injection-in-spoo.patch
new file mode 100644
index 0000000..3864de2
--- /dev/null
+++ b/debian/patches/84_10-CVE-2020-28026-Line-truncation-and-injection-in-spoo.patch
@@ -0,0 +1,102 @@
+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
+
diff --git a/debian/patches/84_11-CVE-2020-28015-28021-New-line-injection-into-spool-h.patch b/debian/patches/84_11-CVE-2020-28015-28021-New-line-injection-into-spool-h.patch
new file mode 100644
index 0000000..1d2cc7e
--- /dev/null
+++ b/debian/patches/84_11-CVE-2020-28015-28021-New-line-injection-into-spool-h.patch
@@ -0,0 +1,69 @@
+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
+
diff --git a/debian/patches/84_12-CVE-2020-28009-Integer-overflow-in-get_stdinput.patch b/debian/patches/84_12-CVE-2020-28009-Integer-overflow-in-get_stdinput.patch
new file mode 100644
index 0000000..acde64a
--- /dev/null
+++ b/debian/patches/84_12-CVE-2020-28009-Integer-overflow-in-get_stdinput.patch
@@ -0,0 +1,61 @@
+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
+
diff --git a/debian/patches/84_13-CVE-2020-28024-Heap-buffer-underflow-in-smtp_ungetc.patch b/debian/patches/84_13-CVE-2020-28024-Heap-buffer-underflow-in-smtp_ungetc.patch
new file mode 100644
index 0000000..4545ff3
--- /dev/null
+++ b/debian/patches/84_13-CVE-2020-28024-Heap-buffer-underflow-in-smtp_ungetc.patch
@@ -0,0 +1,41 @@
+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
+
diff --git a/debian/patches/84_14-CVE-2020-28012-Missing-close-on-exec-flag-for-privil.patch b/debian/patches/84_14-CVE-2020-28012-Missing-close-on-exec-flag-for-privil.patch
new file mode 100644
index 0000000..c9b2f65
--- /dev/null
+++ b/debian/patches/84_14-CVE-2020-28012-Missing-close-on-exec-flag-for-privil.patch
@@ -0,0 +1,31 @@
+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
+
diff --git a/debian/patches/84_15-Security-Safeguard-against-relative-names-for-msglog.patch b/debian/patches/84_15-Security-Safeguard-against-relative-names-for-msglog.patch
new file mode 100644
index 0000000..7b2607a
--- /dev/null
+++ b/debian/patches/84_15-Security-Safeguard-against-relative-names-for-msglog.patch
@@ -0,0 +1,41 @@
+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
+
diff --git a/debian/patches/84_16-Security-Check-overrun-rcpt_count-integer.patch b/debian/patches/84_16-Security-Check-overrun-rcpt_count-integer.patch
new file mode 100644
index 0000000..f8bda54
--- /dev/null
+++ b/debian/patches/84_16-Security-Check-overrun-rcpt_count-integer.patch
@@ -0,0 +1,39 @@
+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
+
diff --git a/debian/patches/84_17-Security-Always-exit-when-LOG_PANIC_DIE-is-set.patch b/debian/patches/84_17-Security-Always-exit-when-LOG_PANIC_DIE-is-set.patch
new file mode 100644
index 0000000..a9eee56
--- /dev/null
+++ b/debian/patches/84_17-Security-Always-exit-when-LOG_PANIC_DIE-is-set.patch
@@ -0,0 +1,24 @@
+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
+
diff --git a/debian/patches/84_18-Security-Fix-off-by-one-in-smtp-transport-read-respo.patch b/debian/patches/84_18-Security-Fix-off-by-one-in-smtp-transport-read-respo.patch
new file mode 100644
index 0000000..47d67d2
--- /dev/null
+++ b/debian/patches/84_18-Security-Fix-off-by-one-in-smtp-transport-read-respo.patch
@@ -0,0 +1,47 @@
+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
+
diff --git a/debian/patches/84_19-Security-Avoid-decrement-of-dkim_collect_input-if-al.patch b/debian/patches/84_19-Security-Avoid-decrement-of-dkim_collect_input-if-al.patch
new file mode 100644
index 0000000..a2b52fe
--- /dev/null
+++ b/debian/patches/84_19-Security-Avoid-decrement-of-dkim_collect_input-if-al.patch
@@ -0,0 +1,59 @@
+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
+
diff --git a/debian/patches/84_20-Security-Leave-a-clean-smtp_out-input-buffer-even-in.patch b/debian/patches/84_20-Security-Leave-a-clean-smtp_out-input-buffer-even-in.patch
new file mode 100644
index 0000000..acf17d3
--- /dev/null
+++ b/debian/patches/84_20-Security-Leave-a-clean-smtp_out-input-buffer-even-in.patch
@@ -0,0 +1,67 @@
+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;
+ }
+
+
diff --git a/debian/patches/84_21-Security-Avoid-modification-of-constant-data-in-dkim.patch b/debian/patches/84_21-Security-Avoid-modification-of-constant-data-in-dkim.patch
new file mode 100644
index 0000000..b723d0f
--- /dev/null
+++ b/debian/patches/84_21-Security-Avoid-modification-of-constant-data-in-dkim.patch
@@ -0,0 +1,89 @@
+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
+
diff --git a/debian/patches/84_22-CVE-2020-28019-Failure-to-reset-function-pointer-aft.patch b/debian/patches/84_22-CVE-2020-28019-Failure-to-reset-function-pointer-aft.patch
new file mode 100644
index 0000000..0d44293
--- /dev/null
+++ b/debian/patches/84_22-CVE-2020-28019-Failure-to-reset-function-pointer-aft.patch
@@ -0,0 +1,135 @@
+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
+
diff --git a/debian/patches/84_23-CVE-2020-28007-Link-attack-in-Exim-s-log-directory.patch b/debian/patches/84_23-CVE-2020-28007-Link-attack-in-Exim-s-log-directory.patch
new file mode 100644
index 0000000..211abe3
--- /dev/null
+++ b/debian/patches/84_23-CVE-2020-28007-Link-attack-in-Exim-s-log-directory.patch
@@ -0,0 +1,542 @@
+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
+
diff --git a/debian/patches/84_24-CVE-2020-28008-Assorted-attacks-in-Exim-s-spool-dire.patch b/debian/patches/84_24-CVE-2020-28008-Assorted-attacks-in-Exim-s-spool-dire.patch
new file mode 100644
index 0000000..2bda99c
--- /dev/null
+++ b/debian/patches/84_24-CVE-2020-28008-Assorted-attacks-in-Exim-s-spool-dire.patch
@@ -0,0 +1,205 @@
+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
+
diff --git a/debian/patches/84_26-CVE-2020-28014-CVE-2021-27216-Arbitrary-PID-file-cre.patch b/debian/patches/84_26-CVE-2020-28014-CVE-2021-27216-Arbitrary-PID-file-cre.patch
new file mode 100644
index 0000000..3e73ae1
--- /dev/null
+++ b/debian/patches/84_26-CVE-2020-28014-CVE-2021-27216-Arbitrary-PID-file-cre.patch
@@ -0,0 +1,303 @@
+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')
diff --git a/debian/patches/84_27-testsuite-adjustments-for-CVE-2020-28014-CVE-2021-27.patch b/debian/patches/84_27-testsuite-adjustments-for-CVE-2020-28014-CVE-2021-27.patch
new file mode 100644
index 0000000..d0dc071
--- /dev/null
+++ b/debian/patches/84_27-testsuite-adjustments-for-CVE-2020-28014-CVE-2021-27.patch
@@ -0,0 +1,57 @@
+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
+
diff --git a/debian/patches/84_29-Fix-BDAT-issue-for-body-w-o-trailing-CRLF-again-Bug-.patch b/debian/patches/84_29-Fix-BDAT-issue-for-body-w-o-trailing-CRLF-again-Bug-.patch
new file mode 100644
index 0000000..f5965ab
--- /dev/null
+++ b/debian/patches/84_29-Fix-BDAT-issue-for-body-w-o-trailing-CRLF-again-Bug-.patch
@@ -0,0 +1,26 @@
+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
+
diff --git a/debian/patches/90_localscan_dlopen.dpatch b/debian/patches/90_localscan_dlopen.dpatch
new file mode 100644
index 0000000..ce71bae
--- /dev/null
+++ b/debian/patches/90_localscan_dlopen.dpatch
@@ -0,0 +1,281 @@
+Description: Allow one to use and switch between different local_scan functions
+ without recompiling exim.
+ http://marc.merlins.org/linux/exim/files/sa-exim-current/ Original patch from
+ David Woodhouse, modified first by Derrick 'dman' Hudson and then by Marc
+ MERLIN for SA-Exim and minor/major API version tracking
+Author: David Woodhouse, Derrick 'dman' Hudson, Marc MERLIN
+Origin: other, http://marc.merlins.org/linux/exim/files/sa-exim-current/
+Forwarded: no
+Last-Update: 2018-12-12
+
+--- a/src/EDITME
++++ b/src/EDITME
+@@ -824,6 +824,21 @@ HEADERS_CHARSET="ISO-8859-1"
+
+
+ #------------------------------------------------------------------------------
++# On systems which support dynamic loading of shared libraries, Exim can
++# load a local_scan function specified in its config file instead of having
++# to be recompiled with the desired local_scan function. For a full
++# description of the API to this function, see the Exim specification.
++
++DLOPEN_LOCAL_SCAN=yes
++
++# If you set DLOPEN_LOCAL_SCAN, then you need to include -rdynamic in the
++# linker flags. Without it, the loaded .so won't be able to access any
++# functions from exim.
++
++LDFLAGS += -rdynamic
++CFLAGS += -fvisibility=hidden
++
++#------------------------------------------------------------------------------
+ # The default distribution of Exim contains only the plain text form of the
+ # documentation. Other forms are available separately. If you want to install
+ # the documentation in "info" format, first fetch the Texinfo documentation
+--- a/src/config.h.defaults
++++ b/src/config.h.defaults
+@@ -32,6 +32,8 @@ Do not put spaces between # and the 'def
+
+ #define AUTH_VARS 3
+
++#define DLOPEN_LOCAL_SCAN
++
+ #define BIN_DIRECTORY
+
+ #define CONFIGURE_FILE
+--- a/src/globals.c
++++ b/src/globals.c
+@@ -141,6 +141,10 @@ int dsn_ret = 0;
+ const pcre *regex_DSN = NULL;
+ uschar *dsn_advertise_hosts = NULL;
+
++#ifdef DLOPEN_LOCAL_SCAN
++uschar *local_scan_path = NULL;
++#endif
++
+ #ifdef SUPPORT_TLS
+ BOOL gnutls_compat_mode = FALSE;
+ BOOL gnutls_allow_auto_pkcs11 = FALSE;
+--- a/src/globals.h
++++ b/src/globals.h
+@@ -138,6 +138,9 @@ extern int dsn_ret; /
+ extern const pcre *regex_DSN; /* For recognizing DSN settings */
+ extern uschar *dsn_advertise_hosts; /* host for which TLS is advertised */
+
++#ifdef DLOPEN_LOCAL_SCAN
++extern uschar *local_scan_path; /* Path to local_scan() library */
++#endif
+ /* Input-reading functions for messages, so we can use special ones for
+ incoming TCP/IP. */
+
+--- a/src/local_scan.c
++++ b/src/local_scan.c
+@@ -5,61 +5,131 @@
+ /* Copyright (c) University of Cambridge 1995 - 2009 */
+ /* See the file NOTICE for conditions of use and distribution. */
+
++#include "exim.h"
+
+-/******************************************************************************
+-This file contains a template local_scan() function that just returns ACCEPT.
+-If you want to implement your own version, you should copy this file to, say
+-Local/local_scan.c, and edit the copy. To use your version instead of the
+-default, you must set
+-
+-HAVE_LOCAL_SCAN=yes
+-LOCAL_SCAN_SOURCE=Local/local_scan.c
+-
+-in your Local/Makefile. This makes it easy to copy your version for use with
+-subsequent Exim releases.
+-
+-For a full description of the API to this function, see the Exim specification.
+-******************************************************************************/
+-
+-
+-/* This is the only Exim header that you should include. The effect of
+-including any other Exim header is not defined, and may change from release to
+-release. Use only the documented interface! */
+-
+-#include "local_scan.h"
+-
+-
+-/* This is a "do-nothing" version of a local_scan() function. The arguments
+-are:
+-
+- fd The file descriptor of the open -D file, which contains the
+- body of the message. The file is open for reading and
+- writing, but modifying it is dangerous and not recommended.
+-
+- return_text A pointer to an unsigned char* variable which you can set in
+- order to return a text string. It is initialized to NULL.
+-
+-The return values of this function are:
+-
+- LOCAL_SCAN_ACCEPT
+- The message is to be accepted. The return_text argument is
+- saved in $local_scan_data.
+-
+- LOCAL_SCAN_REJECT
+- The message is to be rejected. The returned text is used
+- in the rejection message.
+-
+- LOCAL_SCAN_TEMPREJECT
+- This specifies a temporary rejection. The returned text
+- is used in the rejection message.
+-*/
++#ifdef DLOPEN_LOCAL_SCAN
++#include <dlfcn.h>
++static int (*local_scan_fn)(int fd, uschar **return_text) = NULL;
++static int load_local_scan_library(void);
++#endif
+
+ int
+ local_scan(int fd, uschar **return_text)
+ {
+ fd = fd; /* Keep picky compilers happy */
+ return_text = return_text;
+-return LOCAL_SCAN_ACCEPT;
++#ifdef DLOPEN_LOCAL_SCAN
++/* local_scan_path is defined AND not the empty string */
++if (local_scan_path && *local_scan_path)
++ {
++ if (!local_scan_fn)
++ {
++ if (!load_local_scan_library())
++ {
++ char *base_msg , *error_msg , *final_msg ;
++ int final_length = -1 ;
++
++ base_msg=US"Local configuration error - local_scan() library failure\n";
++ error_msg = dlerror() ;
++
++ final_length = strlen(base_msg) + strlen(error_msg) + 1 ;
++ final_msg = (char*)malloc( final_length*sizeof(char) ) ;
++ *final_msg = '\0' ;
++
++ strcat( final_msg , base_msg ) ;
++ strcat( final_msg , error_msg ) ;
++
++ *return_text = final_msg ;
++ return LOCAL_SCAN_TEMPREJECT;
++ }
++ }
++ return local_scan_fn(fd, return_text);
++ }
++else
++#endif
++ return LOCAL_SCAN_ACCEPT;
++}
++
++#ifdef DLOPEN_LOCAL_SCAN
++
++static int load_local_scan_library(void)
++{
++/* No point in keeping local_scan_lib since we'll never dlclose() anyway */
++void *local_scan_lib = NULL;
++int (*local_scan_version_fn)(void);
++int vers_maj;
++int vers_min;
++
++local_scan_lib = dlopen(local_scan_path, RTLD_NOW);
++if (!local_scan_lib)
++ {
++ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() library open failed - "
++ "message temporarily rejected");
++ return FALSE;
++ }
++
++local_scan_version_fn = dlsym(local_scan_lib, "local_scan_version_major");
++if (!local_scan_version_fn)
++ {
++ dlclose(local_scan_lib);
++ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() library doesn't contain "
++ "local_scan_version_major() function - message temporarily rejected");
++ return FALSE;
++ }
++
++/* The major number is increased when the ABI is changed in a non
++ backward compatible way. */
++vers_maj = local_scan_version_fn();
++
++local_scan_version_fn = dlsym(local_scan_lib, "local_scan_version_minor");
++if (!local_scan_version_fn)
++ {
++ dlclose(local_scan_lib);
++ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() library doesn't contain "
++ "local_scan_version_minor() function - message temporarily rejected");
++ return FALSE;
++ }
++
++/* The minor number is increased each time a new feature is added (in a
++ way that doesn't break backward compatibility) -- Marc */
++vers_min = local_scan_version_fn();
++
++
++if (vers_maj != LOCAL_SCAN_ABI_VERSION_MAJOR)
++ {
++ dlclose(local_scan_lib);
++ local_scan_lib = NULL;
++ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() has an incompatible major"
++ "version number, you need to recompile your module for this version"
++ "of exim (The module was compiled for version %d.%d and this exim provides"
++ "ABI version %d.%d)", vers_maj, vers_min, LOCAL_SCAN_ABI_VERSION_MAJOR,
++ LOCAL_SCAN_ABI_VERSION_MINOR);
++ return FALSE;
++ }
++else if (vers_min > LOCAL_SCAN_ABI_VERSION_MINOR)
++ {
++ dlclose(local_scan_lib);
++ local_scan_lib = NULL;
++ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() has an incompatible minor"
++ "version number, you need to recompile your module for this version"
++ "of exim (The module was compiled for version %d.%d and this exim provides"
++ "ABI version %d.%d)", vers_maj, vers_min, LOCAL_SCAN_ABI_VERSION_MAJOR,
++ LOCAL_SCAN_ABI_VERSION_MINOR);
++ return FALSE;
++ }
++
++local_scan_fn = dlsym(local_scan_lib, "local_scan");
++if (!local_scan_fn)
++ {
++ dlclose(local_scan_lib);
++ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() library doesn't contain "
++ "local_scan() function - message temporarily rejected");
++ return FALSE;
++ }
++
++return TRUE;
+ }
+
++#endif /* DLOPEN_LOCAL_SCAN */
++
+ /* End of local_scan.c */
+--- a/src/local_scan.h
++++ b/src/local_scan.h
+@@ -17,6 +17,7 @@ settings, and the store functions. */
+
+ #include <stdarg.h>
+ #include <sys/types.h>
++#pragma GCC visibility push(default)
+ #include "config.h"
+ #include "mytypes.h"
+ #include "store.h"
+@@ -192,4 +193,6 @@ extern uschar *string_copy(const uschar
+ extern uschar *string_copyn(const uschar *, int);
+ extern uschar *string_sprintf(const char *, ...) ALMOST_PRINTF(1,2);
+
++#pragma GCC visibility pop
++
+ /* End of local_scan.h */
+--- a/src/readconf.c
++++ b/src/readconf.c
+@@ -199,6 +199,9 @@ static optionlist optionlist_config[] =
+ { "local_from_prefix", opt_stringptr, &local_from_prefix },
+ { "local_from_suffix", opt_stringptr, &local_from_suffix },
+ { "local_interfaces", opt_stringptr, &local_interfaces },
++#ifdef DLOPEN_LOCAL_SCAN
++ { "local_scan_path", opt_stringptr, &local_scan_path },
++#endif
+ #ifdef HAVE_LOCAL_SCAN
+ { "local_scan_timeout", opt_time, &local_scan_timeout },
+ #endif
diff --git a/debian/patches/series b/debian/patches/series
new file mode 100644
index 0000000..e448ccf
--- /dev/null
+++ b/debian/patches/series
@@ -0,0 +1,60 @@
+31_eximmanpage.dpatch
+32_exim4.dpatch
+33_eximon.binary.dpatch
+34_eximstatsmanpage.dpatch
+35_install.dpatch
+60_convert4r4.dpatch
+67_unnecessaryCopt.diff
+70_remove_exim-users_references.dpatch
+75_01-Fix-json-extract-operator-for-unfound-case.patch
+75_02-Fix-transport-buffer-size-handling.patch
+75_03-Fix-info-on-using-local_scan-in-the-default-Makefile.patch
+75_04-GnuTLS-Fix-client-detection-of-server-reject-of-clie.patch
+75_05-Fix-expansions-for-RFC-822-addresses-having-comments.patch
+75_06-Docs-Add-note-on-lsearch-for-IPv4-mapped-IPv6-addres.patch
+75_07-Fix-crash-from-SRV-lookup-hitting-a-CNAME.patch
+75_08-Logging-fix-initial-listening-on-log-line.patch
+75_09-OpenSSL-Fix-aggregation-of-messages.patch
+75_10-Harden-plaintext-authenticator.patch
+75_11-GnuTLS-fix-tls_out_ocsp-under-hosts_request_ocsp.patch
+75_12-GnuTLS-fix-the-advertising-of-acceptable-certs-by-th.patch
+75_13-Use-dsn_from-for-success-DSN-messages.-Bug-2404.patch
+75_14-Fix-smtp-response-timeout.patch
+75_15-Fix-detection-of-32b-platform-at-build-time.-Bug-240.patch
+77_Avoid-re-expansion-in-sort-CVE-2019-13917-OVE-201907.patch
+78_01-string.c-do-not-interpret-before-0-CVE-2019-15846.patch
+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