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.dpatch17
-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.dpatch38
-rw-r--r--debian/patches/73_01-Fix-DANE-SNI-handling-Bug-2265.patch36
-rw-r--r--debian/patches/73_02-Fix-ipv6norm.patch47
-rw-r--r--debian/patches/73_03-Named-Queues-fix-immediate-delivery.-Bug-2743.patch50
-rw-r--r--debian/patches/73_04-Fix-host_name_lookup-Close-2747.patch80
-rw-r--r--debian/patches/73_05-Fix-tainted-message-for-fakereject.patch44
-rw-r--r--debian/patches/75_01-Introduce-main-config-option-allow_insecure_tainted_.patch230
-rw-r--r--debian/patches/75_02-search.patch39
-rw-r--r--debian/patches/75_03-dbstuff.patch30
-rw-r--r--debian/patches/75_04-acl.patch67
-rw-r--r--debian/patches/75_05-parse.patch30
-rw-r--r--debian/patches/75_06-rda.patch28
-rw-r--r--debian/patches/75_07-appendfile.patch34
-rw-r--r--debian/patches/75_08-autoreply.patch70
-rw-r--r--debian/patches/75_09-pipe.patch36
-rw-r--r--debian/patches/75_10-deliver.patch49
-rw-r--r--debian/patches/75_11-directory.patch26
-rw-r--r--debian/patches/75_12-expand.patch34
-rw-r--r--debian/patches/75_13-lf_sqlperform.patch49
-rw-r--r--debian/patches/75_14-rf_get_transport.patch28
-rw-r--r--debian/patches/75_15-deliver.patch31
-rw-r--r--debian/patches/75_16-smtp_out.patch38
-rw-r--r--debian/patches/75_17-smtp.patch29
-rw-r--r--debian/patches/75_18-update-doc.patch154
-rw-r--r--debian/patches/75_20-Set-mainlog_name-and-rejectlog_name-unconditionally.patch42
-rw-r--r--debian/patches/75_21-tidy-log.c.patch124
-rw-r--r--debian/patches/75_22-Silence-compiler.patch222
-rw-r--r--debian/patches/75_23-Do-not-close-the-main-_log-if-we-do-not-see-a-chance.patch166
-rw-r--r--debian/patches/75_24-Silence-the-compiler.patch57
-rw-r--r--debian/patches/75_26-Disable-taintchecks-for-mkdir-this-isn-t-part-of-4.9.patch27
-rw-r--r--debian/patches/75_27_Fix-logging-with-empty-element-in-log_file_path-Bug-.patch275
-rw-r--r--debian/patches/75_28_Fix-logging-with-build-time-config-and-empty-element.patch118
-rw-r--r--debian/patches/75_29-Auths-fix-possible-OOB-write-in-external-authenticat.patch22
-rw-r--r--debian/patches/75_30-Auths-use-uschar-more-in-spa-authenticator.patch226
-rw-r--r--debian/patches/75_31-Auths-fix-possible-OOB-write-in-SPA-authenticator.-B.patch24
-rw-r--r--debian/patches/75_32-Auths-fix-possible-OOB-read-in-SPA-authenticator.-Bu.patch75
-rw-r--r--debian/patches/78_01-Command-line-option-for-no-notifier-socket.-Bug-2616.patch198
-rw-r--r--debian/patches/79_CVE-2023-51766_4.97.1-release.diff435
-rw-r--r--debian/patches/90_localscan_dlopen.dpatch307
-rw-r--r--debian/patches/series46
47 files changed, 4213 insertions, 0 deletions
diff --git a/debian/patches/31_eximmanpage.dpatch b/debian/patches/31_eximmanpage.dpatch
new file mode 100755
index 0000000..cfbfc26
--- /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: 2019-10-17
+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
++/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..e0f5593
--- /dev/null
+++ b/debian/patches/33_eximon.binary.dpatch
@@ -0,0 +1,17 @@
+Description: We move eximon.bin out of $PATH to /usr/libexec/. Let it be
+ found there.
+Author: Andreas Piesk
+Last-Update: 2020-03-21
+Forwarded: not-needed
+
+--- a/OS/eximon.conf-Default
++++ b/OS/eximon.conf-Default
+@@ -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/libexec/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..23fb0ae
--- /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: not-needed
+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..f3dda95
--- /dev/null
+++ b/debian/patches/70_remove_exim-users_references.dpatch
@@ -0,0 +1,38 @@
+Description: Point Debian users to Debian specific ML.
+Author: Marc Haber <mh+debian-packages@zugschlus.de>
+Last-Update: 2018-12-31
+Forwarded: not-needed
+
+--- 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/73_01-Fix-DANE-SNI-handling-Bug-2265.patch b/debian/patches/73_01-Fix-DANE-SNI-handling-Bug-2265.patch
new file mode 100644
index 0000000..b5a6418
--- /dev/null
+++ b/debian/patches/73_01-Fix-DANE-SNI-handling-Bug-2265.patch
@@ -0,0 +1,36 @@
+From e8ac8be0a3d56ba0a189fb970c339ac6e84769be Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Mon, 3 May 2021 15:53:28 +0200
+Subject: [PATCH] Fix DANE + SNI handling (Bug 2265)
+
+Broken in d8e99d6047e709b35eabb1395c2046100d1a1dda
+Thanks to JGH and Wolfgang Breyha for contributions.
+---
+ 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 f26e2337a..9ee6a578a 100644
+--- a/src/transports/smtp.c
++++ b/src/transports/smtp.c
+@@ -2015,7 +2015,7 @@ if (continue_hostname && continue_proxy_cipher)
+ {
+ case OK: sx->conn_args.dane = TRUE;
+ ob->tls_tempfail_tryclear = FALSE; /* force TLS */
+- ob->tls_sni = sx->first_addr->domain; /* force SNI */
++ ob->tls_sni = sx->conn_args.host->name; /* force SNI */
+ break;
+ case FAIL_FORCED: break;
+ default: set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
+@@ -2097,7 +2097,7 @@ if (!continue_hostname)
+ {
+ case OK: sx->conn_args.dane = TRUE;
+ ob->tls_tempfail_tryclear = FALSE; /* force TLS */
+- ob->tls_sni = sx->first_addr->domain; /* force SNI */
++ ob->tls_sni = sx->conn_args.host->name; /* force SNI */
+ break;
+ case FAIL_FORCED: break;
+ default: set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
+--
+2.30.2
+
diff --git a/debian/patches/73_02-Fix-ipv6norm.patch b/debian/patches/73_02-Fix-ipv6norm.patch
new file mode 100644
index 0000000..fb98bc8
--- /dev/null
+++ b/debian/patches/73_02-Fix-ipv6norm.patch
@@ -0,0 +1,47 @@
+From ed64b5c2f0f44db27ae48128fc97d5ad8406a28e Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Tue, 4 May 2021 13:06:31 +0100
+Subject: [PATCH 2/2] Fix ${ipv6norm:}
+
+(cherry picked from commit 8b4b6ac90766b11fa74fa3001778b49456adbe42)
+---
+ doc/ChangeLog | 3 +++
+ src/host.c | 4 ++--
+ test/scripts/0000-Basic/0002 | 2 +-
+ test/stdout/0002 | 2 +-
+ 4 files changed, 7 insertions(+), 4 deletions(-)
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -218,10 +218,13 @@
+ executed child processes (if any).
+
+ QS/04 Always die if requested from internal logging, even is logging is
+ disabled.
+
++JH/52 Fix ${ip6norm:} operator. Previously, any trailing line text was dropped,
++ making it unusable in complex expressions.
++
+
+ Exim version 4.94
+ -----------------
+
+ JH/01 Avoid costly startup code when not strictly needed. This reduces time
+--- a/src/host.c
++++ b/src/host.c
+@@ -1195,13 +1195,13 @@
+ }
+ while (*++c != ':') ;
+ c++;
+ }
+
+-c[-1] = '\0'; /* drop trailing colon */
++*--c = '\0'; /* drop trailing colon */
+
+-/* debug_printf("%s: D k %d <%s> <%s>\n", __FUNCTION__, k, d, d + 2*(k+1)); */
++/* debug_printf("%s: D k %d <%s> <%s>\n", __FUNCTION__, k, buffer, buffer + 2*(k+1)); */
+ if (k >= 0)
+ { /* collapse */
+ c = d + 2*(k+1);
+ if (d == buffer) c--; /* need extra colon */
+ *d++ = ':'; /* 1st 0 */
diff --git a/debian/patches/73_03-Named-Queues-fix-immediate-delivery.-Bug-2743.patch b/debian/patches/73_03-Named-Queues-fix-immediate-delivery.-Bug-2743.patch
new file mode 100644
index 0000000..dc4d86a
--- /dev/null
+++ b/debian/patches/73_03-Named-Queues-fix-immediate-delivery.-Bug-2743.patch
@@ -0,0 +1,50 @@
+From c1faf04b865465894c7ca41ab4585fb69d4a5936 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Wed, 12 May 2021 15:01:12 +0100
+Subject: [PATCH 3/3] Named Queues: fix immediate-delivery. Bug 2743
+
+(cherry picked from commit 159cf206c97f876b07829d92db2217689745c1e8)
+---
+ doc/ChangeLog | 4 ++++
+ src/exim.c | 6 ++++--
+ test/confs/0576 | 6 ++++++
+ test/log/0576 | 3 +++
+ test/scripts/0000-Basic/0576 | 4 ++++
+ 5 files changed, 21 insertions(+), 2 deletions(-)
+
+diff --git a/doc/ChangeLog b/doc/ChangeLog
+index 9f104b4e6..e60c1cad5 100644
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -223,6 +223,10 @@ QS/04 Always die if requested from internal logging, even is logging is
+ JH/52 Fix ${ip6norm:} operator. Previously, any trailing line text was dropped,
+ making it unusable in complex expressions.
+
++JH/53 Bug 2743: fix immediate-delivery via named queue. Previously this would
++ fail with a taint-check on the spoolfile name, and leave the message
++ queued.
++
+
+ Exim version 4.94
+ -----------------
+diff --git a/src/exim.c b/src/exim.c
+index ee75739ec..7411f0467 100644
+--- a/src/exim.c
++++ b/src/exim.c
+@@ -2789,9 +2789,11 @@ on the second character (the one after '-'), to save some effort. */
+ else badarg = TRUE;
+ break;
+
+- /* -MCG: set the queue name, to a non-default value */
++ /* -MCG: set the queue name, to a non-default value. Arguably, anything
++ from the commandline should be tainted - but we will need an untainted
++ value for the spoolfile when doing a -odi delivery process. */
+
+- case 'G': if (++i < argc) queue_name = string_copy_taint(exim_str_fail_toolong(argv[i], EXIM_DRIVERNAME_MAX, "-MCG"), TRUE);
++ case 'G': if (++i < argc) queue_name = string_copy_taint(exim_str_fail_toolong(argv[i], EXIM_DRIVERNAME_MAX, "-MCG"), FALSE);
+ else badarg = TRUE;
+ break;
+
+--
+2.30.2
+
diff --git a/debian/patches/73_04-Fix-host_name_lookup-Close-2747.patch b/debian/patches/73_04-Fix-host_name_lookup-Close-2747.patch
new file mode 100644
index 0000000..dd5c982
--- /dev/null
+++ b/debian/patches/73_04-Fix-host_name_lookup-Close-2747.patch
@@ -0,0 +1,80 @@
+From 20812729e3e47a193a21d326ecd036d67a8b2724 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Sun, 16 May 2021 19:11:19 +0200
+Subject: [PATCH 4/4] Fix host_name_lookup (Close 2747)
+
+Thanks to Nico R for providing a reproducing configuration.
+
+ host_lookup = *
+ message_size_limit = ${if def:sender_host_name {32M}{32M}}
+ acl_smtp_connect = acl_smtp_connect
+ acl_smtp_rcpt = acl_smtp_rcpt
+
+ begin acl
+ acl_smtp_connect:
+ warn ratelimit = 256 / 1m / per_conn
+ accept
+
+ acl_smtp_rcpt:
+ accept hosts = 127.0.0.*
+
+ begin routers
+ null:
+ driver = accept
+ transport = null
+
+ begin transports
+ null:
+ driver = appendfile
+ file = /dev/null
+
+Tested with
+
+ swaks -f mailbox@example.org -t mailbox@example.org --pipe 'exim -bh 127.0.0.1 -C /opt/exim/etc/exim-bug.conf'
+
+The IP must have a PTR to "localhost." to reproduce it.
+---
+ src/host.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/src/host.c b/src/host.c
+index ee9d323a7..2047b9798 100644
+--- a/src/host.c
++++ b/src/host.c
+@@ -1577,15 +1577,15 @@ Put it in permanent memory. */
+
+ sender_host_name = string_copylc(US hosts->h_name);
+
+ /* If the host has aliases, build a copy of the alias list */
+
+ if (hosts->h_aliases)
+ {
+- int count = 1;
++ int count = 1; /* need 1 more for terminating NULL */
+ uschar **ptr;
+
+ for (uschar ** aliases = USS hosts->h_aliases; *aliases; aliases++) count++;
+ store_pool = POOL_PERM;
+ ptr = sender_host_aliases = store_get(count * sizeof(uschar *), FALSE);
+ store_pool = POOL_TAINT_PERM;
+
+@@ -1686,15 +1686,15 @@ while ((ordername = string_nextinlist(&list, &sep, NULL, 0)))
+ if failure. (PTR records that yield empty names have been encountered in
+ the DNS.) */
+
+ if (rc == DNS_SUCCEED)
+ {
+ uschar **aptr = NULL;
+ int ssize = 264;
+- int count = 0;
++ int count = 1; /* need 1 more for terminating NULL */
+ int old_pool = store_pool;
+
+ sender_host_dnssec = dns_is_secure(dnsa);
+ DEBUG(D_dns)
+ debug_printf("Reverse DNS security status: %s\n",
+ sender_host_dnssec ? "DNSSEC verified (AD)" : "unverified");
+
+--
+2.30.2
+
diff --git a/debian/patches/73_05-Fix-tainted-message-for-fakereject.patch b/debian/patches/73_05-Fix-tainted-message-for-fakereject.patch
new file mode 100644
index 0000000..a152417
--- /dev/null
+++ b/debian/patches/73_05-Fix-tainted-message-for-fakereject.patch
@@ -0,0 +1,44 @@
+From c819f3bcad02bcb06004ae2ad135b68fab0ae888 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Wed, 7 Jul 2021 22:19:07 +0100
+Subject: [PATCH 5/5] Fix tainted message for fakereject
+
+(cherry picked from commit a9ac2d7fc219e41a353abf1f599258b9b9d21b7e)
+---
+ doc/ChangeLog | 4 ++++
+ src/acl.c | 4 +++-
+ 2 files changed, 7 insertions(+), 1 deletion(-)
+
+diff --git a/doc/ChangeLog b/doc/ChangeLog
+index e60c1cad5..3e93f653f 100644
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -227,6 +227,10 @@ JH/53 Bug 2743: fix immediate-delivery via named queue. Previously this would
+ fail with a taint-check on the spoolfile name, and leave the message
+ queued.
+
++JH/57 Fix control=fakreject for a custom message containing tainted data.
++ Previously this resulted in a log complaint, due to a re-expansion present
++ since fakereject was originally introduced.
++
+
+ Exim version 4.94
+ -----------------
+diff --git a/src/acl.c b/src/acl.c
+index 7061230b4..65324405c 100644
+--- a/src/acl.c
++++ b/src/acl.c
+@@ -3137,7 +3137,9 @@ for (; cb; cb = cb->next)
+ {
+ const uschar *pp = p + 1;
+ while (*pp) pp++;
+- fake_response_text = expand_string(string_copyn(p+1, pp-p-1));
++ /* The entire control= line was expanded at top so no need to expand
++ the part after the / */
++ fake_response_text = string_copyn(p+1, pp-p-1);
+ p = pp;
+ }
+ else /* Explicitly reset to default string */
+--
+2.30.2
+
diff --git a/debian/patches/75_01-Introduce-main-config-option-allow_insecure_tainted_.patch b/debian/patches/75_01-Introduce-main-config-option-allow_insecure_tainted_.patch
new file mode 100644
index 0000000..0295ec1
--- /dev/null
+++ b/debian/patches/75_01-Introduce-main-config-option-allow_insecure_tainted_.patch
@@ -0,0 +1,230 @@
+From ec06d64532e4952fc36429f73e0222d26997ef7c Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Thu, 1 Apr 2021 22:44:31 +0200
+Subject: [PATCH 01/23] Introduce main config option
+ allow_insecure_tainted_data
+
+This option is deprecated already now.
+---
+ src/EDITME | 7 +++++
+ src/config.h.defaults | 2 ++
+ src/functions.h | 54 ++++++++++++++++++++++++++++++---------
+ src/globals.c | 10 ++++++++
+ src/globals.h | 4 +++
+ src/macros.h | 3 +++
+ src/readconf.c | 3 +++
+ 7 files changed, 71 insertions(+), 12 deletions(-)
+
+diff --git a/src/EDITME b/src/EDITME
+index 8da36a353..cebb8e2ec 100644
+--- a/src/EDITME
++++ b/src/EDITME
+@@ -749,6 +749,13 @@ FIXED_NEVER_USERS=root
+
+ # WHITELIST_D_MACROS=TLS:SPOOL
+
++# The next setting enables a main config option
++# "allow_insecure_tainted_data" to turn taint failures into warnings.
++# Though this option is new, it is deprecated already now, and will be
++# ignored in future releases of Exim. It is meant as mitigation for
++# upgrading old (possibly insecure) configurations to more secure ones.
++ALLOW_INSECURE_TAINTED_DATA=yes
++
+ #------------------------------------------------------------------------------
+ # Exim has support for the AUTH (authentication) extension of the SMTP
+ # protocol, as defined by RFC 2554. If you don't know what SMTP authentication
+diff --git a/src/config.h.defaults b/src/config.h.defaults
+index e17f015f9..4e8b18904 100644
+--- a/src/config.h.defaults
++++ b/src/config.h.defaults
+@@ -17,6 +17,8 @@ Do not put spaces between # and the 'define'.
+ #define ALT_CONFIG_PREFIX
+ #define TRUSTED_CONFIG_LIST
+
++#define ALLOW_INSECURE_TAINTED_DATA
++
+ #define APPENDFILE_MODE 0600
+ #define APPENDFILE_DIRECTORY_MODE 0700
+ #define APPENDFILE_LOCKFILE_MODE 0600
+diff --git a/src/functions.h b/src/functions.h
+index 51bb17a09..1e8083673 100644
+--- a/src/functions.h
++++ b/src/functions.h
+@@ -1083,36 +1083,66 @@ if (f.running_in_test_harness && f.testsuite_delays) millisleep(millisec);
+
+ /******************************************************************************/
+ /* Taint-checked file opens */
++static inline uschar *
++is_tainted2(const void *p, int lflags, const uschar* fmt, ...)
++{
++va_list ap;
++uschar *msg;
++rmark mark;
++
++if (!is_tainted(p))
++ return NULL;
++
++mark = store_mark();
++va_start(ap, fmt);
++msg = string_from_gstring(string_vformat(NULL, SVFMT_TAINT_NOCHK|SVFMT_EXTEND, fmt, ap));
++va_end(ap);
++
++#ifdef ALLOW_INSECURE_TAINTED_DATA
++if (allow_insecure_tainted_data)
++ {
++ if LOGGING(tainted) log_write(0, LOG_MAIN, "Warning: %s", msg);
++ store_reset(mark);
++ return NULL;
++ }
++#endif
++
++if (lflags) log_write(0, lflags, "%s", msg);
++return msg; /* no store_reset(), as the message might be used afterwards and Exim
++ is expected to exit anyway, so we do not care about the leaked
++ storage */
++}
+
+ static inline int
+ exim_open2(const char *pathname, int flags)
+ {
+-if (!is_tainted(pathname)) return open(pathname, flags);
+-log_write(0, LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname);
++if (!is_tainted2(pathname, LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname))
++ return open(pathname, flags);
+ errno = EACCES;
+ return -1;
+ }
++
+ static inline int
+ exim_open(const char *pathname, int flags, mode_t mode)
+ {
+-if (!is_tainted(pathname)) return open(pathname, flags, mode);
+-log_write(0, LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname);
++if (!is_tainted2(pathname, LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname))
++ return open(pathname, flags, mode);
+ errno = EACCES;
+ return -1;
+ }
+ static inline int
+ exim_openat(int dirfd, const char *pathname, int flags)
+ {
+-if (!is_tainted(pathname)) return openat(dirfd, pathname, flags);
+-log_write(0, LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname);
++if (!is_tainted2(pathname, LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname))
++ return openat(dirfd, pathname, flags);
+ errno = EACCES;
+ return -1;
+ }
+ static inline int
+ exim_openat4(int dirfd, const char *pathname, int flags, mode_t mode)
+ {
+-if (!is_tainted(pathname)) return openat(dirfd, pathname, flags, mode);
+-log_write(0, LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname);
++if (!is_tainted2(pathname, LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname))
++ return openat(dirfd, pathname, flags, mode);
+ errno = EACCES;
+ return -1;
+ }
+@@ -1120,8 +1150,8 @@ return -1;
+ static inline FILE *
+ exim_fopen(const char *pathname, const char *mode)
+ {
+-if (!is_tainted(pathname)) return fopen(pathname, mode);
+-log_write(0, LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname);
++if (!is_tainted2(pathname, LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname))
++ return fopen(pathname, mode);
+ errno = EACCES;
+ return NULL;
+ }
+@@ -1129,8 +1159,8 @@ return NULL;
+ static inline DIR *
+ exim_opendir(const uschar * name)
+ {
+-if (!is_tainted(name)) return opendir(CCS name);
+-log_write(0, LOG_MAIN|LOG_PANIC, "Tainted dirname '%s'", name);
++if (!is_tainted2(name, LOG_MAIN|LOG_PANIC, "Tainted dirname '%s'", name))
++ return opendir(CCS name);
+ errno = EACCES;
+ return NULL;
+ }
+diff --git a/src/globals.c b/src/globals.c
+index c34ac9ddd..ff660c352 100644
+--- a/src/globals.c
++++ b/src/globals.c
+@@ -98,6 +98,10 @@ int sqlite_lock_timeout = 5;
+ BOOL move_frozen_messages = FALSE;
+ #endif
+
++#ifdef ALLOW_INSECURE_TAINTED_DATA
++BOOL allow_insecure_tainted_data = FALSE;
++#endif
++
+ /* These variables are outside the #ifdef because it keeps the code less
+ cluttered in several places (e.g. during logging) if we can always refer to
+ them. Also, the tls_ variables are now always visible. Note that these are
+@@ -1033,6 +1037,9 @@ int log_default[] = { /* for initializing log_selector */
+ Li_size_reject,
+ Li_skip_delivery,
+ Li_smtp_confirmation,
++#ifdef ALLOW_INSECURE_TAINTED_DATA
++ Li_tainted,
++#endif
+ Li_tls_certificate_verified,
+ Li_tls_cipher,
+ -1
+@@ -1100,6 +1107,9 @@ bit_table log_options[] = { /* must be in alphabetical order,
+ BIT_TABLE(L, smtp_protocol_error),
+ BIT_TABLE(L, smtp_syntax_error),
+ BIT_TABLE(L, subject),
++#ifdef ALLOW_INSECURE_TAINTED_DATA
++ BIT_TABLE(L, tainted),
++#endif
+ BIT_TABLE(L, tls_certificate_verified),
+ BIT_TABLE(L, tls_cipher),
+ BIT_TABLE(L, tls_peerdn),
+diff --git a/src/globals.h b/src/globals.h
+index a4c1143b7..8d72577e0 100644
+--- a/src/globals.h
++++ b/src/globals.h
+@@ -77,6 +77,10 @@ extern int sqlite_lock_timeout; /* Internal lock waiting timeout */
+ extern BOOL move_frozen_messages; /* Get them out of the normal directory */
+ #endif
+
++#ifdef ALLOW_INSECURE_TAINTED_DATA
++extern BOOL allow_insecure_tainted_data;
++#endif
++
+ /* These variables are outside the #ifdef because it keeps the code less
+ cluttered in several places (e.g. during logging) if we can always refer to
+ them. Also, the tls_ variables are now always visible. */
+diff --git a/src/macros.h b/src/macros.h
+index f78ae2e3d..322ddbf56 100644
+--- a/src/macros.h
++++ b/src/macros.h
+@@ -498,6 +498,9 @@ enum logbit {
+ Li_smtp_mailauth,
+ Li_smtp_no_mail,
+ Li_subject,
++#ifdef ALLOW_INSECURE_TAINTED_DATA
++ Li_tainted,
++#endif
+ Li_tls_certificate_verified,
+ Li_tls_cipher,
+ Li_tls_peerdn,
+diff --git a/src/readconf.c b/src/readconf.c
+index 948fa2403..133135f8f 100644
+--- a/src/readconf.c
++++ b/src/readconf.c
+@@ -68,6 +68,9 @@ static optionlist optionlist_config[] = {
+ { "add_environment", opt_stringptr, {&add_environment} },
+ { "admin_groups", opt_gidlist, {&admin_groups} },
+ { "allow_domain_literals", opt_bool, {&allow_domain_literals} },
++#ifdef ALLOW_INSECURE_TAINTED_DATA
++ { "allow_insecure_tainted_data", opt_bool, {&allow_insecure_tainted_data} },
++#endif
+ { "allow_mx_to_ip", opt_bool, {&allow_mx_to_ip} },
+ { "allow_utf8_domains", opt_bool, {&allow_utf8_domains} },
+ { "auth_advertise_hosts", opt_stringptr, {&auth_advertise_hosts} },
+--
+2.30.2
+
diff --git a/debian/patches/75_02-search.patch b/debian/patches/75_02-search.patch
new file mode 100644
index 0000000..226a350
--- /dev/null
+++ b/debian/patches/75_02-search.patch
@@ -0,0 +1,39 @@
+From b71d675f695c2cf17357b190476129535d5f446c Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Thu, 1 Apr 2021 22:45:03 +0200
+Subject: [PATCH 02/23] search
+
+---
+ src/search.c | 8 ++------
+ 1 file changed, 2 insertions(+), 6 deletions(-)
+
+diff --git a/src/search.c b/src/search.c
+index f8aaacb04..f6e4d1f5b 100644
+--- a/src/search.c
++++ b/src/search.c
+@@ -343,12 +343,8 @@ lookup_info *lk = lookup_list[search_type];
+ uschar keybuffer[256];
+ int old_pool = store_pool;
+
+-if (filename && is_tainted(filename))
+- {
+- log_write(0, LOG_MAIN|LOG_PANIC,
+- "Tainted filename for search: '%s'", filename);
++if (filename && is_tainted2(filename, LOG_MAIN|LOG_PANIC, "Tainted filename for search '%s'", filename))
+ return NULL;
+- }
+
+ /* Change to the search store pool and remember our reset point */
+
+@@ -639,7 +635,7 @@ DEBUG(D_lookup)
+ /* Arrange to put this database at the top of the LRU chain if it is a type
+ that opens real files. */
+
+-if ( open_top != (tree_node *)handle
++if ( open_top != (tree_node *)handle
+ && lookup_list[t->name[0]-'0']->type == lookup_absfile)
+ {
+ search_cache *c = (search_cache *)(t->data.ptr);
+--
+2.30.2
+
diff --git a/debian/patches/75_03-dbstuff.patch b/debian/patches/75_03-dbstuff.patch
new file mode 100644
index 0000000..dc9da8e
--- /dev/null
+++ b/debian/patches/75_03-dbstuff.patch
@@ -0,0 +1,30 @@
+From 35b11dd0e52b5ac176849f807cca8898bcaf0c3d Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Sun, 28 Mar 2021 10:49:49 +0200
+Subject: [PATCH 03/23] dbstuff
+
+---
+ src/dbstuff.h | 6 ++----
+ 1 file changed, 2 insertions(+), 4 deletions(-)
+
+diff --git a/src/dbstuff.h b/src/dbstuff.h
+index c1fb54346..dcee78696 100644
+--- a/src/dbstuff.h
++++ b/src/dbstuff.h
+@@ -643,11 +643,9 @@ after reading data. */
+ : (flags) == O_RDWR ? "O_RDWR" \
+ : (flags) == (O_RDWR|O_CREAT) ? "O_RDWR|O_CREAT" \
+ : "??"); \
+- if (is_tainted(name) || is_tainted(dirname)) \
+- { \
+- log_write(0, LOG_MAIN|LOG_PANIC, "Tainted name for DB file not permitted"); \
++ if (is_tainted2(name, LOG_MAIN|LOG_PANIC, "Tainted name '%s' for DB file not permitted", name) \
++ || is_tainted2(dirname, LOG_MAIN|LOG_PANIC, "Tainted name '%s' for DB directory not permitted", dirname)) \
+ *dbpp = NULL; \
+- } \
+ else \
+ { EXIM_DBOPEN__(name, dirname, flags, mode, dbpp); } \
+ DEBUG(D_hints_lookup) debug_printf_indent("returned from EXIM_DBOPEN: %p\n", *dbpp); \
+--
+2.30.2
+
diff --git a/debian/patches/75_04-acl.patch b/debian/patches/75_04-acl.patch
new file mode 100644
index 0000000..810b2e5
--- /dev/null
+++ b/debian/patches/75_04-acl.patch
@@ -0,0 +1,67 @@
+From 44fd80ad8abcd885fc1c8dbb294fc2140e4ef481 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Sun, 28 Mar 2021 10:50:14 +0200
+Subject: [PATCH 04/23] acl
+Last-Update: 2021-05-01
+
+---
+ src/acl.c | 32 ++++++++++++++++----------------
+ 1 file changed, 16 insertions(+), 16 deletions(-)
+
+--- a/src/acl.c
++++ b/src/acl.c
+@@ -3596,24 +3596,26 @@
+ rc = mime_regex(&arg);
+ break;
+ #endif
+
+ case ACLC_QUEUE:
+- if (is_tainted(arg))
+ {
+- *log_msgptr = string_sprintf("Tainted name '%s' for queue not permitted",
+- arg);
+- return ERROR;
++ uschar *m;
++ if (m = is_tainted2(arg, 0, "Tainted name '%s' for queue not permitted", arg))
++ {
++ *log_msgptr = m;
++ return ERROR;
++ }
++ if (Ustrchr(arg, '/'))
++ {
++ *log_msgptr = string_sprintf(
++ "Directory separator not permitted in queue name: '%s'", arg);
++ return ERROR;
++ }
++ queue_name = string_copy_perm(arg, FALSE);
++ break;
+ }
+- if (Ustrchr(arg, '/'))
+- {
+- *log_msgptr = string_sprintf(
+- "Directory separator not permitted in queue name: '%s'", arg);
+- return ERROR;
+- }
+- queue_name = string_copy_perm(arg, FALSE);
+- break;
+
+ case ACLC_RATELIMIT:
+ rc = acl_ratelimit(arg, where, log_msgptr);
+ break;
+
+@@ -4005,14 +4007,12 @@
+ }
+
+ else if (*ss == '/')
+ {
+ struct stat statbuf;
+- if (is_tainted(ss))
++ if (is_tainted2(ss, LOG_MAIN|LOG_PANIC, "Tainted ACL file name '%s'", ss))
+ {
+- log_write(0, LOG_MAIN|LOG_PANIC,
+- "attempt to open tainted ACL file name \"%s\"", ss);
+ /* Avoid leaking info to an attacker */
+ *log_msgptr = US"internal configuration error";
+ return ERROR;
+ }
+ if ((fd = Uopen(ss, O_RDONLY, 0)) < 0)
diff --git a/debian/patches/75_05-parse.patch b/debian/patches/75_05-parse.patch
new file mode 100644
index 0000000..f9dab90
--- /dev/null
+++ b/debian/patches/75_05-parse.patch
@@ -0,0 +1,30 @@
+From 7eeeb6f26af05322814ecc77c87f09c72ab2216a Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Sun, 28 Mar 2021 10:58:46 +0200
+Subject: [PATCH 05/23] parse
+
+---
+ src/parse.c | 6 +-----
+ 1 file changed, 1 insertion(+), 5 deletions(-)
+
+diff --git a/src/parse.c b/src/parse.c
+index 3ea758ac9..d1bc79039 100644
+--- a/src/parse.c
++++ b/src/parse.c
+@@ -1402,12 +1402,8 @@ for (;;)
+ return FF_ERROR;
+ }
+
+- if (is_tainted(filename))
+- {
+- *error = string_sprintf("Tainted name '%s' for included file not permitted\n",
+- filename);
++ if (*error = is_tainted2(filename, 0, "Tainted name '%s' for included file not permitted\n", filename))
+ return FF_ERROR;
+- }
+
+ /* Check file name if required */
+
+--
+2.30.2
+
diff --git a/debian/patches/75_06-rda.patch b/debian/patches/75_06-rda.patch
new file mode 100644
index 0000000..f4ca2af
--- /dev/null
+++ b/debian/patches/75_06-rda.patch
@@ -0,0 +1,28 @@
+From a6da9c67acaee699616516be141d600cc178a633 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Sun, 28 Mar 2021 10:59:46 +0200
+Subject: [PATCH 06/23] rda
+
+---
+ src/rda.c | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+diff --git a/src/rda.c b/src/rda.c
+index aed8abc24..6ad7dd8bd 100644
+--- a/src/rda.c
++++ b/src/rda.c
+@@ -179,10 +179,8 @@ struct stat statbuf;
+ /* Reading a file is a form of expansion; we wish to deny attackers the
+ capability to specify the file name. */
+
+-if (is_tainted(filename))
++if (*error = is_tainted2(filename, 0, "Tainted name '%s' for file read not permitted\n", filename))
+ {
+- *error = string_sprintf("Tainted name '%s' for file read not permitted\n",
+- filename);
+ *yield = FF_ERROR;
+ return NULL;
+ }
+--
+2.30.2
+
diff --git a/debian/patches/75_07-appendfile.patch b/debian/patches/75_07-appendfile.patch
new file mode 100644
index 0000000..5a9e378
--- /dev/null
+++ b/debian/patches/75_07-appendfile.patch
@@ -0,0 +1,34 @@
+From c29b50d2fe17cc108d751175ed4f4113c25c1768 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Sun, 28 Mar 2021 11:00:06 +0200
+Subject: [PATCH 07/23] appendfile
+
+---
+ src/transports/appendfile.c | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/src/transports/appendfile.c b/src/transports/appendfile.c
+index 8ab8b6016..7dbbaa2f9 100644
+--- a/src/transports/appendfile.c
++++ b/src/transports/appendfile.c
+@@ -1286,12 +1286,14 @@ if (!(path = expand_string(fdname)))
+ expand_string_message);
+ goto ret_panic;
+ }
+-if (is_tainted(path))
++{ uschar *m;
++if (m = is_tainted2(path, 0, "Tainted '%s' (file or directory "
++ "name for %s transport) not permitted", path, tblock->name))
+ {
+- addr->message = string_sprintf("Tainted '%s' (file or directory "
+- "name for %s transport) not permitted", path, tblock->name);
++ addr->message = m;
+ goto ret_panic;
+ }
++}
+
+ if (path[0] != '/')
+ {
+--
+2.30.2
+
diff --git a/debian/patches/75_08-autoreply.patch b/debian/patches/75_08-autoreply.patch
new file mode 100644
index 0000000..de5eb1d
--- /dev/null
+++ b/debian/patches/75_08-autoreply.patch
@@ -0,0 +1,70 @@
+From 26de37d8960da80473866fb59b9dfd10a5761538 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Sun, 28 Mar 2021 11:06:27 +0200
+Subject: [PATCH 08/23] autoreply
+
+---
+ src/transports/autoreply.c | 21 ++++++++++++---------
+ 1 file changed, 12 insertions(+), 9 deletions(-)
+
+diff --git a/src/transports/autoreply.c b/src/transports/autoreply.c
+index 865abbf4f..ed99de4c6 100644
+--- a/src/transports/autoreply.c
++++ b/src/transports/autoreply.c
+@@ -404,14 +404,15 @@ recipient cache. */
+
+ if (oncelog && *oncelog && to)
+ {
++ uschar *m;
+ time_t then = 0;
+
+- if (is_tainted(oncelog))
++ if (m = is_tainted2(oncelog, 0, "Tainted '%s' (once file for %s transport)"
++ " not permitted", oncelog, tblock->name))
+ {
+ addr->transport_return = DEFER;
+ addr->basic_errno = EACCES;
+- addr->message = string_sprintf("Tainted '%s' (once file for %s transport)"
+- " not permitted", oncelog, tblock->name);
++ addr->message = m;
+ goto END_OFF;
+ }
+
+@@ -515,13 +516,14 @@ if (oncelog && *oncelog && to)
+
+ if (then != 0 && (once_repeat_sec <= 0 || now - then < once_repeat_sec))
+ {
++ uschar *m;
+ int log_fd;
+- if (is_tainted(logfile))
++ if (m = is_tainted2(logfile, 0, "Tainted '%s' (logfile for %s transport)"
++ " not permitted", logfile, tblock->name))
+ {
+ addr->transport_return = DEFER;
+ addr->basic_errno = EACCES;
+- addr->message = string_sprintf("Tainted '%s' (logfile for %s transport)"
+- " not permitted", logfile, tblock->name);
++ addr->message = m;
+ goto END_OFF;
+ }
+
+@@ -548,12 +550,13 @@ if (oncelog && *oncelog && to)
+ /* We are going to send a message. Ensure any requested file is available. */
+ if (file)
+ {
+- if (is_tainted(file))
++ uschar *m;
++ if (m = is_tainted2(file, 0, "Tainted '%s' (file for %s transport)"
++ " not permitted", file, tblock->name))
+ {
+ addr->transport_return = DEFER;
+ addr->basic_errno = EACCES;
+- addr->message = string_sprintf("Tainted '%s' (file for %s transport)"
+- " not permitted", file, tblock->name);
++ addr->message = m;
+ return FALSE;
+ }
+ if (!(ff = Ufopen(file, "rb")) && !ob->file_optional)
+--
+2.30.2
+
diff --git a/debian/patches/75_09-pipe.patch b/debian/patches/75_09-pipe.patch
new file mode 100644
index 0000000..0ec9bcf
--- /dev/null
+++ b/debian/patches/75_09-pipe.patch
@@ -0,0 +1,36 @@
+From f9628406706112be459adb3f121db8e6cf282c2d Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Fri, 2 Apr 2021 17:30:27 +0200
+Subject: [PATCH 09/23] pipe
+
+---
+ src/transports/pipe.c | 9 ++++++---
+ 1 file changed, 6 insertions(+), 3 deletions(-)
+
+diff --git a/src/transports/pipe.c b/src/transports/pipe.c
+index 27422bd42..4c9e68beb 100644
+--- a/src/transports/pipe.c
++++ b/src/transports/pipe.c
+@@ -599,13 +599,16 @@ if (!cmd || !*cmd)
+ tblock->name);
+ return FALSE;
+ }
+-if (is_tainted(cmd))
++
++{ uschar *m;
++if (m = is_tainted2(cmd, 0, "Tainted '%s' (command "
++ "for %s transport) not permitted", cmd, tblock->name))
+ {
+- addr->message = string_sprintf("Tainted '%s' (command "
+- "for %s transport) not permitted", cmd, tblock->name);
+ addr->transport_return = PANIC;
++ addr->message = m;
+ return FALSE;
+ }
++}
+
+ /* When a pipe is set up by a filter file, there may be values for $thisaddress
+ and numerical the variables in existence. These are passed in
+--
+2.30.2
+
diff --git a/debian/patches/75_10-deliver.patch b/debian/patches/75_10-deliver.patch
new file mode 100644
index 0000000..ea4a542
--- /dev/null
+++ b/debian/patches/75_10-deliver.patch
@@ -0,0 +1,49 @@
+From 2fee91ae42e974c21202e0b5e17185f6a87bf8af Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Wed, 31 Mar 2021 23:12:44 +0200
+Subject: [PATCH 10/23] deliver
+
+---
+ src/deliver.c | 16 +++++++++-------
+ 1 file changed, 9 insertions(+), 7 deletions(-)
+
+diff --git a/src/deliver.c b/src/deliver.c
+index d85edd70e..8b7998f37 100644
+--- a/src/deliver.c
++++ b/src/deliver.c
+@@ -5538,10 +5538,11 @@ FILE * fp = NULL;
+ if (!s || !*s)
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "Failed to expand %s: '%s'\n", varname, filename);
+-else if (*s != '/' || is_tainted(s))
+- log_write(0, LOG_MAIN|LOG_PANIC,
+- "%s is not %s after expansion: '%s'\n",
+- varname, *s == '/' ? "untainted" : "absolute", s);
++else if (*s != '/')
++ log_write(0, LOG_MAIN|LOG_PANIC, "%s is not absolute after expansion: '%s'\n",
++ varname, s);
++else if (is_tainted2(s, LOG_MAIN|LOG_PANIC, "Tainted %s after expansion: '%s'\n", varname, s))
++ ;
+ else if (!(fp = Ufopen(s, "rb")))
+ log_write(0, LOG_MAIN|LOG_PANIC, "Failed to open %s for %s "
+ "message texts: %s", s, reason, strerror(errno));
+@@ -6148,12 +6149,13 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT)
+ {
+ uschar *tmp = expand_string(tpname);
+ address_file = address_pipe = NULL;
++ uschar *m;
+ if (!tmp)
+ p->message = string_sprintf("failed to expand \"%s\" as a "
+ "system filter transport name", tpname);
+- if (is_tainted(tmp))
+- p->message = string_sprintf("attempt to used tainted value '%s' for"
+- "transport '%s' as a system filter", tmp, tpname);
++ if (is_tainted2(tmp, 0, m = string_sprintf("Tainted values '%s' "
++ "for transport '%s' as a system filter", tmp, tpname)))
++ p->message = m;
+ tpname = tmp;
+ }
+ else
+--
+2.30.2
+
diff --git a/debian/patches/75_11-directory.patch b/debian/patches/75_11-directory.patch
new file mode 100644
index 0000000..4c3a684
--- /dev/null
+++ b/debian/patches/75_11-directory.patch
@@ -0,0 +1,26 @@
+From 5f41e800ce9cc7ad154047298914df955e905bf4 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Thu, 1 Apr 2021 21:28:59 +0200
+Subject: [PATCH 11/23] directory
+
+---
+ src/directory.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/src/directory.c b/src/directory.c
+index 2d4d565f4..9f88f4141 100644
+--- a/src/directory.c
++++ b/src/directory.c
+@@ -44,6 +44,9 @@ uschar c = 1;
+ struct stat statbuf;
+ uschar * path;
+
++if (is_tainted2(name, LOG_MAIN|LOG_PANIC, "Tainted path '%s' for new directory", name))
++ { p = US"create"; path = US name; errno = EACCES; goto bad; }
++
+ if (parent)
+ {
+ path = string_sprintf("%s%s%s", parent, US"/", name);
+--
+2.30.2
+
diff --git a/debian/patches/75_12-expand.patch b/debian/patches/75_12-expand.patch
new file mode 100644
index 0000000..ebb099d
--- /dev/null
+++ b/debian/patches/75_12-expand.patch
@@ -0,0 +1,34 @@
+From c02ea85f525ff256d78e084d6f76fe3032fd52e1 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Thu, 1 Apr 2021 21:33:50 +0200
+Subject: [PATCH 12/23] expand
+
+---
+ src/expand.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/src/expand.c b/src/expand.c
+index 05de94c49..21b86ebf5 100644
+--- a/src/expand.c
++++ b/src/expand.c
+@@ -4383,13 +4383,13 @@ DEBUG(D_expand)
+ f.expand_string_forcedfail = FALSE;
+ expand_string_message = US"";
+
+-if (is_tainted(string))
++{ uschar *m;
++if (m = is_tainted2(string, LOG_MAIN|LOG_PANIC, "Tainted string '%s' in expansion", s))
+ {
+- expand_string_message =
+- string_sprintf("attempt to expand tainted string '%s'", s);
+- log_write(0, LOG_MAIN|LOG_PANIC, "%s", expand_string_message);
++ expand_string_message = m;
+ goto EXPAND_FAILED;
+ }
++}
+
+ while (*s != 0)
+ {
+--
+2.30.2
+
diff --git a/debian/patches/75_13-lf_sqlperform.patch b/debian/patches/75_13-lf_sqlperform.patch
new file mode 100644
index 0000000..67283a0
--- /dev/null
+++ b/debian/patches/75_13-lf_sqlperform.patch
@@ -0,0 +1,49 @@
+From 9810dfc25d8b9687b46e57963a3ac30bf5c9b2c9 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Thu, 1 Apr 2021 21:36:12 +0200
+Subject: [PATCH 13/23] lf_sqlperform
+
+---
+ src/lookups/lf_sqlperform.c | 14 +++++++++-----
+ 1 file changed, 9 insertions(+), 5 deletions(-)
+
+diff --git a/src/lookups/lf_sqlperform.c b/src/lookups/lf_sqlperform.c
+index ad1df29d1..eda3089e2 100644
+--- a/src/lookups/lf_sqlperform.c
++++ b/src/lookups/lf_sqlperform.c
+@@ -102,11 +102,13 @@ if (Ustrncmp(query, "servers", 7) == 0)
+ }
+ }
+
+- if (is_tainted(server))
+- {
+- *errmsg = string_sprintf("%s server \"%s\" is tainted", name, server);
++ { uschar *m;
++ if (m = is_tainted2(server, 0, "Tainted %s server '%s'", name, server))
++ {
++ *errmsg = m;
+ return DEFER;
+ }
++ }
+
+ rc = (*fn)(ss+1, server, result, errmsg, &defer_break, do_cache, opts);
+ if (rc != DEFER || defer_break) return rc;
+@@ -158,11 +160,13 @@ else
+ server = ele;
+ }
+
+- if (is_tainted(server))
++ { uschar *m;
++ if (is_tainted2(server, 0, "Tainted %s server '%s'", name, server))
+ {
+- *errmsg = string_sprintf("%s server \"%s\" is tainted", name, server);
++ *errmsg = m;
+ return DEFER;
+ }
++ }
+
+ rc = (*fn)(query, server, result, errmsg, &defer_break, do_cache, opts);
+ if (rc != DEFER || defer_break) return rc;
+--
+2.30.2
+
diff --git a/debian/patches/75_14-rf_get_transport.patch b/debian/patches/75_14-rf_get_transport.patch
new file mode 100644
index 0000000..9e8b69d
--- /dev/null
+++ b/debian/patches/75_14-rf_get_transport.patch
@@ -0,0 +1,28 @@
+From 015fff57c854184f8bce61476c46a2830a97daf8 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Fri, 2 Apr 2021 08:36:24 +0200
+Subject: [PATCH 14/23] rf_get_transport
+
+---
+ src/routers/rf_get_transport.c | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+diff --git a/src/routers/rf_get_transport.c b/src/routers/rf_get_transport.c
+index 4a43818ff..32bde9ec3 100644
+--- a/src/routers/rf_get_transport.c
++++ b/src/routers/rf_get_transport.c
+@@ -66,10 +66,8 @@ if (expandable)
+ "\"%s\" in %s router: %s", tpname, router_name, expand_string_message);
+ return FALSE;
+ }
+- if (is_tainted(ss))
++ if (is_tainted2(ss, LOG_MAIN|LOG_PANIC, "Tainted tainted value '%s' from '%s' for transport", ss, tpname))
+ {
+- log_write(0, LOG_MAIN|LOG_PANIC,
+- "attempt to use tainted value '%s' from '%s' for transport", ss, tpname);
+ addr->basic_errno = ERRNO_BADTRANSPORT;
+ /* Avoid leaking info to an attacker */
+ addr->message = US"internal configuration error";
+--
+2.30.2
+
diff --git a/debian/patches/75_15-deliver.patch b/debian/patches/75_15-deliver.patch
new file mode 100644
index 0000000..0c2ca27
--- /dev/null
+++ b/debian/patches/75_15-deliver.patch
@@ -0,0 +1,31 @@
+From 2bafe3fc82cf62f0c21f939f5891b8d067f3abc7 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Sat, 3 Apr 2021 10:54:22 +0200
+Subject: [PATCH 15/23] deliver
+
+---
+ src/deliver.c | 5 +++--
+ test/paniclog/0622 | 2 +-
+ test/stderr/0622 | 2 +-
+ 3 files changed, 5 insertions(+), 4 deletions(-)
+
+diff --git a/src/deliver.c b/src/deliver.c
+index 8b7998f37..87e944b03 100644
+--- a/src/deliver.c
++++ b/src/deliver.c
+@@ -6153,9 +6153,10 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT)
+ if (!tmp)
+ p->message = string_sprintf("failed to expand \"%s\" as a "
+ "system filter transport name", tpname);
+- if (is_tainted2(tmp, 0, m = string_sprintf("Tainted values '%s' "
+- "for transport '%s' as a system filter", tmp, tpname)))
++ { uschar *m;
++ if (m = is_tainted2(tmp, 0, "Tainted values '%s' " "for transport '%s' as a system filter", tmp, tpname))
+ p->message = m;
++ }
+ tpname = tmp;
+ }
+ else
+--
+2.30.2
+
diff --git a/debian/patches/75_16-smtp_out.patch b/debian/patches/75_16-smtp_out.patch
new file mode 100644
index 0000000..a0280af
--- /dev/null
+++ b/debian/patches/75_16-smtp_out.patch
@@ -0,0 +1,38 @@
+From b9b967cca71a4da51506f8ba596b9ae40cfcef57 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Thu, 1 Apr 2021 21:42:38 +0200
+Subject: [PATCH 16/23] smtp_out
+
+---
+ src/smtp_out.c | 7 ++-----
+ 1 file changed, 2 insertions(+), 5 deletions(-)
+
+diff --git a/src/smtp_out.c b/src/smtp_out.c
+index c4c409677..9c160e697 100644
+--- a/src/smtp_out.c
++++ b/src/smtp_out.c
+@@ -53,11 +53,8 @@ if (!(expint = expand_string(istring)))
+ return FALSE;
+ }
+
+-if (is_tainted(expint))
++if (is_tainted2(expint, LOG_MAIN|LOG_PANIC, "Tainted value '%s' from '%s' for interface", expint, istring))
+ {
+- log_write(0, LOG_MAIN|LOG_PANIC,
+- "attempt to use tainted value '%s' from '%s' for interface",
+- expint, istring);
+ addr->transport_return = PANIC;
+ addr->message = string_sprintf("failed to expand \"interface\" "
+ "option for %s: configuration error", msg);
+@@ -425,7 +422,7 @@ 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)
+--
+2.30.2
+
diff --git a/debian/patches/75_17-smtp.patch b/debian/patches/75_17-smtp.patch
new file mode 100644
index 0000000..c77a284
--- /dev/null
+++ b/debian/patches/75_17-smtp.patch
@@ -0,0 +1,29 @@
+From 8b7d4ba8903ace7e3e3db70343798a5a0b7cea23 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Thu, 1 Apr 2021 22:02:27 +0200
+Subject: [PATCH 17/23] smtp
+
+---
+ src/transports/smtp.c | 5 +----
+ 1 file changed, 1 insertion(+), 4 deletions(-)
+
+diff --git a/src/transports/smtp.c b/src/transports/smtp.c
+index 6540e4d2b..8fecf7eef 100644
+--- a/src/transports/smtp.c
++++ b/src/transports/smtp.c
+@@ -4715,11 +4715,8 @@ if (!hostlist || (ob->hosts_override && ob->hosts))
+ else
+ if (ob->hosts_randomize) s = expanded_hosts = string_copy(s);
+
+- if (is_tainted(s))
++ if (is_tainted2(s, LOG_MAIN|LOG_PANIC, "Tainted host list '%s' from '%s' in transport %s", s, ob->hosts, tblock->name))
+ {
+- log_write(0, LOG_MAIN|LOG_PANIC,
+- "attempt to use tainted host list '%s' from '%s' in transport %s",
+- s, ob->hosts, tblock->name);
+ /* Avoid leaking info to an attacker */
+ addrlist->message = US"internal configuration error";
+ addrlist->transport_return = PANIC;
+--
+2.30.2
+
diff --git a/debian/patches/75_18-update-doc.patch b/debian/patches/75_18-update-doc.patch
new file mode 100644
index 0000000..2edba69
--- /dev/null
+++ b/debian/patches/75_18-update-doc.patch
@@ -0,0 +1,154 @@
+From 77cc1ad3058e4ef7ae82adb914ccff0be9fe2c8b Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Sat, 3 Apr 2021 09:29:13 +0200
+Subject: [PATCH 18/23] update doc
+
+---
+ doc/doc-docbook/spec.xfpt | 45 ++++++++++++++++++++++++++++++++++++++-
+ doc/NewStuff | 45 +++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 89 insertions(+), 1 deletion(-)
+
+--- a/doc/NewStuff
++++ b/doc/NewStuff
+@@ -4,10 +4,55 @@
+ This file contains descriptions of new features that have been added to Exim.
+ Before a formal release, there may be quite a lot of detail so that people can
+ test from the snapshots or the Git before the documentation is updated. Once
+ the documentation is updated, this file is reduced to a short list.
+
++Version 4.95
++------------
++
++ 1. The fast-ramp two phase queue run support, previously experimental, is
++ now supported by default.
++
++ 2. The native SRS support, previously experimental, is now supported. It is
++ not built unless specified in the Local/Makefile.
++
++ 3. TLS resumption support, previously experimental, is now supported and
++ included in default builds.
++
++ 4. Single-key LMDB lookups, previously experimental, are now supported.
++ The support is not built unless specified in the Local/Makefile.
++
++ 5. Option "message_linelength_limit" on the smtp transport to enforce (by
++ default) the RFC 998 character limit.
++
++ 6. An option to ignore the cache on a lookup.
++
++ 7. Quota checking during reception (i.e. at SMTP time) for appendfile-
++ transport-managed quotas.
++
++ 8. Sqlite lookups accept a "file=<path>" option to specify a per-operation
++ db file, replacing the previous prefix to the SQL string (which had
++ issues when the SQL used tainted values).
++
++ 9. Lsearch lookups accept a "ret=full" option, to return both the portion
++ of the line matching the key, and the remainder.
++
++10. A command-line option to have a daemon not create a notifier socket.
++
++11. Faster TLS startup. When various configuration options contain no
++ expandable elements, the information can be preloaded and cached rather
++ than the provious behaviour of always loading at startup time for every
++ connection. This helps particularly for the CA bundle.
++
++12. Proxy Protocol Timeout is configurable via "proxy_protocol_timeout"
++ main config option.
++
++13. Option "smtp_accept_msx_per_connection" is now expanded.
++
++13. A main config option "allow_insecure_tainted_data" allows to turn
++ taint errors into warnings.
++
+ Version 4.94
+ ------------
+
+ 1. EXPERIMENTAL_SRS_NATIVE optional build feature. See the experimental.spec
+ file.
+--- a/doc/spec.txt
++++ b/doc/spec.txt
+@@ -8650,12 +8650,20 @@
+ Whether a string is expanded depends upon the context. Usually this is solely
+ dependent upon the option for which a value is sought; in this documentation,
+ options for which string expansion is performed are marked with * after the
+ data type. ACL rules always expand strings. A couple of expansion conditions do
+ not expand some of the brace-delimited branches, for security reasons, and
+-expansion of data deriving from the sender ("tainted data") is not permitted.
+-
++expansion of data deriving from the sender ("tainted data") is not permitted
++(including acessing a file using a tainted name). The main config
++option allow_insecure_tainted_data can be used as mitigation during
++uprades to more secure configurations.
++
++Common ways of obtaining untainted equivalents of variables with tainted
++values come down to using the tainted value as a lookup key in a trusted
++database. This database could be the filesystem structure, or the
++password file, or accessed via a DBMS. Specific methods are indexed
++under "de-tainting".
+
+ 11.1 Literal text in expanded strings
+ -------------------------------------
+
+ An uninterpreted dollar can be included in an expanded string by putting a
+@@ -12946,10 +12954,12 @@
+
+
+ 14.1 Miscellaneous
+ ------------------
+
++add_environment environment variables
++allow_insecure_tainted_data turn taint errors into warnings
+ bi_command to run for -bi command line option
+ debug_store do extra internal checks
+ disable_ipv6 do no IPv6 processing
+ keep_malformed for broken files - should not happen
+ localhost_number for unique message ids in clusters
+@@ -13553,10 +13563,20 @@
+ true, and also to add "@[]" to the list of local domains (defined in the named
+ domain list local_domains in the default configuration). This "magic string"
+ matches the domain literal form of all the local host's IP addresses.
+
+ +-----------------------------------------------------+
++|allow_insecure_tainted_data main boolean false |
+++-----------------------------------------------------+
++
++The handling of tainted data may break older (pre 4.94) configurations.
++Setting this option to "true" turns taint errors (which result in a temporary
++message rejection) into warnings. This option is meant as mitigation only
++and deprecated already today. Future releases of Exim may ignore it.
++The taint log selector can be used to suppress even the warnings.
++
+++-----------------------------------------------------+
+ |allow_mx_to_ip|Use: main|Type: boolean|Default: false|
+ +-----------------------------------------------------+
+
+ It appears that more and more DNS zone administrators are breaking the rules
+ and putting domain names that look like IP addresses on the right hand side of
+@@ -35316,10 +35336,11 @@
+ smtp_mailauth AUTH argument to MAIL commands
+ smtp_no_mail session with no MAIL commands
+ smtp_protocol_error SMTP protocol errors
+ smtp_syntax_error SMTP syntax errors
+ subject contents of Subject: on <= lines
++*taint taint errors or warnings
+ *tls_certificate_verified certificate verification status
+ *tls_cipher TLS cipher suite on <= and => lines
+ tls_peerdn TLS peer DN on <= and => lines
+ tls_sni TLS SNI on <= lines
+ unknown_in_list DNS lookup failed in list match
+@@ -35604,11 +35625,13 @@
+
+ * tls_certificate_verified: An extra item is added to <= and => log lines
+ when TLS is in use. The item is "CV=yes" if the peer's certificate was
+ verified using a CA trust anchor, "CA=dane" if using a DNS trust anchor,
+ and "CV=no" if not.
+-
++ * taint: Log warnings about tainted data. This selector can't be
++ turned of if allow_insecure_tainted_data is false (which is the
++ default).
+ * tls_cipher: When a message is sent or received over an encrypted
+ connection, the cipher suite used is added to the log line, preceded by X=.
+
+ * tls_peerdn: When a message is sent or received over an encrypted
+ connection, and a certificate is supplied by the remote host, the peer DN
diff --git a/debian/patches/75_20-Set-mainlog_name-and-rejectlog_name-unconditionally.patch b/debian/patches/75_20-Set-mainlog_name-and-rejectlog_name-unconditionally.patch
new file mode 100644
index 0000000..a660aeb
--- /dev/null
+++ b/debian/patches/75_20-Set-mainlog_name-and-rejectlog_name-unconditionally.patch
@@ -0,0 +1,42 @@
+From 41c494e2465efadc2e82002a07430e8aec85bc9b Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Mon, 12 Apr 2021 08:41:44 +0200
+Subject: [PATCH 20/23] Set mainlog_name and rejectlog_name unconditionally.
+
+(cherry picked from commit 3f06b9b4c7244b169d50bce216c1f54b4dfe7efb)
+---
+ src/log.c | 10 ++++++----
+ 1 file changed, 6 insertions(+), 4 deletions(-)
+
+diff --git a/src/log.c b/src/log.c
+index 99eba5f90..011c4debc 100644
+--- a/src/log.c
++++ b/src/log.c
+@@ -402,18 +402,20 @@ it gets statted to see if it has been cycled. With a datestamp, the datestamp
+ will be compared. The static slot for saving it is the same size as buffer,
+ and the text has been checked above to fit, so this use of strcpy() is OK. */
+
+-if (type == lt_main && string_datestamp_offset >= 0)
++if (type == lt_main)
+ {
+ Ustrcpy(mainlog_name, buffer);
+- mainlog_datestamp = mainlog_name + string_datestamp_offset;
++ if (string_datestamp_offset > 0)
++ mainlog_datestamp = mainlog_name + string_datestamp_offset;
+ }
+
+ /* Ditto for the reject log */
+
+-else if (type == lt_reject && string_datestamp_offset >= 0)
++else if (type == lt_reject)
+ {
+ Ustrcpy(rejectlog_name, buffer);
+- rejectlog_datestamp = rejectlog_name + string_datestamp_offset;
++ if (string_datestamp_offset > 0)
++ rejectlog_datestamp = rejectlog_name + string_datestamp_offset;
+ }
+
+ /* and deal with the debug log (which keeps the datestamp, but does not
+--
+2.30.2
+
diff --git a/debian/patches/75_21-tidy-log.c.patch b/debian/patches/75_21-tidy-log.c.patch
new file mode 100644
index 0000000..b99f0c6
--- /dev/null
+++ b/debian/patches/75_21-tidy-log.c.patch
@@ -0,0 +1,124 @@
+From 8021b95c2e266861aba29c97b4bb90dc6f7637a2 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Mon, 12 Apr 2021 09:19:21 +0200
+Subject: [PATCH 21/23] tidy log.c
+
+(cherry picked from commit 0327b6460eec64da6b0c1543c7e9b3d0f8cb9294)
+---
+ src/log.c | 97 +++++++++++++++++++++++----------------------------
+ 1 file changed, 44 insertions(+), 53 deletions(-)
+
+diff --git a/src/log.c b/src/log.c
+index 011c4debc..7ef7074ec 100644
+--- a/src/log.c
++++ b/src/log.c
+@@ -397,62 +397,53 @@ people want, I hope. */
+
+ ok = string_format(buffer, sizeof(buffer), CS file_path, log_names[type]);
+
+-/* Save the name of the mainlog for rollover processing. Without a datestamp,
+-it gets statted to see if it has been cycled. With a datestamp, the datestamp
+-will be compared. The static slot for saving it is the same size as buffer,
+-and the text has been checked above to fit, so this use of strcpy() is OK. */
+-
+-if (type == lt_main)
++switch (type)
+ {
+- Ustrcpy(mainlog_name, buffer);
+- if (string_datestamp_offset > 0)
+- mainlog_datestamp = mainlog_name + string_datestamp_offset;
+- }
+-
+-/* Ditto for the reject log */
+-
+-else if (type == lt_reject)
+- {
+- Ustrcpy(rejectlog_name, buffer);
+- if (string_datestamp_offset > 0)
+- rejectlog_datestamp = rejectlog_name + string_datestamp_offset;
+- }
+-
+-/* and deal with the debug log (which keeps the datestamp, but does not
+-update it) */
+-
+-else if (type == lt_debug)
+- {
+- Ustrcpy(debuglog_name, buffer);
+- if (tag)
+- {
+- /* this won't change the offset of the datestamp */
+- ok2 = string_format(buffer, sizeof(buffer), "%s%s",
+- debuglog_name, tag);
+- if (ok2)
+- Ustrcpy(debuglog_name, buffer);
+- }
+- }
+-
+-/* Remove any datestamp if this is the panic log. This is rare, so there's no
+-need to optimize getting the datestamp length. We remove one non-alphanumeric
+-char afterwards if at the start, otherwise one before. */
+-
+-else if (string_datestamp_offset >= 0)
+- {
+- uschar * from = buffer + string_datestamp_offset;
+- uschar * to = from + string_datestamp_length;
++ case lt_main:
++ /* Save the name of the mainlog for rollover processing. Without a datestamp,
++ it gets statted to see if it has been cycled. With a datestamp, the datestamp
++ will be compared. The static slot for saving it is the same size as buffer,
++ and the text has been checked above to fit, so this use of strcpy() is OK. */
++ Ustrcpy(mainlog_name, buffer);
++ if (string_datestamp_offset > 0)
++ mainlog_datestamp = mainlog_name + string_datestamp_offset;
++ case lt_reject:
++ /* Ditto for the reject log */
++ Ustrcpy(rejectlog_name, buffer);
++ if (string_datestamp_offset > 0)
++ rejectlog_datestamp = rejectlog_name + string_datestamp_offset;
++ case lt_debug:
++ /* and deal with the debug log (which keeps the datestamp, but does not
++ update it) */
++ Ustrcpy(debuglog_name, buffer);
++ if (tag)
++ {
++ /* this won't change the offset of the datestamp */
++ ok2 = string_format(buffer, sizeof(buffer), "%s%s",
++ debuglog_name, tag);
++ if (ok2)
++ Ustrcpy(debuglog_name, buffer);
++ }
++ default:
++ /* Remove any datestamp if this is the panic log. This is rare, so there's no
++ need to optimize getting the datestamp length. We remove one non-alphanumeric
++ char afterwards if at the start, otherwise one before. */
++ if (string_datestamp_offset >= 0)
++ {
++ uschar * from = buffer + string_datestamp_offset;
++ uschar * to = from + string_datestamp_length;
+
+- if (from == buffer || from[-1] == '/')
+- {
+- if (!isalnum(*to)) to++;
+- }
+- else
+- if (!isalnum(from[-1])) from--;
++ if (from == buffer || from[-1] == '/')
++ {
++ if (!isalnum(*to)) to++;
++ }
++ else
++ if (!isalnum(from[-1])) from--;
+
+- /* This copy is ok, because we know that to is a substring of from. But
+- due to overlap we must use memmove() not Ustrcpy(). */
+- memmove(from, to, Ustrlen(to)+1);
++ /* This copy is ok, because we know that to is a substring of from. But
++ due to overlap we must use memmove() not Ustrcpy(). */
++ memmove(from, to, Ustrlen(to)+1);
++ }
+ }
+
+ /* If the file name is too long, it is an unrecoverable disaster */
+--
+2.30.2
+
diff --git a/debian/patches/75_22-Silence-compiler.patch b/debian/patches/75_22-Silence-compiler.patch
new file mode 100644
index 0000000..ce6e898
--- /dev/null
+++ b/debian/patches/75_22-Silence-compiler.patch
@@ -0,0 +1,222 @@
+From 2c9869d0622cc690b424cc74166d4a8393017ece Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Fri, 23 Apr 2021 17:40:40 +0200
+Subject: [PATCH 22/23] Silence compiler
+
+---
+ src/acl.c | 2 +-
+ src/deliver.c | 3 +--
+ src/expand.c | 6 +++++-
+ src/functions.h | 2 +-
+ src/lookups/lf_sqlperform.c | 4 ++--
+ src/parse.c | 2 +-
+ src/rda.c | 2 +-
+ src/transports/appendfile.c | 4 ++--
+ src/transports/autoreply.c | 12 ++++++------
+ src/transports/pipe.c | 4 ++--
+ 10 files changed, 22 insertions(+), 19 deletions(-)
+
+diff --git a/src/acl.c b/src/acl.c
+index 81beab5f3..b62af5c65 100644
+--- a/src/acl.c
++++ b/src/acl.c
+@@ -3600,7 +3600,7 @@ for (; cb; cb = cb->next)
+ case ACLC_QUEUE:
+ {
+ uschar *m;
+- if (m = is_tainted2(arg, 0, "Tainted name '%s' for queue not permitted", arg))
++ if ((m = is_tainted2(arg, 0, "Tainted name '%s' for queue not permitted", arg)))
+ {
+ *log_msgptr = m;
+ return ERROR;
+diff --git a/src/deliver.c b/src/deliver.c
+index 87e944b03..b40eed4f9 100644
+--- a/src/deliver.c
++++ b/src/deliver.c
+@@ -6149,12 +6149,11 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT)
+ {
+ uschar *tmp = expand_string(tpname);
+ address_file = address_pipe = NULL;
+- uschar *m;
+ if (!tmp)
+ p->message = string_sprintf("failed to expand \"%s\" as a "
+ "system filter transport name", tpname);
+ { uschar *m;
+- if (m = is_tainted2(tmp, 0, "Tainted values '%s' " "for transport '%s' as a system filter", tmp, tpname))
++ if ((m = is_tainted2(tmp, 0, "Tainted values '%s' " "for transport '%s' as a system filter", tmp, tpname)))
+ p->message = m;
+ }
+ tpname = tmp;
+diff --git a/src/expand.c b/src/expand.c
+index 21b86ebf5..dc4b4e102 100644
+--- a/src/expand.c
++++ b/src/expand.c
+@@ -4384,7 +4384,7 @@ f.expand_string_forcedfail = FALSE;
+ expand_string_message = US"";
+
+ { uschar *m;
+-if (m = is_tainted2(string, LOG_MAIN|LOG_PANIC, "Tainted string '%s' in expansion", s))
++if ((m = is_tainted2(string, LOG_MAIN|LOG_PANIC, "Tainted string '%s' in expansion", s)))
+ {
+ expand_string_message = m;
+ goto EXPAND_FAILED;
+@@ -7629,10 +7629,12 @@ while (*s != 0)
+ /* Manually track tainting, as we deal in individual chars below */
+
+ if (is_tainted(sub))
++ {
+ if (yield->s && yield->ptr)
+ gstring_rebuffer(yield);
+ else
+ yield->s = store_get(yield->size = Ustrlen(sub), TRUE);
++ }
+
+ /* Check the UTF-8, byte-by-byte */
+
+@@ -8193,6 +8195,7 @@ that is a bad idea, because expand_string_message is in dynamic store. */
+ EXPAND_FAILED:
+ if (left) *left = s;
+ DEBUG(D_expand)
++ {
+ DEBUG(D_noutf8)
+ {
+ debug_printf_indent("|failed to expand: %s\n", string);
+@@ -8212,6 +8215,7 @@ DEBUG(D_expand)
+ if (f.expand_string_forcedfail)
+ debug_printf_indent(UTF8_UP_RIGHT "failure was forced\n");
+ }
++ }
+ if (resetok_p && !resetok) *resetok_p = FALSE;
+ expand_level--;
+ return NULL;
+diff --git a/src/functions.h b/src/functions.h
+index 1e8083673..b4d23c4bc 100644
+--- a/src/functions.h
++++ b/src/functions.h
+@@ -1084,7 +1084,7 @@ if (f.running_in_test_harness && f.testsuite_delays) millisleep(millisec);
+ /******************************************************************************/
+ /* Taint-checked file opens */
+ static inline uschar *
+-is_tainted2(const void *p, int lflags, const uschar* fmt, ...)
++is_tainted2(const void *p, int lflags, const char* fmt, ...)
+ {
+ va_list ap;
+ uschar *msg;
+diff --git a/src/lookups/lf_sqlperform.c b/src/lookups/lf_sqlperform.c
+index eda3089e2..38b7c2ad3 100644
+--- a/src/lookups/lf_sqlperform.c
++++ b/src/lookups/lf_sqlperform.c
+@@ -103,7 +103,7 @@ if (Ustrncmp(query, "servers", 7) == 0)
+ }
+
+ { uschar *m;
+- if (m = is_tainted2(server, 0, "Tainted %s server '%s'", name, server))
++ if ((m = is_tainted2(server, 0, "Tainted %s server '%s'", name, server)))
+ {
+ *errmsg = m;
+ return DEFER;
+@@ -161,7 +161,7 @@ else
+ }
+
+ { uschar *m;
+- if (is_tainted2(server, 0, "Tainted %s server '%s'", name, server))
++ if ((m = is_tainted2(server, 0, "Tainted %s server '%s'", name, server)))
+ {
+ *errmsg = m;
+ return DEFER;
+diff --git a/src/parse.c b/src/parse.c
+index d1bc79039..0622b3127 100644
+--- a/src/parse.c
++++ b/src/parse.c
+@@ -1402,7 +1402,7 @@ for (;;)
+ return FF_ERROR;
+ }
+
+- if (*error = is_tainted2(filename, 0, "Tainted name '%s' for included file not permitted\n", filename))
++ if ((*error = is_tainted2(filename, 0, "Tainted name '%s' for included file not permitted\n", filename)))
+ return FF_ERROR;
+
+ /* Check file name if required */
+diff --git a/src/rda.c b/src/rda.c
+index 6ad7dd8bd..bba0b719b 100644
+--- a/src/rda.c
++++ b/src/rda.c
+@@ -179,7 +179,7 @@ struct stat statbuf;
+ /* Reading a file is a form of expansion; we wish to deny attackers the
+ capability to specify the file name. */
+
+-if (*error = is_tainted2(filename, 0, "Tainted name '%s' for file read not permitted\n", filename))
++if ((*error = is_tainted2(filename, 0, "Tainted name '%s' for file read not permitted\n", filename)))
+ {
+ *yield = FF_ERROR;
+ return NULL;
+diff --git a/src/transports/appendfile.c b/src/transports/appendfile.c
+index 7dbbaa2f9..6772b338b 100644
+--- a/src/transports/appendfile.c
++++ b/src/transports/appendfile.c
+@@ -1287,8 +1287,8 @@ if (!(path = expand_string(fdname)))
+ goto ret_panic;
+ }
+ { uschar *m;
+-if (m = is_tainted2(path, 0, "Tainted '%s' (file or directory "
+- "name for %s transport) not permitted", path, tblock->name))
++if ((m = is_tainted2(path, 0, "Tainted '%s' (file or directory "
++ "name for %s transport) not permitted", path, tblock->name)))
+ {
+ addr->message = m;
+ goto ret_panic;
+diff --git a/src/transports/autoreply.c b/src/transports/autoreply.c
+index ed99de4c6..80c7c0db0 100644
+--- a/src/transports/autoreply.c
++++ b/src/transports/autoreply.c
+@@ -407,8 +407,8 @@ if (oncelog && *oncelog && to)
+ uschar *m;
+ time_t then = 0;
+
+- if (m = is_tainted2(oncelog, 0, "Tainted '%s' (once file for %s transport)"
+- " not permitted", oncelog, tblock->name))
++ if ((m = is_tainted2(oncelog, 0, "Tainted '%s' (once file for %s transport)"
++ " not permitted", oncelog, tblock->name)))
+ {
+ addr->transport_return = DEFER;
+ addr->basic_errno = EACCES;
+@@ -518,8 +518,8 @@ if (oncelog && *oncelog && to)
+ {
+ uschar *m;
+ int log_fd;
+- if (m = is_tainted2(logfile, 0, "Tainted '%s' (logfile for %s transport)"
+- " not permitted", logfile, tblock->name))
++ if ((m = is_tainted2(logfile, 0, "Tainted '%s' (logfile for %s transport)"
++ " not permitted", logfile, tblock->name)))
+ {
+ addr->transport_return = DEFER;
+ addr->basic_errno = EACCES;
+@@ -551,8 +551,8 @@ if (oncelog && *oncelog && to)
+ if (file)
+ {
+ uschar *m;
+- if (m = is_tainted2(file, 0, "Tainted '%s' (file for %s transport)"
+- " not permitted", file, tblock->name))
++ if ((m = is_tainted2(file, 0, "Tainted '%s' (file for %s transport)"
++ " not permitted", file, tblock->name)))
+ {
+ addr->transport_return = DEFER;
+ addr->basic_errno = EACCES;
+diff --git a/src/transports/pipe.c b/src/transports/pipe.c
+index 4c9e68beb..fc44fa585 100644
+--- a/src/transports/pipe.c
++++ b/src/transports/pipe.c
+@@ -601,8 +601,8 @@ if (!cmd || !*cmd)
+ }
+
+ { uschar *m;
+-if (m = is_tainted2(cmd, 0, "Tainted '%s' (command "
+- "for %s transport) not permitted", cmd, tblock->name))
++if ((m = is_tainted2(cmd, 0, "Tainted '%s' (command "
++ "for %s transport) not permitted", cmd, tblock->name)))
+ {
+ addr->transport_return = PANIC;
+ addr->message = m;
+--
+2.30.2
+
diff --git a/debian/patches/75_23-Do-not-close-the-main-_log-if-we-do-not-see-a-chance.patch b/debian/patches/75_23-Do-not-close-the-main-_log-if-we-do-not-see-a-chance.patch
new file mode 100644
index 0000000..12d9163
--- /dev/null
+++ b/debian/patches/75_23-Do-not-close-the-main-_log-if-we-do-not-see-a-chance.patch
@@ -0,0 +1,166 @@
+From 235c7030ee9ee1c1aad507786506a470b580bfe2 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Fri, 23 Apr 2021 22:41:57 +0200
+Subject: [PATCH 23/23] Do not close the (main)_log, if we do not see a chance
+ to open it again.
+
+The process doing local deliveries runs as an unprivileged user. If this
+process needs to log failures or warnings (as caused by the
+is_tainting2() function), it can't re-open the main_log and just exits.
+---
+ src/log.c | 84 ++++++++++++++++-----------------
+ src/transports/appendfile.c | 6 +++
+ 2 files changed, 47 insertions(+), 43 deletions(-)
+
+diff --git a/src/log.c b/src/log.c
+index 7ef7074ec..c2ef698e7 100644
+--- a/src/log.c
++++ b/src/log.c
+@@ -646,18 +646,36 @@ return total_written;
+ }
+
+
+-
+-static void
+-set_file_path(void)
++void
++set_file_path(BOOL *multiple)
+ {
++uschar *s;
+ int sep = ':'; /* Fixed separator - outside use */
+-uschar *t;
+-const uschar *tt = US LOG_FILE_PATH;
+-while ((t = string_nextinlist(&tt, &sep, log_buffer, LOG_BUFFER_SIZE)))
++uschar *ss = *log_file_path ? log_file_path : LOG_FILE_PATH;
++
++logging_mode = 0;
++while ((s = string_nextinlist(&ss, &sep, log_buffer, LOG_BUFFER_SIZE)))
+ {
+- if (Ustrcmp(t, "syslog") == 0 || t[0] == 0) continue;
+- file_path = string_copy(t);
+- break;
++ if (Ustrcmp(s, "syslog") == 0)
++ logging_mode |= LOG_MODE_SYSLOG;
++ else if (logging_mode & LOG_MODE_FILE) /* we know a file already */
++ {
++ if (multiple) *multiple = TRUE;
++ }
++ else
++ {
++ logging_mode |= LOG_MODE_FILE;
++
++ /* If a non-empty path is given, use it */
++
++ if (*s)
++ file_path = string_copy(s);
++
++ /* If the path is empty, we want to use the first non-empty, non-
++ syslog item in LOG_FILE_PATH, if there is one, since the value of
++ log_file_path may have been set at runtime. If there is no such item,
++ use the ultimate default in the spool directory. */
++ }
+ }
+ }
+
+@@ -665,7 +683,11 @@ while ((t = string_nextinlist(&tt, &sep, log_buffer, LOG_BUFFER_SIZE)))
+ void
+ mainlog_close(void)
+ {
+-if (mainlogfd < 0) return;
++/* avoid closing it if it is closed already or if we do not see a chance
++to open the file mainlog later again */
++if (mainlogfd < 0 /* already closed */
++ || !(geteuid() == 0 || geteuid() == exim_uid))
++ return;
+ (void)close(mainlogfd);
+ mainlogfd = -1;
+ mainlog_inode = 0;
+@@ -780,38 +802,7 @@ if (!path_inspected)
+ /* If nothing has been set, don't waste effort... the default values for the
+ statics are file_path="" and logging_mode = LOG_MODE_FILE. */
+
+- if (*log_file_path)
+- {
+- int sep = ':'; /* Fixed separator - outside use */
+- uschar *s;
+- const uschar *ss = log_file_path;
+-
+- logging_mode = 0;
+- while ((s = string_nextinlist(&ss, &sep, log_buffer, LOG_BUFFER_SIZE)))
+- {
+- if (Ustrcmp(s, "syslog") == 0)
+- logging_mode |= LOG_MODE_SYSLOG;
+- else if (logging_mode & LOG_MODE_FILE)
+- multiple = TRUE;
+- else
+- {
+- logging_mode |= LOG_MODE_FILE;
+-
+- /* If a non-empty path is given, use it */
+-
+- if (*s)
+- file_path = string_copy(s);
+-
+- /* If the path is empty, we want to use the first non-empty, non-
+- syslog item in LOG_FILE_PATH, if there is one, since the value of
+- log_file_path may have been set at runtime. If there is no such item,
+- use the ultimate default in the spool directory. */
+-
+- else
+- set_file_path(); /* Empty item in log_file_path */
+- } /* First non-syslog item in log_file_path */
+- } /* Scan of log_file_path */
+- }
++ if (*log_file_path) set_file_path(&multiple);
+
+ /* If no modes have been selected, it is a major disaster */
+
+@@ -1431,7 +1422,7 @@ if (opts)
+ resulting in certain setup not having been done. Hack this for now so we
+ do not segfault; note that nondefault log locations will not work */
+
+-if (!*file_path) set_file_path();
++if (!*file_path) set_file_path(NULL);
+
+ open_log(&fd, lt_debug, tag_name);
+
+@@ -1453,5 +1444,12 @@ debug_file = NULL;
+ unlink_log(lt_debug);
+ }
+
++void
++open_logs(const char *m)
++{
++set_file_path(NULL);
++open_log(&mainlogfd, lt_main, 0);
++open_log(&rejectlogfd, lt_reject, 0);
++}
+
+ /* End of log.c */
+diff --git a/src/transports/appendfile.c b/src/transports/appendfile.c
+index 6772b338b..706af6dde 100644
+--- a/src/transports/appendfile.c
++++ b/src/transports/appendfile.c
+@@ -217,6 +217,9 @@ Arguments:
+ Returns: OK, FAIL, or DEFER
+ */
+
++void
++openlogs();
++
+ static int
+ appendfile_transport_setup(transport_instance *tblock, address_item *addrlist,
+ transport_feedback *dummy, uid_t uid, gid_t gid, uschar **errmsg)
+@@ -231,6 +234,9 @@ dummy = dummy;
+ uid = uid;
+ gid = gid;
+
++/* we can't wait until we're not privileged anymore */
++open_logs("appendfile");
++
+ if (ob->expand_maildir_use_size_file)
+ ob->maildir_use_size_file = expand_check_condition(ob->expand_maildir_use_size_file,
+ US"`maildir_use_size_file` in transport", tblock->name);
+--
+2.30.2
+
diff --git a/debian/patches/75_24-Silence-the-compiler.patch b/debian/patches/75_24-Silence-the-compiler.patch
new file mode 100644
index 0000000..14c3d5b
--- /dev/null
+++ b/debian/patches/75_24-Silence-the-compiler.patch
@@ -0,0 +1,57 @@
+From 33d5b8e8e4c2f23b4e834e3a095e3c9dd9f0686b Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Sun, 25 Apr 2021 18:58:35 +0200
+Subject: [PATCH 1/4] Silence the compiler
+
+---
+ src/log.c | 4 ++--
+ src/transports/appendfile.c | 4 ++--
+ 2 files changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/src/log.c b/src/log.c
+index c2ef698e7..11d259197 100644
+--- a/src/log.c
++++ b/src/log.c
+@@ -651,7 +651,7 @@ set_file_path(BOOL *multiple)
+ {
+ uschar *s;
+ int sep = ':'; /* Fixed separator - outside use */
+-uschar *ss = *log_file_path ? log_file_path : LOG_FILE_PATH;
++const uschar *ss = *log_file_path ? log_file_path : US LOG_FILE_PATH;
+
+ logging_mode = 0;
+ while ((s = string_nextinlist(&ss, &sep, log_buffer, LOG_BUFFER_SIZE)))
+@@ -1445,7 +1445,7 @@ unlink_log(lt_debug);
+ }
+
+ void
+-open_logs(const char *m)
++open_logs(void)
+ {
+ set_file_path(NULL);
+ open_log(&mainlogfd, lt_main, 0);
+diff --git a/src/transports/appendfile.c b/src/transports/appendfile.c
+index 706af6dde..c0f4de4c8 100644
+--- a/src/transports/appendfile.c
++++ b/src/transports/appendfile.c
+@@ -218,7 +218,7 @@ Returns: OK, FAIL, or DEFER
+ */
+
+ void
+-openlogs();
++open_logs(void);
+
+ static int
+ appendfile_transport_setup(transport_instance *tblock, address_item *addrlist,
+@@ -235,7 +235,7 @@ uid = uid;
+ gid = gid;
+
+ /* we can't wait until we're not privileged anymore */
+-open_logs("appendfile");
++open_logs();
+
+ if (ob->expand_maildir_use_size_file)
+ ob->maildir_use_size_file = expand_check_condition(ob->expand_maildir_use_size_file,
+--
+2.30.2
+
diff --git a/debian/patches/75_26-Disable-taintchecks-for-mkdir-this-isn-t-part-of-4.9.patch b/debian/patches/75_26-Disable-taintchecks-for-mkdir-this-isn-t-part-of-4.9.patch
new file mode 100644
index 0000000..cee6066
--- /dev/null
+++ b/debian/patches/75_26-Disable-taintchecks-for-mkdir-this-isn-t-part-of-4.9.patch
@@ -0,0 +1,27 @@
+From 1416743e923cacf42955392e92995f5fe7e1c680 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Sun, 25 Apr 2021 10:19:32 +0200
+Subject: [PATCH 3/4] Disable taintchecks for mkdir, this isn't part of 4.94
+
+---
+ src/directory.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/src/directory.c b/src/directory.c
+index 9f88f4141..ece1ee8f3 100644
+--- a/src/directory.c
++++ b/src/directory.c
+@@ -44,8 +44,10 @@ uschar c = 1;
+ struct stat statbuf;
+ uschar * path;
+
++/* does not work with 4.94
+ if (is_tainted2(name, LOG_MAIN|LOG_PANIC, "Tainted path '%s' for new directory", name))
+ { p = US"create"; path = US name; errno = EACCES; goto bad; }
++*/
+
+ if (parent)
+ {
+--
+2.30.2
+
diff --git a/debian/patches/75_27_Fix-logging-with-empty-element-in-log_file_path-Bug-.patch b/debian/patches/75_27_Fix-logging-with-empty-element-in-log_file_path-Bug-.patch
new file mode 100644
index 0000000..58da37c
--- /dev/null
+++ b/debian/patches/75_27_Fix-logging-with-empty-element-in-log_file_path-Bug-.patch
@@ -0,0 +1,275 @@
+From e19790f7707cc901435849e78d20f249056c16b5 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Sat, 15 May 2021 13:37:04 +0200
+Subject: [PATCH 3/4] Fix logging with empty element in log_file_path (Bug
+ 2733)
+
+---
+ src/log.c | 84 +++++++++++++++++++++++++++++++++------------------
+ 1 file changed, 55 insertions(+), 29 deletions(-)
+
+diff --git a/src/log.c b/src/log.c
+index 2108ed46f..716bec553 100644
+--- a/src/log.c
++++ b/src/log.c
+@@ -283,16 +283,19 @@ problem. */
+
+ if (fd < 0 && errno == ENOENT)
+ {
+ BOOL created;
+ uschar *lastslash = Ustrrchr(name, '/');
+ *lastslash = 0;
+ created = directory_make(NULL, name, LOG_DIRECTORY_MODE, FALSE);
+- DEBUG(D_any) debug_printf("%s log directory %s\n",
+- created ? "created" : "failed to create", name);
++ DEBUG(D_any)
++ if (created)
++ debug_printf("created log directory %s\n", name);
++ else
++ debug_printf("failed to create log directory %s: %s\n", name, strerror(errno));
+ *lastslash = '/';
+ if (created) fd = Uopen(name, flags, LOG_MODE);
+ }
+
+ return fd;
+ }
+
+@@ -390,17 +393,15 @@ Returns: a file descriptor, or < 0 on failure (errno set)
+ int
+ log_open_as_exim(uschar * const name)
+ {
+ int fd = -1;
+ const uid_t euid = geteuid();
+
+ if (euid == exim_uid)
+- {
+ 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 = exim_fork(US"logfile-open");
+ if (pid == 0)
+@@ -495,38 +496,48 @@ ok = string_format(buffer, sizeof(buffer), CS file_path, log_names[type]);
+ switch (type)
+ {
+ case lt_main:
+ /* Save the name of the mainlog for rollover processing. Without a datestamp,
+ it gets statted to see if it has been cycled. With a datestamp, the datestamp
+ will be compared. The static slot for saving it is the same size as buffer,
+ and the text has been checked above to fit, so this use of strcpy() is OK. */
++
+ Ustrcpy(mainlog_name, buffer);
+ if (string_datestamp_offset > 0)
+ mainlog_datestamp = mainlog_name + string_datestamp_offset;
++ break;
++
+ case lt_reject:
+ /* Ditto for the reject log */
++
+ Ustrcpy(rejectlog_name, buffer);
+ if (string_datestamp_offset > 0)
+ rejectlog_datestamp = rejectlog_name + string_datestamp_offset;
++ break;
++
+ case lt_debug:
+ /* and deal with the debug log (which keeps the datestamp, but does not
+ update it) */
++
+ Ustrcpy(debuglog_name, buffer);
+ if (tag)
+ {
+ /* this won't change the offset of the datestamp */
+ ok2 = string_format(buffer, sizeof(buffer), "%s%s",
+ debuglog_name, tag);
+ if (ok2)
+ Ustrcpy(debuglog_name, buffer);
+ }
++ break;
++
+ default:
+ /* Remove any datestamp if this is the panic log. This is rare, so there's no
+ need to optimize getting the datestamp length. We remove one non-alphanumeric
+ char afterwards if at the start, otherwise one before. */
++
+ if (string_datestamp_offset >= 0)
+ {
+ uschar * from = buffer + string_datestamp_offset;
+ uschar * to = from + string_datestamp_length;
+
+ if (from == buffer || from[-1] == '/')
+ {
+@@ -535,30 +546,29 @@ switch (type)
+ else
+ if (!isalnum(from[-1])) from--;
+
+ /* This copy is ok, because we know that to is a substring of from. But
+ due to overlap we must use memmove() not Ustrcpy(). */
+ memmove(from, to, Ustrlen(to)+1);
+ }
++ break;
+ }
+
+ /* If the file name is too long, it is an unrecoverable disaster */
+
+ if (!ok)
+ die(US"exim: log file path too long: aborting",
+ US"Logging failure; please try later");
+
+ /* We now have the file name. After a successful open, return. */
+
+ *fd = log_open_as_exim(buffer);
+
+ if (*fd >= 0)
+- {
+ return;
+- }
+
+ euid = geteuid();
+
+ /* 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
+ just bombing out, force the log to stderr and carry on if stderr is available.
+@@ -702,45 +712,61 @@ while (1)
+ left -= wrote;
+ }
+ }
+ return total_written;
+ }
+
+
++/* Pull the file out of the configured or the compiled-in list.
++Called for an empty log_file_path element, for debug logging activation
++when file_path has not previously been set, and from the appenfile transport setup. */
++
+ void
+ set_file_path(BOOL *multiple)
+ {
+ uschar *s;
+ int sep = ':'; /* Fixed separator - outside use */
+ const uschar *ss = *log_file_path ? log_file_path : US LOG_FILE_PATH;
+
+-logging_mode = 0;
+-while ((s = string_nextinlist(&ss, &sep, log_buffer, LOG_BUFFER_SIZE)))
+- {
+- if (Ustrcmp(s, "syslog") == 0)
+- logging_mode |= LOG_MODE_SYSLOG;
+- else if (logging_mode & LOG_MODE_FILE) /* we know a file already */
++if (*ss)
++ for (logging_mode = 0;
++ s = string_nextinlist(&ss, &sep, log_buffer, LOG_BUFFER_SIZE); )
+ {
+- if (multiple) *multiple = TRUE;
+- }
+- else
+- {
+- logging_mode |= LOG_MODE_FILE;
++ if (Ustrcmp(s, "syslog") == 0)
++ logging_mode |= LOG_MODE_SYSLOG;
++ else if (!(logging_mode & LOG_MODE_FILE)) /* no file yet */
++ {
++ /* If a non-empty path is given, use it */
+
+- /* If a non-empty path is given, use it */
++ if (*s)
++ file_path = string_copy(s);
+
+- if (*s)
+- file_path = string_copy(s);
++ /* If handling the config option, and the element is empty, we want to use
++ the first non-empty, non-syslog item in LOG_FILE_PATH, if there is one,
++ since the value of log_file_path may have been set at runtime. If there is
++ no such item, use the ultimate default in the spool directory. */
+
+- /* If the path is empty, we want to use the first non-empty, non-
+- syslog item in LOG_FILE_PATH, if there is one, since the value of
+- log_file_path may have been set at runtime. If there is no such item,
+- use the ultimate default in the spool directory. */
++ else if (*log_file_path && LOG_FILE_PATH[0])
++ {
++ ss = US LOG_FILE_PATH;
++ continue;
++ }
++
++ logging_mode |= LOG_MODE_FILE;
++ }
++ else
++ if (multiple) *multiple = TRUE;
+ }
+- }
++ else
++ logging_mode = LOG_MODE_FILE;
++
++/* Set up the ultimate default if necessary. */
++
++if (logging_mode & LOG_MODE_FILE && !*file_path)
++ file_path = string_sprintf("%s/log/%%slog", spool_directory);
+ }
+
+
+ void
+ mainlog_close(void)
+ {
+ /* avoid closing it if it is closed already or if we do not see a chance
+@@ -866,19 +892,16 @@ if (!path_inspected)
+
+ /* If no modes have been selected, it is a major disaster */
+
+ if (logging_mode == 0)
+ die(US"Neither syslog nor file logging set in log_file_path",
+ US"Unexpected logging failure");
+
+- /* Set up the ultimate default if necessary. Then revert to the old store
+- pool, and record that we've sorted out the path. */
++ /* Revert to the old store pool, and record that we've sorted out the path. */
+
+- if (logging_mode & LOG_MODE_FILE && !file_path[0])
+- file_path = string_sprintf("%s/log/%%slog", spool_directory);
+ store_pool = old_pool;
+ path_inspected = TRUE;
+
+ /* If more than one file path was given, log a complaint. This recursive call
+ should work since we have now set up the routing. */
+
+ if (multiple)
+@@ -1224,14 +1247,15 @@ if (flags & LOG_PANIC)
+ write_syslog(LOG_ALERT, log_buffer);
+
+ /* If this panic logging was caused by a failure to open the main log,
+ the original log line is in panic_save_buffer. Make an attempt to write it. */
+
+ if (logging_mode & LOG_MODE_FILE)
+ {
++ if (!*file_path) set_file_path(NULL);
+ panic_recurseflag = TRUE;
+ open_log(&paniclogfd, lt_panic, NULL); /* Won't return on failure */
+ panic_recurseflag = FALSE;
+
+ if (panic_save_buffer)
+ {
+ int i = write(paniclogfd, panic_save_buffer, Ustrlen(panic_save_buffer));
+@@ -1501,16 +1525,18 @@ if (!debug_file || !debuglog_name[0]) return;
+
+ debug_selector = 0;
+ fclose(debug_file);
+ debug_file = NULL;
+ unlink_log(lt_debug);
+ }
+
++/* Called from the appendfile transport setup. */
+ void
+ open_logs(void)
+ {
+ set_file_path(NULL);
++if (!(logging_mode & LOG_MODE_FILE)) return;
+ open_log(&mainlogfd, lt_main, 0);
+ open_log(&rejectlogfd, lt_reject, 0);
+ }
+
+ /* End of log.c */
+--
+2.30.2
+
diff --git a/debian/patches/75_28_Fix-logging-with-build-time-config-and-empty-element.patch b/debian/patches/75_28_Fix-logging-with-build-time-config-and-empty-element.patch
new file mode 100644
index 0000000..b6d092f
--- /dev/null
+++ b/debian/patches/75_28_Fix-logging-with-build-time-config-and-empty-element.patch
@@ -0,0 +1,118 @@
+From 66392b270e3a6c8202e4626d43bbc9b77545ae23 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Sat, 15 May 2021 13:40:46 +0200
+Subject: [PATCH 4/4] Fix logging with build-time config and empty elements
+ (Closes 2733)
+
+---
+ src/log.c | 49 +++++++++++++++++++++++--------------------------
+ 1 file changed, 23 insertions(+), 26 deletions(-)
+
+diff --git a/src/log.c b/src/log.c
+index 716bec553..1d308d008 100644
+--- a/src/log.c
++++ b/src/log.c
+@@ -454,15 +454,15 @@ return fd;
+ * Open a log file *
+ *************************************************/
+
+ /* This function opens one of a number of logs, creating the log directory if
+ it does not exist. This may be called recursively on failure, in order to open
+ the panic log.
+
+-The directory is in the static variable file_path. This is static so that it
++The directory is in the static variable file_path. This is static so that
+ the work of sorting out the path is done just once per Exim process.
+
+ Exim is normally configured to avoid running as root wherever possible, the log
+ files must be owned by the non-privileged exim user. To ensure this, first try
+ an open without O_CREAT - most of the time this will succeed. If it fails, try
+ to create the file; if running as root, this must be done in a subprocess to
+ avoid races.
+@@ -731,42 +731,40 @@ if (*ss)
+ for (logging_mode = 0;
+ s = string_nextinlist(&ss, &sep, log_buffer, LOG_BUFFER_SIZE); )
+ {
+ if (Ustrcmp(s, "syslog") == 0)
+ logging_mode |= LOG_MODE_SYSLOG;
+ else if (!(logging_mode & LOG_MODE_FILE)) /* no file yet */
+ {
+- /* If a non-empty path is given, use it */
+-
+- if (*s)
+- file_path = string_copy(s);
+-
+- /* If handling the config option, and the element is empty, we want to use
+- the first non-empty, non-syslog item in LOG_FILE_PATH, if there is one,
+- since the value of log_file_path may have been set at runtime. If there is
+- no such item, use the ultimate default in the spool directory. */
+-
+- else if (*log_file_path && LOG_FILE_PATH[0])
+- {
+- ss = US LOG_FILE_PATH;
+- continue;
+- }
+-
+ logging_mode |= LOG_MODE_FILE;
++ if (*s) file_path = string_copy(s); /* If a non-empty path is given, use it */
+ }
+- else
+- if (multiple) *multiple = TRUE;
++ else if (multiple) *multiple = TRUE;
+ }
+- else
+- logging_mode = LOG_MODE_FILE;
++else
++ logging_mode = LOG_MODE_FILE;
+
+ /* Set up the ultimate default if necessary. */
+
+ if (logging_mode & LOG_MODE_FILE && !*file_path)
+- file_path = string_sprintf("%s/log/%%slog", spool_directory);
++ if (LOG_FILE_PATH[0])
++ {
++ /* If we still do not have a file_path, we take
++ the first non-empty, non-syslog item in LOG_FILE_PATH, if there is
++ one. If there is no such item, use the ultimate default in the
++ spool directory. */
++
++ for (ss = US LOG_FILE_PATH;
++ s = string_nextinlist(&ss, &sep, log_buffer, LOG_BUFFER_SIZE);)
++ {
++ if (*s != '/') continue;
++ file_path = string_copy(s);
++ }
++ }
++ else file_path = string_sprintf("%s/log/%%slog", spool_directory);
+ }
+
+
+ void
+ mainlog_close(void)
+ {
+ /* avoid closing it if it is closed already or if we do not see a chance
+@@ -881,18 +879,17 @@ the process. */
+ if (!path_inspected)
+ {
+ BOOL multiple = FALSE;
+ int old_pool = store_pool;
+
+ store_pool = POOL_PERM;
+
+- /* If nothing has been set, don't waste effort... the default values for the
+- statics are file_path="" and logging_mode = LOG_MODE_FILE. */
+-
+- if (*log_file_path) set_file_path(&multiple);
++ /* make sure that we have a valid log file path in "file_path",
++ the open_log() later relies on it */
++ set_file_path(&multiple);
+
+ /* If no modes have been selected, it is a major disaster */
+
+ if (logging_mode == 0)
+ die(US"Neither syslog nor file logging set in log_file_path",
+ US"Unexpected logging failure");
+
+--
+2.30.2
+
diff --git a/debian/patches/75_29-Auths-fix-possible-OOB-write-in-external-authenticat.patch b/debian/patches/75_29-Auths-fix-possible-OOB-write-in-external-authenticat.patch
new file mode 100644
index 0000000..f6936a9
--- /dev/null
+++ b/debian/patches/75_29-Auths-fix-possible-OOB-write-in-external-authenticat.patch
@@ -0,0 +1,22 @@
+From 7bb5bc2c6592e062bf0b514cc71afd2d93e2e0dd Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Thu, 11 May 2023 18:02:43 +0100
+Subject: [PATCH 1/4] Auths: fix possible OOB write in external authenticator.
+ Bug 2999
+
+---
+ doc/doc-txt/ChangeLog | 3 +++
+ src/src/auths/external.c | 2 +-
+ 2 files changed, 4 insertions(+), 1 deletion(-)
+
+--- a/src/auths/external.c
++++ b/src/auths/external.c
+@@ -103,7 +103,7 @@ if (expand_nmax == 0) /* skip if rxd da
+ if (ob->server_param2)
+ {
+ uschar * s = expand_string(ob->server_param2);
+- auth_vars[expand_nmax] = s;
++ auth_vars[expand_nmax = 1] = s;
+ expand_nstring[++expand_nmax] = s;
+ expand_nlength[expand_nmax] = Ustrlen(s);
+ if (ob->server_param3)
diff --git a/debian/patches/75_30-Auths-use-uschar-more-in-spa-authenticator.patch b/debian/patches/75_30-Auths-use-uschar-more-in-spa-authenticator.patch
new file mode 100644
index 0000000..03958fc
--- /dev/null
+++ b/debian/patches/75_30-Auths-use-uschar-more-in-spa-authenticator.patch
@@ -0,0 +1,226 @@
+From 0519dcfb5f149154a416b54865fd8026abb57791 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Thu, 11 May 2023 18:53:25 +0100
+Subject: [PATCH 2/4] Auths: use uschar more in spa authenticator
+
+---
+ src/src/auths/auth-spa.c | 72 +++++++++++++++++++++-------------------
+ src/src/auths/auth-spa.h | 8 ++---
+ src/src/auths/spa.c | 13 ++++----
+ 3 files changed, 47 insertions(+), 46 deletions(-)
+
+--- a/src/auths/auth-spa.c
++++ b/src/auths/auth-spa.c
+@@ -155,6 +155,9 @@ int main (int argc, char ** argv)
+ up with a different answer to the one above)
+ */
+
++#ifndef MACRO_PREDEF
++
++
+ #define DEBUG_X(a,b) ;
+
+ extern int DEBUGLEVEL;
+@@ -1229,21 +1232,21 @@ else \
+
+ #define spa_string_add(ptr, header, string) \
+ { \
+-char *p = string; \
++uschar * p = string; \
+ int len = 0; \
+-if (p) len = strlen(p); \
+-spa_bytes_add(ptr, header, (US p), len); \
++if (p) len = Ustrlen(p); \
++spa_bytes_add(ptr, header, p, len); \
+ }
+
+ #define spa_unicode_add_string(ptr, header, string) \
+ { \
+-char *p = string; \
+-uschar *b = NULL; \
++uschar * p = string; \
++uschar * b = NULL; \
+ int len = 0; \
+ if (p) \
+ { \
+- len = strlen(p); \
+- b = strToUnicode(p); \
++ len = Ustrlen(p); \
++ b = US strToUnicode(CS p); \
+ } \
+ spa_bytes_add(ptr, header, b, len*2); \
+ }
+@@ -1366,15 +1369,15 @@ fprintf (fp, " Flags = %08x\n", IVA
+ #endif
+
+ void
+-spa_build_auth_request (SPAAuthRequest * request, char *user, char *domain)
++spa_build_auth_request (SPAAuthRequest * request, uschar * user, uschar * domain)
+ {
+-char *u = strdup (user);
+-char *p = strchr (u, '@');
++uschar * u = string_copy(user);
++uschar * p = Ustrchr(u, '@');
+
+ if (p)
+ {
+ if (!domain)
+- domain = p + 1;
++ domain = p + 1;
+ *p = '\0';
+ }
+
+@@ -1384,7 +1387,6 @@ SIVAL (&request->msgType, 0, 1);
+ SIVAL (&request->flags, 0, 0x0000b207); /* have to figure out what these mean */
+ spa_string_add (request, user, u);
+ spa_string_add (request, domain, domain);
+-free (u);
+ }
+
+
+@@ -1475,16 +1477,16 @@ free (u);
+
+ void
+ spa_build_auth_response (SPAAuthChallenge * challenge,
+- SPAAuthResponse * response, char *user,
+- char *password)
++ SPAAuthResponse * response, uschar * user,
++ uschar * password)
+ {
+ uint8x lmRespData[24];
+ uint8x ntRespData[24];
+ uint32x cf = IVAL(&challenge->flags, 0);
+-char *u = strdup (user);
+-char *p = strchr (u, '@');
+-char *d = NULL;
+-char *domain;
++uschar * u = string_copy(user);
++uschar * p = Ustrchr(u, '@');
++uschar * d = NULL;
++uschar * domain;
+
+ if (p)
+ {
+@@ -1492,33 +1494,33 @@ if (p)
+ *p = '\0';
+ }
+
+-else domain = d = strdup((cf & 0x1)?
+- CCS GetUnicodeString(challenge, uDomain) :
+- CCS GetString(challenge, uDomain));
++else domain = d = string_copy(cf & 0x1
++ ? CUS GetUnicodeString(challenge, uDomain)
++ : CUS GetString(challenge, uDomain));
+
+-spa_smb_encrypt (US password, challenge->challengeData, lmRespData);
+-spa_smb_nt_encrypt (US password, challenge->challengeData, ntRespData);
++spa_smb_encrypt(password, challenge->challengeData, lmRespData);
++spa_smb_nt_encrypt(password, challenge->challengeData, ntRespData);
+
+ response->bufIndex = 0;
+ memcpy (response->ident, "NTLMSSP\0\0\0", 8);
+ SIVAL (&response->msgType, 0, 3);
+
+-spa_bytes_add (response, lmResponse, lmRespData, (cf & 0x200) ? 24 : 0);
+-spa_bytes_add (response, ntResponse, ntRespData, (cf & 0x8000) ? 24 : 0);
++spa_bytes_add(response, lmResponse, lmRespData, cf & 0x200 ? 24 : 0);
++spa_bytes_add(response, ntResponse, ntRespData, cf & 0x8000 ? 24 : 0);
+
+ if (cf & 0x1) { /* Unicode Text */
+- spa_unicode_add_string (response, uDomain, domain);
+- spa_unicode_add_string (response, uUser, u);
+- spa_unicode_add_string (response, uWks, u);
++ spa_unicode_add_string(response, uDomain, domain);
++ spa_unicode_add_string(response, uUser, u);
++ spa_unicode_add_string(response, uWks, u);
+ } else { /* OEM Text */
+- spa_string_add (response, uDomain, domain);
+- spa_string_add (response, uUser, u);
+- spa_string_add (response, uWks, u);
++ spa_string_add(response, uDomain, domain);
++ spa_string_add(response, uUser, u);
++ spa_string_add(response, uWks, u);
+ }
+
+-spa_string_add (response, sessionKey, NULL);
++spa_string_add(response, sessionKey, NULL);
+ response->flags = challenge->flags;
+-
+-if (d != NULL) free (d);
+-free (u);
+ }
++
++
++#endif /*!MACRO_PREDEF*/
+--- a/src/auths/auth-spa.h
++++ b/src/auths/auth-spa.h
+@@ -79,10 +79,10 @@ typedef struct
+
+ void spa_bits_to_base64 (unsigned char *, const unsigned char *, int);
+ int spa_base64_to_bits(char *, int, const char *);
+-void spa_build_auth_response (SPAAuthChallenge *challenge,
+- SPAAuthResponse *response, char *user, char *password);
+-void spa_build_auth_request (SPAAuthRequest *request, char *user,
+- char *domain);
++void spa_build_auth_response (SPAAuthChallenge * challenge,
++ SPAAuthResponse * response, uschar * user, uschar * password);
++void spa_build_auth_request (SPAAuthRequest * request, uschar * user,
++ uschar * domain);
+ extern void spa_smb_encrypt (unsigned char * passwd, unsigned char * c8,
+ unsigned char * p24);
+ extern void spa_smb_nt_encrypt (unsigned char * passwd, unsigned char * c8,
+--- a/src/auths/spa.c
++++ b/src/auths/spa.c
+@@ -284,14 +284,13 @@ SPAAuthRequest request;
+ SPAAuthChallenge challenge;
+ SPAAuthResponse response;
+ char msgbuf[2048];
+-char *domain = NULL;
+-char *username, *password;
++uschar * domain = NULL, * username, * password;
+
+ /* Code added by PH to expand the options */
+
+ *buffer = 0; /* Default no message when cancelled */
+
+-if (!(username = CS expand_string(ob->spa_username)))
++if (!(username = expand_string(ob->spa_username)))
+ {
+ if (f.expand_string_forcedfail) return CANCELLED;
+ string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
+@@ -300,7 +299,7 @@ if (!(username = CS expand_string(ob->sp
+ return ERROR;
+ }
+
+-if (!(password = CS expand_string(ob->spa_password)))
++if (!(password = expand_string(ob->spa_password)))
+ {
+ if (f.expand_string_forcedfail) return CANCELLED;
+ string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
+@@ -310,7 +309,7 @@ if (!(password = CS expand_string(ob->sp
+ }
+
+ if (ob->spa_domain)
+- if (!(domain = CS expand_string(ob->spa_domain)))
++ if (!(domain = expand_string(ob->spa_domain)))
+ {
+ if (f.expand_string_forcedfail) return CANCELLED;
+ string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
+@@ -330,7 +329,7 @@ if (!smtp_read_response(sx, US buffer, b
+
+ DSPA("\n\n%s authenticator: using domain %s\n\n", ablock->name, domain);
+
+-spa_build_auth_request(&request, CS username, domain);
++spa_build_auth_request(&request, username, domain);
+ spa_bits_to_base64(US msgbuf, US &request, spa_request_length(&request));
+
+ DSPA("\n\n%s authenticator: sending request (%s)\n\n", ablock->name, msgbuf);
+@@ -347,7 +346,7 @@ if (!smtp_read_response(sx, US buffer, b
+ DSPA("\n\n%s authenticator: challenge (%s)\n\n", ablock->name, buffer + 4);
+ spa_base64_to_bits(CS (&challenge), sizeof(challenge), CCS (buffer + 4));
+
+-spa_build_auth_response(&challenge, &response, CS username, CS password);
++spa_build_auth_response(&challenge, &response, username, password);
+ spa_bits_to_base64(US msgbuf, US &response, spa_request_length(&response));
+ DSPA("\n\n%s authenticator: challenge response (%s)\n\n", ablock->name, msgbuf);
+
diff --git a/debian/patches/75_31-Auths-fix-possible-OOB-write-in-SPA-authenticator.-B.patch b/debian/patches/75_31-Auths-fix-possible-OOB-write-in-SPA-authenticator.-B.patch
new file mode 100644
index 0000000..8c763fa
--- /dev/null
+++ b/debian/patches/75_31-Auths-fix-possible-OOB-write-in-SPA-authenticator.-B.patch
@@ -0,0 +1,24 @@
+From e17b8b0f19b25a223b0cc41933b881c3a1073e61 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Thu, 11 May 2023 19:31:54 +0100
+Subject: [PATCH 3/4] Auths: fix possible OOB write in SPA authenticator. Bug
+ 3000
+
+---
+ doc/doc-txt/ChangeLog | 3 +++
+ src/src/auths/auth-spa.c | 4 +++-
+ 2 files changed, 6 insertions(+), 1 deletion(-)
+
+--- a/src/auths/auth-spa.c
++++ b/src/auths/auth-spa.c
+@@ -1214,7 +1214,9 @@ char versionString[] = "libntlm version
+
+ #define spa_bytes_add(ptr, header, buf, count) \
+ { \
+-if (buf && (count) != 0) /* we hate -Wint-in-bool-contex */ \
++if ( buf && (count) != 0 /* we hate -Wint-in-bool-contex */ \
++ && ptr->bufIndex + count < sizeof(ptr->buffer) \
++ ) \
+ { \
+ SSVAL(&ptr->header.len,0,count); \
+ SSVAL(&ptr->header.maxlen,0,count); \
diff --git a/debian/patches/75_32-Auths-fix-possible-OOB-read-in-SPA-authenticator.-Bu.patch b/debian/patches/75_32-Auths-fix-possible-OOB-read-in-SPA-authenticator.-Bu.patch
new file mode 100644
index 0000000..d4e0eb6
--- /dev/null
+++ b/debian/patches/75_32-Auths-fix-possible-OOB-read-in-SPA-authenticator.-Bu.patch
@@ -0,0 +1,75 @@
+From 04107e98d58efb69f7e2d7b81176e5374c7098a3 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Thu, 11 May 2023 21:08:08 +0100
+Subject: [PATCH 4/4] Auths: fix possible OOB read in SPA authenticator. Bug
+ 3001
+
+---
+ doc/doc-txt/ChangeLog | 3 +++
+ src/src/auths/auth-spa.c | 36 ++++++++++++++++++++++++++++--------
+ 2 files changed, 31 insertions(+), 8 deletions(-)
+
+--- a/src/auths/auth-spa.c
++++ b/src/auths/auth-spa.c
+@@ -1254,15 +1254,10 @@ spa_bytes_add(ptr, header, b, len*2); \
+ }
+
+
+-#define GetUnicodeString(structPtr, header) \
+-unicodeToString(((char*)structPtr) + IVAL(&structPtr->header.offset,0) , SVAL(&structPtr->header.len,0)/2)
+-#define GetString(structPtr, header) \
+-toString(((CS structPtr) + IVAL(&structPtr->header.offset,0)), SVAL(&structPtr->header.len,0))
+-
+ #ifdef notdef
+
+ #define DumpBuffer(fp, structPtr, header) \
+-dumpRaw(fp,(US structPtr)+IVAL(&structPtr->header.offset,0),SVAL(&structPtr->header.len,0))
++ dumpRaw(fp,(US structPtr)+IVAL(&structPtr->header.offset,0),SVAL(&structPtr->header.len,0))
+
+
+ static void
+@@ -1326,8 +1321,33 @@ buf[len] = 0;
+ return buf;
+ }
+
++static inline uschar *
++get_challenge_unistr(SPAAuthChallenge * challenge, SPAStrHeader * hdr)
++{
++int off = IVAL(&hdr->offset, 0);
++int len = SVAL(&hdr->len, 0);
++return off + len < sizeof(SPAAuthChallenge)
++ ? US unicodeToString(CS challenge + off, len/2) : US"";
++}
++
++static inline uschar *
++get_challenge_str(SPAAuthChallenge * challenge, SPAStrHeader * hdr)
++{
++int off = IVAL(&hdr->offset, 0);
++int len = SVAL(&hdr->len, 0);
++return off + len < sizeof(SPAAuthChallenge)
++ ? US toString(CS challenge + off, len) : US"";
++}
++
+ #ifdef notdef
+
++#define GetUnicodeString(structPtr, header) \
++ unicodeToString(((char*)structPtr) + IVAL(&structPtr->header.offset,0) , SVAL(&structPtr->header.len,0)/2)
++
++#define GetString(structPtr, header) \
++ toString(((CS structPtr) + IVAL(&structPtr->header.offset,0)), SVAL(&structPtr->header.len,0))
++
++
+ void
+ dumpSmbNtlmAuthRequest (FILE * fp, SPAAuthRequest * request)
+ {
+@@ -1497,8 +1517,8 @@ if (p)
+ }
+
+ else domain = d = string_copy(cf & 0x1
+- ? CUS GetUnicodeString(challenge, uDomain)
+- : CUS GetString(challenge, uDomain));
++ ? CUS get_challenge_unistr(challenge, &challenge->uDomain)
++ : CUS get_challenge_str(challenge, &challenge->uDomain));
+
+ spa_smb_encrypt(password, challenge->challengeData, lmRespData);
+ spa_smb_nt_encrypt(password, challenge->challengeData, ntRespData);
diff --git a/debian/patches/78_01-Command-line-option-for-no-notifier-socket.-Bug-2616.patch b/debian/patches/78_01-Command-line-option-for-no-notifier-socket.-Bug-2616.patch
new file mode 100644
index 0000000..9ab1d42
--- /dev/null
+++ b/debian/patches/78_01-Command-line-option-for-no-notifier-socket.-Bug-2616.patch
@@ -0,0 +1,198 @@
+From 99ea5f6faeaf714e34bbcd75fdc50cc94dc7a1c8 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Fri, 10 Jul 2020 13:55:25 +0100
+Subject: [PATCH] Command-line option for no notifier socket. Bug 2616
+
+---
+ doc/doc-docbook/spec.xfpt | 33 +++++++++++++++++++++++++--
+ doc/NewStuff | 2 ++
+ src/daemon.c | 5 ++++
+ src/exim.c | 9 +++++++-
+ test/scripts/0999-EXP-Queue-Ramp/0999 | 2 +-
+ 5 files changed, 47 insertions(+), 4 deletions(-)
+
+--- a/doc/NewStuff
++++ b/doc/NewStuff
+@@ -2,14 +2,20 @@ New Features in Exim
+ --------------------
+
+ This file contains descriptions of new features that have been added to Exim.
+ Before a formal release, there may be quite a lot of detail so that people can
+ test from the snapshots or the Git before the documentation is updated. Once
+ the documentation is updated, this file is reduced to a short list.
+
++Cherrypicked from GIT master:
++------------
++
++10. A command-line option to have a daemon not create a notifier socket.
++
++
+ Version 4.95
+ ------------
+
+ 1. The fast-ramp two phase queue run support, previously experimental, is
+ now supported by default.
+
+ 2. The native SRS support, previously experimental, is now supported. It is
+--- a/src/daemon.c
++++ b/src/daemon.c
+@@ -1140,14 +1140,19 @@ static void
+ daemon_notifier_socket(void)
+ {
+ int fd;
+ const uschar * where;
+ struct sockaddr_un sa_un = {.sun_family = AF_UNIX};
+ int len;
+
++if (!notifier_socket || !*notifier_socket)
++ {
++ DEBUG(D_any) debug_printf("-oY used so not creating notifier socket\n");
++ return;
++ }
+ if (override_local_interfaces && !override_pid_file_path)
+ {
+ DEBUG(D_any)
+ debug_printf("-oX used without -oP so not creating notifier socket\n");
+ return;
+ }
+
+--- a/src/exim.c
++++ b/src/exim.c
+@@ -3231,14 +3231,21 @@ on the second character (the one after '
+ /* Limits: Is there a real limit we want here? 1024 is very arbitrary. */
+
+ case 'X':
+ if (*argrest) badarg = TRUE;
+ else override_local_interfaces = string_copy_taint(exim_str_fail_toolong(argv[++i], 1024, "-oX"), TRUE);
+ break;
+
++ /* -oY: Override creation of daemon notifier socket */
++
++ case 'Y':
++ if (*argrest) badarg = TRUE;
++ else notifier_socket = NULL;
++ break;
++
+ /* Unknown -o argument */
+
+ default:
+ badarg = TRUE;
+ }
+ break;
+
+@@ -4818,15 +4825,15 @@ if (originator_login == NULL || f.runnin
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Failed to get user name for uid %d",
+ (int)real_uid);
+ }
+
+ /* Ensure that the user name is in a suitable form for use as a "phrase" in an
+ RFC822 address.*/
+
+-originator_name = parse_fix_phrase(originator_name, Ustrlen(originator_name));
++originator_name = US parse_fix_phrase(originator_name, Ustrlen(originator_name));
+
+ /* If a message is created by this call of Exim, the uid/gid of its originator
+ are those of the caller. These values are overridden if an existing message is
+ read in from the spool. */
+
+ originator_uid = real_uid;
+ originator_gid = real_gid;
+--- a/doc/spec.txt
++++ b/doc/spec.txt
+@@ -4189,14 +4189,27 @@ brief message about itself and exits.
+
+ This option is relevant only when the -bd (start listening daemon) option
+ is also given. It controls which ports and interfaces the daemon uses.
+ Details of the syntax, and how it interacts with configuration file
+ options, are given in chapter 13. When -oX is used to start a daemon, no
+ pid file is written unless -oP is also present to specify a pid filename.
+
++-oY
++
++ This option controls the creation of an inter-process communications
++ endpoint by the Exim daemon. It is only relevant when the -bd
++ (start listening daemon) option is also given. Normally the daemon
++ creates this socket, unless a oX and no -oP option is also
++ present. If this option is given then the socket will not be created.
++ This could be required if the system is running multiple daemons.
++
++ The socket is currently used for
++ * fast ramp-up of queue runner processes
++ * obtaining a current queue size
++
+ -pd
+
+ This option applies when an embedded Perl interpreter is linked with Exim
+ (see chapter 12). It overrides the setting of the perl_at_start option,
+ forcing the starting of the interpreter to be delayed until it is needed.
+
+ -ps
+@@ -11733,14 +11746,16 @@ $queue_name
+
+ The name of the spool queue in use; empty for the default queue.
+
+ $queue_size
+
+ This variable contains the number of messages queued. It is evaluated on
+ demand, but no more often than once every minute.
++ If there is no daemon notifier socket open, the value will be
++ an empty string.
+
+ $r_...
+
+ Values can be placed in these variables by the set option of a router. They
+ can be given any name that starts with $r_. The values persist for the
+ address being handled through subsequent routers and the eventual
+ transport.
+@@ -15227,18 +15242,20 @@ driver.
+ +-----------------------------------------------------------------------------+
+
+ This option gives the name for a unix-domain socket on which the daemon listens
+ for work and information-requests. Only installations running multiple daemons
+ sharing a spool directory should need to modify the default.
+
+ The option is expanded before use. If the platform supports Linux-style
+-abstract socket names, the result is used with a nul byte prefixed. Otherwise,
+-it should be a full path name and use a directory accessible to Exim.
++abstract socket names, the result is used with a nul byte prefixed.
++Otherwise, if nonempty, it should be a full path name and use a directory
++accessible to Exim.
+
+-If the Exim command line uses a -oX option and does not use -oP then a notifier
++If this option is set as empty, or the command line -oY option is used, or
++the command line uses a -oX option and does not use -oP, then a notifier
+ socket is not created.
+
+ +-----------------------------------------------------------------------------+
+ | |Use: | Type: |Default: +no_sslv2 +no_sslv3 +single_dh_use|
+ |openssl_options| main | string | +no_ticket +no_renegotiation|
+ | | | list | |
+ +-----------------------------------------------------------------------------+
+--- a/doc/exim.8
++++ b/doc/exim.8
+@@ -1445,14 +1445,25 @@ the \fBsmtp_receive_timeout\fP option; i
+ This option has exactly the same effect as \fB\-v\fP.
+ .TP 10
+ \fB\-oX\fP <\fInumber or string\fP>
+ This option is relevant only when the \fB\-bd\fP (start listening daemon) option
+ is also given. It controls which ports and interfaces the daemon uses. When \fB\-oX\fP is used to start a daemon, no pid
+ file is written unless \fB\-oP\fP is also present to specify a pid filename.
+ .TP 10
++\fB\-oY\fP
++This option controls the creation of an inter-process communications endpoint
++by the Exim daemon. It is only relevant when the \fB\-bd\fP (start listening
++daemon) option is also given.
++Normally the daemon creates this socket, unless a \fB\-oX\fP and no \fB\-oP\fP
++option is also present.
++If this option is given then the socket will not be created. This could be
++required if the system is running multiple daemons.
++The socket is currently used for fast ramp-up of queue runner processes and
++obtaining a current queue size.
++.TP 10
+ \fB\-pd\fP
+ This option applies when an embedded Perl interpreter is linked with Exim. It overrides the setting of the \fBperl_at_start\fP
+ option, forcing the starting of the interpreter to be delayed until it is
+ needed.
+ .TP 10
+ \fB\-ps\fP
+ This option applies when an embedded Perl interpreter is linked with Exim. It overrides the setting of the \fBperl_at_start\fP
diff --git a/debian/patches/79_CVE-2023-51766_4.97.1-release.diff b/debian/patches/79_CVE-2023-51766_4.97.1-release.diff
new file mode 100644
index 0000000..6199af9
--- /dev/null
+++ b/debian/patches/79_CVE-2023-51766_4.97.1-release.diff
@@ -0,0 +1,435 @@
+Description: Fix smtp-smuggling (CVE-2023-51766)
+ Pull upstream changes from 4.97.1 security release.
+Author: Jeremy Harris <jgh146exb@wizmail.org>
+Bug-Debian: https://bugs.debian.org/1059387
+Origin: upstream
+Last-Update: 2023-12-31
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -229,10 +229,15 @@ JH/53 Bug 2743: fix immediate-delivery v
+
+ JH/57 Fix control=fakreject for a custom message containing tainted data.
+ Previously this resulted in a log complaint, due to a re-expansion present
+ since fakereject was originally introduced.
+
++JH/s1 Refuse to accept a line "dot, LF" as end-of-DATA unless operating in
++ LF-only mode (as detected from the first header line). Previously we did
++ accept that in (normal) CRLF mode; this has been raised as a possible
++ attack scenario (under the name "smtp smuggling", CVE-2023-51766).
++
+
+ Exim version 4.94
+ -----------------
+
+ JH/01 Avoid costly startup code when not strictly needed. This reduces time
+--- /dev/null
++++ b/doc/doc-txt/cve-2023-51766
+@@ -0,0 +1,69 @@
++CVE ID: CVE-2023-51766
++Date: 2016-12-15
++Credits: https://sec-consult.com/blog/detail/smtp-smuggling-spoofing-e-mails-worldwide/
++Version(s): all up to 4.97 inclusive
++Issue: Given a buggy relay, Exim can be induced to accept a second message embedded
++ as part of the body of a first message
++
++Conditions
++==========
++
++If *all* the following conditions are met
++
++ Runtime options
++ ---------------
++
++ * Exim offers PIPELINING on incoming connections
++
++ * Exim offers CHUNKING on incoming connections
++
++ Operation
++ ---------
++
++ * DATA (as opposed to BDAT) is used for a message reception
++
++ * The relay host sends to the Exim MTA message data including
++ one of "LF . LF" or "CR LF . LF" or "LF . CR LF".
++
++ * Exim interprets the sequence as signalling the end of data for
++ the SMTP DATA command, and hence a first message.
++
++ * Exim interprets further input which the relay had as message body
++ data, as SMTP commands and data. This could include a MAIL, RCPT,
++ BDAT (etc) sequence, resulting in a further message acceptance.
++
++Impact
++======
++
++One or more messages can be accepted by Exim that have not been
++properly validated by the buggy relay.
++
++Fix
++===
++
++Install a fixed Exim version:
++
++ 4.98 (once available)
++ 4.97.1
++
++If you can't install one of the above versions, ask your package
++maintainer for a version containing the backported fix. On request and
++depending on our resources we will support you in backporting the fix.
++(Please note, that Exim project officially doesn't support versions
++prior the current stable version.)
++
++
++Workaround
++==========
++
++ Disable CHUNKING advertisement for incoming connections.
++
++ An attempt to "smuggle" a DATA command will trip a syncronisation
++ check.
++
++*or*
++
++ Disable PIPELINING advertisement for incoming connections.
++
++ The "smuggled" MAIL FROM command will then trip a syncronisation
++ check.
+--- a/src/receive.c
++++ b/src/receive.c
+@@ -805,104 +805,118 @@ we make the CRs optional in all cases.
+
+ July 2003: Bare CRs cause trouble. We now treat them as line terminators as
+ well, so that there are no CRs in spooled messages. However, the message
+ terminating dot is not recognized between two bare CRs.
+
++Dec 2023: getting a site to send a body including an "LF . LF" sequence
++followed by SMTP commands is a possible "smtp smuggling" attack. If
++the first (header) line for the message has a proper CRLF then enforce
++that for the body: convert bare LF to a space.
++
+ Arguments:
+- fout a FILE to which to write the message; NULL if skipping
++ fout a FILE to which to write the message; NULL if skipping
++ strict_crlf require full CRLF sequence as a line ending
+
+ Returns: One of the END_xxx values indicating why it stopped reading
+ */
+
+ static int
+-read_message_data_smtp(FILE *fout)
++read_message_data_smtp(FILE * fout, BOOL strict_crlf)
+ {
+-int ch_state = 0;
+-int ch;
+-int linelength = 0;
++enum { s_linestart, s_normal, s_had_cr, s_had_nl_dot, s_had_dot_cr } ch_state =
++ s_linestart;
++int linelength = 0, ch;
+
+ while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF)
+ {
+ if (ch == 0) body_zerocount++;
+ switch (ch_state)
+ {
+- case 0: /* After LF or CRLF */
+- if (ch == '.')
+- {
+- ch_state = 3;
+- continue; /* Don't ever write . after LF */
+- }
+- ch_state = 1;
++ case s_linestart: /* After LF or CRLF */
++ if (ch == '.')
++ {
++ ch_state = s_had_nl_dot;
++ continue; /* Don't ever write . after LF */
++ }
++ ch_state = s_normal;
+
+- /* Else fall through to handle as normal uschar. */
++ /* Else fall through to handle as normal uschar. */
+
+- case 1: /* Normal state */
+- if (ch == '\n')
+- {
+- ch_state = 0;
+- body_linecount++;
++ case s_normal: /* Normal state */
++ if (ch == '\r')
++ {
++ ch_state = s_had_cr;
++ continue; /* Don't write the CR */
++ }
++ if (ch == '\n') /* Bare LF at end of line */
++ if (strict_crlf)
++ ch = ' '; /* replace LF with space */
++ else
++ { /* treat as line ending */
++ ch_state = s_linestart;
++ body_linecount++;
++ if (linelength > max_received_linelength)
++ max_received_linelength = linelength;
++ linelength = -1;
++ }
++ break;
++
++ case s_had_cr: /* After (unwritten) CR */
++ body_linecount++; /* Any char ends line */
+ if (linelength > max_received_linelength)
+- max_received_linelength = linelength;
++ max_received_linelength = linelength;
+ linelength = -1;
+- }
+- else if (ch == '\r')
+- {
+- ch_state = 2;
+- continue;
+- }
+- break;
++ if (ch == '\n') /* proper CRLF */
++ ch_state = s_linestart;
++ else
++ {
++ message_size++; /* convert the dropped CR to a stored NL */
++ if (fout && fputc('\n', fout) == EOF) return END_WERROR;
++ cutthrough_data_put_nl();
++ if (ch == '\r') /* CR; do not write */
++ continue;
++ ch_state = s_normal; /* not LF or CR; process as standard */
++ }
++ break;
+
+- case 2: /* After (unwritten) CR */
+- body_linecount++;
+- if (linelength > max_received_linelength)
+- max_received_linelength = linelength;
+- linelength = -1;
+- if (ch == '\n')
+- {
+- ch_state = 0;
+- }
+- else
+- {
+- message_size++;
+- if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
+- cutthrough_data_put_nl();
+- if (ch != '\r') ch_state = 1; else continue;
+- }
+- break;
++ case s_had_nl_dot: /* After [CR] LF . */
++ if (ch == '\n') /* [CR] LF . LF */
++ if (strict_crlf)
++ ch = ' '; /* replace LF with space */
++ else
++ return END_DOT;
++ else if (ch == '\r') /* [CR] LF . CR */
++ {
++ ch_state = s_had_dot_cr;
++ continue; /* Don't write the CR */
++ }
++ /* The dot was removed on reaching s_had_nl_dot. For a doubled dot, here,
++ reinstate it to cutthrough. The current ch, dot or not, is passed both to
++ cutthrough and to file below. */
++ else if (ch == '.')
++ {
++ uschar c = ch;
++ cutthrough_data_puts(&c, 1);
++ }
++ ch_state = s_normal;
++ break;
+
+- case 3: /* After [CR] LF . */
+- if (ch == '\n')
+- return END_DOT;
+- if (ch == '\r')
+- {
+- ch_state = 4;
+- continue;
+- }
+- /* The dot was removed at state 3. For a doubled dot, here, reinstate
+- it to cutthrough. The current ch, dot or not, is passed both to cutthrough
+- and to file below. */
+- if (ch == '.')
+- {
+- uschar c= ch;
+- cutthrough_data_puts(&c, 1);
+- }
+- ch_state = 1;
+- break;
++ case s_had_dot_cr: /* After [CR] LF . CR */
++ if (ch == '\n')
++ return END_DOT; /* Preferred termination */
+
+- case 4: /* After [CR] LF . CR */
+- if (ch == '\n') return END_DOT;
+- message_size++;
+- body_linecount++;
+- if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
+- cutthrough_data_put_nl();
+- if (ch == '\r')
+- {
+- ch_state = 2;
+- continue;
+- }
+- ch_state = 1;
+- break;
++ message_size++; /* convert the dropped CR to a stored NL */
++ body_linecount++;
++ if (fout && fputc('\n', fout) == EOF) return END_WERROR;
++ cutthrough_data_put_nl();
++ if (ch == '\r')
++ {
++ ch_state = s_had_cr;
++ continue; /* CR; do not write */
++ }
++ ch_state = s_normal;
++ break;
+ }
+
+ /* Add the character to the spool file, unless skipping; then loop for the
+ next. */
+
+@@ -1114,11 +1128,11 @@ Returns: nothing
+ void
+ receive_swallow_smtp(void)
+ {
+ if (message_ended >= END_NOTENDED)
+ message_ended = chunking_state <= CHUNKING_OFFERED
+- ? read_message_data_smtp(NULL)
++ ? read_message_data_smtp(NULL, FALSE)
+ : read_message_bdat_smtp_wire(NULL);
+ }
+
+
+
+@@ -1899,12 +1913,14 @@ for (;;)
+ LF specially by inserting a white space after it to ensure that the header
+ line is not terminated. */
+
+ if (ch == '\n')
+ {
+- if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = FALSE;
+- else if (first_line_ended_crlf) receive_ungetc(' ');
++ if (first_line_ended_crlf == TRUE_UNSET)
++ first_line_ended_crlf = FALSE;
++ else if (first_line_ended_crlf)
++ receive_ungetc(' ');
+ goto EOL;
+ }
+
+ /* This is not the end of the line. If this is SMTP input and this is
+ the first character in the line and it is a "." character, ignore it.
+@@ -1915,12 +1931,17 @@ for (;;)
+ prevent further reading), and break out of the loop, having freed the
+ empty header, and set next = NULL to indicate no data line. */
+
+ if (ptr == 0 && ch == '.' && f.dot_ends)
+ {
++ /* leading dot while in headers-read mode */
+ ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
+- if (ch == '\r')
++ if (ch == '\n' && first_line_ended_crlf == TRUE /* and not TRUE_UNSET */ )
++ /* dot, LF but we are in CRLF mode. Attack? */
++ ch = ' '; /* replace the LF with a space */
++
++ else if (ch == '\r')
+ {
+ ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
+ if (ch != '\n')
+ {
+ receive_ungetc(ch);
+@@ -1952,11 +1973,12 @@ for (;;)
+ if (ch == '\r')
+ {
+ ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
+ if (ch == '\n')
+ {
+- if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = TRUE;
++ if (first_line_ended_crlf == TRUE_UNSET)
++ first_line_ended_crlf = TRUE;
+ goto EOL;
+ }
+
+ /* Otherwise, put back the character after CR, and turn the bare CR
+ into LF SP. */
+@@ -3084,11 +3106,11 @@ if (cutthrough.cctx.sock >= 0 && cutthro
+ (void) cutthrough_headers_send();
+ }
+
+
+ /* Open a new spool file for the data portion of the message. We need
+-to access it both via a file descriptor and a stream. Try to make the
++to access it both via a file descriptor and a stdio stream. Try to make the
+ directory if it isn't there. */
+
+ spool_name = spool_fname(US"input", message_subdir, message_id, US"-D");
+ DEBUG(D_receive) debug_printf("Data file name: %s\n", spool_name);
+
+@@ -3153,11 +3175,11 @@ message id or "next" line. */
+ if (!ferror(spool_data_file) && !(receive_feof)() && message_ended != END_DOT)
+ {
+ if (smtp_input)
+ {
+ message_ended = chunking_state <= CHUNKING_OFFERED
+- ? read_message_data_smtp(spool_data_file)
++ ? read_message_data_smtp(spool_data_file, first_line_ended_crlf)
+ : spool_wireformat
+ ? read_message_bdat_smtp_wire(spool_data_file)
+ : read_message_bdat_smtp(spool_data_file);
+ receive_linecount++; /* The terminating "." line */
+ }
+--- a/src/smtp_in.c
++++ b/src/smtp_in.c
+@@ -5393,16 +5393,16 @@ while (done <= 0)
+ }
+ break;
+ }
+
+ if (chunking_state > CHUNKING_OFFERED)
+- rc = OK; /* No predata ACL or go-ahead output for BDAT */
++ rc = OK; /* There is no predata ACL or go-ahead output for BDAT */
+ else
+ {
+- /* If there is an ACL, re-check the synchronization afterwards, since the
+- ACL may have delayed. To handle cutthrough delivery enforce a dummy call
+- to get the DATA command sent. */
++ /* If there is a predata-ACL, re-check the synchronization afterwards,
++ since the ACL may have delayed. To handle cutthrough delivery enforce a
++ dummy call to get the DATA command sent. */
+
+ if (acl_smtp_predata == NULL && cutthrough.cctx.sock < 0)
+ rc = OK;
+ else
+ {
+--- a/doc/spec.txt
++++ b/doc/spec.txt
+@@ -32960,12 +32960,10 @@ MTA within an operating system would use
+ has shown that this is not the case; for example, there are Unix applications
+ that use CRLF in this circumstance. For this reason, and for compatibility with
+ other MTAs, the way Exim handles line endings for all messages is now as
+ follows:
+
+- * LF not preceded by CR is treated as a line ending.
+-
+ * CR is treated as a line ending; if it is immediately followed by LF, the LF
+ is ignored.
+
+ * The sequence "CR, dot, CR" does not terminate an incoming SMTP message, nor
+ a local message in the state where a line containing only a dot is a
+@@ -32976,11 +32974,14 @@ follows:
+ behind this is that bare CRs in header lines are most likely either to be
+ mistakes, or people trying to play silly games.
+
+ * If the first header line received in a message ends with CRLF, a subsequent
+ bare LF in a header line is treated in the same way as a bare CR in a
+- header line.
++ header line and a bare LF in a body line is replaced with a space.
++
++ * If the first header line received in a message does not end with CRLF, a
++ subsequent LF not preceded by CR is treated as a line ending.
+
+
+ 48.3 Unqualified addresses
+ --------------------------
+
diff --git a/debian/patches/90_localscan_dlopen.dpatch b/debian/patches/90_localscan_dlopen.dpatch
new file mode 100644
index 0000000..592b7d7
--- /dev/null
+++ b/debian/patches/90_localscan_dlopen.dpatch
@@ -0,0 +1,307 @@
+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: https://bugs.exim.org/show_bug.cgi?id=2671
+Last-Update: 2019-10-20
+
+--- a/src/EDITME
++++ b/src/EDITME
+@@ -878,6 +878,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
+@@ -33,6 +33,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
+@@ -117,6 +117,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
++
+ #ifndef DISABLE_TLS
+ BOOL gnutls_compat_mode = FALSE;
+ BOOL gnutls_allow_auto_pkcs11 = FALSE;
+--- a/src/globals.h
++++ b/src/globals.h
+@@ -148,6 +148,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
+@@ -6,22 +6,6 @@
+ /* See the file NOTICE for conditions of use and distribution. */
+
+
+-/******************************************************************************
+-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! */
+@@ -29,37 +13,129 @@ release. Use only the documented interfa
+ #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
+@@ -27,6 +27,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"
+@@ -166,6 +167,9 @@ extern header_line *header_list; /
+ extern BOOL host_checking; /* Set when checking a host */
+ extern uschar *interface_address; /* Interface for incoming call */
+ extern int interface_port; /* Port number for incoming call */
++#ifdef DLOPEN_LOCAL_SCAN
++extern uschar *local_scan_path;
++#endif
+ extern uschar *message_id; /* Internal id of message being handled */
+ extern uschar *received_protocol; /* Name of incoming protocol */
+ extern int recipients_count; /* Number of recipients */
+@@ -235,4 +239,6 @@ extern pid_t child_open_exim2_functio
+ extern pid_t child_open_function(uschar **, uschar **, int, int *, int *, BOOL, const uschar *);
+ #endif
+
++#pragma GCC visibility pop
++
+ /* End of local_scan.h */
+--- a/src/readconf.c
++++ b/src/readconf.c
+@@ -205,6 +205,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
+--- a/src/string.c
++++ b/src/string.c
+@@ -413,6 +413,7 @@ return ss;
+
+ #if (defined(HAVE_LOCAL_SCAN) || defined(EXPAND_DLFUNC)) \
+ && !defined(MACRO_PREDEF) && !defined(COMPILE_UTILITY)
++#pragma GCC visibility push(default)
+ /*************************************************
+ * Copy and save string *
+ *************************************************/
+@@ -465,6 +466,7 @@ Ustrncpy(ss, s, n);
+ ss[n] = 0;
+ return ss;
+ }
++#pragma GCC visibility pop
+ #endif
+
+
diff --git a/debian/patches/series b/debian/patches/series
new file mode 100644
index 0000000..3d95d1d
--- /dev/null
+++ b/debian/patches/series
@@ -0,0 +1,46 @@
+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
+73_01-Fix-DANE-SNI-handling-Bug-2265.patch
+73_02-Fix-ipv6norm.patch
+73_03-Named-Queues-fix-immediate-delivery.-Bug-2743.patch
+73_04-Fix-host_name_lookup-Close-2747.patch
+73_05-Fix-tainted-message-for-fakereject.patch
+75_01-Introduce-main-config-option-allow_insecure_tainted_.patch
+75_02-search.patch
+75_03-dbstuff.patch
+75_04-acl.patch
+75_05-parse.patch
+75_06-rda.patch
+75_07-appendfile.patch
+75_08-autoreply.patch
+75_09-pipe.patch
+75_10-deliver.patch
+75_11-directory.patch
+75_12-expand.patch
+75_13-lf_sqlperform.patch
+75_14-rf_get_transport.patch
+75_15-deliver.patch
+75_16-smtp_out.patch
+75_17-smtp.patch
+75_18-update-doc.patch
+75_20-Set-mainlog_name-and-rejectlog_name-unconditionally.patch
+75_21-tidy-log.c.patch
+75_22-Silence-compiler.patch
+75_23-Do-not-close-the-main-_log-if-we-do-not-see-a-chance.patch
+75_24-Silence-the-compiler.patch
+75_26-Disable-taintchecks-for-mkdir-this-isn-t-part-of-4.9.patch
+75_27_Fix-logging-with-empty-element-in-log_file_path-Bug-.patch
+75_28_Fix-logging-with-build-time-config-and-empty-element.patch
+75_29-Auths-fix-possible-OOB-write-in-external-authenticat.patch
+75_30-Auths-use-uschar-more-in-spa-authenticator.patch
+75_31-Auths-fix-possible-OOB-write-in-SPA-authenticator.-B.patch
+75_32-Auths-fix-possible-OOB-read-in-SPA-authenticator.-Bu.patch
+78_01-Command-line-option-for-no-notifier-socket.-Bug-2616.patch
+79_CVE-2023-51766_4.97.1-release.diff
+90_localscan_dlopen.dpatch