From 318a1a2246a9f521e5a02313dcc1f6d68a0af7ec Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 18:16:14 +0200 Subject: Adding debian version 4.96-15+deb12u4. Signed-off-by: Daniel Baumann --- debian/patches/31_eximmanpage.dpatch | 250 ++++++++++++ debian/patches/32_exim4.dpatch | 106 +++++ debian/patches/33_eximon.binary.dpatch | 17 + debian/patches/34_eximstatsmanpage.dpatch | 20 + debian/patches/35_install.dpatch | 49 +++ debian/patches/60_convert4r4.dpatch | 41 ++ debian/patches/67_unnecessaryCopt.diff | 69 ++++ .../patches/70_remove_exim-users_references.dpatch | 38 ++ ...-attempt-to-rewrite-a-malformed-address.-.patch | 57 +++ ...-SPF-fix-memory-accounting-for-error-case.patch | 25 ++ ...5_08-Fix-regex-n-use-after-free.-Bug-2915.patch | 196 +++++++++ .../75_09-Fix-non-WITH_CONTENT_SCAN-build.patch | 58 +++ .../75_10-Fix-non-WITH_CONTENT_SCAN-build-2.patch | 135 +++++++ .../75_11-Fix-non-WITH_CONTENT_SCAN-build-3.patch | 45 +++ ...ix-for-clients-offering-no-TLS-extensions.patch | 114 ++++++ ...-Build-with-libopendmarc-1.4.x-fixes-2728.patch | 88 +++++ ...RC-fix-use-after-free-in-dmarc_dns_lookup.patch | 39 ++ .../75_22-Fix-daemon-startup.-Bug-2930.patch | 68 ++++ ..._23-Fix-reccipients-after-run.-.-Bug-2929.patch | 45 +++ ...substring-capture-variables-for-null-matc.patch | 79 ++++ ...ubstring-capture-variables-for-null-match.patch | 94 +++++ ...ex-substring-capture-commentary.-Bug-2933.patch | 48 +++ ...n-preloading-creds-do-the-server-certs-be.patch | 232 +++++++++++ ...-double-expansion-of-tls_verify_certifica.patch | 217 ++++++++++ debian/patches/75_42-Fix-run-arg-parsing.patch | 100 +++++ .../75_50-Fix-logging-of-max-size-log-line.patch | 82 ++++ ...ion-on-dns_again_means_nonexist.-Bug-2911.patch | 76 ++++ ...r-smtp-socket-explicitly-on-connect-ACL-d.patch | 50 +++ ...-tls_eccurve-setting-explicit-curve-group.patch | 184 +++++++++ ...-tls_eccurve-on-earlier-versions-than-3.0.patch | 42 ++ ...-conns-rejected-for-bad-ALPN-with-the-off.patch | 99 +++++ ...-check-dns_again_means_nonexist-for-TLSA-.patch | 96 +++++ debian/patches/75_66-Fix-crash-in-expansions.patch | 84 ++++ ...68-Fix-srs_encode-.-for-mod-1024-day-zero.patch | 62 +++ ...e-initialisation-in-smtp-transport.-Bug-2.patch | 46 +++ ...ossible-OOB-write-in-external-authenticat.patch | 22 ++ ...uths-use-uschar-more-in-spa-authenticator.patch | 226 +++++++++++ ...ossible-OOB-write-in-SPA-authenticator.-B.patch | 24 ++ ...ossible-OOB-read-in-SPA-authenticator.-Bu.patch | 75 ++++ ...ly-pipe-on-an-observed-advertising-change.patch | 35 ++ ...disallow-UTF-16-surrogates-from-utf8clean.patch | 99 +++++ ...77-GnuTLS-fix-crash-with-tls_dhparam-none.patch | 92 +++++ ...nts-expansion-when-used-within-run.-.-Bug.patch | 294 ++++++++++++++ ...LS-fix-autogen-cert-expiry-date.-Bug-3014.patch | 42 ++ ...-variable-value-free.-The-inital-fix-resu.patch | 77 ++++ ...ing_is_ip_address-CVE-2023-42117-Bug-3031.patch | 309 +++++++++++++++ ...-SPF-harden-against-crafted-DNS-responses.patch | 64 +++ ...b-against-crafted-DNS-responses.-Bug-3033.patch | 244 ++++++++++++ ...76-10-Fix-tr.-and-empty-strings.-Bug-3023.patch | 56 +++ ...-more-hardening-against-crafted-responses.patch | 219 ++++++++++ ...-dnsdb-lookup-of-multi-chunk-TXT.-Bug-305.patch | 118 ++++++ .../patches/77_CVE-2023-51766_4.97.1-release.diff | 440 +++++++++++++++++++++ debian/patches/90_localscan_dlopen.dpatch | 347 ++++++++++++++++ debian/patches/series | 53 +++ 54 files changed, 5887 insertions(+) create mode 100755 debian/patches/31_eximmanpage.dpatch create mode 100644 debian/patches/32_exim4.dpatch create mode 100755 debian/patches/33_eximon.binary.dpatch create mode 100755 debian/patches/34_eximstatsmanpage.dpatch create mode 100755 debian/patches/35_install.dpatch create mode 100755 debian/patches/60_convert4r4.dpatch create mode 100644 debian/patches/67_unnecessaryCopt.diff create mode 100755 debian/patches/70_remove_exim-users_references.dpatch create mode 100644 debian/patches/75_01-Fix-exit-on-attempt-to-rewrite-a-malformed-address.-.patch create mode 100644 debian/patches/75_05-SPF-fix-memory-accounting-for-error-case.patch create mode 100644 debian/patches/75_08-Fix-regex-n-use-after-free.-Bug-2915.patch create mode 100644 debian/patches/75_09-Fix-non-WITH_CONTENT_SCAN-build.patch create mode 100644 debian/patches/75_10-Fix-non-WITH_CONTENT_SCAN-build-2.patch create mode 100644 debian/patches/75_11-Fix-non-WITH_CONTENT_SCAN-build-3.patch create mode 100644 debian/patches/75_16-GnuTLS-fix-for-clients-offering-no-TLS-extensions.patch create mode 100644 debian/patches/75_18-Fix-Build-with-libopendmarc-1.4.x-fixes-2728.patch create mode 100644 debian/patches/75_19-DMARC-fix-use-after-free-in-dmarc_dns_lookup.patch create mode 100644 debian/patches/75_22-Fix-daemon-startup.-Bug-2930.patch create mode 100644 debian/patches/75_23-Fix-reccipients-after-run.-.-Bug-2929.patch create mode 100644 debian/patches/75_31-Fix-regext-substring-capture-variables-for-null-matc.patch create mode 100644 debian/patches/75_32-Fix-regex-substring-capture-variables-for-null-match.patch create mode 100644 debian/patches/75_34-Fix-regex-substring-capture-commentary.-Bug-2933.patch create mode 100644 debian/patches/75_37-OpenSSL-when-preloading-creds-do-the-server-certs-be.patch create mode 100644 debian/patches/75_38-OpenSSL-fix-double-expansion-of-tls_verify_certifica.patch create mode 100644 debian/patches/75_42-Fix-run-arg-parsing.patch create mode 100644 debian/patches/75_50-Fix-logging-of-max-size-log-line.patch create mode 100644 debian/patches/75_55-Fix-recursion-on-dns_again_means_nonexist.-Bug-2911.patch create mode 100644 debian/patches/75_58-Close-server-smtp-socket-explicitly-on-connect-ACL-d.patch create mode 100644 debian/patches/75_60-OpenSSL-fix-tls_eccurve-setting-explicit-curve-group.patch create mode 100644 debian/patches/75_62-OpenSSL-Fix-tls_eccurve-on-earlier-versions-than-3.0.patch create mode 100644 debian/patches/75_63-OpenSSL-log-conns-rejected-for-bad-ALPN-with-the-off.patch create mode 100644 debian/patches/75_64-DANE-do-not-check-dns_again_means_nonexist-for-TLSA-.patch create mode 100644 debian/patches/75_66-Fix-crash-in-expansions.patch create mode 100644 debian/patches/75_68-Fix-srs_encode-.-for-mod-1024-day-zero.patch create mode 100644 debian/patches/75_70-Fix-variable-initialisation-in-smtp-transport.-Bug-2.patch create mode 100644 debian/patches/75_71-Auths-fix-possible-OOB-write-in-external-authenticat.patch create mode 100644 debian/patches/75_72-Auths-use-uschar-more-in-spa-authenticator.patch create mode 100644 debian/patches/75_73-Auths-fix-possible-OOB-write-in-SPA-authenticator.-B.patch create mode 100644 debian/patches/75_74-Auths-fix-possible-OOB-read-in-SPA-authenticator.-Bu.patch create mode 100644 debian/patches/75_74-Cancel-early-pipe-on-an-observed-advertising-change.patch create mode 100644 debian/patches/75_76-Expansions-disallow-UTF-16-surrogates-from-utf8clean.patch create mode 100644 debian/patches/75_77-GnuTLS-fix-crash-with-tls_dhparam-none.patch create mode 100644 debian/patches/75_79-Fix-recipients-expansion-when-used-within-run.-.-Bug.patch create mode 100644 debian/patches/75_82-GnuTLS-fix-autogen-cert-expiry-date.-Bug-3014.patch create mode 100644 debian/patches/75_83-Re-fix-live-variable-value-free.-The-inital-fix-resu.patch create mode 100644 debian/patches/76-01-fix-string_is_ip_address-CVE-2023-42117-Bug-3031.patch create mode 100644 debian/patches/76-02-SPF-harden-against-crafted-DNS-responses.patch create mode 100644 debian/patches/76-03-Harden-dnsdb-against-crafted-DNS-responses.-Bug-3033.patch create mode 100644 debian/patches/76-10-Fix-tr.-and-empty-strings.-Bug-3023.patch create mode 100644 debian/patches/76-12-DNS-more-hardening-against-crafted-responses.patch create mode 100644 debian/patches/76-14-Lookups-Fix-dnsdb-lookup-of-multi-chunk-TXT.-Bug-305.patch create mode 100644 debian/patches/77_CVE-2023-51766_4.97.1-release.diff create mode 100644 debian/patches/90_localscan_dlopen.dpatch create mode 100644 debian/patches/series (limited to 'debian/patches') 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 , + Andreas Metzler +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 ++ /usr/sbin/exim4 -i + + 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 ' 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 +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 () +--- 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 +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 you can use ++C ++instead. ++ + =item B<-chartdir>I + + Create the charts in the directory 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- - disable this feature. +Author: Andreas Metzler +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 +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 < +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 () +--- 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 +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/75_01-Fix-exit-on-attempt-to-rewrite-a-malformed-address.-.patch b/debian/patches/75_01-Fix-exit-on-attempt-to-rewrite-a-malformed-address.-.patch new file mode 100644 index 0000000..bf0f649 --- /dev/null +++ b/debian/patches/75_01-Fix-exit-on-attempt-to-rewrite-a-malformed-address.-.patch @@ -0,0 +1,57 @@ +From e7ec503729970a03d4509921342bc81313976126 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Tue, 12 Jul 2022 22:14:04 +0100 +Subject: [PATCH] Fix exit on attempt to rewrite a malformed address. Bug 2903 + +--- + doc/ChangeLog | 5 + + src/rewrite.c | 9 +- + test/confs/0471 | 7 + + test/log/0471 | 5 + + test/scripts/0000-Basic/0471 | 4 +- + test/stderr/0471 | 245 ++++++++++++++++++++++++++++++++++- + 6 files changed, 267 insertions(+), 8 deletions(-) + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -1,9 +1,14 @@ + This document describes *changes* to previous versions, that might + affect Exim's operation, with an unchanged configuration file. For new + options, and new features, see the NewStuff file next to this ChangeLog. + ++JH/04 Bug 2903: avoid exit on an attempt to rewrite a malformed address. ++ Make the rewrite never match and keep the logging. Trust the ++ admin to be using verify=header-syntax (to actually reject the message). ++ ++ + Exim version 4.96 + ----------------- + + JH/01 Move the wait-for-next-tick (needed for unique messmage IDs) from + after reception to before a subsequent reception. This should +--- a/src/rewrite.c ++++ b/src/rewrite.c +@@ -493,19 +493,18 @@ + empty address, overlong addres. Sometimes the result matters, sometimes not. + It seems this function is called for *any* header we see. */ + + if (!recipient) + { +- /* Handle unparesable addresses in the header. Slightly ugly because a ++ /* Log unparesable addresses in the header. Slightly ugly because a + null output from the extract can also result from a header without an +- address, "To: undisclosed recpients:;" being the classic case. */ ++ address, "To: undisclosed recpients:;" being the classic case. Ignore ++ this one and carry on. */ + + if ((rewrite_rules || routed_old) && Ustrcmp(errmess, "empty address") != 0) +- { + log_write(0, LOG_MAIN, "rewrite: %s", errmess); +- exim_exit(EXIT_FAILURE); +- } ++ + loop_reset_point = store_reset(loop_reset_point); + continue; + } + + /* If routed_old is not NULL, this is a rewrite caused by a router, diff --git a/debian/patches/75_05-SPF-fix-memory-accounting-for-error-case.patch b/debian/patches/75_05-SPF-fix-memory-accounting-for-error-case.patch new file mode 100644 index 0000000..e474acf --- /dev/null +++ b/debian/patches/75_05-SPF-fix-memory-accounting-for-error-case.patch @@ -0,0 +1,25 @@ +From 93c722ce0549360af68269f088f4e59ed8fc130e Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Sun, 7 Aug 2022 17:00:27 +0100 +Subject: [PATCH] SPF: fix memory accounting for error case + +--- + src/spf.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/spf.c b/src/spf.c +index db6eea3a8..a8c0f75c4 100644 +--- a/src/spf.c ++++ b/src/spf.c +@@ -204,7 +204,7 @@ spf_nxdomain = SPF_dns_rr_new_init(spf_dns_server, + "", ns_t_any, 24 * 60 * 60, HOST_NOT_FOUND); + if (!spf_nxdomain) + { +- free(spf_dns_server); ++ store_free(spf_dns_server); + return NULL; + } + +-- +2.35.1 + diff --git a/debian/patches/75_08-Fix-regex-n-use-after-free.-Bug-2915.patch b/debian/patches/75_08-Fix-regex-n-use-after-free.-Bug-2915.patch new file mode 100644 index 0000000..be6ccae --- /dev/null +++ b/debian/patches/75_08-Fix-regex-n-use-after-free.-Bug-2915.patch @@ -0,0 +1,196 @@ +From 4e9ed49f8f12eb331b29bd5b6dc3693c520fddc2 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Wed, 31 Aug 2022 15:37:40 +0100 +Subject: [PATCH] Fix $regex use-after-free. Bug 2915 + +--- + doc/ChangeLog | 8 +++++++- + src/exim.c | 4 +--- + src/expand.c | 2 +- + src/functions.h | 1 + + src/globals.c | 2 +- + src/regex.c | 29 ++++++++++++++++++----------- + src/smtp_in.c | 2 ++ + test/confs/4002 | 10 ++++++++++ + test/mail/4002.userx | 7 +++++++ + test/scripts/4000-scanning/4002 | 7 +++++++ + 10 files changed, 55 insertions(+), 17 deletions(-) + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -4,15 +4,21 @@ + + JH/04 Bug 2903: avoid exit on an attempt to rewrite a malformed address. + Make the rewrite never match and keep the logging. Trust the + admin to be using verify=header-syntax (to actually reject the message). + ++JH/08 Bug 2915: Fix use-after-free for $regex variables. Previously when ++ more than one message arrived in a single connection a reference from ++ the earlier message could be re-used. Often a sigsegv resulted. ++ These variables were introduced in Exim 4.87. ++ Debug help from Graeme Fowler. ++ + + Exim version 4.96 + ----------------- + +-JH/01 Move the wait-for-next-tick (needed for unique messmage IDs) from ++JH/01 Move the wait-for-next-tick (needed for unique message IDs) from + after reception to before a subsequent reception. This should + mean slightly faster delivery, and also confirmation of reception + to senders. + + JH/02 Move from using the pcre library to pcre2. The former is no longer +--- a/src/exim.c ++++ b/src/exim.c +@@ -1999,12 +1999,10 @@ + + regex_whitelisted_macro = + regex_must_compile(US"^[A-Za-z0-9_/.-]*$", FALSE, TRUE); + #endif + +-for (i = 0; i < REGEX_VARS; i++) regex_vars[i] = NULL; +- + /* If the program is called as "mailq" treat it as equivalent to "exim -bp"; + this seems to be a generally accepted convention, since one finds symbolic + links called "mailq" in standard OS configurations. */ + + if ((namelen == 5 && Ustrcmp(argv[0], "mailq") == 0) || +@@ -6082,11 +6080,11 @@ + callout_address = NULL; + sending_ip_address = NULL; + deliver_localpart_data = deliver_domain_data = + recipient_data = sender_data = NULL; + acl_var_m = NULL; +- for(int i = 0; i < REGEX_VARS; i++) regex_vars[i] = NULL; ++ regex_vars_clear(); + + store_reset(reset_point); + } + + exim_exit(EXIT_SUCCESS); /* Never returns */ +--- a/src/expand.c ++++ b/src/expand.c +@@ -1871,11 +1871,11 @@ + { + tree_node * node = tree_search(router_var, name + 2); + return node ? node->data.ptr : strict_acl_vars ? NULL : US""; + } + +-/* Handle $auth variables. */ ++/* Handle $auth, $regex variables. */ + + if (Ustrncmp(name, "auth", 4) == 0) + { + uschar *endptr; + int n = Ustrtoul(name + 4, &endptr, 10); +--- a/src/functions.h ++++ b/src/functions.h +@@ -436,10 +436,11 @@ + extern int regex(const uschar **); + #endif + extern BOOL regex_match(const pcre2_code *, const uschar *, int, uschar **); + extern BOOL regex_match_and_setup(const pcre2_code *, const uschar *, int, int); + extern const pcre2_code *regex_must_compile(const uschar *, BOOL, BOOL); ++extern void regex_vars_clear(void); + extern void retry_add_item(address_item *, uschar *, int); + extern BOOL retry_check_address(const uschar *, host_item *, uschar *, BOOL, + uschar **, uschar **); + extern retry_config *retry_find_config(const uschar *, const uschar *, int, int); + extern BOOL retry_ultimate_address_timeout(uschar *, const uschar *, +--- a/src/globals.c ++++ b/src/globals.c +@@ -1313,11 +1313,11 @@ + #ifndef DISABLE_PIPE_CONNECT + const pcre2_code *regex_EARLY_PIPE = NULL; + #endif + const pcre2_code *regex_ismsgid = NULL; + const pcre2_code *regex_smtp_code = NULL; +-const uschar *regex_vars[REGEX_VARS]; ++const uschar *regex_vars[REGEX_VARS] = { 0 };; + #ifdef WHITELIST_D_MACROS + const pcre2_code *regex_whitelisted_macro = NULL; + #endif + #ifdef WITH_CONTENT_SCAN + uschar *regex_match_string = NULL; +--- a/src/regex.c ++++ b/src/regex.c +@@ -94,22 +94,32 @@ + } + pcre2_match_data_free(md); + return FAIL; + } + ++ ++/* reset expansion variables */ ++void ++regex_vars_clear(void) ++{ ++regex_match_string = NULL; ++for (int i = 0; i < REGEX_VARS; i++) regex_vars[i] = NULL; ++} ++ ++ ++ + int +-regex(const uschar **listptr) ++regex(const uschar ** listptr) + { + unsigned long mbox_size; +-FILE *mbox_file; +-pcre_list *re_list_head; +-uschar *linebuffer; ++FILE * mbox_file; ++pcre_list * re_list_head; ++uschar * linebuffer; + long f_pos = 0; + int ret = FAIL; + +-/* reset expansion variable */ +-regex_match_string = NULL; ++regex_vars_clear(); + + if (!mime_stream) /* We are in the DATA ACL */ + { + if (!(mbox_file = spool_mbox(&mbox_size, NULL, NULL))) + { /* error while spooling */ +@@ -167,18 +177,17 @@ + + + int + mime_regex(const uschar **listptr) + { +-pcre_list *re_list_head = NULL; +-FILE *f; +-uschar *mime_subject = NULL; ++pcre_list * re_list_head = NULL; ++FILE * f; ++uschar * mime_subject = NULL; + int mime_subject_len = 0; + int ret; + +-/* reset expansion variable */ +-regex_match_string = NULL; ++regex_vars_clear(); + + /* precompile our regexes */ + if (!(re_list_head = compile(*listptr))) + return FAIL; /* no regexes -> nothing to do */ + +--- a/src/smtp_in.c ++++ b/src/smtp_in.c +@@ -2155,12 +2155,14 @@ + prdr_requested = FALSE; + #endif + #ifdef SUPPORT_I18N + message_smtputf8 = FALSE; + #endif ++regex_vars_clear(); + body_linecount = body_zerocount = 0; + ++lookup_value = NULL; /* Can be set by ACL */ + sender_rate = sender_rate_limit = sender_rate_period = NULL; + ratelimiters_mail = NULL; /* Updated by ratelimit ACL condition */ + /* Note that ratelimiters_conn persists across resets. */ + + /* Reset message ACL variables */ diff --git a/debian/patches/75_09-Fix-non-WITH_CONTENT_SCAN-build.patch b/debian/patches/75_09-Fix-non-WITH_CONTENT_SCAN-build.patch new file mode 100644 index 0000000..6071fa7 --- /dev/null +++ b/debian/patches/75_09-Fix-non-WITH_CONTENT_SCAN-build.patch @@ -0,0 +1,58 @@ +From d8ecc7bf97934a1e2244788c610c958cacd740bd Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Wed, 31 Aug 2022 17:03:37 +0100 +Subject: [PATCH 1/3] Fix non-WITH_CONTENT_SCAN build. + +Broken-by: 4e9ed49f8f +--- + src/exim.c | 11 +++++++++++ + src/regex.c | 10 ---------- + 2 files changed, 11 insertions(+), 10 deletions(-) + +--- a/src/exim.c ++++ b/src/exim.c +@@ -1677,10 +1677,21 @@ + if ((s = expand_string(big_buffer))) printf("%s\n", CS s); + else printf("Failed: %s\n", expand_string_message); + } + + ++/* reset regex expansion variables */ ++void ++regex_vars_clear(void) ++{ ++regex_match_string = NULL; ++for (int i = 0; i < REGEX_VARS; i++) regex_vars[i] = NULL; ++} ++ ++ ++ ++ + + /************************************************* + * Entry point and high-level code * + *************************************************/ + +--- a/src/regex.c ++++ b/src/regex.c +@@ -95,20 +95,10 @@ + pcre2_match_data_free(md); + return FAIL; + } + + +-/* reset expansion variables */ +-void +-regex_vars_clear(void) +-{ +-regex_match_string = NULL; +-for (int i = 0; i < REGEX_VARS; i++) regex_vars[i] = NULL; +-} +- +- +- + int + regex(const uschar ** listptr) + { + unsigned long mbox_size; + FILE * mbox_file; diff --git a/debian/patches/75_10-Fix-non-WITH_CONTENT_SCAN-build-2.patch b/debian/patches/75_10-Fix-non-WITH_CONTENT_SCAN-build-2.patch new file mode 100644 index 0000000..0a8ed51 --- /dev/null +++ b/debian/patches/75_10-Fix-non-WITH_CONTENT_SCAN-build-2.patch @@ -0,0 +1,135 @@ +From 158dff9936e36a2d31d037d3988b9353458d6471 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Wed, 31 Aug 2022 17:17:59 +0100 +Subject: [PATCH 2/3] Fix non-WITH_CONTENT_SCAN build (2) + +Broken-by: d8ecc7bf97 +--- + src/exim.c | 13 +------------ + src/functions.h | 2 +- + src/globals.h | 2 +- + src/regex.c | 10 ++++++++++ + src/smtp_in.c | 2 ++ + 5 files changed, 15 insertions(+), 14 deletions(-) + +--- a/src/exim.c ++++ b/src/exim.c +@@ -1677,21 +1677,10 @@ + if ((s = expand_string(big_buffer))) printf("%s\n", CS s); + else printf("Failed: %s\n", expand_string_message); + } + + +-/* reset regex expansion variables */ +-void +-regex_vars_clear(void) +-{ +-regex_match_string = NULL; +-for (int i = 0; i < REGEX_VARS; i++) regex_vars[i] = NULL; +-} +- +- +- +- + + /************************************************* + * Entry point and high-level code * + *************************************************/ + +@@ -6085,17 +6074,17 @@ + deliver_domain_orig = NULL; + deliver_host = deliver_host_address = NULL; + dnslist_domain = dnslist_matched = NULL; + #ifdef WITH_CONTENT_SCAN + malware_name = NULL; ++ regex_vars_clear(); + #endif + callout_address = NULL; + sending_ip_address = NULL; + deliver_localpart_data = deliver_domain_data = + recipient_data = sender_data = NULL; + acl_var_m = NULL; +- regex_vars_clear(); + + store_reset(reset_point); + } + + exim_exit(EXIT_SUCCESS); /* Never returns */ +--- a/src/functions.h ++++ b/src/functions.h +@@ -432,15 +432,15 @@ + extern BOOL receive_msg(BOOL); + extern int_eximarith_t receive_statvfs(BOOL, int *); + extern void receive_swallow_smtp(void); + #ifdef WITH_CONTENT_SCAN + extern int regex(const uschar **); ++extern void regex_vars_clear(void); + #endif + extern BOOL regex_match(const pcre2_code *, const uschar *, int, uschar **); + extern BOOL regex_match_and_setup(const pcre2_code *, const uschar *, int, int); + extern const pcre2_code *regex_must_compile(const uschar *, BOOL, BOOL); +-extern void regex_vars_clear(void); + extern void retry_add_item(address_item *, uschar *, int); + extern BOOL retry_check_address(const uschar *, host_item *, uschar *, BOOL, + uschar **, uschar **); + extern retry_config *retry_find_config(const uschar *, const uschar *, int, int); + extern BOOL retry_ultimate_address_timeout(uschar *, const uschar *, +--- a/src/globals.h ++++ b/src/globals.h +@@ -895,16 +895,16 @@ + #ifndef DISABLE_PIPE_CONNECT + extern const pcre2_code *regex_EARLY_PIPE; /* For recognizing PIPE_CONNCT */ + #endif + extern const pcre2_code *regex_ismsgid; /* Compiled r.e. for message ID */ + extern const pcre2_code *regex_smtp_code; /* For recognizing SMTP codes */ +-extern const uschar *regex_vars[]; /* $regexN variables */ + #ifdef WHITELIST_D_MACROS + extern const pcre2_code *regex_whitelisted_macro; /* For -D macro values */ + #endif + #ifdef WITH_CONTENT_SCAN + extern uschar *regex_match_string; /* regex that matched a line (regex ACL condition) */ ++extern const uschar *regex_vars[]; + #endif + extern int remote_delivery_count; /* Number of remote addresses */ + extern int remote_max_parallel; /* Maximum parallel delivery */ + extern uschar *remote_sort_domains; /* Remote domain sorting order */ + extern retry_config *retries; /* Chain of retry config information */ +--- a/src/regex.c ++++ b/src/regex.c +@@ -95,10 +95,20 @@ + pcre2_match_data_free(md); + return FAIL; + } + + ++/* reset expansion variables */ ++void ++regex_vars_clear(void) ++{ ++regex_match_string = NULL; ++for (int i = 0; i < REGEX_VARS; i++) regex_vars[i] = NULL; ++} ++ ++ ++ + int + regex(const uschar ** listptr) + { + unsigned long mbox_size; + FILE * mbox_file; +--- a/src/smtp_in.c ++++ b/src/smtp_in.c +@@ -2155,11 +2155,13 @@ + prdr_requested = FALSE; + #endif + #ifdef SUPPORT_I18N + message_smtputf8 = FALSE; + #endif ++#ifdef WITH_CONTENT_SCAN + regex_vars_clear(); ++#endif + body_linecount = body_zerocount = 0; + + lookup_value = NULL; /* Can be set by ACL */ + sender_rate = sender_rate_limit = sender_rate_period = NULL; + ratelimiters_mail = NULL; /* Updated by ratelimit ACL condition */ diff --git a/debian/patches/75_11-Fix-non-WITH_CONTENT_SCAN-build-3.patch b/debian/patches/75_11-Fix-non-WITH_CONTENT_SCAN-build-3.patch new file mode 100644 index 0000000..b06d896 --- /dev/null +++ b/debian/patches/75_11-Fix-non-WITH_CONTENT_SCAN-build-3.patch @@ -0,0 +1,45 @@ +From 32da6327e434e986a18b75a84f2d8c687ba14619 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Thu, 1 Sep 2022 15:54:35 +0100 +Subject: [PATCH 3/3] Fix non-WITH_CONTENT_SCAN build (3) + +Broken-by: d8ecc7bf97 +--- + src/expand.c | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/src/expand.c b/src/expand.c +index 89de56255..831ca2b75 100644 +--- a/src/expand.c ++++ b/src/expand.c +@@ -1869,6 +1869,7 @@ if (Ustrncmp(name, "auth", 4) == 0) + if (!*endptr && n != 0 && n <= AUTH_VARS) + return auth_vars[n-1] ? auth_vars[n-1] : US""; + } ++#ifdef WITH_CONTENT_SCAN + else if (Ustrncmp(name, "regex", 5) == 0) + { + uschar *endptr; +@@ -1876,6 +1877,7 @@ else if (Ustrncmp(name, "regex", 5) == 0) + if (!*endptr && n != 0 && n <= REGEX_VARS) + return regex_vars[n-1] ? regex_vars[n-1] : US""; + } ++#endif + + /* For all other variables, search the table */ + +@@ -8715,9 +8717,11 @@ assert_variable_notin() treats as const, so deconst is safe. */ + for (int i = 0; i < AUTH_VARS; i++) if (auth_vars[i]) + assert_variable_notin(US"auth", US auth_vars[i], &e); + ++#ifdef WITH_CONTENT_SCAN + /* check regex variables. assert_variable_notin() treats as const. */ + for (int i = 0; i < REGEX_VARS; i++) if (regex_vars[i]) + assert_variable_notin(US"regex", US regex_vars[i], &e); ++#endif + + /* check known-name variables */ + for (var_entry * v = var_table; v < var_table + var_table_size; v++) +-- +2.35.1 + diff --git a/debian/patches/75_16-GnuTLS-fix-for-clients-offering-no-TLS-extensions.patch b/debian/patches/75_16-GnuTLS-fix-for-clients-offering-no-TLS-extensions.patch new file mode 100644 index 0000000..ae2fa16 --- /dev/null +++ b/debian/patches/75_16-GnuTLS-fix-for-clients-offering-no-TLS-extensions.patch @@ -0,0 +1,114 @@ +From ece23f05d6a430a461a75639197271c23f6858ec Mon Sep 17 00:00:00 2001 +From: Jasen Betts +Date: Fri, 30 Sep 2022 13:49:41 +0100 +Subject: [PATCH] GnuTLS: fix for clients offering no TLS extensions + +--- + doc/ChangeLog | 3 +++ + src/tls-gnu.c | 3 ++- + src/tls-openssl.c | 39 +++++++++++++++--------------- + test/confs/2091 | 1 + + test/log/2091 | 3 +++ + test/scripts/2090-GnuTLS-ALPN/2091 | 19 +++++++++++++++ + test/stdout/2091 | 21 ++++++++++++++++ + 7 files changed, 68 insertions(+), 21 deletions(-) + create mode 120000 test/confs/2091 + create mode 100644 test/log/2091 + create mode 100644 test/scripts/2090-GnuTLS-ALPN/2091 + create mode 100644 test/stdout/2091 + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -10,10 +10,14 @@ + more than one message arrived in a single connection a reference from + the earlier message could be re-used. Often a sigsegv resulted. + These variables were introduced in Exim 4.87. + Debug help from Graeme Fowler. + ++JH/10 GnuTLS: fix for (IOT?) clients offering no TLS extensions at all. ++ Find and fix by Jasen Betts. ++ ++ + + Exim version 4.96 + ----------------- + + JH/01 Move the wait-for-next-tick (needed for unique message IDs) from +--- a/src/tls-gnu.c ++++ b/src/tls-gnu.c +@@ -1130,12 +1130,13 @@ + static int + tls_server_clienthello_cb(gnutls_session_t session, unsigned int htype, + unsigned when, unsigned int incoming, const gnutls_datum_t * msg) + { + /* Call fn for each extension seen. 3.6.3 onwards */ +-return gnutls_ext_raw_parse(NULL, tls_server_clienthello_ext, msg, ++int rc = gnutls_ext_raw_parse(NULL, tls_server_clienthello_ext, msg, + GNUTLS_EXT_RAW_FLAG_TLS_CLIENT_HELLO); ++return rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE ? 0 : rc; + } + + + # ifdef notdef_crashes + /* Make a note that we saw a status-response */ +--- a/src/tls-openssl.c ++++ b/src/tls-openssl.c +@@ -940,40 +940,39 @@ + + Returns: nothing + */ + + static void +-info_callback(SSL *s, int where, int ret) ++info_callback(SSL * s, int where, int ret) + { + DEBUG(D_tls) + { +- const uschar * str; ++ gstring * g = NULL; + +- if (where & SSL_ST_CONNECT) +- str = US"SSL_connect"; +- else if (where & SSL_ST_ACCEPT) +- str = US"SSL_accept"; +- else +- str = US"SSL info (undefined)"; ++ if (where & SSL_ST_CONNECT) g = string_append_listele(g, ',', US"SSL_connect"); ++ if (where & SSL_ST_ACCEPT) g = string_append_listele(g, ',', US"SSL_accept"); ++ if (where & SSL_CB_LOOP) g = string_append_listele(g, ',', US"state_chg"); ++ if (where & SSL_CB_EXIT) g = string_append_listele(g, ',', US"hshake_exit"); ++ if (where & SSL_CB_READ) g = string_append_listele(g, ',', US"read"); ++ if (where & SSL_CB_WRITE) g = string_append_listele(g, ',', US"write"); ++ if (where & SSL_CB_ALERT) g = string_append_listele(g, ',', US"alert"); ++ if (where & SSL_CB_HANDSHAKE_START) g = string_append_listele(g, ',', US"hshake_start"); ++ if (where & SSL_CB_HANDSHAKE_DONE) g = string_append_listele(g, ',', US"hshake_done"); + + if (where & SSL_CB_LOOP) +- debug_printf("%s: %s\n", str, SSL_state_string_long(s)); ++ debug_printf("SSL %s: %s\n", g->s, SSL_state_string_long(s)); + else if (where & SSL_CB_ALERT) +- debug_printf("SSL3 alert %s:%s:%s\n", +- str = where & SSL_CB_READ ? US"read" : US"write", ++ debug_printf("SSL %s %s:%s\n", g->s, + SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); + else if (where & SSL_CB_EXIT) + { +- if (ret == 0) +- debug_printf("%s: failed in %s\n", str, SSL_state_string_long(s)); +- else if (ret < 0) +- debug_printf("%s: error in %s\n", str, SSL_state_string_long(s)); ++ if (ret <= 0) ++ debug_printf("SSL %s: %s in %s\n", g->s, ++ ret == 0 ? "failed" : "error", SSL_state_string_long(s)); + } +- else if (where & SSL_CB_HANDSHAKE_START) +- debug_printf("%s: hshake start: %s\n", str, SSL_state_string_long(s)); +- else if (where & SSL_CB_HANDSHAKE_DONE) +- debug_printf("%s: hshake done: %s\n", str, SSL_state_string_long(s)); ++ else if (where & (SSL_CB_HANDSHAKE_START | SSL_CB_HANDSHAKE_DONE)) ++ debug_printf("SSL %s: %s\n", g->s, SSL_state_string_long(s)); + } + } + + #ifdef OPENSSL_HAVE_KEYLOG_CB + static void diff --git a/debian/patches/75_18-Fix-Build-with-libopendmarc-1.4.x-fixes-2728.patch b/debian/patches/75_18-Fix-Build-with-libopendmarc-1.4.x-fixes-2728.patch new file mode 100644 index 0000000..f261d62 --- /dev/null +++ b/debian/patches/75_18-Fix-Build-with-libopendmarc-1.4.x-fixes-2728.patch @@ -0,0 +1,88 @@ +From 1561c5d88b3a23a4348d8e3c1ce28554fcbcfe46 Mon Sep 17 00:00:00 2001 +From: "Heiko Schlittermann (HS12-RIPE)" +Date: Sat, 15 Oct 2022 19:30:58 +0200 +Subject: [PATCH 1/2] Fix: Build with libopendmarc 1.4.x (fixes 2728) + +--- + doc/ChangeLog | 3 +++ + src/EDITME | 7 +++++-- + src/config.h.defaults | 1 + + src/dmarc.c | 7 ++++++- + 4 files changed, 15 insertions(+), 3 deletions(-) + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -13,10 +13,13 @@ + Debug help from Graeme Fowler. + + JH/10 GnuTLS: fix for (IOT?) clients offering no TLS extensions at all. + Find and fix by Jasen Betts. + ++HS/01 Bug 2728: Introduce EDITME option "DMARC_API" to work around incompatible ++ API changes in libopendmarc. ++ + + + Exim version 4.96 + ----------------- + +--- a/src/EDITME ++++ b/src/EDITME +@@ -600,18 +600,21 @@ + + # EXPERIMENTAL_DCC=yes + + # Uncomment the following line to add DMARC checking capability, implemented + # using libopendmarc libraries. You must have SPF and DKIM support enabled also. +-# Library version libopendmarc-1.4.1-1.fc33.x86_64 (on Fedora 33) is known broken; +-# 1.3.2-3 works. I seems that the OpenDMARC project broke their API. + # SUPPORT_DMARC=yes + # CFLAGS += -I/usr/local/include + # LDFLAGS += -lopendmarc + # Uncomment the following if you need to change the default. You can + # override it at runtime (main config option dmarc_tld_file) + # DMARC_TLD_FILE=/etc/exim/opendmarc.tlds ++# ++# Library version libopendmarc-1.4.1-1.fc33.x86_64 (on Fedora 33) is known broken; ++# 1.3.2-3 works. It seems that the OpenDMARC project broke their API. ++# Use this option if you need to build with an old library (1.3.x) ++# DMARC_API=100300 + + # Uncomment the following line to add ARC (Authenticated Received Chain) + # support. You must have SPF and DKIM support enabled also. + # EXPERIMENTAL_ARC=yes + +--- a/src/config.h.defaults ++++ b/src/config.h.defaults +@@ -148,10 +148,11 @@ + #define STRING_SPRINTF_BUFFER_SIZE (8192 * 4) + + #define SUPPORT_CRYPTEQ + #define SUPPORT_DANE + #define SUPPORT_DMARC ++#define DMARC_API 100400 + #define DMARC_TLD_FILE "/etc/exim/opendmarc.tlds" + #define SUPPORT_I18N + #define SUPPORT_I18N_2008 + #define SUPPORT_MAILDIR + #define SUPPORT_MAILSTORE +--- a/src/dmarc.c ++++ b/src/dmarc.c +@@ -457,11 +457,16 @@ + dkim_result = vs == PDKIM_VERIFY_PASS ? DMARC_POLICY_DKIM_OUTCOME_PASS : + vs == PDKIM_VERIFY_FAIL ? DMARC_POLICY_DKIM_OUTCOME_FAIL : + vs == PDKIM_VERIFY_INVALID ? DMARC_POLICY_DKIM_OUTCOME_TMPFAIL : + DMARC_POLICY_DKIM_OUTCOME_NONE; + libdm_status = opendmarc_policy_store_dkim(dmarc_pctx, US sig->domain, +- dkim_result, US""); ++/* The opendmarc project broke its API in a way we can't detect * easily. ++ * The EDITME provides a DMARC_API variable */ ++#if DMARC_API >= 100400 ++ sig->selector, ++#endif ++ dkim_result, US""); + DEBUG(D_receive) + debug_printf("DMARC adding DKIM sender domain = %s\n", sig->domain); + if (libdm_status != DMARC_PARSE_OKAY) + log_write(0, LOG_MAIN|LOG_PANIC, + "failure to store dkim (%s) for DMARC: %s", diff --git a/debian/patches/75_19-DMARC-fix-use-after-free-in-dmarc_dns_lookup.patch b/debian/patches/75_19-DMARC-fix-use-after-free-in-dmarc_dns_lookup.patch new file mode 100644 index 0000000..e8bda9e --- /dev/null +++ b/debian/patches/75_19-DMARC-fix-use-after-free-in-dmarc_dns_lookup.patch @@ -0,0 +1,39 @@ +From 12fb3842f81bcbd4a4519d5728f2d7e0e3ca1445 Mon Sep 17 00:00:00 2001 +From: Lorenz Brun +Date: Fri, 14 Oct 2022 21:02:51 +0200 +Subject: [PATCH 2/2] DMARC: fix use-after-free in dmarc_dns_lookup + +This fixes a use-after-free in dmarc_dns_lookup where the result +of dns_lookup in dnsa is freed before the required data is copied out. + +Fixes: 9258363 ("DNS: explicit alloc/free of workspace") +--- + src/dmarc.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/src/dmarc.c b/src/dmarc.c +index ad0c26c91..53c2752ac 100644 +--- a/src/dmarc.c ++++ b/src/dmarc.c +@@ -226,16 +226,17 @@ dns_scan dnss; + int rc = dns_lookup(dnsa, string_sprintf("_dmarc.%s", dom), T_TXT, NULL); + + if (rc == DNS_SUCCEED) + for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; + rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) + if (rr->type == T_TXT && rr->size > 3) + { ++ uschar *record = string_copyn_taint(US rr->data, rr->size, GET_TAINTED); + store_free_dns_answer(dnsa); +- return string_copyn_taint(US rr->data, rr->size, GET_TAINTED); ++ return record; + } + store_free_dns_answer(dnsa); + return NULL; + } + + + static int +-- +2.35.1 + diff --git a/debian/patches/75_22-Fix-daemon-startup.-Bug-2930.patch b/debian/patches/75_22-Fix-daemon-startup.-Bug-2930.patch new file mode 100644 index 0000000..2a3434f --- /dev/null +++ b/debian/patches/75_22-Fix-daemon-startup.-Bug-2930.patch @@ -0,0 +1,68 @@ +From 221321d2c51b83d1feced80ecd6c2fe33ec5456c Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Thu, 3 Nov 2022 20:08:25 +0000 +Subject: [PATCH 1/2] Fix daemon startup. Bug 2930 + +Broken-by: 7d5055276a +--- + doc/ChangeLog | 4 ++++ + src/daemon.c | 8 ++++++-- + 2 files changed, 10 insertions(+), 2 deletions(-) + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -16,10 +16,14 @@ + Find and fix by Jasen Betts. + + HS/01 Bug 2728: Introduce EDITME option "DMARC_API" to work around incompatible + API changes in libopendmarc. + ++JH/12 Bug 2930: Fix daemon startup. When started from any process apart from ++ pid 1, in the normal "background daemon" mode, having to drop process- ++ group leadership also lost track of needing to create listener sockets. ++ + + + Exim version 4.96 + ----------------- + +--- a/src/daemon.c ++++ b/src/daemon.c +@@ -1744,19 +1744,23 @@ + { + /* If the parent process of this one has pid == 1, we are re-initializing the + daemon as the result of a SIGHUP. In this case, there is no need to do + anything, because the controlling terminal has long gone. Otherwise, fork, in + case current process is a process group leader (see 'man setsid' for an +- explanation) before calling setsid(). */ ++ explanation) before calling setsid(). ++ All other forks want daemon_listen cleared. Rather than blow a register, jsut ++ restore it here. */ + + if (getppid() != 1) + { ++ BOOL daemon_listen = f.daemon_listen; + pid_t pid = exim_fork(US"daemon"); + if (pid < 0) log_write(0, LOG_MAIN|LOG_PANIC_DIE, + "fork() failed when starting daemon: %s", strerror(errno)); + if (pid > 0) exit(EXIT_SUCCESS); /* in parent process, just exit */ + (void)setsid(); /* release controlling terminal */ ++ f.daemon_listen = daemon_listen; + } + } + + /* We are now in the disconnected, daemon process (unless debugging). Set up + the listening sockets if required. */ +@@ -2090,11 +2094,11 @@ + { /* found; append port to list */ + for (p = i2->log; *p; ) p++; /* end of existing string */ + if (*--p == '}') *p = '\0'; /* drop EOL */ + while (isdigit(*--p)) ; /* char before port */ + +- i2->log = *p == ':' /* no list yet? */ ++ i2->log = *p == ':' /* no list yet? { */ + ? string_sprintf("%.*s{%s,%d}", + (int)(p - i2->log + 1), i2->log, p+1, ipa->port) + : string_sprintf("%s,%d}", i2->log, ipa->port); + ipa->log = NULL; + break; diff --git a/debian/patches/75_23-Fix-reccipients-after-run.-.-Bug-2929.patch b/debian/patches/75_23-Fix-reccipients-after-run.-.-Bug-2929.patch new file mode 100644 index 0000000..1c98ef2 --- /dev/null +++ b/debian/patches/75_23-Fix-reccipients-after-run.-.-Bug-2929.patch @@ -0,0 +1,45 @@ +From 6b331d5834d12bdda21857cd6fffac17038ce3c7 Mon Sep 17 00:00:00 2001 +From: Ruben Jenster +Date: Thu, 3 Nov 2022 21:38:15 +0000 +Subject: [PATCH 2/2] Fix $reccipients after ${run...}. Bug 2929 + +Broken-by: cfe6acff2d +--- + doc/ChangeLog | 3 +++ + src/transport.c | 3 ++- + 2 files changed, 5 insertions(+), 1 deletion(-) + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -20,10 +20,13 @@ + + JH/12 Bug 2930: Fix daemon startup. When started from any process apart from + pid 1, in the normal "background daemon" mode, having to drop process- + group leadership also lost track of needing to create listener sockets. + ++JH/13 Bug 2929: Fix using $recipients after ${run...}. A change made for 4.96 ++ resulted in the variable appearing empty. Find and fix by Ruben Jenster. ++ + + + Exim version 4.96 + ----------------- + +--- a/src/transport.c ++++ b/src/transport.c +@@ -2342,13 +2342,14 @@ + /* Handle normal expansion string */ + + else + { + const uschar *expanded_arg; ++ BOOL enable_dollar_recipients_g = f.enable_dollar_recipients; + f.enable_dollar_recipients = allow_dollar_recipients; + expanded_arg = expand_cstring(argv[i]); +- f.enable_dollar_recipients = FALSE; ++ f.enable_dollar_recipients = enable_dollar_recipients_g; + + if (!expanded_arg) + { + uschar *msg = string_sprintf("Expansion of \"%s\" " + "from command \"%s\" in %s failed: %s", diff --git a/debian/patches/75_31-Fix-regext-substring-capture-variables-for-null-matc.patch b/debian/patches/75_31-Fix-regext-substring-capture-variables-for-null-matc.patch new file mode 100644 index 0000000..bd250f2 --- /dev/null +++ b/debian/patches/75_31-Fix-regext-substring-capture-variables-for-null-matc.patch @@ -0,0 +1,79 @@ +From e63825824cc406c160ccbf2b154c5d81b168604a Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Fri, 11 Nov 2022 00:05:59 +0000 +Subject: [PATCH 1/2] Fix regext substring capture variables for null matches. + Bug 2933 + +broken-by: 59d66fdc13f0 +--- + doc/ChangeLog | 5 +++++ + src/exim.c | 2 ++ + src/malware.c | 3 +++ + src/regex.c | 2 +- + 4 files changed, 11 insertions(+), 1 deletion(-) + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -22,10 +22,15 @@ + pid 1, in the normal "background daemon" mode, having to drop process- + group leadership also lost track of needing to create listener sockets. + + JH/13 Bug 2929: Fix using $recipients after ${run...}. A change made for 4.96 + resulted in the variable appearing empty. Find and fix by Ruben Jenster. ++ ++JH/14 Bug 2933: Fix regex substring match variables for null matches. Since 4.96 ++ a capture group which obtained no text (eg. "(abc)*" matching zero ++ occurrences) could cause a segfault if the corresponding $ was ++ expanded. + + + + Exim version 4.96 + ----------------- +--- a/src/exim.c ++++ b/src/exim.c +@@ -167,10 +167,12 @@ + for (int matchnum = setup < 0 ? 0 : 1; matchnum < res; matchnum++) + { + PCRE2_SIZE len; + pcre2_substring_get_bynumber(md, matchnum, + (PCRE2_UCHAR **)&expand_nstring[expand_nmax], &len); ++ if (!expand_nstring[expand_nmax]) ++ { expand_nstring[expand_nmax] = US""; len = 0; } + expand_nlength[expand_nmax++] = (int)len; + } + expand_nmax--; + } + else if (res != PCRE2_ERROR_NOMATCH) DEBUG(D_any) +--- a/src/malware.c ++++ b/src/malware.c +@@ -323,11 +323,14 @@ + int i = pcre2_match(cre, text, PCRE2_ZERO_TERMINATED, 0, 0, md, pcre_mtc_ctx); + PCRE2_UCHAR * substr = NULL; + PCRE2_SIZE slen; + + if (i >= 2) /* Got it */ ++ { + pcre2_substring_get_bynumber(md, 1, &substr, &slen); ++ if (!substr) substr = US""; ++ } + return US substr; + } + + static const pcre2_code * + m_pcre_nextinlist(const uschar ** list, int * sep, +--- a/src/regex.c ++++ b/src/regex.c +@@ -84,11 +84,11 @@ + for (int nn = 1; nn < n; nn++) + { + PCRE2_UCHAR * cstr; + PCRE2_SIZE cslen; + pcre2_substring_get_bynumber(md, nn, &cstr, &cslen); +- regex_vars[nn-1] = CUS cstr; ++ regex_vars[nn-1] = cstr ? CUS cstr : CUS""; + } + + return OK; + } + } diff --git a/debian/patches/75_32-Fix-regex-substring-capture-variables-for-null-match.patch b/debian/patches/75_32-Fix-regex-substring-capture-variables-for-null-match.patch new file mode 100644 index 0000000..7c2c14a --- /dev/null +++ b/debian/patches/75_32-Fix-regex-substring-capture-variables-for-null-match.patch @@ -0,0 +1,94 @@ +From 7ad1a2b2cc57b5f4bcb59186a9a8abcbed9f4f76 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Fri, 11 Nov 2022 18:22:00 +0000 +Subject: [PATCH 2/2] Fix regex substring capture variables for null matches + (again). Bug 2933 + +Broken-by: 59d66fdc13f0 +--- + src/exim.c | 11 +++++------ + src/malware.c | 10 +++++----- + src/regex.c | 8 ++++---- + test/aux-var-src/0383.F | 4 ++-- + test/log/0383 | 4 ++-- + test/mail/0383.CALLER | 8 ++++---- + test/scripts/0000-Basic/0002 | 2 ++ + test/stdout/0002 | 2 ++ + 8 files changed, 26 insertions(+), 23 deletions(-) + +--- a/src/exim.c ++++ b/src/exim.c +@@ -160,20 +160,19 @@ + PCRE_EOPT | options, md, pcre_mtc_ctx); + BOOL yield; + + if ((yield = (res >= 0))) + { ++ PCRE2_SIZE * ovec = pcre2_get_ovector_pointer(md); + res = pcre2_get_ovector_count(md); + expand_nmax = setup < 0 ? 0 : setup + 1; + for (int matchnum = setup < 0 ? 0 : 1; matchnum < res; matchnum++) + { +- PCRE2_SIZE len; +- pcre2_substring_get_bynumber(md, matchnum, +- (PCRE2_UCHAR **)&expand_nstring[expand_nmax], &len); +- if (!expand_nstring[expand_nmax]) +- { expand_nstring[expand_nmax] = US""; len = 0; } +- expand_nlength[expand_nmax++] = (int)len; ++ int off = matchnum * 2; ++ int len = ovec[off + 1] - ovec[off]; ++ expand_nstring[expand_nmax] = string_copyn(subject + ovec[off], len); ++ expand_nlength[expand_nmax++] = len; + } + expand_nmax--; + } + else if (res != PCRE2_ERROR_NOMATCH) DEBUG(D_any) + { +--- a/src/malware.c ++++ b/src/malware.c +@@ -319,19 +319,19 @@ + uschar * + m_pcre_exec(const pcre2_code * cre, uschar * text) + { + pcre2_match_data * md = pcre2_match_data_create(2, pcre_gen_ctx); + int i = pcre2_match(cre, text, PCRE2_ZERO_TERMINATED, 0, 0, md, pcre_mtc_ctx); +-PCRE2_UCHAR * substr = NULL; +-PCRE2_SIZE slen; ++uschar * substr = NULL; + + if (i >= 2) /* Got it */ + { +- pcre2_substring_get_bynumber(md, 1, &substr, &slen); +- if (!substr) substr = US""; ++ PCRE2_SIZE * ovec = pcre2_get_ovector_pointer(md); ++ int len = ovec[3] - ovec[2]; ++ substr = string_copyn(text + ovec[2], len); + } +-return US substr; ++return substr; + } + + static const pcre2_code * + m_pcre_nextinlist(const uschar ** list, int * sep, + char * listerr, uschar ** errstr) +--- a/src/regex.c ++++ b/src/regex.c +@@ -81,14 +81,14 @@ + sizeof(regex_match_string_buffer)-1); + regex_match_string = regex_match_string_buffer; + + for (int nn = 1; nn < n; nn++) + { +- PCRE2_UCHAR * cstr; +- PCRE2_SIZE cslen; +- pcre2_substring_get_bynumber(md, nn, &cstr, &cslen); +- regex_vars[nn-1] = cstr ? CUS cstr : CUS""; ++ PCRE2_SIZE * ovec = pcre2_get_ovector_pointer(md); ++ int off = nn * 2; ++ int len = ovec[off + 1] - ovec[off]; ++ regex_vars[nn-1] = string_copyn(linebuffer + ovec[off], len); + } + + return OK; + } + } diff --git a/debian/patches/75_34-Fix-regex-substring-capture-commentary.-Bug-2933.patch b/debian/patches/75_34-Fix-regex-substring-capture-commentary.-Bug-2933.patch new file mode 100644 index 0000000..61d4483 --- /dev/null +++ b/debian/patches/75_34-Fix-regex-substring-capture-commentary.-Bug-2933.patch @@ -0,0 +1,48 @@ +From 9ba47886c71d40edc99b026a99edee269d9c9c6f Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Sat, 12 Nov 2022 12:38:22 +0000 +Subject: [PATCH] Fix regex substring capture - commentary. Bug 2933 + +Broken-by (corrected): 22ed7a5295f1 +--- + src/exim.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/src/exim.c b/src/exim.c +index 16c0184e0..625494ce4 100644 +--- a/src/exim.c ++++ b/src/exim.c +@@ -102,11 +102,13 @@ pcre_gen_mtc_ctx = pcre2_match_context_create(pcre_gen_ctx); + * Execute regular expression and set strings * + *************************************************/ + + /* This function runs a regular expression match, and sets up the pointers to + the matched substrings. The matched strings are copied so the lifetime of +-the subject is not a problem. ++the subject is not a problem. Matched strings will have the same taint status ++as the subject string (this is not a de-taint method, and must not be made so ++given the support for wildcards in REs). + + Arguments: + re the compiled expression + subject the subject string + options additional PCRE options +@@ -130,10 +132,15 @@ if ((yield = (res >= 0))) + PCRE2_SIZE * ovec = pcre2_get_ovector_pointer(md); + res = pcre2_get_ovector_count(md); + expand_nmax = setup < 0 ? 0 : setup + 1; + for (int matchnum = setup < 0 ? 0 : 1; matchnum < res; matchnum++) + { ++ /* Although PCRE2 has a pcre2_substring_get_bynumber() conveneience, it ++ seems to return a bad pointer when a capture group had no data, eg. (.*) ++ matching zero letters. So use the underlying ovec and hope (!) that the ++ offsets are sane (including that case). Should we go further and range- ++ check each one vs. the subject string length? */ + int off = matchnum * 2; + int len = ovec[off + 1] - ovec[off]; + expand_nstring[expand_nmax] = string_copyn(subject + ovec[off], len); + expand_nlength[expand_nmax++] = len; + } +-- +2.35.1 + diff --git a/debian/patches/75_37-OpenSSL-when-preloading-creds-do-the-server-certs-be.patch b/debian/patches/75_37-OpenSSL-when-preloading-creds-do-the-server-certs-be.patch new file mode 100644 index 0000000..2e21065 --- /dev/null +++ b/debian/patches/75_37-OpenSSL-when-preloading-creds-do-the-server-certs-be.patch @@ -0,0 +1,232 @@ +From 7f65a63b60c6ea86db683ac00e221939f3bb1d47 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Tue, 25 Oct 2022 21:26:30 +0100 +Subject: [PATCH 1/2] OpenSSL: when preloading creds do the server certs before + the OCSP proofs so that the latter can ve verified before loading + +--- + src/tls-openssl.c | 113 ++++++++++++++++++++++-------------------- + 1 file changed, 58 insertions(+), 55 deletions(-) + +diff --git a/src/tls-openssl.c b/src/tls-openssl.c +index 68ad6f15b..fdf0d92b2 100644 +--- a/src/tls-openssl.c ++++ b/src/tls-openssl.c +@@ -441,14 +441,16 @@ exim_openssl_state_st state_server = {.is_server = TRUE}; + static int + setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, + uschar ** errstr ); + + /* Callbacks */ + #ifndef DISABLE_OCSP + static int tls_server_stapling_cb(SSL *s, void *arg); ++static void x509_stack_dump_cert_s_names(const STACK_OF(X509) * sk); ++static void x509_store_dump_cert_s_names(X509_STORE * store); + #endif + + + + /* Daemon-called, before every connection, key create/rotate */ + #ifndef DISABLE_TLS_RESUME + static void tk_init(void); +@@ -1307,15 +1309,14 @@ ocsp_load_response(exim_openssl_state_st * state, const uschar * filename, + { + BIO * bio; + OCSP_RESPONSE * resp; + OCSP_BASICRESP * basic_response; + OCSP_SINGLERESP * single_response; + ASN1_GENERALIZEDTIME * rev, * thisupd, * nextupd; + STACK_OF(X509) * sk; +-unsigned long verify_flags; + int status, reason, i; + + DEBUG(D_tls) + debug_printf("tls_ocsp_file (%s) '%s'\n", is_pem ? "PEM" : "DER", filename); + + if (!filename || !*filename) return; + +@@ -1372,28 +1373,28 @@ if ((status = OCSP_response_status(resp)) != OCSP_RESPONSE_STATUS_SUCCESSFUL) + if (!(basic_response = OCSP_response_get1_basic(resp))) + { + DEBUG(D_tls) + debug_printf("OCSP response parse error: unable to extract basic response.\n"); + goto bad; + } + +-sk = state->verify_stack; +-verify_flags = OCSP_NOVERIFY; /* check sigs, but not purpose */ ++sk = state->verify_stack; /* set by setup_certs() / chain_from_pem_file() */ + + /* May need to expose ability to adjust those flags? + OCSP_NOSIGS OCSP_NOVERIFY OCSP_NOCHAIN OCSP_NOCHECKS OCSP_NOEXPLICIT + OCSP_TRUSTOTHER OCSP_NOINTERN */ + +-/* This does a full verify on the OCSP proof before we load it for serving +-up; possibly overkill - just date-checks might be nice enough. ++/* This does a partial verify (only the signer link, not the whole chain-to-CA) ++on the OCSP proof before we load it for serving up; possibly overkill - ++just date-checks might be nice enough. + + OCSP_basic_verify takes a "store" arg, but does not +-use it for the chain verification, which is all we do +-when OCSP_NOVERIFY is set. The content from the wire +-"basic_response" and a cert-stack "sk" are all that is used. ++use it for the chain verification, when OCSP_NOVERIFY is set. ++The content from the wire "basic_response" and a cert-stack "sk" are all ++that is used. + + We have a stack, loaded in setup_certs() if tls_verify_certificates + was a file (not a directory, or "system"). It is unfortunate we + cannot used the connection context store, as that would neatly + handle the "system" case too, but there seems to be no library + function for getting a stack from a store. + [ In OpenSSL 1.1 - ? X509_STORE_CTX_get0_chain(ctx) ? ] +@@ -1402,15 +1403,15 @@ SNI handling. + + Separately we might try to replace using OCSP_basic_verify() - which seems to not + be a public interface into the OpenSSL library (there's no manual entry) - + But what with? We also use OCSP_basic_verify in the client stapling callback. + And there we NEED it; we must verify that status... unless the + library does it for us anyway? */ + +-if ((i = OCSP_basic_verify(basic_response, sk, NULL, verify_flags)) < 0) ++if ((i = OCSP_basic_verify(basic_response, sk, NULL, OCSP_NOVERIFY)) < 0) + { + DEBUG(D_tls) + { + ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring)); + debug_printf("OCSP response verify failure: %s\n", US ssl_errstring); + } + goto bad; +@@ -1747,61 +1748,18 @@ if (opt_unset_or_noexpand(tls_eccurve)) + if (init_ecdh(ctx, &dummy_errstr)) + state_server.lib_state.ecdh = TRUE; + } + else + DEBUG(D_tls) debug_printf("TLS: not preloading ECDH curve for server\n"); + + #if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT) +-/* If we can, preload the server-side cert, key and ocsp */ +- +-if ( opt_set_and_noexpand(tls_certificate) +-# ifndef DISABLE_OCSP +- && opt_unset_or_noexpand(tls_ocsp_file) +-#endif +- && opt_unset_or_noexpand(tls_privatekey)) +- { +- /* Set watches on the filenames. The implementation does de-duplication +- so we can just blindly do them all. */ +- +- if ( tls_set_watch(tls_certificate, TRUE) +-# ifndef DISABLE_OCSP +- && tls_set_watch(tls_ocsp_file, TRUE) +-#endif +- && tls_set_watch(tls_privatekey, TRUE)) +- { +- state_server.certificate = tls_certificate; +- state_server.privatekey = tls_privatekey; +-#ifndef DISABLE_OCSP +- state_server.u_ocsp.server.file = tls_ocsp_file; +-#endif +- +- DEBUG(D_tls) debug_printf("TLS: preloading server certs\n"); +- if (tls_expand_session_files(ctx, &state_server, &dummy_errstr) == OK) +- state_server.lib_state.conn_certs = TRUE; +- } +- } +-else if ( !tls_certificate && !tls_privatekey +-# ifndef DISABLE_OCSP +- && !tls_ocsp_file +-#endif +- ) +- { /* Generate & preload a selfsigned cert. No files to watch. */ +- if (tls_expand_session_files(ctx, &state_server, &dummy_errstr) == OK) +- { +- state_server.lib_state.conn_certs = TRUE; +- lifetime = f.running_in_test_harness ? 2 : 60 * 60; /* 1 hour */ +- } +- } +-else +- DEBUG(D_tls) debug_printf("TLS: not preloading server certs\n"); +- +- + /* If we can, preload the Authorities for checking client certs against. + Actual choice to do verify is made (tls_{,try_}verify_hosts) +-at TLS conn startup */ ++at TLS conn startup. ++Do this before the server ocsp so that its info can verify the ocsp. */ + + if ( opt_set_and_noexpand(tls_verify_certificates) + && opt_unset_or_noexpand(tls_crl)) + { + /* Watch the default dir also as they are always included */ + + if ( tls_set_watch(CUS X509_get_default_cert_file(), FALSE) +@@ -1809,18 +1767,63 @@ if ( opt_set_and_noexpand(tls_verify_certificates) + && tls_set_watch(tls_crl, FALSE)) + { + DEBUG(D_tls) debug_printf("TLS: preloading CA bundle for server\n"); + + if (setup_certs(ctx, tls_verify_certificates, tls_crl, NULL, &dummy_errstr) + == OK) + state_server.lib_state.cabundle = TRUE; +- } ++ ++ /* If we can, preload the server-side cert, key and ocsp */ ++ ++ if ( opt_set_and_noexpand(tls_certificate) ++# ifndef DISABLE_OCSP ++ && opt_unset_or_noexpand(tls_ocsp_file) ++# endif ++ && opt_unset_or_noexpand(tls_privatekey)) ++ { ++ /* Set watches on the filenames. The implementation does de-duplication ++ so we can just blindly do them all. */ ++ ++ if ( tls_set_watch(tls_certificate, TRUE) ++# ifndef DISABLE_OCSP ++ && tls_set_watch(tls_ocsp_file, TRUE) ++# endif ++ && tls_set_watch(tls_privatekey, TRUE)) ++ { ++ state_server.certificate = tls_certificate; ++ state_server.privatekey = tls_privatekey; ++#ifndef DISABLE_OCSP ++ state_server.u_ocsp.server.file = tls_ocsp_file; ++# endif ++ ++ DEBUG(D_tls) debug_printf("TLS: preloading server certs\n"); ++ if (tls_expand_session_files(ctx, &state_server, &dummy_errstr) == OK) ++ state_server.lib_state.conn_certs = TRUE; ++ } ++ } ++ else if ( !tls_certificate && !tls_privatekey ++# ifndef DISABLE_OCSP ++ && !tls_ocsp_file ++# endif ++ ) ++ { /* Generate & preload a selfsigned cert. No files to watch. */ ++ if (tls_expand_session_files(ctx, &state_server, &dummy_errstr) == OK) ++ { ++ state_server.lib_state.conn_certs = TRUE; ++ lifetime = f.running_in_test_harness ? 2 : 60 * 60; /* 1 hour */ ++ } ++ } ++ else ++ DEBUG(D_tls) debug_printf("TLS: not preloading server certs\n"); ++ } + } + else + DEBUG(D_tls) debug_printf("TLS: not preloading CA bundle for server\n"); ++ ++ + #endif /* EXIM_HAVE_INOTIFY */ + + + /* If we can, preload the ciphers control string */ + + if (opt_set_and_noexpand(tls_require_ciphers)) + { +-- +2.35.1 + diff --git a/debian/patches/75_38-OpenSSL-fix-double-expansion-of-tls_verify_certifica.patch b/debian/patches/75_38-OpenSSL-fix-double-expansion-of-tls_verify_certifica.patch new file mode 100644 index 0000000..09e4f11 --- /dev/null +++ b/debian/patches/75_38-OpenSSL-fix-double-expansion-of-tls_verify_certifica.patch @@ -0,0 +1,217 @@ +From 62b97c2ecf148ee86053d82e5509e4c3a5a20054 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Sat, 29 Oct 2022 22:33:43 +0100 +Subject: [PATCH 2/2] OpenSSL: fix double-expansion of tls_verify_certificates + +--- + src/tls-openssl.c | 66 +++++++++++++++++++++---------------------- + 1 file changed, 33 insertions(+), 33 deletions(-) + +diff --git a/src/tls-openssl.c b/src/tls-openssl.c +index fdf0d92b2..2e09882d2 100644 +--- a/src/tls-openssl.c ++++ b/src/tls-openssl.c +@@ -435,15 +435,15 @@ typedef struct exim_openssl_state { + /* should figure out a cleanup of API to handle state preserved per + implementation, for various reasons, which can be void * in the APIs. + For now, we hack around it. */ + exim_openssl_state_st *client_static_state = NULL; /*XXX should not use static; multiple concurrent clients! */ + exim_openssl_state_st state_server = {.is_server = TRUE}; + + static int +-setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, ++setup_certs(SSL_CTX * sctx, uschar ** certs, uschar * crl, host_item * host, + uschar ** errstr ); + + /* Callbacks */ + #ifndef DISABLE_OCSP + static int tls_server_stapling_cb(SSL *s, void *arg); + static void x509_stack_dump_cert_s_names(const STACK_OF(X509) * sk); + static void x509_store_dump_cert_s_names(X509_STORE * store); +@@ -1762,18 +1762,18 @@ if ( opt_set_and_noexpand(tls_verify_certificates) + { + /* Watch the default dir also as they are always included */ + + if ( tls_set_watch(CUS X509_get_default_cert_file(), FALSE) + && tls_set_watch(tls_verify_certificates, FALSE) + && tls_set_watch(tls_crl, FALSE)) + { ++ uschar * v_certs = tls_verify_certificates; + DEBUG(D_tls) debug_printf("TLS: preloading CA bundle for server\n"); + +- if (setup_certs(ctx, tls_verify_certificates, tls_crl, NULL, &dummy_errstr) +- == OK) ++ if (setup_certs(ctx, &v_certs, tls_crl, NULL, &dummy_errstr) == OK) + state_server.lib_state.cabundle = TRUE; + + /* If we can, preload the server-side cert, key and ocsp */ + + if ( opt_set_and_noexpand(tls_certificate) + # ifndef DISABLE_OCSP + && opt_unset_or_noexpand(tls_ocsp_file) +@@ -1897,18 +1897,19 @@ if ( opt_set_and_noexpand(ob->tls_verify_certificates) + { + if ( !watch + || tls_set_watch(CUS X509_get_default_cert_file(), FALSE) + && tls_set_watch(ob->tls_verify_certificates, FALSE) + && tls_set_watch(ob->tls_crl, FALSE) + ) + { ++ uschar * v_certs = ob->tls_verify_certificates; + DEBUG(D_tls) + debug_printf("TLS: preloading CA bundle for transport '%s'\n", t->name); + +- if (setup_certs(ctx, ob->tls_verify_certificates, ++ if (setup_certs(ctx, &v_certs, + ob->tls_crl, dummy_host, &dummy_errstr) == OK) + ob->tls_preload.cabundle = TRUE; + } + } + else + DEBUG(D_tls) + debug_printf("TLS: not preloading CA bundle, for transport '%s'\n", t->name); +@@ -2238,22 +2239,20 @@ if (state->u_ocsp.server.file) + { + SSL_CTX_set_tlsext_status_cb(server_sni, tls_server_stapling_cb); + SSL_CTX_set_tlsext_status_arg(server_sni, state); + } + #endif + + { +- uschar * expcerts; +- if ( !expand_check(tls_verify_certificates, US"tls_verify_certificates", +- &expcerts, &dummy_errstr) +- || (rc = setup_certs(server_sni, expcerts, tls_crl, NULL, ++ uschar * v_certs = tls_verify_certificates; ++ if ((rc = setup_certs(server_sni, &v_certs, tls_crl, NULL, + &dummy_errstr)) != OK) + goto bad; + +- if (expcerts && *expcerts) ++ if (v_certs && *v_certs) + setup_cert_verify(server_sni, FALSE, verify_callback_server); + } + + /* do this after setup_certs, because this can require the certs for verifying + OCSP information. */ + if ((rc = tls_expand_session_files(server_sni, state, &dummy_errstr)) != OK) + goto bad; +@@ -3017,32 +3016,33 @@ return TRUE; + + + /* Called by both client and server startup; on the server possibly + repeated after a Server Name Indication. + + Arguments: + sctx SSL_CTX* to initialise +- certs certs file, expanded ++ certs certs file, returned expanded + crl CRL file or NULL + host NULL in a server; the remote host in a client + errstr error string pointer + + Returns: OK/DEFER/FAIL + */ + + static int +-setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, ++setup_certs(SSL_CTX * sctx, uschar ** certsp, uschar * crl, host_item * host, + uschar ** errstr) + { +-uschar *expcerts, *expcrl; ++uschar * expcerts, * expcrl; + +-if (!expand_check(certs, US"tls_verify_certificates", &expcerts, errstr)) ++if (!expand_check(*certsp, US"tls_verify_certificates", &expcerts, errstr)) + return DEFER; + DEBUG(D_tls) debug_printf("tls_verify_certificates: %s\n", expcerts); + ++*certsp = expcerts; + if (expcerts && *expcerts) + { + /* Tell the library to use its compiled-in location for the system default + CA bundle. Then add the ones specified in the config, if any. */ + + if (!SSL_CTX_set_default_verify_paths(sctx)) + return tls_error(US"SSL_CTX_set_default_verify_paths", host, NULL, errstr); +@@ -3330,28 +3330,28 @@ if (verify_check_host(&tls_verify_hosts) == OK) + server_verify_optional = FALSE; + else if (verify_check_host(&tls_try_verify_hosts) == OK) + server_verify_optional = TRUE; + else + goto skip_certs; + + { +- uschar * expcerts; +- if (!expand_check(tls_verify_certificates, US"tls_verify_certificates", +- &expcerts, errstr)) +- return DEFER; +- DEBUG(D_tls) debug_printf("tls_verify_certificates: %s\n", expcerts); ++ uschar * v_certs = tls_verify_certificates; + + if (state_server.lib_state.cabundle) +- { DEBUG(D_tls) debug_printf("TLS: CA bundle for server was preloaded\n"); } ++ { ++ DEBUG(D_tls) debug_printf("TLS: CA bundle for server was preloaded\n"); ++ setup_cert_verify(ctx, server_verify_optional, verify_callback_server); ++ } + else +- if ((rc = setup_certs(ctx, expcerts, tls_crl, NULL, errstr)) != OK) ++ { ++ if ((rc = setup_certs(ctx, &v_certs, tls_crl, NULL, errstr)) != OK) + return rc; +- +- if (expcerts && *expcerts) +- setup_cert_verify(ctx, server_verify_optional, verify_callback_server); ++ if (v_certs && *v_certs) ++ setup_cert_verify(ctx, server_verify_optional, verify_callback_server); ++ } + } + skip_certs: ; + + #ifndef DISABLE_TLS_RESUME + # if OPENSSL_VERSION_NUMBER < 0x30000000L + SSL_CTX_set_tlsext_ticket_key_cb(ctx, ticket_key_callback); + /* despite working, appears to always return failure, so ignoring */ +@@ -3606,28 +3606,28 @@ if ( ( ( !ob->tls_verify_hosts || !ob->tls_verify_hosts + client_verify_optional = FALSE; + else if (verify_check_given_host(CUSS &ob->tls_try_verify_hosts, host) == OK) + client_verify_optional = TRUE; + else + return OK; + + { +- uschar * expcerts; +- if (!expand_check(ob->tls_verify_certificates, US"tls_verify_certificates", +- &expcerts, errstr)) +- return DEFER; +- DEBUG(D_tls) debug_printf("tls_verify_certificates: %s\n", expcerts); ++ uschar * v_certs = ob->tls_verify_certificates; + + if (state->lib_state.cabundle) +- { DEBUG(D_tls) debug_printf("TLS: CA bundle was preloaded\n"); } ++ { ++ DEBUG(D_tls) debug_printf("TLS: CA bundle for tpt was preloaded\n"); ++ setup_cert_verify(ctx, client_verify_optional, verify_callback_client); ++ } + else +- if ((rc = setup_certs(ctx, expcerts, ob->tls_crl, host, errstr)) != OK) ++ { ++ if ((rc = setup_certs(ctx, &v_certs, ob->tls_crl, host, errstr)) != OK) + return rc; +- +- if (expcerts && *expcerts) +- setup_cert_verify(ctx, client_verify_optional, verify_callback_client); ++ if (v_certs && *v_certs) ++ setup_cert_verify(ctx, client_verify_optional, verify_callback_client); ++ } + } + + if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK) + { + state->verify_cert_hostnames = + #ifdef SUPPORT_I18N + string_domain_utf8_to_alabel(host->certname, NULL); +-- +2.35.1 + diff --git a/debian/patches/75_42-Fix-run-arg-parsing.patch b/debian/patches/75_42-Fix-run-arg-parsing.patch new file mode 100644 index 0000000..79e55d6 --- /dev/null +++ b/debian/patches/75_42-Fix-run-arg-parsing.patch @@ -0,0 +1,100 @@ +From 44b6e099b76f403a55e77650821f8a69e9d2682e Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Sat, 3 Dec 2022 23:13:53 +0000 +Subject: [PATCH] Fix ${run } arg parsing + . + Backported by Bryce Harrington for Ubuntu + +Broken-by: cfe6acff2ddc +--- + doc/ChangeLog | 4 ++++ + src/expand.c | 13 ++++++++++--- + src/transport.c | 4 +++- + test/scripts/0000-Basic/0002 | 2 ++ + test/stdout/0002 | 2 ++ + 5 files changed, 21 insertions(+), 4 deletions(-) + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -28,10 +28,14 @@ JH/13 Bug 2929: Fix using $recipients af + JH/14 Bug 2933: Fix regex substring match variables for null matches. Since 4.96 + a capture group which obtained no text (eg. "(abc)*" matching zero + occurrences) could cause a segfault if the corresponding $ was + expanded. + ++JH/15 Fix argument parsing for ${run } expansion. Previously, when an argument ++ included a close-brace character (eg. it itself used an expansion) an ++ error occurred. ++ + + + Exim version 4.96 + ----------------- + +--- a/src/expand.c ++++ b/src/expand.c +@@ -5529,11 +5529,11 @@ while (*s) + { + FILE * f; + const uschar * arg, ** argv; + BOOL late_expand = TRUE; + +- if ((expand_forbid & RDO_RUN) != 0) ++ if (expand_forbid & RDO_RUN) + { + expand_string_message = US"running a command is not permitted"; + goto EXPAND_FAILED; + } + +@@ -5561,16 +5561,22 @@ while (*s) + } + s++; + + if (late_expand) /* this is the default case */ + { +- int n = Ustrcspn(s, "}"); ++ int n; ++ const uschar * t; ++ /* Locate the end of the args */ ++ (void) expand_string_internal(s, TRUE, &t, TRUE, TRUE, NULL); ++ n = t - s; + arg = skipping ? NULL : string_copyn(s, n); + s += n; + } + else + { ++ DEBUG(D_expand) ++ debug_printf_indent("args string for ${run} expand before split\n"); + if (!(arg = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok))) + goto EXPAND_FAILED; + Uskip_whitespace(&s); + } + /*{*/ +--- a/src/transport.c ++++ b/src/transport.c +@@ -2187,10 +2187,12 @@ if (expand_arguments) + BOOL allow_dollar_recipients = addr && addr->parent + && Ustrcmp(addr->parent->address, "system-filter") == 0; + + for (int i = 0; argv[i]; i++) + { ++ DEBUG(D_expand) debug_printf_indent("arg %d\n", i); ++ + /* Handle special fudge for passing an address list */ + + if (addr && + (Ustrcmp(argv[i], "$pipe_addresses") == 0 || + Ustrcmp(argv[i], "${pipe_addresses}") == 0)) +@@ -2361,11 +2363,11 @@ if (expand_arguments) + } + else *errptr = msg; + return FALSE; + } + +- if ( f.running_in_test_harness && is_tainted(expanded_arg) ++ if ( f.running_in_test_harness && is_tainted(expanded_arg) + && Ustrcmp(etext, "queryprogram router") == 0) + { /* hack, would be good to not need it */ + DEBUG(D_transport) + debug_printf("SPECIFIC TESTSUITE EXEMPTION: tainted arg '%s'\n", + expanded_arg); diff --git a/debian/patches/75_50-Fix-logging-of-max-size-log-line.patch b/debian/patches/75_50-Fix-logging-of-max-size-log-line.patch new file mode 100644 index 0000000..55c983e --- /dev/null +++ b/debian/patches/75_50-Fix-logging-of-max-size-log-line.patch @@ -0,0 +1,82 @@ +From 1ed24e36e279c922d3366f6c3144570cc5f54d7a Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Mon, 19 Dec 2022 21:09:17 +0000 +Subject: [PATCH] Fix logging of max-size log line + +Broken-by: d12746bc15d8 +--- + doc/ChangeLog | 5 +++++ + src/log.c | 7 ++++--- + test/confs/0633 | 21 ++++++++++++++++++++ + test/scripts/0000-Basic/0633 | 9 +++++++++ + test/stderr/0633 | 38 ++++++++++++++++++++++++++++++++++++ + test/stdout/0633 | 15 ++++++++++++++ + 6 files changed, 92 insertions(+), 3 deletions(-) + create mode 100644 test/confs/0633 + create mode 100644 test/scripts/0000-Basic/0633 + create mode 100644 test/stderr/0633 + create mode 100644 test/stdout/0633 + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -32,10 +32,15 @@ JH/14 Bug 2933: Fix regex substring matc + + JH/15 Fix argument parsing for ${run } expansion. Previously, when an argument + included a close-brace character (eg. it itself used an expansion) an + error occurred. + ++JH/18 Fix a fencepost error in logging. Previously (since 4.92) when a log line ++ was exactly sized compared to the log buffer, a crash occurred with the ++ misleading message "bad memory reference; pool not found". ++ Found and traced by Jasen Betts. ++ + + + Exim version 4.96 + ----------------- + +--- a/src/log.c ++++ b/src/log.c +@@ -803,11 +803,11 @@ Returns: nothing + void + log_write(unsigned int selector, int flags, const char *format, ...) + { + int paniclogfd; + ssize_t written_len; +-gstring gs = { .size = LOG_BUFFER_SIZE-1, .ptr = 0, .s = log_buffer }; ++gstring gs = { .size = LOG_BUFFER_SIZE-2, .ptr = 0, .s = log_buffer }; + gstring * g; + va_list ap; + + /* If panic_recurseflag is set, we have failed to open the panic log. This is + the ultimate disaster. First try to write the message to a debug file and/or +@@ -949,15 +949,14 @@ DEBUG(D_any|D_v) + g->ptr = i; + g = string_cat(g, US"**** log string overflowed log buffer ****"); + } + va_end(ap); + +- g->size = LOG_BUFFER_SIZE; + g = string_catn(g, US"\n", 1); + debug_printf("%s", string_from_gstring(g)); + +- gs.size = LOG_BUFFER_SIZE-1; /* Having used the buffer for debug output, */ ++ gs.size = LOG_BUFFER_SIZE-2; /* Having used the buffer for debug output, */ + gs.ptr = 0; /* reset it for the real use. */ + gs.s = log_buffer; + } + /* If no log file is specified, we are in a mess. */ + +@@ -1035,10 +1034,12 @@ if ( flags & LOG_RECIPIENTS + if (LOG_BUFFER_SIZE - g->ptr < Ustrlen(s) + 3) break; + g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " %s", s); + } + } + ++/* actual size, now we are placing the newline (and space for NUL) */ ++gs.size = LOG_BUFFER_SIZE; + g = string_catn(g, US"\n", 1); + string_from_gstring(g); + + /* Handle loggable errors when running a utility, or when address testing. + Write to log_stderr unless debugging (when it will already have been written), diff --git a/debian/patches/75_55-Fix-recursion-on-dns_again_means_nonexist.-Bug-2911.patch b/debian/patches/75_55-Fix-recursion-on-dns_again_means_nonexist.-Bug-2911.patch new file mode 100644 index 0000000..bbbfbe9 --- /dev/null +++ b/debian/patches/75_55-Fix-recursion-on-dns_again_means_nonexist.-Bug-2911.patch @@ -0,0 +1,76 @@ +From 1d38781da934809e6ce0b8c3718c4b3bccdfe1d2 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Wed, 28 Dec 2022 19:39:06 +0000 +Subject: [PATCH] Fix recursion on dns_again_means_nonexist. Bug 2911 + +--- + doc/ChangeLog | 8 +++++ + src/dns.c | 12 ++++++++ + test/confs/2202 | 18 +++++++++-- + test/scripts/2200-dnsdb/2202 | 8 +++++ + test/stderr/2202 | 58 +++++++++++++++++++++++++++++++++++- + test/stdout/2202 | 8 +++++ + 6 files changed, 108 insertions(+), 4 deletions(-) + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -33,10 +33,18 @@ JH/14 Bug 2933: Fix regex substring matc + JH/18 Fix a fencepost error in logging. Previously (since 4.92) when a log line + was exactly sized compared to the log buffer, a crash occurred with the + misleading message "bad memory reference; pool not found". + Found and traced by Jasen Betts. + ++JH/19 Bug 2911: Fix a recursion in DNS lookups. Previously, if the main option ++ dns_again_means_nonexist included an element causing a DNS lookup which ++ iteslf returned DNS_AGAIN, unbounded recursion occurred. Possible results ++ included (though probably not limited to) a process crash from stack ++ memory limit, or from excessive open files. Replace this with a paniclog ++ whine (as this is likely a configuration error), and returning ++ DNS_NOMATCH. ++ + + + Exim version 4.96 + ----------------- + +--- a/src/dns.c ++++ b/src/dns.c +@@ -799,10 +799,11 @@ int + dns_basic_lookup(dns_answer * dnsa, const uschar * name, int type) + { + int rc; + #ifndef STAND_ALONE + const uschar * save_domain; ++static BOOL try_again_recursion = FALSE; + #endif + + /* DNS lookup failures of any kind are cached in a tree. This is mainly so that + a timeout on one domain doesn't happen time and time again for messages that + have many addresses in the same domain. We rely on the resolver and name server +@@ -903,15 +904,26 @@ if (dnsa->answerlen < 0) switch (h_errno + DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave TRY_AGAIN\n", + name, dns_text_type(type)); + + /* Cut this out for various test programs */ + #ifndef STAND_ALONE ++ if (try_again_recursion) ++ { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "dns_again_means_nonexist recursion seen for %s (assuming nonexist)", ++ name); ++ return dns_fail_return(name, type, dns_expire_from_soa(dnsa, type), DNS_NOMATCH); ++ } ++ ++ try_again_recursion = TRUE; + save_domain = deliver_domain; + deliver_domain = string_copy(name); /* set $domain */ + rc = match_isinlist(name, CUSS &dns_again_means_nonexist, 0, + &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL); + deliver_domain = save_domain; ++ try_again_recursion = FALSE; ++ + if (rc != OK) + { + DEBUG(D_dns) debug_printf("returning DNS_AGAIN\n"); + return dns_fail_return(name, type, 0, DNS_AGAIN); + } diff --git a/debian/patches/75_58-Close-server-smtp-socket-explicitly-on-connect-ACL-d.patch b/debian/patches/75_58-Close-server-smtp-socket-explicitly-on-connect-ACL-d.patch new file mode 100644 index 0000000..ddf54fa --- /dev/null +++ b/debian/patches/75_58-Close-server-smtp-socket-explicitly-on-connect-ACL-d.patch @@ -0,0 +1,50 @@ +From 57d70161718e02927a22d6a3481803b72035ac46 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Sat, 31 Dec 2022 13:37:17 +0000 +Subject: [PATCH] Close server smtp socket explicitly on connect ACL "drop" + +--- + src/smtp_in.c | 13 ++++++++ + test/confs/0022 | 2 ++ + test/log/0022 | 2 ++ + test/rejectlog/0022 | 3 ++ + test/scripts/0000-Basic/0022 | 13 ++++++++ + test/stderr/0022 | 60 ++++++++++++++++++------------------ + test/stdout/0022 | 6 ++++ + 7 files changed, 69 insertions(+), 30 deletions(-) + create mode 100644 test/rejectlog/0022 + +diff --git a/src/smtp_in.c b/src/smtp_in.c +index 1cfcc0404..6880e3c09 100644 +--- a/src/smtp_in.c ++++ b/src/smtp_in.c +@@ -3563,10 +3563,23 @@ log_write(L_smtp_connection, LOG_MAIN, "%s closed by DROP in ACL", + /* Run the not-quit ACL, but without any custom messages. This should not be a + problem, because we get here only if some other ACL has issued "drop", and + in that case, *its* custom messages will have been used above. */ + + smtp_notquit_exit(US"acl-drop", NULL, NULL); ++ ++/* An overenthusiastic fail2ban/iptables implimentation has been seen to result ++in the TCP conn staying open, and retrying, despite this process exiting. A ++malicious client could possibly do the same, tying up server netowrking ++resources. Close the socket explicitly to try to avoid that (there's a note in ++the Linux socket(7) manpage, SO_LINGER para, to the effect that exim() without ++close() results in the socket always lingering). */ ++ ++(void) poll_one_fd(fileno(smtp_in), POLLIN, 200); ++DEBUG(D_any) debug_printf_indent("SMTP(close)>>\n"); ++(void) fclose(smtp_in); ++(void) fclose(smtp_out); ++ + return 2; + } + + + +diff --git a/test/rejectlog/0022 b/test/rejectlog/0022 +new file mode 100644 +index 000000000..68e21fff3 +-- +2.39.0 + diff --git a/debian/patches/75_60-OpenSSL-fix-tls_eccurve-setting-explicit-curve-group.patch b/debian/patches/75_60-OpenSSL-fix-tls_eccurve-setting-explicit-curve-group.patch new file mode 100644 index 0000000..da05ba9 --- /dev/null +++ b/debian/patches/75_60-OpenSSL-fix-tls_eccurve-setting-explicit-curve-group.patch @@ -0,0 +1,184 @@ +From ca4014de81e6aa367aa0a54c49b4c3d4b137814c Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Sun, 1 Jan 2023 12:18:38 +0000 +Subject: [PATCH] OpenSSL: fix tls_eccurve setting explicit curve/group. Bug + 2954 + +--- + doc/ChangeLog | 4 +++ + src/tls-openssl.c | 39 ++++++++++++++---------- + test/confs/2148 | 54 ++++++++++++++++++++++++++++++++++ + test/confs/2149 | 39 +++++++++++++----------- + test/log/2148 | 48 ++++++++++++++++++++++++++++++ + test/log/2149 | 39 ++++++++++++------------ + test/paniclog/{2149 => 2148} | 0 + test/scripts/2100-OpenSSL/2148 | 50 +++++++++++++++++++++++++++++++ + test/scripts/2100-OpenSSL/2149 | 50 ++++++++++++++++--------------- + test/stderr/2148 | 5 ++++ + test/stderr/2149 | 3 -- + 11 files changed, 250 insertions(+), 81 deletions(-) + create mode 100644 test/confs/2148 + create mode 100644 test/log/2148 + rename test/paniclog/{2149 => 2148} (100%) + create mode 100644 test/scripts/2100-OpenSSL/2148 + create mode 100644 test/stderr/2148 + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -41,10 +41,14 @@ JH/19 Bug 2911: Fix a recursion in DNS l + included (though probably not limited to) a process crash from stack + memory limit, or from excessive open files. Replace this with a paniclog + whine (as this is likely a configuration error), and returning + DNS_NOMATCH. + ++JH/20 Bug 2954: (OpenSSL) Fix setting of explicit EC curve/group. Previously ++ this always failed, probably leading to the usual downgrade to in-clear ++ connections. ++ + + + Exim version 4.96 + ----------------- + +--- a/src/tls-openssl.c ++++ b/src/tls-openssl.c +@@ -657,16 +657,16 @@ if (dh_bitsize <= tls_dh_max_bits) + /* EVP_PKEY_free(pkey); crashes */ + #endif + } + else + DEBUG(D_tls) +- debug_printf("Diffie-Hellman initialized from %s with %d-bit prime\n", ++ debug_printf(" Diffie-Hellman initialized from %s with %d-bit prime\n", + dhexpanded ? dhexpanded : US"default", dh_bitsize); + } + else + DEBUG(D_tls) +- debug_printf("dhparams '%s' %d bits, is > tls_dh_max_bits limit of %d\n", ++ debug_printf(" dhparams '%s' %d bits, is > tls_dh_max_bits limit of %d\n", + dhexpanded ? dhexpanded : US"default", dh_bitsize, tls_dh_max_bits); + + #if OPENSSL_VERSION_NUMBER < 0x30000000L + DH_free(dh); + #endif +@@ -712,23 +712,31 @@ init_ecdh(SSL_CTX * sctx, uschar ** errs + #ifdef OPENSSL_NO_ECDH + return TRUE; + #else + + uschar * exp_curve; +-int nid; +-BOOL rv; ++int nid, rc; + + # ifndef EXIM_HAVE_ECDH + DEBUG(D_tls) +- debug_printf("No OpenSSL API to define ECDH parameters, skipping\n"); ++ debug_printf(" No OpenSSL API to define ECDH parameters, skipping\n"); + return TRUE; + # else + + if (!expand_check(tls_eccurve, US"tls_eccurve", &exp_curve, errstr)) + return FALSE; ++ ++/* Is the option deliberately empty? */ ++ + if (!exp_curve || !*exp_curve) ++ { ++#if OPENSSL_VERSION_NUMBER >= 0x10002000L ++ DEBUG(D_tls) debug_printf( " ECDH OpenSSL 1.0.2+: clearing curves list\n"); ++ (void) SSL_CTX_set1_curves(sctx, &nid, 0); ++#endif + return TRUE; ++ } + + /* "auto" needs to be handled carefully. + * OpenSSL < 1.0.2: we do not select anything, but fallback to prime256v1 + * OpenSSL < 1.1.0: we have to call SSL_CTX_set_ecdh_auto + * (openssl/ssl.h defines SSL_CTRL_SET_ECDH_AUTO) +@@ -737,27 +745,26 @@ if (!exp_curve || !*exp_curve) + */ + if (Ustrcmp(exp_curve, "auto") == 0) + { + #if OPENSSL_VERSION_NUMBER < 0x10002000L + DEBUG(D_tls) debug_printf( +- "ECDH OpenSSL < 1.0.2: temp key parameter settings: overriding \"auto\" with \"prime256v1\"\n"); ++ " ECDH OpenSSL < 1.0.2: temp key parameter settings: overriding \"auto\" with \"prime256v1\"\n"); + exp_curve = US"prime256v1"; + #else + # if defined SSL_CTRL_SET_ECDH_AUTO + DEBUG(D_tls) debug_printf( +- "ECDH OpenSSL 1.0.2+: temp key parameter settings: autoselection\n"); ++ " ECDH OpenSSL 1.0.2+: temp key parameter settings: autoselection\n"); + SSL_CTX_set_ecdh_auto(sctx, 1); + return TRUE; + # else + DEBUG(D_tls) debug_printf( +- "ECDH OpenSSL 1.1.0+: temp key parameter settings: default selection\n"); ++ " ECDH OpenSSL 1.1.0+: temp key parameter settings: library default selection\n"); + return TRUE; + # endif + #endif + } + +-DEBUG(D_tls) debug_printf("ECDH: curve '%s'\n", exp_curve); + if ( (nid = OBJ_sn2nid (CCS exp_curve)) == NID_undef + # ifdef EXIM_HAVE_OPENSSL_EC_NIST2NID + && (nid = EC_curve_nist2nid(CCS exp_curve)) == NID_undef + # endif + ) +@@ -777,27 +784,27 @@ if ( (nid = OBJ_sn2nid (CCS exp_c + } + + /* The "tmp" in the name here refers to setting a temporary key + not to the stability of the interface. */ + +- if ((rv = SSL_CTX_set_tmp_ecdh(sctx, ecdh) == 0)) ++ if ((rc = SSL_CTX_set_tmp_ecdh(sctx, ecdh) == 0)) + tls_error(string_sprintf("Error enabling '%s' curve", exp_curve), NULL, NULL, errstr); + else +- DEBUG(D_tls) debug_printf("ECDH: enabled '%s' curve\n", exp_curve); ++ DEBUG(D_tls) debug_printf(" ECDH: enabled '%s' curve\n", exp_curve); + EC_KEY_free(ecdh); + } + + #else /* v 3.0.0 + */ + +-if ((rv = SSL_CTX_set1_groups(sctx, &nid, 1)) == 0) ++if ((rc = SSL_CTX_set1_groups(sctx, &nid, 1)) == 0) + tls_error(string_sprintf("Error enabling '%s' group", exp_curve), NULL, NULL, errstr); + else +- DEBUG(D_tls) debug_printf("ECDH: enabled '%s' group\n", exp_curve); ++ DEBUG(D_tls) debug_printf(" ECDH: enabled '%s' group\n", exp_curve); + + #endif + +-return !rv; ++return !!rc; + + # endif /*EXIM_HAVE_ECDH*/ + #endif /*OPENSSL_NO_ECDH*/ + } + +@@ -1719,19 +1726,19 @@ state_server.lib_state.lib_ctx = ctx; + + /* Preload DH params and EC curve */ + + if (opt_unset_or_noexpand(tls_dhparam)) + { +- DEBUG(D_tls) debug_printf("TLS: preloading DH params for server\n"); ++ DEBUG(D_tls) debug_printf("TLS: preloading DH params '%s' for server\n", tls_dhparam); + if (init_dh(ctx, tls_dhparam, &dummy_errstr)) + state_server.lib_state.dh = TRUE; + } + else + DEBUG(D_tls) debug_printf("TLS: not preloading DH params for server\n"); + if (opt_unset_or_noexpand(tls_eccurve)) + { +- DEBUG(D_tls) debug_printf("TLS: preloading ECDH curve for server\n"); ++ DEBUG(D_tls) debug_printf("TLS: preloading ECDH curve '%s' for server\n", tls_eccurve); + if (init_ecdh(ctx, &dummy_errstr)) + state_server.lib_state.ecdh = TRUE; + } + else + DEBUG(D_tls) debug_printf("TLS: not preloading ECDH curve for server\n"); diff --git a/debian/patches/75_62-OpenSSL-Fix-tls_eccurve-on-earlier-versions-than-3.0.patch b/debian/patches/75_62-OpenSSL-Fix-tls_eccurve-on-earlier-versions-than-3.0.patch new file mode 100644 index 0000000..e346df1 --- /dev/null +++ b/debian/patches/75_62-OpenSSL-Fix-tls_eccurve-on-earlier-versions-than-3.0.patch @@ -0,0 +1,42 @@ +From 7fa5764c203f2f4a900898a79ed02d674075313f Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Mon, 2 Jan 2023 15:04:14 +0000 +Subject: [PATCH 1/3] OpenSSL: Fix tls_eccurve on earlier versions than 3.0.0. + Bug 2954 + +Broken-by: ca4014de81e6 +--- + src/tls-openssl.c | 7 ++++--- + test/log/2149 | 28 ++++++++++++++-------------- + test/runtest | 3 +++ + test/scripts/2100-OpenSSL/2149 | 22 ++++++++++++---------- + 4 files changed, 33 insertions(+), 27 deletions(-) + +diff --git a/src/tls-openssl.c b/src/tls-openssl.c +index 4d0f99ea9..e063d29bd 100644 +--- a/src/tls-openssl.c ++++ b/src/tls-openssl.c +@@ -786,8 +786,9 @@ if ( (nid = OBJ_sn2nid (CCS exp_curve)) == NID_undef + # endif + ) + { +- tls_error(string_sprintf("Unknown curve name tls_eccurve '%s'", exp_curve), +- NULL, NULL, errstr); ++ uschar * s = string_sprintf("Unknown curve name tls_eccurve '%s'", exp_curve); ++ DEBUG(D_tls) debug_printf("TLS error '%s'\n", s); ++ if (errstr) *errstr = s; + return FALSE; + } + +@@ -803,7 +804,7 @@ if ( (nid = OBJ_sn2nid (CCS exp_curve)) == NID_undef + /* The "tmp" in the name here refers to setting a temporary key + not to the stability of the interface. */ + +- if ((rc = SSL_CTX_set_tmp_ecdh(sctx, ecdh) == 0)) ++ if ((rc = SSL_CTX_set_tmp_ecdh(sctx, ecdh)) == 0) + tls_error(string_sprintf("Error enabling '%s' curve", exp_curve), NULL, NULL, errstr); + else + DEBUG(D_tls) debug_printf(" ECDH: enabled '%s' curve\n", exp_curve); +-- +2.39.0 + diff --git a/debian/patches/75_63-OpenSSL-log-conns-rejected-for-bad-ALPN-with-the-off.patch b/debian/patches/75_63-OpenSSL-log-conns-rejected-for-bad-ALPN-with-the-off.patch new file mode 100644 index 0000000..15b1b8a --- /dev/null +++ b/debian/patches/75_63-OpenSSL-log-conns-rejected-for-bad-ALPN-with-the-off.patch @@ -0,0 +1,99 @@ +From e1aca33756f73c22b00a98d40ce2be8ed94464b1 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Thu, 5 Jan 2023 13:03:37 +0000 +Subject: [PATCH 2/3] OpenSSL: log conns rejected for bad ALPN, with the + offered value + +Unfortunately, no way to do this under GnuTLS +--- + src/match.c | 1 + + src/tls-gnu.c | 9 ++++++++- + src/tls-openssl.c | 13 +++++++++++-- + test/log/1190 | 2 ++ + test/runtest | 3 +++ + 5 files changed, 25 insertions(+), 3 deletions(-) + +diff --git a/src/match.c b/src/match.c +index 91a49c0f0..07070362d 100644 +--- a/src/match.c ++++ b/src/match.c +@@ -968,6 +968,7 @@ Arguments: + s string to search for + listptr ptr to ptr to colon separated list of patterns, or NULL + sep a separator value for the list (see string_nextinlist()) ++ or zero for auto + anchorptr ptr to tree for named items, or NULL if no named items + cache_bits ptr to cache_bits for ditto, or NULL if not caching + type MCL_DOMAIN when matching a domain list +diff --git a/src/tls-gnu.c b/src/tls-gnu.c +index 729fb5879..b47fabf1d 100644 +--- a/src/tls-gnu.c ++++ b/src/tls-gnu.c +@@ -1119,21 +1119,28 @@ switch (tls_id) + /* The format of "data" here doesn't seem to be documented, but appears + to be a 2-byte field with a (redundant, given the "size" arg) total length + then a sequence of one-byte size then string (not nul-term) names. The +- latter is as described in OpenSSL documentation. */ ++ latter is as described in OpenSSL documentation. ++ Note that we do not get called for a match_fail, making it hard to log ++ a single bad ALPN being offered (the common case). */ ++ { ++ gstring * g = NULL; + + DEBUG(D_tls) debug_printf("Seen ALPN extension from client (s=%u):", size); + for (const uschar * s = data+2; s-data < size-1; s += *s + 1) + { + server_seen_alpn++; ++ g = string_append_listele_n(g, ':', s+1, *s); + DEBUG(D_tls) debug_printf(" '%.*s'", (int)*s, s+1); + } + DEBUG(D_tls) debug_printf("\n"); + if (server_seen_alpn > 1) + { ++ log_write(0, LOG_MAIN, "TLS ALPN (%s) rejected", string_from_gstring(g)); + DEBUG(D_tls) debug_printf("TLS: too many ALPNs presented in handshake\n"); + return GNUTLS_E_NO_APPLICATION_PROTOCOL; + } + break; ++ } + #endif + } + return 0; +diff --git a/src/tls-openssl.c b/src/tls-openssl.c +index e063d29bd..513ba0d3a 100644 +--- a/src/tls-openssl.c ++++ b/src/tls-openssl.c +@@ -2324,6 +2324,8 @@ static int + tls_server_alpn_cb(SSL *ssl, const uschar ** out, uschar * outlen, + const uschar * in, unsigned int inlen, void * arg) + { ++gstring * g = NULL; ++ + server_seen_alpn = TRUE; + DEBUG(D_tls) + { +@@ -2354,12 +2356,19 @@ if ( inlen > 1 /* at least one name */ + } + } + +-/* More than one name from clilent, or name did not match our list. */ ++/* More than one name from client, or name did not match our list. */ + + /* This will be fatal to the TLS conn; would be nice to kill TCP also. + Maybe as an option in future; for now leave control to the config (must-tls). */ + +-DEBUG(D_tls) debug_printf("TLS ALPN rejected\n"); ++for (int pos = 0, siz; pos < inlen; pos += siz+1) ++ { ++ siz = in[pos]; ++ if (pos + 1 + siz > inlen) siz = inlen - pos - 1; ++ g = string_append_listele_n(g, ':', in + pos + 1, siz); ++ } ++log_write(0, LOG_MAIN, "TLS ALPN (%s) rejected", string_from_gstring(g)); ++gstring_release_unused(g); + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + #endif /* EXIM_HAVE_ALPN */ +-- +2.39.0 + diff --git a/debian/patches/75_64-DANE-do-not-check-dns_again_means_nonexist-for-TLSA-.patch b/debian/patches/75_64-DANE-do-not-check-dns_again_means_nonexist-for-TLSA-.patch new file mode 100644 index 0000000..417ffa4 --- /dev/null +++ b/debian/patches/75_64-DANE-do-not-check-dns_again_means_nonexist-for-TLSA-.patch @@ -0,0 +1,96 @@ +From 30520c8f87fcf660ed99a2344cae7f9787f7bc89 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Thu, 5 Jan 2023 18:39:51 +0000 +Subject: [PATCH 3/3] DANE: do not check dns_again_means_nonexist for TLSA + results of TRY_AGAIN + +--- + doc/doc-docbook/spec.xfpt | 7 ++++++- + doc/ChangeLog | 4 ++++ + src/dns.c | 35 ++++++++++++++++++++++------------- + 3 files changed, 32 insertions(+), 14 deletions(-) + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -46,10 +46,14 @@ JH/19 Bug 2911: Fix a recursion in DNS l + JH/20 Bug 2954: (OpenSSL) Fix setting of explicit EC curve/group. Previously + this always failed, probably leading to the usual downgrade to in-clear + connections. + + ++JH/20 Fix TLSA lookups. Previously dns_again_means_nonexist would affect ++ SERVFAIL results, which breaks the downgrade resistance of DANE. Change ++ to not checking that list for these looks. ++ + + Exim version 4.96 + ----------------- + + JH/01 Move the wait-for-next-tick (needed for unique message IDs) from +--- a/src/dns.c ++++ b/src/dns.c +@@ -904,25 +904,34 @@ if (dnsa->answerlen < 0) switch (h_errno + DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave TRY_AGAIN\n", + name, dns_text_type(type)); + + /* Cut this out for various test programs */ + #ifndef STAND_ALONE +- if (try_again_recursion) ++ /* Permitting dns_again_means nonexist for TLSA lookups breaks the ++ doewngrade resistance of dane, so avoid for those. */ ++ ++ if (type == T_TLSA) ++ rc = FAIL; ++ else + { +- log_write(0, LOG_MAIN|LOG_PANIC, +- "dns_again_means_nonexist recursion seen for %s (assuming nonexist)", +- name); +- return dns_fail_return(name, type, dns_expire_from_soa(dnsa, type), DNS_NOMATCH); +- } ++ if (try_again_recursion) ++ { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "dns_again_means_nonexist recursion seen for %s" ++ " (assuming nonexist)", name); ++ return dns_fail_return(name, type, dns_expire_from_soa(dnsa, type), ++ DNS_NOMATCH); ++ } + +- try_again_recursion = TRUE; +- save_domain = deliver_domain; +- deliver_domain = string_copy(name); /* set $domain */ +- rc = match_isinlist(name, CUSS &dns_again_means_nonexist, 0, +- &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL); +- deliver_domain = save_domain; +- try_again_recursion = FALSE; ++ try_again_recursion = TRUE; ++ save_domain = deliver_domain; ++ deliver_domain = string_copy(name); /* set $domain */ ++ rc = match_isinlist(name, CUSS &dns_again_means_nonexist, 0, ++ &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL); ++ deliver_domain = save_domain; ++ try_again_recursion = FALSE; ++ } + + if (rc != OK) + { + DEBUG(D_dns) debug_printf("returning DNS_AGAIN\n"); + return dns_fail_return(name, type, 0, DNS_AGAIN); +--- a/doc/spec.txt ++++ b/doc/spec.txt +@@ -14246,11 +14246,13 @@ dns_again_means_nonexist, it is treated + should be used with care. You can make it apply to reverse lookups by a setting + such as this: + + dns_again_means_nonexist = *.in-addr.arpa + +-This option applies to all DNS lookups that Exim does. It also applies when the ++This option applies to all DNS lookups that Exim does, except for TLSA lookups ++(where knowing about such failures +is security-relevant). It also applies ++when the + gethostbyname() or getipnodebyname() functions give temporary errors, since + these are most likely to be caused by DNS lookup problems. The dnslookup router + has some options of its own for controlling what happens when lookups for MX or + SRV records give temporary errors. These more specific options are applied + after this global option. diff --git a/debian/patches/75_66-Fix-crash-in-expansions.patch b/debian/patches/75_66-Fix-crash-in-expansions.patch new file mode 100644 index 0000000..d776c8e --- /dev/null +++ b/debian/patches/75_66-Fix-crash-in-expansions.patch @@ -0,0 +1,84 @@ +From 70069b65a39a7ba73a36fbd95371ff03cde1eb23 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Thu, 2 Feb 2023 20:00:35 +0000 +Subject: [PATCH] Fix crash in expansions + +Broken-by: 1058096b8c53 +--- + doc/ChangeLog | 4 ++++ + src/expand.c | 9 +++++---- + test/stderr/0630 | 1 + + 3 files changed, 10 insertions(+), 4 deletions(-) + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -50,10 +50,14 @@ JH/20 Bug 2954: (OpenSSL) Fix setting of + + JH/20 Fix TLSA lookups. Previously dns_again_means_nonexist would affect + SERVFAIL results, which breaks the downgrade resistance of DANE. Change + to not checking that list for these looks. + ++JH/23 Fix crash in string expansions. Previously, if an empty variable was ++ immediately followed by an expansion operator, a null-indirection read ++ was done, killing the process. ++ + + Exim version 4.96 + ----------------- + + JH/01 Move the wait-for-next-tick (needed for unique message IDs) from +--- a/src/expand.c ++++ b/src/expand.c +@@ -4652,11 +4652,11 @@ while (*s) + yield = string_catn(yield, value, len); + + continue; + } + +- if (isdigit(*s)) ++ if (isdigit(*s)) /* A $ variable */ + { + int n; + s = read_cnumber(&n, s); + if (n >= 0 && n <= expand_nmax) + yield = string_catn(yield, expand_nstring[n], expand_nlength[n]); +@@ -7060,10 +7060,11 @@ NOT_ITEM: ; + if (arg) *arg++ = '_'; /* Put back for error messages */ + } + + /* Deal specially with operators that might take a certificate variable + as we do not want to do the usual expansion. For most, expand the string.*/ ++ + switch(c) + { + #ifndef DISABLE_TLS + case EOP_MD5: + case EOP_SHA1: +@@ -7107,11 +7108,11 @@ NOT_ITEM: ; + + /* Otherwise, switch on the operator type. After handling go back + to the main loop top. */ + + { +- int start = yield->ptr; ++ unsigned expansion_start = gstring_length(yield); + switch(c) + { + case EOP_BASE32: + { + uschar *t; +@@ -8168,12 +8169,12 @@ NOT_ITEM: ; + goto EXPAND_FAILED; + } /* EOP_* switch */ + + DEBUG(D_expand) + { +- const uschar * s = yield->s + start; +- int i = yield->ptr - start; ++ const uschar * s = yield->s + expansion_start; ++ int i = gstring_length(yield) - expansion_start; + BOOL tainted = is_tainted(s); + + DEBUG(D_noutf8) + { + debug_printf_indent("|-----op-res: %.*s\n", i, s); diff --git a/debian/patches/75_68-Fix-srs_encode-.-for-mod-1024-day-zero.patch b/debian/patches/75_68-Fix-srs_encode-.-for-mod-1024-day-zero.patch new file mode 100644 index 0000000..dddd5e9 --- /dev/null +++ b/debian/patches/75_68-Fix-srs_encode-.-for-mod-1024-day-zero.patch @@ -0,0 +1,62 @@ +From 51f9c07cd341c9c1a09b3816df988c6f44477c99 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Tue, 11 Apr 2023 11:59:08 +0100 +Subject: [PATCH] Fix ${srs_encode ..} for mod-1024 day zero + +--- + doc/ChangeLog | 3 +++ + src/expand.c | 10 ++++------ + 2 files changed, 7 insertions(+), 6 deletions(-) + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -54,10 +54,13 @@ JH/20 Fix TLSA lookups. Previously dns_ + + JH/23 Fix crash in string expansions. Previously, if an empty variable was + immediately followed by an expansion operator, a null-indirection read + was done, killing the process. + ++JH/27 Fix ${srs_encode ..}. Previously it would give a bad result for one day ++ every 1024 days. ++ + + Exim version 4.96 + ----------------- + + JH/01 Move the wait-for-next-tick (needed for unique message IDs) from +--- a/src/expand.c ++++ b/src/expand.c +@@ -3440,11 +3440,11 @@ switch(cond_type = identify_operator(&s, + case 3: return NULL; + } + + /* Match the given local_part against the SRS-encoded pattern */ + +- re = regex_must_compile(US"^(?i)SRS0=([^=]+)=([A-Z2-7]+)=([^=]*)=(.*)$", ++ re = regex_must_compile(US"^(?i)SRS0=([^=]+)=([A-Z2-7]{2})=([^=]*)=(.*)$", + TRUE, FALSE); + md = pcre2_match_data_create(4+1, pcre_gen_ctx); + if (pcre2_match(re, sub[0], PCRE2_ZERO_TERMINATED, 0, PCRE_EOPT, + md, pcre_mtc_ctx) < 0) + { +@@ -6957,17 +6957,15 @@ while (*s) + + /* ${base32:${eval:$tod_epoch/86400&0x3ff}}= */ + { + struct timeval now; + unsigned long i; +- gstring * h = NULL; + + gettimeofday(&now, NULL); +- for (unsigned long i = (now.tv_sec / 86400) & 0x3ff; i; i >>= 5) +- h = string_catn(h, &base32_chars[i & 0x1f], 1); +- if (h) while (h->ptr > 0) +- g = string_catn(g, &h->s[--h->ptr], 1); ++ i = (now.tv_sec / 86400) & 0x3ff; ++ g = string_catn(g, &base32_chars[i >> 5], 1); ++ g = string_catn(g, &base32_chars[i & 0x1f], 1); + } + g = string_catn(g, US"=", 1); + + /* ${domain:$return_path}=${local_part:$return_path} */ + { diff --git a/debian/patches/75_70-Fix-variable-initialisation-in-smtp-transport.-Bug-2.patch b/debian/patches/75_70-Fix-variable-initialisation-in-smtp-transport.-Bug-2.patch new file mode 100644 index 0000000..4a2e3b5 --- /dev/null +++ b/debian/patches/75_70-Fix-variable-initialisation-in-smtp-transport.-Bug-2.patch @@ -0,0 +1,46 @@ +From a8786a66feb3c003c74551399b345b1634cc6739 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Thu, 4 May 2023 15:41:46 +0100 +Subject: [PATCH 1/3] Fix variable initialisation in smtp transport. Bug 2996 + +--- + doc/ChangeLog | 8 ++++++++ + src/transports/smtp.c | 2 +- + 2 files changed, 9 insertions(+), 1 deletion(-) + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -61,10 +61,18 @@ JH/23 Fix crash in string expansions. Pr + was done, killing the process. + + JH/27 Fix ${srs_encode ..}. Previously it would give a bad result for one day + every 1024 days. + ++JH/28 Bug 2996: Fix a crash in the smtp transport. When finding that the ++ message being considered for delivery was already being handled by ++ another process, and having an SMTP connection already open, the function ++ to close it tried to use an uninitialized variable. This would afftect ++ high-volume sites more, especially when running mailing-list-style loads. ++ Pollution of logs was the major effect, as the other process delivered ++ the message. Found and partly investigated by Graeme Fowler. ++ + + Exim version 4.96 + ----------------- + + JH/01 Move the wait-for-next-tick (needed for unique message IDs) from +--- a/src/transports/smtp.c ++++ b/src/transports/smtp.c +@@ -4950,11 +4950,11 @@ Returns: nothing + void + smtp_transport_closedown(transport_instance *tblock) + { + smtp_transport_options_block * ob = SOB tblock->options_block; + client_conn_ctx cctx; +-smtp_context sx; ++smtp_context sx = {0}; + uschar buffer[256]; + uschar inbuffer[4096]; + uschar outbuffer[16]; + + /*XXX really we need an active-smtp-client ctx, rather than assuming stdout */ diff --git a/debian/patches/75_71-Auths-fix-possible-OOB-write-in-external-authenticat.patch b/debian/patches/75_71-Auths-fix-possible-OOB-write-in-external-authenticat.patch new file mode 100644 index 0000000..f6936a9 --- /dev/null +++ b/debian/patches/75_71-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 +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_72-Auths-use-uschar-more-in-spa-authenticator.patch b/debian/patches/75_72-Auths-use-uschar-more-in-spa-authenticator.patch new file mode 100644 index 0000000..03958fc --- /dev/null +++ b/debian/patches/75_72-Auths-use-uschar-more-in-spa-authenticator.patch @@ -0,0 +1,226 @@ +From 0519dcfb5f149154a416b54865fd8026abb57791 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +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_73-Auths-fix-possible-OOB-write-in-SPA-authenticator.-B.patch b/debian/patches/75_73-Auths-fix-possible-OOB-write-in-SPA-authenticator.-B.patch new file mode 100644 index 0000000..8c763fa --- /dev/null +++ b/debian/patches/75_73-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 +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_74-Auths-fix-possible-OOB-read-in-SPA-authenticator.-Bu.patch b/debian/patches/75_74-Auths-fix-possible-OOB-read-in-SPA-authenticator.-Bu.patch new file mode 100644 index 0000000..d4e0eb6 --- /dev/null +++ b/debian/patches/75_74-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 +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/75_74-Cancel-early-pipe-on-an-observed-advertising-change.patch b/debian/patches/75_74-Cancel-early-pipe-on-an-observed-advertising-change.patch new file mode 100644 index 0000000..bcec88f --- /dev/null +++ b/debian/patches/75_74-Cancel-early-pipe-on-an-observed-advertising-change.patch @@ -0,0 +1,35 @@ +From 4d108e7777e9b8e5fb212c31812fef61529cd414 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Mon, 12 Jun 2023 22:13:46 +0100 +Subject: [PATCH] Cancel early-pipe on an observed advertising change + +--- + src/transports/smtp.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/src/transports/smtp.c b/src/transports/smtp.c +index c72028ce9..24ee577a2 100644 +--- a/src/transports/smtp.c ++++ b/src/transports/smtp.c +@@ -1111,15 +1111,18 @@ if (pending_EHLO) + *(tls_out.active.sock < 0 + ? &sx->ehlo_resp.cleartext_features : &sx->ehlo_resp.crypted_features) = + peer_offered; + *ap = authbits; + write_ehlo_cache_entry(sx); + } + else ++ { + invalidate_ehlo_cache_entry(sx); ++ sx->early_pipe_active = FALSE; /* cancel further early-pipe on this conn */ ++ } + + return OK; /* just carry on */ + } + # ifdef EXPERIMENTAL_ESMTP_LIMITS + /* If we are handling LIMITS, compare the actual EHLO LIMITS values with the + cached values and invalidate cache if different. OK to carry on with + connect since values are advisory. */ +-- +2.40.1 + diff --git a/debian/patches/75_76-Expansions-disallow-UTF-16-surrogates-from-utf8clean.patch b/debian/patches/75_76-Expansions-disallow-UTF-16-surrogates-from-utf8clean.patch new file mode 100644 index 0000000..713435b --- /dev/null +++ b/debian/patches/75_76-Expansions-disallow-UTF-16-surrogates-from-utf8clean.patch @@ -0,0 +1,99 @@ +From 1209e3e19e292cee517e43a2ccfe9b44b33bb1dc Mon Sep 17 00:00:00 2001 +From: Jasen Betts +Date: Sun, 23 Jul 2023 13:43:59 +0100 +Subject: [PATCH] Expansions: disallow UTF-16 surrogates from ${utf8clean:...}. + Bug 2998 + +--- + doc/ChangeLog | 4 ++++ + src/expand.c | 27 +++++++++++++++++---------- + 2 files changed, 21 insertions(+), 10 deletions(-) + +--- a/src/expand.c ++++ b/src/expand.c +@@ -7731,11 +7731,11 @@ NOT_ITEM: ; + + case EOP_UTF8CLEAN: + { + int seq_len = 0, index = 0; + int bytes_left = 0; +- long codepoint = -1; ++ ulong codepoint = (ulong)-1; + int complete; + uschar seq_buff[4]; /* accumulate utf-8 here */ + + /* Manually track tainting, as we deal in individual chars below */ + +@@ -7761,40 +7761,47 @@ NOT_ITEM: ; + codepoint = (codepoint << 6) | (c & 0x3f); + seq_buff[index++] = c; + if (--bytes_left == 0) /* codepoint complete */ + if(codepoint > 0x10FFFF) /* is it too large? */ + complete = -1; /* error (RFC3629 limit) */ ++ else if ( (codepoint & 0x1FF800 ) == 0xD800 ) /* surrogate */ ++ /* A UTF-16 surrogate (which should be one of a pair that ++ encode a Unicode codepoint that is outside the Basic ++ Multilingual Plane). Error, not UTF8. ++ RFC2279.2 is slightly unclear on this, but ++ https://unicodebook.readthedocs.io/issues.html#strict-utf8-decoder ++ says "Surrogates characters are also invalid in UTF-8: ++ characters in U+D800—U+DFFF have to be rejected." */ ++ complete = -1; + else + { /* finished; output utf-8 sequence */ + yield = string_catn(yield, seq_buff, seq_len); + index = 0; + } + } + } + else /* no bytes left: new sequence */ + { +- if(!(c & 0x80)) /* 1-byte sequence, US-ASCII, keep it */ ++ if (!(c & 0x80)) /* 1-byte sequence, US-ASCII, keep it */ + { + yield = string_catn(yield, &c, 1); + continue; + } +- if((c & 0xe0) == 0xc0) /* 2-byte sequence */ +- { +- if(c == 0xc0 || c == 0xc1) /* 0xc0 and 0xc1 are illegal */ ++ if ((c & 0xe0) == 0xc0) /* 2-byte sequence */ ++ if (c == 0xc0 || c == 0xc1) /* 0xc0 and 0xc1 are illegal */ + complete = -1; + else + { +- bytes_left = 1; +- codepoint = c & 0x1f; ++ bytes_left = 1; ++ codepoint = c & 0x1f; + } +- } +- else if((c & 0xf0) == 0xe0) /* 3-byte sequence */ ++ else if ((c & 0xf0) == 0xe0) /* 3-byte sequence */ + { + bytes_left = 2; + codepoint = c & 0x0f; + } +- else if((c & 0xf8) == 0xf0) /* 4-byte sequence */ ++ else if ((c & 0xf8) == 0xf0) /* 4-byte sequence */ + { + bytes_left = 3; + codepoint = c & 0x07; + } + else /* invalid or too long (RFC3629 allows only 4 bytes) */ +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -69,10 +69,13 @@ JH/28 Bug 2996: Fix a crash in the smtp + to close it tried to use an uninitialized variable. This would afftect + high-volume sites more, especially when running mailing-list-style loads. + Pollution of logs was the major effect, as the other process delivered + the message. Found and partly investigated by Graeme Fowler. + ++JH/31 Bug 2998: Fix ${utf8clean:...} to disallow UTF-16 surrogate codepoints. ++ Found and fixed by Jasen Betts. No testcase for this as my usual text ++ editor insists on emitting only valid UTF-8. + + Exim version 4.96 + ----------------- + + JH/01 Move the wait-for-next-tick (needed for unique message IDs) from diff --git a/debian/patches/75_77-GnuTLS-fix-crash-with-tls_dhparam-none.patch b/debian/patches/75_77-GnuTLS-fix-crash-with-tls_dhparam-none.patch new file mode 100644 index 0000000..ced0ce9 --- /dev/null +++ b/debian/patches/75_77-GnuTLS-fix-crash-with-tls_dhparam-none.patch @@ -0,0 +1,92 @@ +From 8e9770348dc4173ab83657ee023c22f479ebb712 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Mon, 24 Jul 2023 13:30:40 +0100 +Subject: [PATCH] GnuTLS: fix crash with "tls_dhparam = none" + +--- + doc/ChangeLog | 4 ++++ + src/tls-gnu.c | 16 +++++++++------- + test/log/2049 | 7 +++++++ + test/scripts/2000-GnuTLS/2049 | 8 ++++++++ + 4 files changed, 28 insertions(+), 7 deletions(-) + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -73,10 +73,14 @@ JH/28 Bug 2996: Fix a crash in the smtp + + JH/31 Bug 2998: Fix ${utf8clean:...} to disallow UTF-16 surrogate codepoints. + Found and fixed by Jasen Betts. No testcase for this as my usual text + editor insists on emitting only valid UTF-8. + ++JH/32 Fix "tls_dhparam = none" under GnuTLS. At least with 3.7.9 this gave ++ a null-indireciton SIGSEGV for the receive process. ++ ++ + Exim version 4.96 + ----------------- + + JH/01 Move the wait-for-next-tick (needed for unique message IDs) from + after reception to before a subsequent reception. This should +--- a/src/tls-gnu.c ++++ b/src/tls-gnu.c +@@ -712,11 +712,11 @@ exist, we generate them. This means that + The new file is written as a temporary file and renamed, so that an incomplete + file is never present. If two processes both compute some new parameters, you + waste a bit of effort, but it doesn't seem worth messing around with locking to + prevent this. + +-Returns: OK/DEFER/FAIL ++Returns: OK/DEFER (expansion issue)/FAIL (requested none) + */ + + static int + init_server_dh(uschar ** errstr) + { +@@ -750,11 +750,11 @@ if (!exp_tls_dhparam) + else if (Ustrcmp(exp_tls_dhparam, "historic") == 0) + use_file_in_spool = TRUE; + else if (Ustrcmp(exp_tls_dhparam, "none") == 0) + { + DEBUG(D_tls) debug_printf("Requested no DH parameters\n"); +- return OK; ++ return FAIL; + } + else if (exp_tls_dhparam[0] != '/') + { + if (!(m.data = US std_dh_prime_named(exp_tls_dhparam))) + return tls_error(US"No standard prime named", exp_tls_dhparam, NULL, errstr); +@@ -1971,27 +1971,29 @@ Arguments: + + Returns: OK/DEFER/FAIL + */ + + static int +-tls_set_remaining_x509(exim_gnutls_state_st *state, uschar ** errstr) ++tls_set_remaining_x509(exim_gnutls_state_st * state, uschar ** errstr) + { +-int rc; +-const host_item *host = state->host; /* macro should be reconsidered? */ ++int rc = OK; ++const host_item * host = state->host; /* macro should be reconsidered? */ + + /* Create D-H parameters, or read them from the cache file. This function does + its own SMTP error messaging. This only happens for the server, TLS D-H ignores + client-side params. */ + + if (!state->host) + { + if (!dh_server_params) +- if ((rc = init_server_dh(errstr)) != OK) return rc; ++ if ((rc = init_server_dh(errstr)) == DEFER) return rc; + + /* Unnecessary & discouraged with 3.6.0 or later, according to docs. But without it, + no DHE- ciphers are advertised. */ +- gnutls_certificate_set_dh_params(state->lib_state.x509_cred, dh_server_params); ++ ++ if (rc == OK) ++ gnutls_certificate_set_dh_params(state->lib_state.x509_cred, dh_server_params); + } + + /* Link the credentials to the session. */ + + if ((rc = gnutls_credentials_set(state->session, diff --git a/debian/patches/75_79-Fix-recipients-expansion-when-used-within-run.-.-Bug.patch b/debian/patches/75_79-Fix-recipients-expansion-when-used-within-run.-.-Bug.patch new file mode 100644 index 0000000..1eb9d8f --- /dev/null +++ b/debian/patches/75_79-Fix-recipients-expansion-when-used-within-run.-.-Bug.patch @@ -0,0 +1,294 @@ +From 6707bfa9fb78858de938a1abca2846c820c5ded7 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Thu, 3 Aug 2023 18:40:42 +0100 +Subject: [PATCH 2/2] Fix $recipients expansion when used within ${run...}. + Bug 3013 + +Broken-by: cfe6acff2ddc +--- + doc/ChangeLog | 3 +++ + src/deliver.c | 2 +- + src/expand.c | 5 ++--- + src/functions.h | 2 +- + src/macros.h | 5 +++++ + src/routers/queryprogram.c | 3 +-- + src/smtp_in.c | 4 ++-- + src/transport.c | 18 +++++++++--------- + src/transports/lmtp.c | 4 ++-- + src/transports/pipe.c | 11 ++++++----- + src/transports/smtp.c | 2 +- + test/log/0635 | 2 +- + 12 files changed, 34 insertions(+), 27 deletions(-) + +--- a/src/deliver.c ++++ b/src/deliver.c +@@ -2382,11 +2382,11 @@ if ((pid = exim_fork(US"delivery-local") + + if (tp->filter_command) + { + ok = transport_set_up_command(&transport_filter_argv, + tp->filter_command, +- TRUE, PANIC, addr, FALSE, US"transport filter", NULL); ++ TSUC_EXPAND_ARGS, PANIC, addr, US"transport filter", NULL); + transport_filter_timeout = tp->filter_timeout; + } + else transport_filter_argv = NULL; + + if (ok) +--- a/src/expand.c ++++ b/src/expand.c +@@ -5527,11 +5527,11 @@ while (*s) + + case EITEM_RUN: + { + FILE * f; + const uschar * arg, ** argv; +- BOOL late_expand = TRUE; ++ unsigned late_expand = TSUC_EXPAND_ARGS | TSUC_ALLOW_TAINTED_ARGS | TSUC_ALLOW_RECIPIENTS; + + if (expand_forbid & RDO_RUN) + { + expand_string_message = US"running a command is not permitted"; + goto EXPAND_FAILED; +@@ -5540,11 +5540,11 @@ while (*s) + /* Handle options to the "run" */ + + while (*s == ',') + { + if (Ustrncmp(++s, "preexpand", 9) == 0) +- { late_expand = FALSE; s += 9; } ++ { late_expand = 0; s += 9; } + else + { + const uschar * t = s; + while (isalpha(*++t)) ; + expand_string_message = string_sprintf("bad option '%.*s' for run", +@@ -5599,11 +5599,10 @@ while (*s) + if (!transport_set_up_command(&argv, /* anchor for arg list */ + arg, /* raw command */ + late_expand, /* expand args if not already done */ + 0, /* not relevant when... */ + NULL, /* no transporting address */ +- late_expand, /* allow tainted args, when expand-after-split */ + US"${run} expansion", /* for error messages */ + &expand_string_message)) /* where to put error message */ + goto EXPAND_FAILED; + + /* Create the child process, making it a group leader. */ +--- a/src/functions.h ++++ b/src/functions.h +@@ -616,11 +616,11 @@ extern BOOL transport_pass_socket(con + , unsigned, unsigned, unsigned + #endif + ); + extern uschar *transport_rcpt_address(address_item *, BOOL); + extern BOOL transport_set_up_command(const uschar ***, const uschar *, +- BOOL, int, address_item *, BOOL, const uschar *, uschar **); ++ unsigned, int, address_item *, const uschar *, uschar **); + extern void transport_update_waiting(host_item *, uschar *); + extern BOOL transport_write_block(transport_ctx *, uschar *, int, BOOL); + extern void transport_write_reset(int); + extern BOOL transport_write_string(int, const char *, ...); + extern BOOL transport_headers_send(transport_ctx *, +--- a/src/macros.h ++++ b/src/macros.h +@@ -1112,6 +1112,11 @@ should not be one active. */ + + #define NOTIFIER_SOCKET_NAME "exim_daemon_notify" + #define NOTIFY_MSG_QRUN 1 /* Notify message types */ + #define NOTIFY_QUEUE_SIZE_REQ 2 + ++/* Flags for transport_set_up_command() */ ++#define TSUC_EXPAND_ARGS BIT(0) ++#define TSUC_ALLOW_TAINTED_ARGS BIT(1) ++#define TSUC_ALLOW_RECIPIENTS BIT(2) ++ + /* End of macros.h */ +--- a/src/routers/queryprogram.c ++++ b/src/routers/queryprogram.c +@@ -286,14 +286,13 @@ if (curr_uid != root_uid && (uid != curr + + /* Set up the command to run */ + + if (!transport_set_up_command(&argvptr, /* anchor for arg list */ + ob->command, /* raw command */ +- TRUE, /* expand the arguments */ ++ TSUC_EXPAND_ARGS, /* arguments expanded but must not be tainted */ + 0, /* not relevant when... */ + NULL, /* no transporting address */ +- FALSE, /* args must be untainted */ + US"queryprogram router", /* for error messages */ + &addr->message)) /* where to put error message */ + return DEFER; + + /* Create the child process, making it a group leader. */ +--- a/src/smtp_in.c ++++ b/src/smtp_in.c +@@ -5840,12 +5840,12 @@ while (done <= 0) + { + uschar *error; + BOOL rc; + etrn_command = smtp_etrn_command; + deliver_domain = smtp_cmd_data; +- rc = transport_set_up_command(&argv, smtp_etrn_command, TRUE, 0, NULL, +- FALSE, US"ETRN processing", &error); ++ rc = transport_set_up_command(&argv, smtp_etrn_command, TSUC_EXPAND_ARGS, 0, NULL, ++ US"ETRN processing", &error); + deliver_domain = NULL; + if (!rc) + { + log_write(0, LOG_MAIN|LOG_PANIC, "failed to set up ETRN command: %s", + error); +--- a/src/transport.c ++++ b/src/transport.c +@@ -2082,34 +2082,34 @@ return FALSE; + * Set up direct (non-shell) command * + *************************************************/ + + /* This function is called when a command line is to be parsed and executed + directly, without the use of /bin/sh. It is called by the pipe transport, +-the queryprogram router, and also from the main delivery code when setting up a ++the queryprogram router, for any ${run } expansion, ++and also from the main delivery code when setting up a + transport filter process. The code for ETRN also makes use of this; in that + case, no addresses are passed. + + Arguments: + argvptr pointer to anchor for argv vector + cmd points to the command string (modified IN PLACE) +- expand_arguments true if expansion is to occur ++ flags bits for expand-args, allow taint, allow $recipients + expand_failed error value to set if expansion fails; not relevant if + addr == NULL + addr chain of addresses, or NULL +- allow_tainted_args as it says; used for ${run} + etext text for use in error messages + errptr where to put error message if addr is NULL; + otherwise it is put in the first address + + Returns: TRUE if all went well; otherwise an error will be + set in the first address and FALSE returned + */ + + BOOL + transport_set_up_command(const uschar *** argvptr, const uschar * cmd, +- BOOL expand_arguments, int expand_failed, address_item * addr, +- BOOL allow_tainted_args, const uschar * etext, uschar ** errptr) ++ unsigned flags, int expand_failed, address_item * addr, ++ const uschar * etext, uschar ** errptr) + { + const uschar ** argv, * s; + int address_count = 0, argcount = 0, max_args; + + /* Get store in which to build an argument list. Count the number of addresses +@@ -2180,14 +2180,14 @@ DEBUG(D_transport) + debug_printf("direct command:\n"); + for (int i = 0; argv[i]; i++) + debug_printf(" argv[%d] = '%s'\n", i, string_printing(argv[i])); + } + +-if (expand_arguments) ++if (flags & TSUC_EXPAND_ARGS) + { +- BOOL allow_dollar_recipients = addr && addr->parent +- && Ustrcmp(addr->parent->address, "system-filter") == 0; ++ BOOL allow_dollar_recipients = (flags & TSUC_ALLOW_RECIPIENTS) ++ || (addr && addr->parent && Ustrcmp(addr->parent->address, "system-filter") == 0); /*XXX could we check this at caller? */ + + for (int i = 0; argv[i]; i++) + { + DEBUG(D_expand) debug_printf_indent("arg %d\n", i); + +@@ -2370,11 +2370,11 @@ if (expand_arguments) + { /* hack, would be good to not need it */ + DEBUG(D_transport) + debug_printf("SPECIFIC TESTSUITE EXEMPTION: tainted arg '%s'\n", + expanded_arg); + } +- else if ( !allow_tainted_args ++ else if ( !(flags & TSUC_ALLOW_TAINTED_ARGS) + && arg_is_tainted(expanded_arg, i, addr, etext, errptr)) + return FALSE; + argv[i] = expanded_arg; + } + } +--- a/src/transports/lmtp.c ++++ b/src/transports/lmtp.c +@@ -487,12 +487,12 @@ argument list and expanding the items. * + + if (ob->cmd) + { + DEBUG(D_transport) debug_printf("using command %s\n", ob->cmd); + sprintf(CS buffer, "%.50s transport", tblock->name); +- if (!transport_set_up_command(&argv, ob->cmd, TRUE, PANIC, addrlist, FALSE, +- buffer, NULL)) ++ if (!transport_set_up_command(&argv, ob->cmd, TSUC_EXPAND_ARGS, PANIC, ++ addrlist, buffer, NULL)) + return FALSE; + + /* If the -N option is set, can't do any more. Presume all has gone well. */ + if (f.dont_deliver) + goto MINUS_N; +--- a/src/transports/pipe.c ++++ b/src/transports/pipe.c +@@ -289,24 +289,25 @@ Arguments: + Returns: TRUE if all went well; otherwise an error will be + set in the first address and FALSE returned + */ + + static BOOL +-set_up_direct_command(const uschar ***argvptr, uschar *cmd, +- BOOL expand_arguments, int expand_fail, address_item *addr, uschar *tname, +- pipe_transport_options_block *ob) ++set_up_direct_command(const uschar *** argvptr, uschar * cmd, ++ BOOL expand_arguments, int expand_fail, address_item * addr, uschar * tname, ++ pipe_transport_options_block * ob) + { + BOOL permitted = FALSE; + const uschar **argv; + + /* Set up "transport " to be put in any error messages, and then + call the common function for creating an argument list and expanding + the items if necessary. If it fails, this function fails (error information + is in the addresses). */ + +-if (!transport_set_up_command(argvptr, cmd, expand_arguments, expand_fail, +- addr, FALSE, string_sprintf("%.50s transport", tname), NULL)) ++if (!transport_set_up_command(argvptr, cmd, ++ expand_arguments ? TSUC_EXPAND_ARGS : 0, ++ expand_fail, addr, string_sprintf("%.50s transport", tname), NULL)) + return FALSE; + + /* Point to the set-up arguments. */ + + argv = *argvptr; +--- a/src/transports/smtp.c ++++ b/src/transports/smtp.c +@@ -3803,11 +3803,11 @@ if (tblock->filter_command) + + /* On failure, copy the error to all addresses, abandon the SMTP call, and + yield ERROR. */ + + if (!transport_set_up_command(&transport_filter_argv, +- tblock->filter_command, TRUE, DEFER, addrlist, FALSE, ++ tblock->filter_command, TSUC_EXPAND_ARGS, DEFER, addrlist, + string_sprintf("%.50s transport filter", tblock->name), NULL)) + { + set_errno_nohost(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER, + FALSE, &sx->delivery_start); + yield = ERROR; +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -76,10 +76,12 @@ JH/31 Bug 2998: Fix ${utf8clean:...} to + editor insists on emitting only valid UTF-8. + + JH/32 Fix "tls_dhparam = none" under GnuTLS. At least with 3.7.9 this gave + a null-indireciton SIGSEGV for the receive process. + ++JH/34 Bug 3013: Fix use of $recipients within arguments for ${run...}. ++ In 4.96 this would expand to empty. + + Exim version 4.96 + ----------------- + + JH/01 Move the wait-for-next-tick (needed for unique message IDs) from diff --git a/debian/patches/75_82-GnuTLS-fix-autogen-cert-expiry-date.-Bug-3014.patch b/debian/patches/75_82-GnuTLS-fix-autogen-cert-expiry-date.-Bug-3014.patch new file mode 100644 index 0000000..cdf062e --- /dev/null +++ b/debian/patches/75_82-GnuTLS-fix-autogen-cert-expiry-date.-Bug-3014.patch @@ -0,0 +1,42 @@ +From 36bc854c86908ee921225c1d30e35c4d59eed822 Mon Sep 17 00:00:00 2001 +From: Andreas Metzler +Date: Mon, 14 Aug 2023 17:27:16 +0100 +Subject: [PATCH] GnuTLS: fix autogen cert expiry date. Bug 3014 + +Broken-by: 48e9099006 +--- + doc/ChangeLog | 3 +++ + src/tls-gnu.c | 2 +- + 2 files changed, 4 insertions(+), 1 deletion(-) + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -79,10 +79,13 @@ JH/32 Fix "tls_dhparam = none" under Gnu + a null-indireciton SIGSEGV for the receive process. + + JH/34 Bug 3013: Fix use of $recipients within arguments for ${run...}. + In 4.96 this would expand to empty. + ++JH/35 Bug 3014: GnuTLS: fix expiry date for an auto-generated server ++ certificate. Find and fix by Andreas Metzler. ++ + Exim version 4.96 + ----------------- + + JH/01 Move the wait-for-next-tick (needed for unique message IDs) from + after reception to before a subsequent reception. This should +--- a/src/tls-gnu.c ++++ b/src/tls-gnu.c +@@ -1001,11 +1001,11 @@ if ((rc = gnutls_x509_privkey_generate(p + where = US"configuring cert"; + now = 1; + if ( (rc = gnutls_x509_crt_set_version(cert, 3)) + || (rc = gnutls_x509_crt_set_serial(cert, &now, sizeof(now))) + || (rc = gnutls_x509_crt_set_activation_time(cert, now = time(NULL))) +- || (rc = gnutls_x509_crt_set_expiration_time(cert, (long)2 * 60 * 60)) /* 2 hour */ ++ || (rc = gnutls_x509_crt_set_expiration_time(cert, now + (long)2 * 60 * 60)) /* 2 hour */ + || (rc = gnutls_x509_crt_set_key(cert, pkey)) + + || (rc = gnutls_x509_crt_set_dn_by_oid(cert, + GNUTLS_OID_X520_COUNTRY_NAME, 0, "UK", 2)) + || (rc = gnutls_x509_crt_set_dn_by_oid(cert, diff --git a/debian/patches/75_83-Re-fix-live-variable-value-free.-The-inital-fix-resu.patch b/debian/patches/75_83-Re-fix-live-variable-value-free.-The-inital-fix-resu.patch new file mode 100644 index 0000000..0cfc69e --- /dev/null +++ b/debian/patches/75_83-Re-fix-live-variable-value-free.-The-inital-fix-resu.patch @@ -0,0 +1,77 @@ +From 21b172df101c2c52faf0cc56a502395451975be9 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Thu, 24 Aug 2023 15:51:21 +0100 +Subject: [PATCH 2/2] Re-fix live variable $value free. The inital fix + resulted in $value from ${run...} not being available later, which is a + documented feature. + +Broken=by: cf3fecb9e873 +--- + doc/doc-docbook/spec.xfpt | 1 + + doc/ChangeLog | 4 ++-- + src/exim.c | 3 ++- + test/confs/0635 | 1 + + test/log/0635 | 1 + + test/mail/0635.CALLER | 13 +++++++++++++ + 6 files changed, 20 insertions(+), 3 deletions(-) + create mode 100644 test/mail/0635.CALLER + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -76,10 +76,13 @@ JH/31 Bug 2998: Fix ${utf8clean:...} to + editor insists on emitting only valid UTF-8. + + JH/32 Fix "tls_dhparam = none" under GnuTLS. At least with 3.7.9 this gave + a null-indireciton SIGSEGV for the receive process. + ++JH/33 Fix free for live variable $value created by a ${run ...} expansion during ++ -bh use. Internal checking would spot this and take a panic. ++ + JH/34 Bug 3013: Fix use of $recipients within arguments for ${run...}. + In 4.96 this would expand to empty. + + JH/35 Bug 3014: GnuTLS: fix expiry date for an auto-generated server + certificate. Find and fix by Andreas Metzler. +--- a/src/exim.c ++++ b/src/exim.c +@@ -5754,11 +5754,11 @@ for (BOOL more = TRUE; more; ) + for (int i = 0; i < count; i++) + { + int start, end, domain; + uschar * errmess; + /* There can be multiple addresses, so EXIM_DISPLAYMAIL_MAX (tuned for 1) is too short. +- * We'll still want to cap it to something, just in case. */ ++ We'll still want to cap it to something, just in case. */ + uschar * s = string_copy_taint( + exim_str_fail_toolong(list[i], BIG_BUFFER_SIZE, "address argument"), + GET_TAINTED); + + /* Loop for each comma-separated address */ +@@ -6089,10 +6089,11 @@ MORELOOP: + callout_address = NULL; + sending_ip_address = NULL; + deliver_localpart_data = deliver_domain_data = + recipient_data = sender_data = NULL; + acl_var_m = NULL; ++ lookup_value = NULL; /* Can be set by ACL */ + + store_reset(reset_point); + } + + exim_exit(EXIT_SUCCESS); /* Never returns */ +--- a/doc/spec.txt ++++ b/doc/spec.txt +@@ -9650,10 +9650,13 @@ ${run {}{ redirect operator, the + shell must be invoked directly, such as with: + + ${run{/bin/bash -c "/usr/bin/id >/tmp/id"}{yes}{yes}} + ++ Note that $value will not persist beyond the reception of a single ++ message. ++ + The return code from the command is put in the variable $runrc, and this + remains set afterwards, so in a filter file you can do things like this: + + if "${run{x y z}{}}$runrc" is 1 then ... + elif $runrc is 2 then ... diff --git a/debian/patches/76-01-fix-string_is_ip_address-CVE-2023-42117-Bug-3031.patch b/debian/patches/76-01-fix-string_is_ip_address-CVE-2023-42117-Bug-3031.patch new file mode 100644 index 0000000..cbddde0 --- /dev/null +++ b/debian/patches/76-01-fix-string_is_ip_address-CVE-2023-42117-Bug-3031.patch @@ -0,0 +1,309 @@ +From a95acb1c19c2e3600ef327c71318e33316d34440 Mon Sep 17 00:00:00 2001 +From: "Heiko Schlittermann (HS12-RIPE)" +Date: Thu, 5 Oct 2023 22:49:57 +0200 +Subject: [PATCH 1/3] fix: string_is_ip_address (CVE-2023-42117) Bug 3031 + +--- + doc/ChangeLog | 206 ++++++++++++++++++++++++++++++++++++++++++ + src/expand.c | 14 ++- + src/functions.h | 1 + + src/string.c | 200 +++++++++++++++++++++------------------- + 4 files changed, 323 insertions(+), 98 deletions(-) + +diff --git a/src/expand.c b/src/expand.c +index 36c9f423b..4986e4657 100644 +--- a/src/expand.c ++++ b/src/expand.c +@@ -2646,17 +2646,25 @@ switch(cond_type = identify_operator(&s, &opname)) + } + *yield = (Ustat(sub[0], &statbuf) == 0) == testfor; + break; + + case ECOND_ISIP: + case ECOND_ISIP4: + case ECOND_ISIP6: +- rc = string_is_ip_address(sub[0], NULL); +- *yield = ((cond_type == ECOND_ISIP)? (rc != 0) : +- (cond_type == ECOND_ISIP4)? (rc == 4) : (rc == 6)) == testfor; ++ { ++ const uschar *errp; ++ const uschar **errpp; ++ DEBUG(D_expand) errpp = &errp; else errpp = 0; ++ if (0 == (rc = string_is_ip_addressX(sub[0], NULL, errpp))) ++ DEBUG(D_expand) debug_printf("failed: %s\n", errp); ++ ++ *yield = ( cond_type == ECOND_ISIP ? rc != 0 : ++ cond_type == ECOND_ISIP4 ? rc == 4 : rc == 6) == testfor; ++ } ++ + break; + + /* Various authentication tests - all optionally compiled */ + + case ECOND_PAM: + #ifdef SUPPORT_PAM + rc = auth_call_pam(sub[0], &expand_string_message); +diff --git a/src/functions.h b/src/functions.h +index 224666cb1..3c8104d25 100644 +--- a/src/functions.h ++++ b/src/functions.h +@@ -552,14 +552,15 @@ extern gstring *string_catn(gstring *, const uschar *, int) WARN_UNUSED_RESULT; + extern int string_compare_by_pointer(const void *, const void *); + extern uschar *string_copy_dnsdomain(uschar *); + extern uschar *string_copy_malloc(const uschar *); + extern uschar *string_dequote(const uschar **); + extern uschar *string_format_size(int, uschar *); + extern int string_interpret_escape(const uschar **); + extern int string_is_ip_address(const uschar *, int *); ++extern int string_is_ip_addressX(const uschar *, int *, const uschar **); + #ifdef SUPPORT_I18N + extern BOOL string_is_utf8(const uschar *); + #endif + extern const uschar *string_printing2(const uschar *, int); + extern uschar *string_split_message(uschar *); + extern uschar *string_unprinting(uschar *); + #ifdef SUPPORT_I18N +diff --git a/src/string.c b/src/string.c +index a5161bb31..9aefc2b58 100644 +--- a/src/string.c ++++ b/src/string.c +@@ -25,131 +25,141 @@ address (assuming HAVE_IPV6 is set). If a mask is permitted and one is present, + and maskptr is not NULL, its offset is placed there. + + Arguments: + s a string + maskptr NULL if no mask is permitted to follow + otherwise, points to an int where the offset of '/' is placed + if there is no / followed by trailing digits, *maskptr is set 0 ++ errp NULL if no diagnostic information is required, and if the netmask ++ length should not be checked. Otherwise it is set pointing to a short ++ descriptive text. + + Returns: 0 if the string is not a textual representation of an IP address + 4 if it is an IPv4 address + 6 if it is an IPv6 address +-*/ + ++The legacy string_is_ip_address() function follows below. ++*/ + int +-string_is_ip_address(const uschar *s, int *maskptr) +-{ +-int yield = 4; ++string_is_ip_addressX(const uschar *ip_addr, int *maskptr, const uschar **errp) { ++ struct addrinfo hints; ++ struct addrinfo *res; + +-/* If an optional mask is permitted, check for it. If found, pass back the +-offset. */ ++ uschar *slash, *percent; + +-if (maskptr) ++ uschar *endp = 0; ++ long int mask = 0; ++ const uschar *addr = 0; ++ ++ /* If there is a slash, but we didn't request a (optional) netmask, ++ we return failure, as we do if the mask isn't a pure numerical value, ++ or if it is negative. The actual length is checked later, once we know ++ the address family. */ ++ if (slash = Ustrchr(ip_addr, '/')) + { +- const uschar *ss = s + Ustrlen(s); +- *maskptr = 0; +- if (s != ss && isdigit(*(--ss))) ++ if (!maskptr) + { +- while (ss > s && isdigit(ss[-1])) ss--; +- if (ss > s && *(--ss) == '/') *maskptr = ss - s; ++ if (errp) *errp = "netmask found, but not requested"; ++ return 0; + } +- } +- +-/* A colon anywhere in the string => IPv6 address */ +- +-if (Ustrchr(s, ':') != NULL) +- { +- BOOL had_double_colon = FALSE; +- BOOL v4end = FALSE; +- +- yield = 6; +- +- /* An IPv6 address must start with hex digit or double colon. A single +- colon is invalid. */ +- +- if (*s == ':' && *(++s) != ':') return 0; +- +- /* Now read up to 8 components consisting of up to 4 hex digits each. There +- may be one and only one appearance of double colon, which implies any number +- of binary zero bits. The number of preceding components is held in count. */ + +- for (int count = 0; count < 8; count++) ++ uschar *rest; ++ mask = Ustrtol(slash+1, &rest, 10); ++ if (*rest || mask < 0) + { +- /* If the end of the string is reached before reading 8 components, the +- address is valid provided a double colon has been read. This also applies +- if we hit the / that introduces a mask or the % that introduces the +- interface specifier (scope id) of a link-local address. */ +- +- if (*s == 0 || *s == '%' || *s == '/') return had_double_colon ? yield : 0; +- +- /* If a component starts with an additional colon, we have hit a double +- colon. This is permitted to appear once only, and counts as at least +- one component. The final component may be of this form. */ +- +- if (*s == ':') +- { +- if (had_double_colon) return 0; +- had_double_colon = TRUE; +- s++; +- continue; +- } +- +- /* If the remainder of the string contains a dot but no colons, we +- can expect a trailing IPv4 address. This is valid if either there has +- been no double-colon and this is the 7th component (with the IPv4 address +- being the 7th & 8th components), OR if there has been a double-colon +- and fewer than 6 components. */ +- +- if (Ustrchr(s, ':') == NULL && Ustrchr(s, '.') != NULL) +- { +- if ((!had_double_colon && count != 6) || +- (had_double_colon && count > 6)) return 0; +- v4end = TRUE; +- yield = 6; +- break; +- } +- +- /* Check for at least one and not more than 4 hex digits for this +- component. */ +- +- if (!isxdigit(*s++)) return 0; +- if (isxdigit(*s) && isxdigit(*(++s)) && isxdigit(*(++s))) s++; +- +- /* If the component is terminated by colon and there is more to +- follow, skip over the colon. If there is no more to follow the address is +- invalid. */ +- +- if (*s == ':' && *(++s) == 0) return 0; ++ if (errp) *errp = "netmask not numeric or <0"; ++ return 0; + } + +- /* If about to handle a trailing IPv4 address, drop through. Otherwise +- all is well if we are at the end of the string or at the mask or at a percent +- sign, which introduces the interface specifier (scope id) of a link local +- address. */ ++ *maskptr = slash - ip_addr; /* offset of the slash */ ++ endp = slash; ++ } else if (maskptr) *maskptr = 0; /* no slash found */ + +- if (!v4end) +- return (*s == 0 || *s == '%' || +- (*s == '/' && maskptr != NULL && *maskptr != 0))? yield : 0; ++ /* The interface-ID suffix (%) is optional (for IPv6). If it ++ exists, we check it syntactically. Later, if we know the address ++ family is IPv4, we might reject it. ++ The interface-ID is mutually exclusive with the netmask, to the ++ best of my knowledge. */ ++ if (percent = Ustrchr(ip_addr, '%')) ++ { ++ if (slash) ++ { ++ if (errp) *errp = "interface-ID and netmask are mutually exclusive"; ++ return 0; ++ } ++ for (uschar *p = percent+1; *p; p++) ++ if (!isalnum(*p) && !ispunct(*p)) ++ { ++ if (errp) *errp = "interface-ID must match [[:alnum:][:punct:]]"; ++ return 0; ++ } ++ endp = percent; + } + +-/* Test for IPv4 address, which may be the tail-end of an IPv6 address. */ +- +-for (int i = 0; i < 4; i++) ++ /* inet_pton() can't parse netmasks and interface IDs, so work on a shortened copy ++ allocated on the current stack */ ++ if (endp) { ++ ptrdiff_t l = endp - ip_addr; ++ if (l > 255) ++ { ++ if (errp) *errp = "rudiculous long ip address string"; ++ return 0; ++ } ++ addr = alloca(l+1); /* *BSD does not have strndupa() */ ++ Ustrncpy((uschar *)addr, ip_addr, l); ++ ((uschar*)addr)[l] = '\0'; ++ } else addr = ip_addr; ++ ++ int af; ++ union { /* we do not need this, but inet_pton() needs a place for storage */ ++ struct in_addr sa4; ++ struct in6_addr sa6; ++ } sa; ++ ++ af = Ustrchr(addr, ':') ? AF_INET6 : AF_INET; ++ if (!inet_pton(af, addr, &sa)) + { +- long n; +- uschar * end; +- +- if (i != 0 && *s++ != '.') return 0; +- n = strtol(CCS s, CSS &end, 10); +- if (n > 255 || n < 0 || end <= s || end > s+3) return 0; +- s = end; ++ if (errp) *errp = af == AF_INET6 ? "IP address string not parsable as IPv6" ++ : "IP address string not parsable IPv4"; ++ return 0; + } ++ /* we do not check the values of the mask here, as ++ this is done on the callers side (but I don't understand why), so ++ actually I'd like to do it here, but it breaks at least 0002 */ ++ switch (af) ++ { ++ case AF_INET6: ++ if (errp && mask > 128) ++ { ++ *errp = "IPv6 netmask value must not be >128"; ++ return 0; ++ } ++ return 6; ++ case AF_INET: ++ if (percent) ++ { ++ if (errp) *errp = "IPv4 address string must not have an interface-ID"; ++ return 0; ++ } ++ if (errp && mask > 32) { ++ *errp = "IPv4 netmask value must not be >32"; ++ return 0; ++ } ++ return 4; ++ default: ++ if (errp) *errp = "unknown address family (should not happen)"; ++ return 0; ++ } ++} + +-return !*s || (*s == '/' && maskptr && *maskptr != 0) ? yield : 0; ++int ++string_is_ip_address(const uschar *ip_addr, int *maskptr) { ++ return string_is_ip_addressX(ip_addr, maskptr, 0); + } ++ + #endif /* COMPILE_UTILITY */ + + + /************************************************* + * Format message size * + *************************************************/ + +-- +2.42.0 + diff --git a/debian/patches/76-02-SPF-harden-against-crafted-DNS-responses.patch b/debian/patches/76-02-SPF-harden-against-crafted-DNS-responses.patch new file mode 100644 index 0000000..b11ef14 --- /dev/null +++ b/debian/patches/76-02-SPF-harden-against-crafted-DNS-responses.patch @@ -0,0 +1,64 @@ +From 654056e44fc93a0ee7c09d1228933e8af6862206 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Tue, 10 Oct 2023 12:45:27 +0100 +Subject: [PATCH 2/3] SPF: harden against crafted DNS responses + +(cherry picked from commit 4f07f38374f8662c318699fb30432273ffcfe0d3) +--- + src/spf.c | 9 +++++++-- + 1 file changed, 7 insertions(+), 2 deletions(-) + +diff --git a/src/spf.c b/src/spf.c +index db6eea3a8..1981d81b6 100644 +--- a/src/spf.c ++++ b/src/spf.c +@@ -116,14 +116,15 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; + { + const uschar * s = rr->data; + + srr.ttl = rr->ttl; + switch(rr_type) + { + case T_MX: ++ if (rr->size < 2) continue; + s += 2; /* skip the MX precedence field */ + case T_PTR: + { + uschar * buf = store_malloc(256); + (void)dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, s, + (DN_EXPAND_ARG4_TYPE)buf, 256); + s = buf; +@@ -131,24 +132,28 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; + } + + case T_TXT: + { + gstring * g = NULL; + uschar chunk_len; + ++ if (rr->size < 1+6) continue; /* min for version str */ + if (strncmpic(rr->data+1, US SPF_VER_STR, 6) != 0) + { + HDEBUG(D_host_lookup) debug_printf("not an spf record: %.*s\n", + (int) s[0], s+1); + continue; + } + +- for (int off = 0; off < rr->size; off += chunk_len) ++ /* require 1 byte for the chunk_len */ ++ for (int off = 0; off < rr->size - 1; off += chunk_len) + { +- if (!(chunk_len = s[off++])) break; ++ if ( !(chunk_len = s[off++]) ++ || rr->size < off + chunk_len /* ignore bogus size chunks */ ++ ) break; + g = string_catn(g, s+off, chunk_len); + } + if (!g) + continue; + gstring_release_unused(g); + s = string_copy_malloc(string_from_gstring(g)); + DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", s); +-- +2.42.0 + diff --git a/debian/patches/76-03-Harden-dnsdb-against-crafted-DNS-responses.-Bug-3033.patch b/debian/patches/76-03-Harden-dnsdb-against-crafted-DNS-responses.-Bug-3033.patch new file mode 100644 index 0000000..b748661 --- /dev/null +++ b/debian/patches/76-03-Harden-dnsdb-against-crafted-DNS-responses.-Bug-3033.patch @@ -0,0 +1,244 @@ +From f6b1f8e7d642f82d830a71b78699a4349e0158e1 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Tue, 10 Oct 2023 23:03:28 +0100 +Subject: [PATCH 3/3] Harden dnsdb against crafted DNS responses. Bug 3033 + +(cherry picked from commit 8787c8994f07c23c3664d76926e02f07314d699d) +--- + doc/ChangeLog | 3 ++ + src/dns.c | 11 +++--- + src/lookups/dnsdb.c | 78 +++++++++++++++++++++++++++-------------- + 3 files changed, 59 insertions(+), 33 deletions(-) + +diff --git a/src/dns.c b/src/dns.c +index 7d7ee0c04..8dc3695a1 100644 +--- a/src/dns.c ++++ b/src/dns.c +@@ -300,15 +300,15 @@ return string_from_gstring(g); + + /* Increment the aptr in dnss, checking against dnsa length. + Return: TRUE for a bad result + */ + static BOOL + dnss_inc_aptr(const dns_answer * dnsa, dns_scan * dnss, unsigned delta) + { +-return (dnss->aptr += delta) >= dnsa->answer + dnsa->answerlen; ++return (dnss->aptr += delta) > dnsa->answer + dnsa->answerlen; + } + + /************************************************* + * Get next DNS record from answer block * + *************************************************/ + + /* Call this with reset == RESET_ANSWERS to scan the answer block, reset == +@@ -384,15 +384,15 @@ if (reset != RESET_NEXT) + namelen = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, + dnss->aptr, (DN_EXPAND_ARG4_TYPE) &dnss->srr.name, DNS_MAXNAME); + if (namelen < 0) goto null_return; + /* skip name, type, class & TTL */ + TRACE trace = "A-hdr"; + if (dnss_inc_aptr(dnsa, dnss, namelen+8)) goto null_return; + GETSHORT(dnss->srr.size, dnss->aptr); /* size of data portion */ +- /* skip over it */ ++ /* skip over it, checking for a bogus size */ + TRACE trace = "A-skip"; + if (dnss_inc_aptr(dnsa, dnss, dnss->srr.size)) goto null_return; + } + dnss->rrcount = reset == RESET_AUTHORITY + ? ntohs(h->nscount) : ntohs(h->arcount); + TRACE debug_printf("%s: reset (%s rrcount %d)\n", __FUNCTION__, + reset == RESET_AUTHORITY ? "NS" : "AR", dnss->rrcount); +@@ -424,18 +424,17 @@ if (dnss_inc_aptr(dnsa, dnss, namelen)) goto null_return; + GETSHORT(dnss->srr.type, dnss->aptr); /* Record type */ + TRACE trace = "R-class"; + if (dnss_inc_aptr(dnsa, dnss, 2)) goto null_return; /* Don't want class */ + GETLONG(dnss->srr.ttl, dnss->aptr); /* TTL */ + GETSHORT(dnss->srr.size, dnss->aptr); /* Size of data portion */ + dnss->srr.data = dnss->aptr; /* The record's data follows */ + +-/* Unchecked increment ok here since no further access on this iteration; +-will be checked on next at "R-name". */ +- +-dnss->aptr += dnss->srr.size; /* Advance to next RR */ ++/* skip over it, checking for a bogus size */ ++if (dnss_inc_aptr(dnsa, dnss, dnss->srr.size)) ++ goto null_return; + + /* Return a pointer to the dns_record structure within the dns_answer. This is + for convenience so that the scans can use nice-looking for loops. */ + + TRACE debug_printf("%s: return %s\n", __FUNCTION__, dns_text_type(dnss->srr.type)); + return &dnss->srr; + +diff --git a/src/lookups/dnsdb.c b/src/lookups/dnsdb.c +index 355be1b5d..020dc9a52 100644 +--- a/src/lookups/dnsdb.c ++++ b/src/lookups/dnsdb.c +@@ -394,80 +394,101 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0))) + /* Other kinds of record just have one piece of data each, but there may be + several of them, of course. */ + + if (yield->ptr) yield = string_catn(yield, outsep, 1); + + if (type == T_TXT || type == T_SPF) + { +- if (outsep2 == NULL) /* output only the first item of data */ +- yield = string_catn(yield, US (rr->data+1), (rr->data)[0]); ++ if (!outsep2) /* output only the first item of data */ ++ { ++ uschar n = (rr->data)[0]; ++ /* size byte + data bytes must not excced the RRs length */ ++ if (n + 1 <= rr->size) ++ yield = string_catn(yield, US (rr->data+1), n); ++ } + else + { + /* output all items */ + int data_offset = 0; + while (data_offset < rr->size) + { +- uschar chunk_len = (rr->data)[data_offset++]; +- if (outsep2[0] != '\0' && data_offset != 1) ++ uschar chunk_len = (rr->data)[data_offset]; ++ int remain = rr->size - data_offset; ++ ++ /* Apparently there are resolvers that do not check RRs before passing ++ them on, and glibc fails to do so. So every application must... ++ Check for chunk len exceeding RR */ ++ ++ if (chunk_len > remain) ++ chunk_len = remain; ++ ++ if (*outsep2 && data_offset != 0) + yield = string_catn(yield, outsep2, 1); +- yield = string_catn(yield, US ((rr->data)+data_offset), chunk_len); ++ yield = string_catn(yield, US ((rr->data) + ++data_offset), --chunk_len); + data_offset += chunk_len; + } + } + } + else if (type == T_TLSA) +- { +- uint8_t usage, selector, matching_type; +- uint16_t payload_length; +- uschar s[MAX_TLSA_EXPANDED_SIZE]; +- uschar * sp = s; +- uschar * p = US rr->data; ++ if (rr->size < 3) ++ continue; ++ else ++ { ++ uint8_t usage, selector, matching_type; ++ uint16_t payload_length; ++ uschar s[MAX_TLSA_EXPANDED_SIZE]; ++ uschar * sp = s; ++ uschar * p = US rr->data; ++ ++ usage = *p++; ++ selector = *p++; ++ matching_type = *p++; ++ /* What's left after removing the first 3 bytes above */ ++ payload_length = rr->size - 3; ++ sp += sprintf(CS s, "%d%c%d%c%d%c", usage, *outsep2, ++ selector, *outsep2, matching_type, *outsep2); ++ /* Now append the cert/identifier, one hex char at a time */ ++ while (payload_length-- > 0 && sp-s < (MAX_TLSA_EXPANDED_SIZE - 4)) ++ sp += sprintf(CS sp, "%02x", *p++); + +- usage = *p++; +- selector = *p++; +- matching_type = *p++; +- /* What's left after removing the first 3 bytes above */ +- payload_length = rr->size - 3; +- sp += sprintf(CS s, "%d%c%d%c%d%c", usage, *outsep2, +- selector, *outsep2, matching_type, *outsep2); +- /* Now append the cert/identifier, one hex char at a time */ +- while (payload_length-- > 0 && sp-s < (MAX_TLSA_EXPANDED_SIZE - 4)) +- sp += sprintf(CS sp, "%02x", *p++); +- +- yield = string_cat(yield, s); +- } ++ yield = string_cat(yield, s); ++ } + else /* T_CNAME, T_CSA, T_MX, T_MXH, T_NS, T_PTR, T_SOA, T_SRV */ + { + int priority, weight, port; + uschar s[264]; + uschar * p = US rr->data; + + switch (type) + { + case T_MXH: ++ if (rr->size < sizeof(u_int16_t)) continue; + /* mxh ignores the priority number and includes only the hostnames */ + GETSHORT(priority, p); + break; + + case T_MX: ++ if (rr->size < sizeof(u_int16_t)) continue; + GETSHORT(priority, p); + sprintf(CS s, "%d%c", priority, *outsep2); + yield = string_cat(yield, s); + break; + + case T_SRV: ++ if (rr->size < 3*sizeof(u_int16_t)) continue; + GETSHORT(priority, p); + GETSHORT(weight, p); + GETSHORT(port, p); + sprintf(CS s, "%d%c%d%c%d%c", priority, *outsep2, + weight, *outsep2, port, *outsep2); + yield = string_cat(yield, s); + break; + + case T_CSA: ++ if (rr->size < 3*sizeof(u_int16_t)) continue; + /* See acl_verify_csa() for more comments about CSA. */ + GETSHORT(priority, p); + GETSHORT(weight, p); + GETSHORT(port, p); + + if (priority != 1) continue; /* CSA version must be 1 */ + +@@ -510,15 +531,15 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0))) + "domain=%s", dns_text_type(type), domain); + break; + } + else yield = string_cat(yield, s); + + if (type == T_SOA && outsep2 != NULL) + { +- unsigned long serial, refresh, retry, expire, minimum; ++ unsigned long serial = 0, refresh = 0, retry = 0, expire = 0, minimum = 0; + + p += rc; + yield = string_catn(yield, outsep2, 1); + + rc = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, p, + (DN_EXPAND_ARG4_TYPE)s, sizeof(s)); + if (rc < 0) +@@ -526,16 +547,19 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0))) + log_write(0, LOG_MAIN, "responsible-mailbox truncated: type=%s " + "domain=%s", dns_text_type(type), domain); + break; + } + else yield = string_cat(yield, s); + + p += rc; +- GETLONG(serial, p); GETLONG(refresh, p); +- GETLONG(retry, p); GETLONG(expire, p); GETLONG(minimum, p); ++ if (rr->size >= p - rr->data - 5*sizeof(u_int32_t)) ++ { ++ GETLONG(serial, p); GETLONG(refresh, p); ++ GETLONG(retry, p); GETLONG(expire, p); GETLONG(minimum, p); ++ } + sprintf(CS s, "%c%lu%c%lu%c%lu%c%lu%c%lu", + *outsep2, serial, *outsep2, refresh, + *outsep2, retry, *outsep2, expire, *outsep2, minimum); + yield = string_cat(yield, s); + } + } + } /* Loop for list of returned records */ +-- +2.42.0 + diff --git a/debian/patches/76-10-Fix-tr.-and-empty-strings.-Bug-3023.patch b/debian/patches/76-10-Fix-tr.-and-empty-strings.-Bug-3023.patch new file mode 100644 index 0000000..f297336 --- /dev/null +++ b/debian/patches/76-10-Fix-tr.-and-empty-strings.-Bug-3023.patch @@ -0,0 +1,56 @@ +From 0f8814a1d8db65d1815a6a544a08fb2b6b9207ed Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Mon, 11 Sep 2023 15:50:35 +0100 +Subject: [PATCH] Fix ${tr...} and empty-strings. Bug 3023 + +(cherry picked from commit b015574531cf18b2126edb9da5a99dad659207dd) +--- + doc/ChangeLog | 3 +++ + src/expand.c | 9 ++++----- + test/scripts/0000-Basic/0002 | 1 + + test/stdout/0002 | 1 + + 4 files changed, 9 insertions(+), 5 deletions(-) + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -85,10 +85,13 @@ JH/34 Bug 3013: Fix use of $recipients w + In 4.96 this would expand to empty. + + JH/35 Bug 3014: GnuTLS: fix expiry date for an auto-generated server + certificate. Find and fix by Andreas Metzler. + ++JH/39 Bug 3023: Fix crash induced by some combinations of zero-length strings ++ and ${tr...}. Found and diagnosed by Heiko Schlichting. ++ + Exim version 4.96 + ----------------- + + JH/01 Move the wait-for-next-tick (needed for unique message IDs) from + after reception to before a subsequent reception. This should +--- a/src/expand.c ++++ b/src/expand.c +@@ -5696,20 +5696,19 @@ while (*s) + case 1: goto EXPAND_FAILED_CURLY; + case 2: + case 3: goto EXPAND_FAILED; + } + +- yield = string_cat(yield, sub[0]); +- o2m = Ustrlen(sub[2]) - 1; +- +- if (o2m >= 0) for (; oldptr < yield->ptr; oldptr++) ++ if ( (yield = string_cat(yield, sub[0])) ++ && (o2m = Ustrlen(sub[2]) - 1) >= 0) ++ for (; oldptr < yield->ptr; oldptr++) + { + uschar *m = Ustrrchr(sub[1], yield->s[oldptr]); + if (m) + { + int o = m - sub[1]; +- yield->s[oldptr] = sub[2][(o < o2m)? o : o2m]; ++ yield->s[oldptr] = sub[2][o < o2m ? o : o2m]; + } + } + + if (skipping) continue; + break; diff --git a/debian/patches/76-12-DNS-more-hardening-against-crafted-responses.patch b/debian/patches/76-12-DNS-more-hardening-against-crafted-responses.patch new file mode 100644 index 0000000..150c92c --- /dev/null +++ b/debian/patches/76-12-DNS-more-hardening-against-crafted-responses.patch @@ -0,0 +1,219 @@ +From b94ea1bd61485a97c2d0dc2cab4c4d86ffe82e89 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Sun, 15 Oct 2023 12:15:06 +0100 +Subject: [PATCH] DNS: more hardening against crafted responses + +--- + src/acl.c | 1 + + src/dns.c | 39 ++++++++++++++++++++++++++++++--------- + src/functions.h | 16 ++++++++++++++++ + src/host.c | 3 +++ + src/lookups/dnsdb.c | 10 +++++----- + 5 files changed, 55 insertions(+), 14 deletions(-) + +diff --git a/src/acl.c b/src/acl.c +index 118e4b35d..302dedaeb 100644 +--- a/src/acl.c ++++ b/src/acl.c +@@ -1434,6 +1434,7 @@ for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); + + /* Extract the numerical SRV fields (p is incremented) */ + ++ if (rr_bad_size(rr, 3 * sizeof(uint16_t))) continue; + GETSHORT(priority, p); + GETSHORT(weight, p); + GETSHORT(port, p); +diff --git a/src/dns.c b/src/dns.c +index db566f2e8..1347deec8 100644 +--- a/src/dns.c ++++ b/src/dns.c +@@ -299,13 +299,23 @@ return string_from_gstring(g); + + + ++/* Check a pointer for being past the end of a dns answer. ++Exactly one past the end is defined as ok. ++Return TRUE iff bad. ++*/ ++static BOOL ++dnsa_bad_ptr(const dns_answer * dnsa, const uschar * ptr) ++{ ++return ptr > dnsa->answer + dnsa->answerlen; ++} ++ + /* Increment the aptr in dnss, checking against dnsa length. + Return: TRUE for a bad result + */ + static BOOL + dnss_inc_aptr(const dns_answer * dnsa, dns_scan * dnss, unsigned delta) + { +-return (dnss->aptr += delta) > dnsa->answer + dnsa->answerlen; ++return dnsa_bad_ptr(dnsa, dnss->aptr += delta); + } + + /************************************************* +@@ -385,10 +395,14 @@ if (reset != RESET_NEXT) + namelen = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, + dnss->aptr, (DN_EXPAND_ARG4_TYPE) &dnss->srr.name, DNS_MAXNAME); + if (namelen < 0) goto null_return; ++ + /* skip name, type, class & TTL */ + TRACE trace = "A-hdr"; + if (dnss_inc_aptr(dnsa, dnss, namelen+8)) goto null_return; ++ ++ if (dnsa_bad_ptr(dnsa, dnss->aptr + sizeof(uint16_t))) goto null_return; + GETSHORT(dnss->srr.size, dnss->aptr); /* size of data portion */ ++ + /* skip over it, checking for a bogus size */ + TRACE trace = "A-skip"; + if (dnss_inc_aptr(dnsa, dnss, dnss->srr.size)) goto null_return; +@@ -422,12 +436,18 @@ from the following bytes. */ + TRACE trace = "R-name"; + if (dnss_inc_aptr(dnsa, dnss, namelen)) goto null_return; + +-GETSHORT(dnss->srr.type, dnss->aptr); /* Record type */ ++/* Check space for type, class, TTL & data-size-word */ ++if (dnsa_bad_ptr(dnsa, dnss->aptr + 3 * sizeof(uint16_t) + sizeof(uint32_t))) ++ goto null_return; ++ ++GETSHORT(dnss->srr.type, dnss->aptr); /* Record type */ ++ + TRACE trace = "R-class"; +-if (dnss_inc_aptr(dnsa, dnss, 2)) goto null_return; /* Don't want class */ +-GETLONG(dnss->srr.ttl, dnss->aptr); /* TTL */ +-GETSHORT(dnss->srr.size, dnss->aptr); /* Size of data portion */ +-dnss->srr.data = dnss->aptr; /* The record's data follows */ ++(void) dnss_inc_aptr(dnsa, dnss, sizeof(uint16_t)); /* skip class */ ++ ++GETLONG(dnss->srr.ttl, dnss->aptr); /* TTL */ ++GETSHORT(dnss->srr.size, dnss->aptr); /* Size of data portion */ ++dnss->srr.data = dnss->aptr; /* The record's data follows */ + + /* skip over it, checking for a bogus size */ + if (dnss_inc_aptr(dnsa, dnss, dnss->srr.size)) +@@ -743,17 +763,17 @@ if (fake_dnsa_len_for_fail(dnsa, type)) + /* Skip the mname & rname strings */ + + if ((len = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, +- p, (DN_EXPAND_ARG4_TYPE)discard_buf, 256)) < 0) ++ p, (DN_EXPAND_ARG4_TYPE)discard_buf, sizeof(discard_buf))) < 0) + break; + p += len; + if ((len = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, +- p, (DN_EXPAND_ARG4_TYPE)discard_buf, 256)) < 0) ++ p, (DN_EXPAND_ARG4_TYPE)discard_buf, sizeof(discard_buf))) < 0) + break; + p += len; + + /* Skip the SOA serial, refresh, retry & expire. Grab the TTL */ + +- if (p > dnsa->answer + dnsa->answerlen - 5 * INT32SZ) ++ if (dnsa_bad_ptr(dnsa, p + 5 * INT32SZ)) + break; + p += 4 * INT32SZ; + GETLONG(ttl, p); +@@ -1257,6 +1277,7 @@ switch (type) + const uschar * p = rr->data; + + /* Extract the numerical SRV fields (p is incremented) */ ++ if (rr_bad_size(rr, 3 * sizeof(uint16_t))) continue; + GETSHORT(priority, p); + GETSHORT(dummy_weight, p); + GETSHORT(port, p); +diff --git a/src/functions.h b/src/functions.h +index 8f85165e7..39119ca09 100644 +--- a/src/functions.h ++++ b/src/functions.h +@@ -1110,6 +1110,22 @@ store_free_dns_answer_trc(dns_answer * dnsa, const uschar * func, unsigned line) + store_free_3(dnsa, CCS func, line); + } + ++ ++/* Check for an RR being large enough. Return TRUE iff bad. */ ++static inline BOOL ++rr_bad_size(const dns_record * rr, size_t minbytes) ++{ ++return rr->size < minbytes; ++} ++ ++/* Check for an RR having further data beyond a given pointer. ++Return TRUE iff bad. */ ++static inline BOOL ++rr_bad_increment(const dns_record * rr, const uschar * ptr, size_t minbytes) ++{ ++return rr_bad_size(rr, ptr - rr->data + minbytes); ++} ++ + /******************************************************************************/ + /* Routines with knowledge of spool layout */ + +diff --git a/src/host.c b/src/host.c +index 3e5a88660..ce7ca2bab 100644 +--- a/src/host.c ++++ b/src/host.c +@@ -2725,6 +2725,7 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); + const uschar * s = rr->data; /* MUST be unsigned for GETSHORT */ + uschar data[256]; + ++ if (rr_bad_size(rr, sizeof(uint16_t))) continue; + GETSHORT(precedence, s); /* Pointer s is advanced */ + + /* For MX records, we use a random "weight" which causes multiple records of +@@ -2737,6 +2738,8 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); + /* SRV records are specified with a port and a weight. The weight is used + in a special algorithm. However, to start with, we just use it to order the + records of equal priority (precedence). */ ++ ++ if (rr_bad_increment(rr, s, 2 * sizeof(uint16_t))) continue; + GETSHORT(weight, s); + GETSHORT(port, s); + } +diff --git a/src/lookups/dnsdb.c b/src/lookups/dnsdb.c +index 35a946447..fcf80e3dd 100644 +--- a/src/lookups/dnsdb.c ++++ b/src/lookups/dnsdb.c +@@ -452,20 +452,20 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0))) + switch (type) + { + case T_MXH: +- if (rr->size < sizeof(u_int16_t)) continue; ++ if (rr_bad_size(rr, sizeof(u_int16_t))) continue; + /* mxh ignores the priority number and includes only the hostnames */ + GETSHORT(priority, p); + break; + + case T_MX: +- if (rr->size < sizeof(u_int16_t)) continue; ++ if (rr_bad_size(rr, sizeof(u_int16_t))) continue; + GETSHORT(priority, p); + sprintf(CS s, "%d%c", priority, *outsep2); + yield = string_cat(yield, s); + break; + + case T_SRV: +- if (rr->size < 3*sizeof(u_int16_t)) continue; ++ if (rr_bad_size(rr, 3*sizeof(u_int16_t))) continue; + GETSHORT(priority, p); + GETSHORT(weight, p); + GETSHORT(port, p); +@@ -475,7 +475,7 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0))) + break; + + case T_CSA: +- if (rr->size < 3*sizeof(u_int16_t)) continue; ++ if (rr_bad_size(rr, 3*sizeof(u_int16_t))) continue; + /* See acl_verify_csa() for more comments about CSA. */ + GETSHORT(priority, p); + GETSHORT(weight, p); +@@ -542,7 +542,7 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0))) + else yield = string_cat(yield, s); + + p += rc; +- if (rr->size >= p - rr->data - 5*sizeof(u_int32_t)) ++ if (!rr_bad_increment(rr, p, 5 * sizeof(u_int32_t))) + { + GETLONG(serial, p); GETLONG(refresh, p); + GETLONG(retry, p); GETLONG(expire, p); GETLONG(minimum, p); +-- +2.42.0 + diff --git a/debian/patches/76-14-Lookups-Fix-dnsdb-lookup-of-multi-chunk-TXT.-Bug-305.patch b/debian/patches/76-14-Lookups-Fix-dnsdb-lookup-of-multi-chunk-TXT.-Bug-305.patch new file mode 100644 index 0000000..7b9eb6e --- /dev/null +++ b/debian/patches/76-14-Lookups-Fix-dnsdb-lookup-of-multi-chunk-TXT.-Bug-305.patch @@ -0,0 +1,118 @@ +From 79670d3c32ccb37fe06f25d8192943b58606a32a Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Fri, 17 Nov 2023 16:55:17 +0000 +Subject: [PATCH] Lookups: Fix dnsdb lookup of multi-chunk TXT. Bug 3054 + +Broken=by: f6b1f8e7d642 +--- + doc/ChangeLog | 6 ++++- + src/dns.c | 4 +++- + src/lookups/dnsdb.c | 45 +++++++++++++++--------------------- + test/dnszones-src/db.test.ex | 1 + + test/scripts/2200-dnsdb/2200 | 1 + + test/src/fakens.c | 1 + + test/stdout/2200 | 1 + + 7 files changed, 31 insertions(+), 28 deletions(-) + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -87,10 +87,13 @@ JH/34 Bug 3013: Fix use of $recipients w + JH/35 Bug 3014: GnuTLS: fix expiry date for an auto-generated server + certificate. Find and fix by Andreas Metzler. + + JH/39 Bug 3023: Fix crash induced by some combinations of zero-length strings + and ${tr...}. Found and diagnosed by Heiko Schlichting. ++ ++JH/06 Bug 3054: Fix dnsdb lookup for a TXT record with multiple chunks. This ++ was broken by hardening introduced for Bug 3033. + + Exim version 4.96 + ----------------- + + JH/01 Move the wait-for-next-tick (needed for unique message IDs) from +--- a/src/dns.c ++++ b/src/dns.c +@@ -428,11 +428,13 @@ TRACE trace = "R-namelen"; + namelen = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, dnss->aptr, + (DN_EXPAND_ARG4_TYPE) &dnss->srr.name, DNS_MAXNAME); + if (namelen < 0) goto null_return; + + /* Move the pointer past the name and fill in the rest of the data structure +-from the following bytes. */ ++from the following bytes. We seem to be assuming here that the RR blob passed ++to us by the resolver library is the same as that defined for an RR by RFC 1035 ++section 3.2.1 */ + + TRACE trace = "R-name"; + if (dnss_inc_aptr(dnsa, dnss, namelen)) goto null_return; + + /* Check space for type, class, TTL & data-size-word */ +--- a/src/lookups/dnsdb.c ++++ b/src/lookups/dnsdb.c +@@ -390,46 +390,36 @@ while ((domain = string_nextinlist(&keys + } + continue; + } + + /* Other kinds of record just have one piece of data each, but there may be +- several of them, of course. */ ++ several of them, of course. TXT & SPF can have data in multiple chunks. */ + + if (yield->ptr) yield = string_catn(yield, outsep, 1); + + if (type == T_TXT || type == T_SPF) +- { +- if (!outsep2) /* output only the first item of data */ ++ for (unsigned data_offset = 0; data_offset + 1 < rr->size; ) + { +- uschar n = (rr->data)[0]; +- /* size byte + data bytes must not excced the RRs length */ +- if (n + 1 <= rr->size) +- yield = string_catn(yield, US (rr->data+1), n); ++ uschar chunk_len = (rr->data)[data_offset]; ++ int remain; ++ ++ if (outsep2 && *outsep2 && data_offset != 0) ++ yield = string_catn(yield, outsep2, 1); ++ ++ /* Apparently there are resolvers that do not check RRs before passing ++ them on, and glibc fails to do so. So every application must... ++ Check for chunk len exceeding RR */ ++ ++ remain = rr->size - ++data_offset; ++ if (chunk_len > remain) ++ chunk_len = remain; ++ yield = string_catn(yield, US ((rr->data) + data_offset), chunk_len); ++ data_offset += chunk_len; ++ ++ if (!outsep2) break; /* output only the first chunk of the RR */ ++ + } +- else +- { +- /* output all items */ +- int data_offset = 0; +- while (data_offset < rr->size) +- { +- uschar chunk_len = (rr->data)[data_offset]; +- int remain = rr->size - data_offset; +- +- /* Apparently there are resolvers that do not check RRs before passing +- them on, and glibc fails to do so. So every application must... +- Check for chunk len exceeding RR */ +- +- if (chunk_len > remain) +- chunk_len = remain; +- +- if (*outsep2 && data_offset != 0) +- yield = string_catn(yield, outsep2, 1); +- yield = string_catn(yield, US ((rr->data) + ++data_offset), --chunk_len); +- data_offset += chunk_len; +- } +- } +- } + else if (type == T_TLSA) + if (rr->size < 3) + continue; + else + { diff --git a/debian/patches/77_CVE-2023-51766_4.97.1-release.diff b/debian/patches/77_CVE-2023-51766_4.97.1-release.diff new file mode 100644 index 0000000..77d52ce --- /dev/null +++ b/debian/patches/77_CVE-2023-51766_4.97.1-release.diff @@ -0,0 +1,440 @@ +Description: Fix smtp-smuggling (CVE-2023-51766) + Pull upstream changes from 4.97.1 security release. +Author: Jeremy Harris +Bug-Debian: https://bugs.debian.org/1059387 +Origin: upstream +Last-Update: 2023-12-31 + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -91,10 +91,16 @@ JH/39 Bug 3023: Fix crash induced by som + and ${tr...}. Found and diagnosed by Heiko Schlichting. + + JH/06 Bug 3054: Fix dnsdb lookup for a TXT record with multiple chunks. This + was broken by hardening introduced for Bug 3033. + ++JH/s1 Refuse to accept a line "dot, LF" as end-of-DATA unless operating in ++ LF-only mode (as detected from the first header line). Previously we did ++ accept that in (normal) CRLF mode; this has been raised as a possible ++ attack scenario (under the name "smtp smuggling", CVE-2023-51766). ++ ++ + Exim version 4.96 + ----------------- + + JH/01 Move the wait-for-next-tick (needed for unique message IDs) from + after reception to before a subsequent reception. This should +--- /dev/null ++++ b/doc/doc-txt/cve-2023-51766 +@@ -0,0 +1,69 @@ ++CVE ID: CVE-2023-51766 ++Date: 2016-12-15 ++Credits: https://sec-consult.com/blog/detail/smtp-smuggling-spoofing-e-mails-worldwide/ ++Version(s): all up to 4.97 inclusive ++Issue: Given a buggy relay, Exim can be induced to accept a second message embedded ++ as part of the body of a first message ++ ++Conditions ++========== ++ ++If *all* the following conditions are met ++ ++ Runtime options ++ --------------- ++ ++ * Exim offers PIPELINING on incoming connections ++ ++ * Exim offers CHUNKING on incoming connections ++ ++ Operation ++ --------- ++ ++ * DATA (as opposed to BDAT) is used for a message reception ++ ++ * The relay host sends to the Exim MTA message data including ++ one of "LF . LF" or "CR LF . LF" or "LF . CR LF". ++ ++ * Exim interprets the sequence as signalling the end of data for ++ the SMTP DATA command, and hence a first message. ++ ++ * Exim interprets further input which the relay had as message body ++ data, as SMTP commands and data. This could include a MAIL, RCPT, ++ BDAT (etc) sequence, resulting in a further message acceptance. ++ ++Impact ++====== ++ ++One or more messages can be accepted by Exim that have not been ++properly validated by the buggy relay. ++ ++Fix ++=== ++ ++Install a fixed Exim version: ++ ++ 4.98 (once available) ++ 4.97.1 ++ ++If you can't install one of the above versions, ask your package ++maintainer for a version containing the backported fix. On request and ++depending on our resources we will support you in backporting the fix. ++(Please note, that Exim project officially doesn't support versions ++prior the current stable version.) ++ ++ ++Workaround ++========== ++ ++ Disable CHUNKING advertisement for incoming connections. ++ ++ An attempt to "smuggle" a DATA command will trip a syncronisation ++ check. ++ ++*or* ++ ++ Disable PIPELINING advertisement for incoming connections. ++ ++ The "smuggled" MAIL FROM command will then trip a syncronisation ++ check. +--- a/src/receive.c ++++ b/src/receive.c +@@ -826,104 +826,118 @@ we make the CRs optional in all cases. + + July 2003: Bare CRs cause trouble. We now treat them as line terminators as + well, so that there are no CRs in spooled messages. However, the message + terminating dot is not recognized between two bare CRs. + ++Dec 2023: getting a site to send a body including an "LF . LF" sequence ++followed by SMTP commands is a possible "smtp smuggling" attack. If ++the first (header) line for the message has a proper CRLF then enforce ++that for the body: convert bare LF to a space. ++ + Arguments: +- fout a FILE to which to write the message; NULL if skipping ++ fout a FILE to which to write the message; NULL if skipping ++ strict_crlf require full CRLF sequence as a line ending + + Returns: One of the END_xxx values indicating why it stopped reading + */ + + static int +-read_message_data_smtp(FILE *fout) ++read_message_data_smtp(FILE * fout, BOOL strict_crlf) + { +-int ch_state = 0; +-int ch; +-int linelength = 0; ++enum { s_linestart, s_normal, s_had_cr, s_had_nl_dot, s_had_dot_cr } ch_state = ++ s_linestart; ++int linelength = 0, ch; + + while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF) + { + if (ch == 0) body_zerocount++; + switch (ch_state) + { +- case 0: /* After LF or CRLF */ +- if (ch == '.') +- { +- ch_state = 3; +- continue; /* Don't ever write . after LF */ +- } +- ch_state = 1; ++ case s_linestart: /* After LF or CRLF */ ++ if (ch == '.') ++ { ++ ch_state = s_had_nl_dot; ++ continue; /* Don't ever write . after LF */ ++ } ++ ch_state = s_normal; + +- /* Else fall through to handle as normal uschar. */ ++ /* Else fall through to handle as normal uschar. */ + +- case 1: /* Normal state */ +- if (ch == '\n') +- { +- ch_state = 0; +- body_linecount++; ++ case s_normal: /* Normal state */ ++ if (ch == '\r') ++ { ++ ch_state = s_had_cr; ++ continue; /* Don't write the CR */ ++ } ++ if (ch == '\n') /* Bare LF at end of line */ ++ if (strict_crlf) ++ ch = ' '; /* replace LF with space */ ++ else ++ { /* treat as line ending */ ++ ch_state = s_linestart; ++ body_linecount++; ++ if (linelength > max_received_linelength) ++ max_received_linelength = linelength; ++ linelength = -1; ++ } ++ break; ++ ++ case s_had_cr: /* After (unwritten) CR */ ++ body_linecount++; /* Any char ends line */ + if (linelength > max_received_linelength) +- max_received_linelength = linelength; ++ max_received_linelength = linelength; + linelength = -1; +- } +- else if (ch == '\r') +- { +- ch_state = 2; +- continue; +- } +- break; ++ if (ch == '\n') /* proper CRLF */ ++ ch_state = s_linestart; ++ else ++ { ++ message_size++; /* convert the dropped CR to a stored NL */ ++ if (fout && fputc('\n', fout) == EOF) return END_WERROR; ++ cutthrough_data_put_nl(); ++ if (ch == '\r') /* CR; do not write */ ++ continue; ++ ch_state = s_normal; /* not LF or CR; process as standard */ ++ } ++ break; + +- case 2: /* After (unwritten) CR */ +- body_linecount++; +- if (linelength > max_received_linelength) +- max_received_linelength = linelength; +- linelength = -1; +- if (ch == '\n') +- { +- ch_state = 0; +- } +- else +- { +- message_size++; +- if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR; +- cutthrough_data_put_nl(); +- if (ch != '\r') ch_state = 1; else continue; +- } +- break; ++ case s_had_nl_dot: /* After [CR] LF . */ ++ if (ch == '\n') /* [CR] LF . LF */ ++ if (strict_crlf) ++ ch = ' '; /* replace LF with space */ ++ else ++ return END_DOT; ++ else if (ch == '\r') /* [CR] LF . CR */ ++ { ++ ch_state = s_had_dot_cr; ++ continue; /* Don't write the CR */ ++ } ++ /* The dot was removed on reaching s_had_nl_dot. For a doubled dot, here, ++ reinstate it to cutthrough. The current ch, dot or not, is passed both to ++ cutthrough and to file below. */ ++ else if (ch == '.') ++ { ++ uschar c = ch; ++ cutthrough_data_puts(&c, 1); ++ } ++ ch_state = s_normal; ++ break; + +- case 3: /* After [CR] LF . */ +- if (ch == '\n') +- return END_DOT; +- if (ch == '\r') +- { +- ch_state = 4; +- continue; +- } +- /* The dot was removed at state 3. For a doubled dot, here, reinstate +- it to cutthrough. The current ch, dot or not, is passed both to cutthrough +- and to file below. */ +- if (ch == '.') +- { +- uschar c= ch; +- cutthrough_data_puts(&c, 1); +- } +- ch_state = 1; +- break; ++ case s_had_dot_cr: /* After [CR] LF . CR */ ++ if (ch == '\n') ++ return END_DOT; /* Preferred termination */ + +- case 4: /* After [CR] LF . CR */ +- if (ch == '\n') return END_DOT; +- message_size++; +- body_linecount++; +- if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR; +- cutthrough_data_put_nl(); +- if (ch == '\r') +- { +- ch_state = 2; +- continue; +- } +- ch_state = 1; +- break; ++ message_size++; /* convert the dropped CR to a stored NL */ ++ body_linecount++; ++ if (fout && fputc('\n', fout) == EOF) return END_WERROR; ++ cutthrough_data_put_nl(); ++ if (ch == '\r') ++ { ++ ch_state = s_had_cr; ++ continue; /* CR; do not write */ ++ } ++ ch_state = s_normal; ++ break; + } + + /* Add the character to the spool file, unless skipping; then loop for the + next. */ + +@@ -1135,11 +1149,11 @@ Returns: nothing + void + receive_swallow_smtp(void) + { + if (message_ended >= END_NOTENDED) + message_ended = chunking_state <= CHUNKING_OFFERED +- ? read_message_data_smtp(NULL) ++ ? read_message_data_smtp(NULL, FALSE) + : read_message_bdat_smtp_wire(NULL); + } + + + +@@ -1946,28 +1960,36 @@ for (;;) + LF specially by inserting a white space after it to ensure that the header + line is not terminated. */ + + if (ch == '\n') + { +- if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = FALSE; +- else if (first_line_ended_crlf) receive_ungetc(' '); ++ if (first_line_ended_crlf == TRUE_UNSET) ++ first_line_ended_crlf = FALSE; ++ else if (first_line_ended_crlf) ++ receive_ungetc(' '); + goto EOL; + } + + /* This is not the end of the line. If this is SMTP input and this is + the first character in the line and it is a "." character, ignore it. + This implements the dot-doubling rule, though header lines starting with + dots aren't exactly common. They are legal in RFC 822, though. If the + following is CRLF or LF, this is the line that that terminates the ++ + entire message. We set message_ended to indicate this has happened (to + prevent further reading), and break out of the loop, having freed the + empty header, and set next = NULL to indicate no data line. */ + + if (f.dot_ends && ptr == 0 && ch == '.') + { ++ /* leading dot while in headers-read mode */ + ch = (receive_getc)(GETC_BUFFER_UNLIMITED); +- if (ch == '\r') ++ if (ch == '\n' && first_line_ended_crlf == TRUE /* and not TRUE_UNSET */ ) ++ /* dot, LF but we are in CRLF mode. Attack? */ ++ ch = ' '; /* replace the LF with a space */ ++ ++ else if (ch == '\r') + { + ch = (receive_getc)(GETC_BUFFER_UNLIMITED); + if (ch != '\n') + { + if (ch >= 0) receive_ungetc(ch); +@@ -1999,11 +2021,12 @@ for (;;) + if (ch == '\r') + { + ch = (receive_getc)(GETC_BUFFER_UNLIMITED); + if (ch == '\n') + { +- if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = TRUE; ++ if (first_line_ended_crlf == TRUE_UNSET) ++ first_line_ended_crlf = TRUE; + goto EOL; + } + + /* Otherwise, put back the character after CR, and turn the bare CR + into LF SP. */ +@@ -3144,11 +3167,11 @@ if (cutthrough.cctx.sock >= 0 && cutthro + (void) cutthrough_headers_send(); + } + + + /* Open a new spool file for the data portion of the message. We need +-to access it both via a file descriptor and a stream. Try to make the ++to access it both via a file descriptor and a stdio stream. Try to make the + directory if it isn't there. */ + + spool_name = spool_fname(US"input", message_subdir, message_id, US"-D"); + DEBUG(D_receive) debug_printf("Data file name: %s\n", spool_name); + +@@ -3213,11 +3236,11 @@ message id or "next" line. */ + if (!ferror(spool_data_file) && !(receive_feof)() && message_ended != END_DOT) + { + if (smtp_input) + { + message_ended = chunking_state <= CHUNKING_OFFERED +- ? read_message_data_smtp(spool_data_file) ++ ? read_message_data_smtp(spool_data_file, first_line_ended_crlf) + : spool_wireformat + ? read_message_bdat_smtp_wire(spool_data_file) + : read_message_bdat_smtp(spool_data_file); + receive_linecount++; /* The terminating "." line */ + } +--- a/src/smtp_in.c ++++ b/src/smtp_in.c +@@ -5447,16 +5447,16 @@ while (done <= 0) + } + break; + } + + if (chunking_state > CHUNKING_OFFERED) +- rc = OK; /* No predata ACL or go-ahead output for BDAT */ ++ rc = OK; /* There is no predata ACL or go-ahead output for BDAT */ + else + { +- /* If there is an ACL, re-check the synchronization afterwards, since the +- ACL may have delayed. To handle cutthrough delivery enforce a dummy call +- to get the DATA command sent. */ ++ /* If there is a predata-ACL, re-check the synchronization afterwards, ++ since the ACL may have delayed. To handle cutthrough delivery enforce a ++ dummy call to get the DATA command sent. */ + + if (!acl_smtp_predata && cutthrough.cctx.sock < 0) + rc = OK; + else + { +--- a/doc/spec.txt ++++ b/doc/spec.txt +@@ -33555,12 +33555,10 @@ MTA within an operating system would use + has shown that this is not the case; for example, there are Unix applications + that use CRLF in this circumstance. For this reason, and for compatibility with + other MTAs, the way Exim handles line endings for all messages is now as + follows: + +- * LF not preceded by CR is treated as a line ending. +- + * CR is treated as a line ending; if it is immediately followed by LF, the LF + is ignored. + + * The sequence "CR, dot, CR" does not terminate an incoming SMTP message, nor + a local message in the state where a line containing only a dot is a +@@ -33571,11 +33569,14 @@ follows: + behind this is that bare CRs in header lines are most likely either to be + mistakes, or people trying to play silly games. + + * If the first header line received in a message ends with CRLF, a subsequent + bare LF in a header line is treated in the same way as a bare CR in a +- header line. ++ header line and a bare LF in a body line is replaced with a space. ++ ++ * If the first header line received in a message does not end with CRLF, a ++ subsequent LF not preceded by CR is treated as a line ending. + + + 48.3 Unqualified addresses + -------------------------- + diff --git a/debian/patches/90_localscan_dlopen.dpatch b/debian/patches/90_localscan_dlopen.dpatch new file mode 100644 index 0000000..21c96b1 --- /dev/null +++ b/debian/patches/90_localscan_dlopen.dpatch @@ -0,0 +1,347 @@ +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: 2022-12-19 + +--- a/src/EDITME ++++ b/src/EDITME +@@ -874,10 +874,25 @@ HEADERS_CHARSET="ISO-8859-1" + # as the traditional crypt() function. + # *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** + + + #------------------------------------------------------------------------------ ++# 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 + # sources from the ftp directory and unpack them, which should create files + # with the extension "texinfo" in the doc directory. You may find that the +--- a/src/config.h.defaults ++++ b/src/config.h.defaults +@@ -31,10 +31,12 @@ Do not put spaces between # and the 'def + #define AUTH_SPA + #define AUTH_TLS + + #define AUTH_VARS 4 + ++#define DLOPEN_LOCAL_SCAN ++ + #define BIN_DIRECTORY + + #define CONFIGURE_FILE + #define CONFIGURE_FILE_USE_EUID + #define CONFIGURE_FILE_USE_NODE +--- a/src/globals.c ++++ b/src/globals.c +@@ -115,10 +115,14 @@ tls_support tls_out = { + uschar *dsn_envid = NULL; + int dsn_ret = 0; + const pcre2_code *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; + uschar *hosts_require_alpn = NULL; + uschar *openssl_options = NULL; +--- a/src/globals.h ++++ b/src/globals.h +@@ -153,10 +153,14 @@ extern uschar *tls_advertise_hosts; / + extern uschar *dsn_envid; /* DSN envid string */ + extern int dsn_ret; /* DSN ret type*/ + extern const pcre2_code *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. */ + + extern int (*lwr_receive_getc)(unsigned); + extern uschar * (*lwr_receive_getbuf)(unsigned *); +--- a/src/local_scan.c ++++ b/src/local_scan.c +@@ -5,60 +5,136 @@ + /* Copyright (c) University of Cambridge 1995 - 2009 */ + /* Copyright (c) The Exim Maintainers 2021 */ + /* 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! */ + + #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 ++#include ++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) + { +-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 +@@ -25,10 +25,11 @@ store.c + /* Some basic types that make some things easier, the Exim configuration + settings, and the store functions. */ + + #include + #include ++#pragma GCC visibility push(default) + #include "config.h" + #include "mytypes.h" + #include "store.h" + + +@@ -164,10 +165,13 @@ extern const uschar *headers_charset; / + extern header_line *header_last; /* Final header */ + extern header_line *header_list; /* First header */ + 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 */ + extern recipient_item *recipients_list;/* List of recipient addresses */ + extern unsigned char *sender_address; /* Sender address */ +@@ -234,6 +238,8 @@ extern uschar * string_copy_taint_functi + extern pid_t child_open_exim_function(int *, const uschar *); + extern pid_t child_open_exim2_function(int *, uschar *, uschar *, const uschar *); + 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 +@@ -210,10 +210,13 @@ static optionlist optionlist_config[] = + #endif + { "local_from_check", opt_bool, {&local_from_check} }, + { "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 + { "local_sender_retain", opt_bool, {&local_sender_retain} }, + { "localhost_number", opt_stringptr, {&host_number_string} }, +--- a/src/string.c ++++ b/src/string.c +@@ -416,10 +416,11 @@ 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 * + *************************************************/ + + /* +@@ -461,10 +462,11 @@ Returns: copy of string in new store + uschar * + string_copyn_function(const uschar * s, int n) + { + return string_copyn(s, n); + } ++#pragma GCC visibility pop + #endif + + + /************************************************* + * Copy and save string in malloc'd store * diff --git a/debian/patches/series b/debian/patches/series new file mode 100644 index 0000000..b57ca5e --- /dev/null +++ b/debian/patches/series @@ -0,0 +1,53 @@ +31_eximmanpage.dpatch +32_exim4.dpatch +33_eximon.binary.dpatch +34_eximstatsmanpage.dpatch +35_install.dpatch +60_convert4r4.dpatch +67_unnecessaryCopt.diff +70_remove_exim-users_references.dpatch +75_01-Fix-exit-on-attempt-to-rewrite-a-malformed-address.-.patch +75_05-SPF-fix-memory-accounting-for-error-case.patch +75_08-Fix-regex-n-use-after-free.-Bug-2915.patch +75_09-Fix-non-WITH_CONTENT_SCAN-build.patch +75_10-Fix-non-WITH_CONTENT_SCAN-build-2.patch +75_11-Fix-non-WITH_CONTENT_SCAN-build-3.patch +75_16-GnuTLS-fix-for-clients-offering-no-TLS-extensions.patch +75_18-Fix-Build-with-libopendmarc-1.4.x-fixes-2728.patch +75_19-DMARC-fix-use-after-free-in-dmarc_dns_lookup.patch +75_22-Fix-daemon-startup.-Bug-2930.patch +75_23-Fix-reccipients-after-run.-.-Bug-2929.patch +75_31-Fix-regext-substring-capture-variables-for-null-matc.patch +75_32-Fix-regex-substring-capture-variables-for-null-match.patch +75_34-Fix-regex-substring-capture-commentary.-Bug-2933.patch +75_37-OpenSSL-when-preloading-creds-do-the-server-certs-be.patch +75_38-OpenSSL-fix-double-expansion-of-tls_verify_certifica.patch +75_42-Fix-run-arg-parsing.patch +75_50-Fix-logging-of-max-size-log-line.patch +75_55-Fix-recursion-on-dns_again_means_nonexist.-Bug-2911.patch +75_58-Close-server-smtp-socket-explicitly-on-connect-ACL-d.patch +75_60-OpenSSL-fix-tls_eccurve-setting-explicit-curve-group.patch +75_62-OpenSSL-Fix-tls_eccurve-on-earlier-versions-than-3.0.patch +75_63-OpenSSL-log-conns-rejected-for-bad-ALPN-with-the-off.patch +75_64-DANE-do-not-check-dns_again_means_nonexist-for-TLSA-.patch +75_66-Fix-crash-in-expansions.patch +75_68-Fix-srs_encode-.-for-mod-1024-day-zero.patch +75_70-Fix-variable-initialisation-in-smtp-transport.-Bug-2.patch +75_71-Auths-fix-possible-OOB-write-in-external-authenticat.patch +75_72-Auths-use-uschar-more-in-spa-authenticator.patch +75_73-Auths-fix-possible-OOB-write-in-SPA-authenticator.-B.patch +75_74-Auths-fix-possible-OOB-read-in-SPA-authenticator.-Bu.patch +75_74-Cancel-early-pipe-on-an-observed-advertising-change.patch +75_76-Expansions-disallow-UTF-16-surrogates-from-utf8clean.patch +75_77-GnuTLS-fix-crash-with-tls_dhparam-none.patch +75_79-Fix-recipients-expansion-when-used-within-run.-.-Bug.patch +75_82-GnuTLS-fix-autogen-cert-expiry-date.-Bug-3014.patch +75_83-Re-fix-live-variable-value-free.-The-inital-fix-resu.patch +76-01-fix-string_is_ip_address-CVE-2023-42117-Bug-3031.patch +76-02-SPF-harden-against-crafted-DNS-responses.patch +76-03-Harden-dnsdb-against-crafted-DNS-responses.-Bug-3033.patch +76-10-Fix-tr.-and-empty-strings.-Bug-3023.patch +76-12-DNS-more-hardening-against-crafted-responses.patch +76-14-Lookups-Fix-dnsdb-lookup-of-multi-chunk-TXT.-Bug-305.patch +77_CVE-2023-51766_4.97.1-release.diff +90_localscan_dlopen.dpatch -- cgit v1.2.3