diff options
Diffstat (limited to '')
61 files changed, 6015 insertions, 0 deletions
diff --git a/debian/patches/31_eximmanpage.dpatch b/debian/patches/31_eximmanpage.dpatch new file mode 100755 index 0000000..b8f8bf6 --- /dev/null +++ b/debian/patches/31_eximmanpage.dpatch @@ -0,0 +1,250 @@ +Description: We ship the binary as exim4 instead of exim, fix manpage + accordingly. +Author: Marc Haber <mh+debian-packages@zugschlus.de>, + Andreas Metzler <ametzler@bebt.de> +Last-Update: 2018-12-31 +Forwarded: not-needed (upstream uses the "exim" name) + +--- a/doc/exim.8 ++++ b/doc/exim.8 +@@ -1,9 +1,9 @@ +-.TH EXIM 8 ++.TH EXIM4 8 + .SH NAME +-exim \- a Mail Transfer Agent ++exim4 \- a Mail Transfer Agent + .SH SYNOPSIS + .nf +-.B exim [options] arguments ... ++.B exim4 [options] arguments ... + .B mailq [options] arguments ... + .B rsmtp [options] arguments ... + .B rmail [options] arguments ... +@@ -40,7 +40,7 @@ local message on the standard input, wit + recipients) is assumed. Thus, for example, if Exim is installed in + \fI/usr/sbin\fP, you can send a message from the command line like this: + .sp +- /usr/sbin/exim -i <recipient-address(es)> ++ /usr/sbin/exim4 -i <recipient-address(es)> + <message content, including all the header lines> + CTRL-D + .sp +@@ -125,8 +125,8 @@ ports, on multiple ports, and only on sp + .sp + When a listening daemon + is started without the use of \fB\-oX\fP (that is, without overriding the normal +-configuration), it writes its process id to a file called exim\-daemon.pid +-in Exim's spool directory. This location can be overridden by setting ++configuration), it writes its process id to a file called ++/var/run/exim4/exim.pid. This location can be overridden by setting + PID_FILE_PATH in Local/Makefile. The file is written while Exim is still + running as root. + .sp +@@ -180,7 +180,7 @@ available to admin users. + This option operates like \fB\-be\fP except that it must be followed by the name + of a file. For example: + .sp +- exim \-bem /tmp/testmessage ++ exim4 \-bem /tmp/testmessage + .sp + The file is read as a message (as if receiving a locally\-submitted non\-SMTP + message) before any of the test expansions are done. Thus, message\-specific +@@ -206,7 +206,7 @@ If you want to test a system filter file + can use both \fB\-bF\fP and \fB\-bf\fP on the same command, in order to test a system + filter and a user filter in the same run. For example: + .sp +- exim \-bF /system/filter \-bf /user/filter </test/message ++ exim4 \-bF /system/filter \-bf /user/filter </test/message + .sp + This is helpful when the system filter adds header lines or sets filter + variables that are used by the user filter. +@@ -258,8 +258,8 @@ This option runs a fake SMTP session as + standard input and output. The IP address may include a port number at the end, + after a full stop. For example: + .sp +- exim \-bh 10.9.8.7.1234 +- exim \-bh fe80::a00:20ff:fe86:a061.5678 ++ exim4 \-bh 10.9.8.7.1234 ++ exim4 \-bh fe80::a00:20ff:fe86:a061.5678 + .sp + When an IPv6 address is given, it is converted into canonical form. In the case + of the second example above, the value of \fI$sender_host_address\fP after +@@ -417,7 +417,7 @@ main configuration options to be written + of one or more specific options can be requested by giving their names as + arguments, for example: + .sp +- exim \-bP qualify_domain hold_domains ++ exim4 \-bP qualify_domain hold_domains + .sp + However, any option setting that is preceded by the word "hide" in the + configuration file is not shown in full, except to an admin user. For other +@@ -445,7 +445,7 @@ written directly into the spool director + .sp + If \fB\-bP\fP is followed by a name preceded by +, for example, + .sp +- exim \-bP +local_domains ++ exim4 \-bP +local_domains + .sp + it searches for a matching named list of any type (domain, host, address, or + local part) and outputs what it finds. +@@ -454,7 +454,7 @@ If one of the words \fBrouter\fP, \fBtra + followed by the name of an appropriate driver instance, the option settings for + that driver are output. For example: + .sp +- exim \-bP transport local_delivery ++ exim4 \-bP transport local_delivery + .sp + The generic driver options are output first, followed by the driver's private + options. A list of the names of drivers of a particular type can be obtained by +@@ -539,7 +539,7 @@ This option is for testing retry rules, + arguments. It causes Exim to look for a retry rule that matches the values + and to write it to the standard output. For example: + .sp +- exim \-brt bach.comp.mus.example ++ exim4 \-brt bach.comp.mus.example + Retry rule: *.comp.mus.example F,2h,15m; F,4d,30m; + .sp + The first +@@ -552,7 +552,7 @@ rule is found that matches the host, one + sought. Finally, an argument that is the name of a specific delivery error, as + used in setting up retry rules, can be given. For example: + .sp +- exim \-brt haydn.comp.mus.example quota_3d ++ exim4 \-brt haydn.comp.mus.example quota_3d + Retry rule: *@haydn.comp.mus.example quota_3d F,1h,15m + .TP 10 + \fB\-brw\fP +@@ -655,7 +655,7 @@ doing such tests. + .TP 10 + \fB\-bV\fP + This option causes Exim to write the current version number, compilation +-number, and compilation date of the \fIexim\fP binary to the standard output. ++number, and compilation date of the \fIexim4\fP binary to the standard output. + It also lists the DBM library that is being used, the optional modules (such as + specific lookup types), the drivers that are included in the binary, and the + name of the runtime configuration file that is in use. +@@ -683,7 +683,7 @@ If no arguments are given, Exim runs in + right angle bracket for addresses to be verified. + .sp + Unlike the \fB\-be\fP test option, you cannot arrange for Exim to use the +-readline() function, because it is running as \fIexim\fP and there are ++readline() function, because it is running as \fIexim4\fP and there are + security issues. + .sp + Verification differs from address testing (the \fB\-bt\fP option) in that routers +@@ -796,14 +796,14 @@ command line item. \fB\-D\fP can be used + string, in which case the equals sign is optional. These two commands are + synonymous: + .sp +- exim \-DABC ... +- exim \-DABC= ... ++ exim4 \-DABC ... ++ exim4 \-DABC= ... + .sp + To include spaces in a macro definition item, quotes must be used. If you use + quotes, spaces are permitted around the macro name and the equals sign. For + example: + .sp +- exim '\-D ABC = something' ... ++ exim4 '\-D ABC = something' ... + .sp + \fB\-D\fP may be repeated up to 10 times on a command line. + Only macro names up to 22 letters long can be set. +@@ -938,8 +938,8 @@ never provoke a bounce. An empty sender + string, or as a pair of angle brackets with nothing between them, as in these + examples of shell commands: + .sp +- exim \-f '<>' user@domain +- exim \-f "" user@domain ++ exim4 \-f '<>' user@domain ++ exim4 \-f "" user@domain + .sp + In addition, the use of \fB\-f\fP is not restricted when testing a filter file + with \fB\-bf\fP or when testing or verifying addresses using the \fB\-bt\fP or +@@ -1315,12 +1315,12 @@ other circumstances, they are ignored un + The \fB\-oMa\fP option sets the sender host address. This may include a port + number at the end, after a full stop (period). For example: + .sp +- exim \-bs \-oMa 10.9.8.7.1234 ++ exim4 \-bs \-oMa 10.9.8.7.1234 + .sp + An alternative syntax is to enclose the IP address in square brackets, + followed by a colon and the port number: + .sp +- exim \-bs \-oMa [10.9.8.7]:1234 ++ exim4 \-bs \-oMa [10.9.8.7]:1234 + .sp + The IP address is placed in the \fI$sender_host_address\fP variable, and the + port, if present, in \fI$sender_host_port\fP. If both \fB\-oMa\fP and \fB\-bh\fP +@@ -1526,22 +1526,22 @@ If other commandline options specify an + will specify a queue to operate on. + For example: + .sp +- exim \-bp \-qGquarantine ++ exim4 \-bp \-qGquarantine + mailq \-qGquarantine +- exim \-qGoffpeak \-Rf @special.domain.example ++ exim4 \-qGoffpeak \-Rf @special.domain.example + .TP 10 + \fB\-q\fP<\fIqflags\fP> <\fIstart id\fP> <\fIend id\fP> + When scanning the queue, Exim can be made to skip over messages whose ids are + lexically less than a given value by following the \fB\-q\fP option with a + starting message id. For example: + .sp +- exim \-q 0t5C6f\-0000c8\-00 ++ exim4 \-q 0t5C6f\-0000c8\-00 + .sp + Messages that arrived earlier than 0t5C6f\-0000c8\-00 are not inspected. If a + second message id is given, messages whose ids are lexically greater than it + are also skipped. If the same id is given twice, for example, + .sp +- exim \-q 0t5C6f\-0000c8\-00 0t5C6f\-0000c8\-00 ++ exim4 \-q 0t5C6f\-0000c8\-00 0t5C6f\-0000c8\-00 + .sp + just one delivery process is started, for that message. This differs from + \fB\-M\fP in that retry data is respected, and it also differs from \fB\-Mc\fP in +@@ -1557,7 +1557,7 @@ starting a queue runner process at inter + single daemon process handles both functions. A common way of starting up a + combined daemon at system boot time is to use a command such as + .sp +- /usr/exim/bin/exim \-bd \-q30m ++ /usr/sbin/exim4 \-bd \-q30m + .sp + Such a daemon listens for incoming SMTP calls, and also starts a queue runner + process every 30 minutes. +@@ -1588,7 +1588,7 @@ regular expression; otherwise it is a li + If you want to do periodic queue runs for messages with specific recipients, + you can combine \fB\-R\fP with \fB\-q\fP and a time value. For example: + .sp +- exim \-q25m \-R @special.domain.example ++ exim4 \-q25m \-R @special.domain.example + .sp + This example does a queue run for messages with recipients in the given domain + every 25 minutes. Any additional flags that are specified with \fB\-q\fP are +@@ -1704,6 +1704,26 @@ under most shells. + .sp + . + .SH "SEE ALSO" ++.BR exicyclog (8), ++.BR exigrep (8), ++.BR exim_checkaccess (8), ++.BR exim_convert4r4 (8), ++.BR exim_db (8), ++.BR exim_dbmbuild (8), ++.BR exim_lock (8), ++.BR eximon (8), ++.BR exinext (8), ++.BR exiqgrep (8), ++.BR exiqsumm (8), ++.BR exiwhat (8), ++.BR update\-exim4.conf (8), ++.BR update\-exim4defaults (8), ++/usr/share/doc/exim4\-base/, ++/usr/share/doc/exim4\-base/README.Debian.[gz|html]. + .rs + .sp + The full Exim specification, the Exim book, and the Exim wiki. ++ ++.SH AUTHOR ++This manual page was provided with the upstream Exim source package. ++It was enhanced for the Debian GNU/Linux system. diff --git a/debian/patches/32_exim4.dpatch b/debian/patches/32_exim4.dpatch new file mode 100644 index 0000000..967869d --- /dev/null +++ b/debian/patches/32_exim4.dpatch @@ -0,0 +1,106 @@ +Description: Accommodate source for installing exim as exim4. +Author: Andreas Metzler <ametzler@debian.org> +Origin: vendor +Forwarded: not-needed +Last-Update: 2018-12-12 + +--- a/OS/Makefile-Linux ++++ b/OS/Makefile-Linux +@@ -28,9 +28,9 @@ XLFLAGS=-L$(X11)/lib + X11_LD_LIB=$(X11)/lib + + EXIWHAT_PS_ARG=ax +-EXIWHAT_EGREP_ARG='/exim( |$$)' ++EXIWHAT_EGREP_ARG='/exim4( |$$)' + EXIWHAT_MULTIKILL_CMD=killall +-EXIWHAT_MULTIKILL_ARG=exim ++EXIWHAT_MULTIKILL_ARG=exim4 + EXIWHAT_KILL_SIGNAL=-USR1 + + # End +--- a/src/exicyclog.src ++++ b/src/exicyclog.src +@@ -149,7 +149,7 @@ done + + st=' ' + exim_path=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"` +-if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim; fi ++if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim4; fi + + spool_directory=`$exim_path -C $config -bP spool_directory | sed 's/.*=[ ]*//'` + +--- a/src/exim_checkaccess.src ++++ b/src/exim_checkaccess.src +@@ -52,7 +52,7 @@ done + # a tab to keep the tab in one place. + + exim_path=`perl -ne 'chop;if (/^\s*exim_path\s*=\s*(.*)/){print "$1\n";last;}' $config` +-if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim; fi ++if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim4; fi + + + ######################################################################### +--- a/src/eximon.src ++++ b/src/eximon.src +@@ -79,7 +79,7 @@ config=${EXIMON_EXIM_CONFIG-$config} + + st=' ' + EXIM_PATH=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"` +-if test "$EXIM_PATH" = ""; then EXIM_PATH=BIN_DIRECTORY/exim; fi ++if test "$EXIM_PATH" = ""; then EXIM_PATH=BIN_DIRECTORY/exim4; fi + + SPOOL_DIRECTORY=`$EXIM_PATH -C $config -bP spool_directory | sed 's/.*=[ ]*//'` + LOG_FILE_PATH=`$EXIM_PATH -C $config -bP log_file_path | sed 's/.*=[ ]*//'` +--- a/src/exinext.src ++++ b/src/exinext.src +@@ -97,7 +97,7 @@ if [ "$exim_path" = "" ]; then + exim_path=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"` + fi + +-if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim; fi ++if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim4; fi + spool_directory=`$exim_path $eximmacdef -C $config -bP spool_directory | sed 's/.*=[ ]*//'` + qualify_domain=`$exim_path $eximmacdef -C $config -bP qualify_domain | sed 's/.*=[ ]*//'` + +@@ -181,7 +181,7 @@ perl - $exim_path "$eximmacdef" $argone + + # Run exim_dumpdb to get out the retry data and pick off what we want + +- open(DATA, "${exim}_dumpdb $spool retry |") || ++ open(DATA, "/usr/sbin/exim_dumpdb $spool retry |") || + die "can't run exim_dumpdb"; + + while (<DATA>) +--- a/src/exiqgrep.src ++++ b/src/exiqgrep.src +@@ -24,7 +24,7 @@ use Getopt::Std; + use File::Basename; + + # Have this variable point to your exim binary. +-my $exim = 'BIN_DIRECTORY/exim'; ++my $exim = 'BIN_DIRECTORY/exim4'; + my $eargs = '-bpu'; + my %id; + my %opt; +--- a/src/exiwhat.src ++++ b/src/exiwhat.src +@@ -98,7 +98,7 @@ fi + + st=' ' + exim_path=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"` +-if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim; fi ++if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim4; fi + spool_directory=`$exim_path -C $config -bP spool_directory | sed "s/.*=[ ]*//"` + process_log_path=`$exim_path -C $config -bP process_log_path | sed "s/.*=[ ]*//"` + +--- a/src/globals.c ++++ b/src/globals.c +@@ -906,7 +906,7 @@ const uschar *event_name = NULL; + + + gid_t exim_gid = EXIM_GID; +-uschar *exim_path = US BIN_DIRECTORY "/exim" ++uschar *exim_path = US BIN_DIRECTORY "/exim4" + "\0<---------------Space to patch exim_path->"; + uid_t exim_uid = EXIM_UID; + int expand_level = 0; /* Nesting depth, indent for debug */ diff --git a/debian/patches/33_eximon.binary.dpatch b/debian/patches/33_eximon.binary.dpatch new file mode 100755 index 0000000..bc78cdc --- /dev/null +++ b/debian/patches/33_eximon.binary.dpatch @@ -0,0 +1,18 @@ +#! /bin/sh /usr/share/dpatch/dpatch-run +## 33_eximon.binary.dpatch by Andreas Piesk +## +## All lines beginning with `## DP:' are a description of the patch. +## DP: eximon.bin is installed in /usr/lib/exim4/ and not in path. + +diff -NurBbp exim-4.80.orig/OS/eximon.conf-Default exim-4.80/OS/eximon.conf-Default +--- exim-4.80.orig/OS/eximon.conf-Default 2012-05-18 05:04:36.000000000 +0200 ++++ exim-4.80/OS/eximon.conf-Default 2012-05-18 19:10:59.000000000 +0200 +@@ -5,7 +5,7 @@ + # The name of the eximon binary, usually the same as the eximon script, + # with .bin stuck on the end. + +-EXIMON_BINARY="${EXIMON_BINARY-$0.bin}" ++EXIMON_BINARY="/usr/lib/exim4/${EXIMON_BINARY-${0##*/}.bin}" + + # The remaining parameters are values likely to be changed to suit the + # user's taste. They are documented in the EDITME file. diff --git a/debian/patches/34_eximstatsmanpage.dpatch b/debian/patches/34_eximstatsmanpage.dpatch new file mode 100755 index 0000000..3245965 --- /dev/null +++ b/debian/patches/34_eximstatsmanpage.dpatch @@ -0,0 +1,20 @@ +Description: Add note about installing perl-modules on Debian to + generated manpage +Author: Andreas Metzler <ametzler@debian.org> +Origin: vendor +Forwarded: not-needed +Last-Update: 2013-09-28 + +--- exim4-4.82~rc1.orig/src/eximstats.src ++++ exim4-4.82~rc1/src/eximstats.src +@@ -501,6 +501,10 @@ To install these, download and unpack th + make test + make install + ++On B<Debian GNU/Linux> you can use ++C<apt-get install libgd-perl libgd-text-perl libgd-graph-perl> ++instead. ++ + =item B<-chartdir>I <dir> + + Create the charts in the directory <dir> diff --git a/debian/patches/35_install.dpatch b/debian/patches/35_install.dpatch new file mode 100755 index 0000000..d0ab602 --- /dev/null +++ b/debian/patches/35_install.dpatch @@ -0,0 +1,49 @@ +Description: Exim's installation scripts install the binary as + exim-<version> - disable this feature. +Author: Andreas Metzler <ametzler@debian.org> +Origin: vendor +Forwarded: not-needed +Last-Update: 2016-09-25 + +--- a/scripts/exim_install ++++ b/scripts/exim_install +@@ -221,6 +221,8 @@ while [ $# -gt 0 ]; do + version=exim-`$exim 2>/dev/null | \ + awk '/Exim version/ { OFS=""; print $3,"-",substr($4,2,length($4)-1) }'`${EXE} + ++ version=exim ++ + if [ "${version}" = "exim-${EXE}" ]; then + echo $com "" + echo $com "*** Could not run $exim to find version number ***" +@@ -370,10 +372,8 @@ done + + + +-# If there is no configuration file, install the default, modifying it to refer +-# to the configured system aliases file. If there is no setting for +-# SYSTEM_ALIASES_FILE, use the traditional /etc/aliases. If the file does not +-# exist, install a default (dummy) for that too. ++# Install default configuration file ++# This is a local Debian modification. + + # However, if CONFIGURE_FILE specifies a list of files, skip this code. + +@@ -396,7 +396,7 @@ elif [ ! -f ${CONFIGURE_FILE} ]; then + ${real} ${MKDIR} -p `${DIRNAME} ${CONFIGURE_FILE}` + + echo sed -e '\\' +- echo " \"/SYSTEM_ALIASES_FILE/ s'SYSTEM_ALIASES_FILE'${ACTUAL_SYSTEM_ALIASES_FILE}'\"" '\\' ++ echo " \"/SYSTEM_ALIASES_FILE/ s'SYSTEM_ALIASES_FILE'/etc/aliases'\"" '\\' + echo " ../src/configure.default > \${CONFIGURE_FILE}" + + # I can't find a way of writing this using the ${real} feature because +@@ -405,7 +405,7 @@ elif [ ! -f ${CONFIGURE_FILE} ]; then + + if [ "$real" = "" ] ; then + sed -e \ +- "/SYSTEM_ALIASES_FILE/ s'SYSTEM_ALIASES_FILE'${ACTUAL_SYSTEM_ALIASES_FILE}'" \ ++ "/SYSTEM_ALIASES_FILE/ s'SYSTEM_ALIASES_FILE'/etc/aliases'" \ + ../src/configure.default > ${CONFIGURE_FILE} + else + true diff --git a/debian/patches/60_convert4r4.dpatch b/debian/patches/60_convert4r4.dpatch new file mode 100755 index 0000000..290b913 --- /dev/null +++ b/debian/patches/60_convert4r4.dpatch @@ -0,0 +1,41 @@ +Description: Add a warning message to convert4r4 +Author: Marc Haber <mh+debian-packages@zugschlus.de> +Origin: vendor +Forwarded: no +Last-Update: 2013-09-28 + +--- a/src/convert4r4.src ++++ b/src/convert4r4.src +@@ -666,6 +666,32 @@ return defined $main{$_[0]} && $main{$_[ + + print STDERR "Runtime configuration file converter for Exim release 4.\n"; + ++if( !defined $ENV{"CONVERT4R4"} || $ENV{"CONVERT4R4"} ne "I understand this is an unsupported tool" ) { ++ ++ print STDERR <<EOF; ++convert4r4 on Debian GNU/Linux deprecated ++ ++This tool is unsupported by upstream and discouraged by the Debian Exim 4 ++maintainers. It has multiple known bugs, and you need to manually ++review its output after using it anyway. Please seriously consider complete ++manual regeneration of the Exim 4 configuration, preferably by using the new ++Debconf interface to Exim 4. ++ ++If you decide to ignore this advice and to use this script anyway, ++setting the environment variable CONVERT4R4 to the value ++\"I understand this is an unsupported tool\" ++will allow you to run the script. If you find bugs, you get to keep ++the pieces. Please do not file bugs against this script in the Debian ++BTS without providing a patch fixing the bugs, and please do not ++expect the upstream exim-users mailing list to answer questions. ++ ++Kind regards ++the Debian Exim4 Maintainers ++EOF ++ ++ exit 1; ++} ++ + $transport_start = $director_start = $router_start = $retry_start + = $rewrite_start = $auth_start = 999999; + diff --git a/debian/patches/67_unnecessaryCopt.diff b/debian/patches/67_unnecessaryCopt.diff new file mode 100644 index 0000000..daae1e4 --- /dev/null +++ b/debian/patches/67_unnecessaryCopt.diff @@ -0,0 +1,69 @@ +Description: Stop using exim's -C option in utility scripts (exiwhat + et al.) since this breaks with ALT_CONFIG_PREFIX. +Author: Andreas Metzler <ametzler@bebt.de> +Forwarded: http://bugs.exim.org/show_bug.cgi?id=1045 +Last-Update: 2018-12-31 + +--- a/src/exicyclog.src ++++ b/src/exicyclog.src +@@ -151,10 +151,10 @@ st=' ' + exim_path=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"` + if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim4; fi + +-spool_directory=`$exim_path -C $config -bP spool_directory | sed 's/.*=[ ]*//'` ++spool_directory=`$exim_path -bP spool_directory | sed 's/.*=[ ]*//'` + + if [ "$log_file_path" = "" ] ; then +- log_file_path=`$exim_path -C $config -bP log_file_path | sed 's/.*=[ ]*//'` ++ log_file_path=`$exim_path -bP log_file_path | sed 's/.*=[ ]*//'` + fi + + # If log_file_path contains only "syslog" then no Exim log files are in use. +--- a/src/eximon.src ++++ b/src/eximon.src +@@ -81,8 +81,8 @@ st=' ' + EXIM_PATH=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"` + if test "$EXIM_PATH" = ""; then EXIM_PATH=BIN_DIRECTORY/exim4; fi + +-SPOOL_DIRECTORY=`$EXIM_PATH -C $config -bP spool_directory | sed 's/.*=[ ]*//'` +-LOG_FILE_PATH=`$EXIM_PATH -C $config -bP log_file_path | sed 's/.*=[ ]*//'` ++SPOOL_DIRECTORY=`$EXIM_PATH -bP spool_directory | sed 's/.*=[ ]*//'` ++LOG_FILE_PATH=`$EXIM_PATH -bP log_file_path | sed 's/.*=[ ]*//'` + + # If log_file_path is "syslog" then logging is only to syslog, and the monitor + # is unable to display a log tail unless EXIMON_LOG_FILE_PATH is set to tell +--- a/src/exinext.src ++++ b/src/exinext.src +@@ -98,8 +98,8 @@ if [ "$exim_path" = "" ]; then + fi + + if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim4; fi +-spool_directory=`$exim_path $eximmacdef -C $config -bP spool_directory | sed 's/.*=[ ]*//'` +-qualify_domain=`$exim_path $eximmacdef -C $config -bP qualify_domain | sed 's/.*=[ ]*//'` ++spool_directory=`$exim_path $eximmacdef -bP spool_directory | sed 's/.*=[ ]*//'` ++qualify_domain=`$exim_path $eximmacdef -bP qualify_domain | sed 's/.*=[ ]*//'` + + # Now do the job. Perl uses $ so frequently that we don't want to have to + # escape them all from the shell, so pass in shell variable values as +@@ -144,7 +144,7 @@ perl - $exim_path "$eximmacdef" $argone + # Run Exim to get a list of hosts for the given domain; for + # each one construct the appropriate retry key. + +- open(LIST, "$exim -C $config -v -bt $address |") || ++ open(LIST, "$exim -v -bt $address |") || + die "can't run exim to route $address"; + + while (<LIST>) +--- a/src/exiwhat.src ++++ b/src/exiwhat.src +@@ -99,8 +99,8 @@ fi + st=' ' + exim_path=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"` + if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim4; fi +-spool_directory=`$exim_path -C $config -bP spool_directory | sed "s/.*=[ ]*//"` +-process_log_path=`$exim_path -C $config -bP process_log_path | sed "s/.*=[ ]*//"` ++spool_directory=`$exim_path -bP spool_directory | sed "s/.*=[ ]*//"` ++process_log_path=`$exim_path -bP process_log_path | sed "s/.*=[ ]*//"` + + # The file that Exim writes when sent the SIGUSR1 signal is specified by + # the process_log_path option. If that is not defined, Exim uses the file diff --git a/debian/patches/70_remove_exim-users_references.dpatch b/debian/patches/70_remove_exim-users_references.dpatch new file mode 100755 index 0000000..9efe04f --- /dev/null +++ b/debian/patches/70_remove_exim-users_references.dpatch @@ -0,0 +1,37 @@ +Description: Point Debian users to Debian specific ML. +Author: Marc Haber <mh+debian-packages@zugschlus.de> +Last-Update: 2018-12-31 + +--- a/README ++++ b/README +@@ -14,8 +14,16 @@ from Exim 3, though the basic structure + older book may be helpful for the background, but a lot of the detail has + changed, so it is likely to be confusing to newcomers. + +-There is a website at https://www.exim.org; this contains details of the +-mailing list exim-users@exim.org. ++Information about the way Debian has built the binary packages is ++obtainable in /usr/share/doc/exim4-base/README.Debian.gz, and there ++is a Debian-centered mailing list, ++pkg-exim4-users@lists.alioth.debian.org. Please ask Debian-specific ++questions there, and only write to the upstream exim-users mailing ++list if you are sure that your question is not Debian-specific. You ++can find the subscription web page on ++http://lists.alioth.debian.org/mailman/listinfo/pkg-exim4-users ++ ++There is a website at https://www.exim.org/. + + A copy of the Exim FAQ should be available from the same source that you used + to obtain the Exim distribution. Additional formats for the documentation +--- a/src/eximstats.src ++++ b/src/eximstats.src +@@ -537,8 +537,7 @@ about how to create charts from the tabl + + =head1 AUTHOR + +-There is a website at https://www.exim.org - this contains details of the +-mailing list exim-users@exim.org. ++There is a website at https://www.exim.org/. + + =head1 TO DO + diff --git a/debian/patches/75_01-Fix-json-extract-operator-for-unfound-case.patch b/debian/patches/75_01-Fix-json-extract-operator-for-unfound-case.patch new file mode 100644 index 0000000..a0978af --- /dev/null +++ b/debian/patches/75_01-Fix-json-extract-operator-for-unfound-case.patch @@ -0,0 +1,69 @@ +From b2734f7b45111f9b7de790c7b334a2ece47675b5 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris <jgh146exb@wizmail.org> +Date: Sat, 9 Feb 2019 16:56:59 +0000 +Subject: [PATCH 1/7] Fix json extract operator for unfound case + +(cherry picked from commit e73798976812e652320f096870359ef35ed069ff) +--- + doc/doc-docbook/spec.xfpt | 4 ++++ + src/expand.c | 13 ++++++++----- + test/scripts/0000-Basic/0002 | 3 +++ + test/stdout/0002 | 3 +++ + 4 files changed, 18 insertions(+), 5 deletions(-) + +--- a/src/expand.c ++++ b/src/expand.c +@@ -3901,7 +3901,8 @@ return NULL; + /* Pull off the leading array or object element, returning + a copy in an allocated string. Update the list pointer. + +-The element may itself be an abject or array. ++The element may itself be an object or array. ++Return NULL when the list is empty. + */ + + uschar * +@@ -3923,6 +3924,7 @@ for (item = s; + case '}': object_depth--; break; + } + *list = *s ? s+1 : s; ++if (item == s) return NULL; + item = string_copyn(item, s - item); + DEBUG(D_expand) debug_printf_indent(" json ele: '%s'\n", item); + return US item; +@@ -5790,10 +5792,11 @@ while (*s != 0) + } + while (field_number > 0 && (item = json_nextinlist(&list))) + field_number--; +- s = item; +- lookup_value = s; +- while (*s) s++; +- while (--s >= lookup_value && isspace(*s)) *s = '\0'; ++ if ((lookup_value = s = item)) ++ { ++ while (*s) s++; ++ while (--s >= lookup_value && isspace(*s)) *s = '\0'; ++ } + } + else + { +--- a/doc/spec.txt ++++ b/doc/spec.txt +@@ -8776,6 +8776,8 @@ ${extract json{<key>}{<string1>}{<string + The braces, commas and colons, and the quoting of the member name are + required; the spaces are optional. Matching of the key against the member + names is done case-sensitively. ++ If a returned value is a JSON string, it retains its leading and ++ trailing quotes. + + The results of matching are handled as above. + +@@ -8813,6 +8815,8 @@ ${extract json{<number>}}{<string1>}{<st + + Field selection and result handling is as above; there is no choice of + field separator. ++ If a returned value is a JSON string, it retains its leading and ++ trailing quotes. + + ${filter{<string>}{<condition>}} + diff --git a/debian/patches/75_02-Fix-transport-buffer-size-handling.patch b/debian/patches/75_02-Fix-transport-buffer-size-handling.patch new file mode 100644 index 0000000..a96350b --- /dev/null +++ b/debian/patches/75_02-Fix-transport-buffer-size-handling.patch @@ -0,0 +1,52 @@ +From 1cfa7822ca8928f95160df8742af11fff888ae7e Mon Sep 17 00:00:00 2001 +From: Jeremy Harris <jgh146exb@wizmail.org> +Date: Tue, 12 Feb 2019 16:52:51 +0000 +Subject: [PATCH 3/7] Fix transport buffer size handling Broken-by: 59932f7dcd + +(cherry picked from commit 05bf16f6217e93594929c8bbbbbc852caf3ed374) +--- + doc/ChangeLog | 7 +++++++ + src/transport.c | 4 ++-- + 2 files changed, 9 insertions(+), 2 deletions(-) + +diff --git a/doc/ChangeLog b/doc/ChangeLog +index 7da07ad4..66c8a7a1 100644 +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -5,6 +5,13 @@ affect Exim's operation, with an unchanged configuration file. For new + options, and new features, see the NewStuff file next to this ChangeLog. + + ++Since version 4.92 ++------------------ ++ ++JH/06 Fix buggy handling of autoreply bounce_return_size_limit, and a possible ++ buffer overrun for (non-chunking) other transports. ++ ++ + Exim version 4.92 + ----------------- + +diff --git a/src/transport.c b/src/transport.c +index 8ccdd038..a069b883 100644 +--- a/src/transport.c ++++ b/src/transport.c +@@ -1115,13 +1115,13 @@ DEBUG(D_transport) + + if (!(tctx->options & topt_no_body)) + { +- int size = size_limit; ++ unsigned long size = size_limit > 0 ? size_limit : ULONG_MAX; + + nl_check_length = abs(nl_check_length); + nl_partial_match = 0; + if (lseek(deliver_datafile, SPOOL_DATA_START_OFFSET, SEEK_SET) < 0) + return FALSE; +- while ( (len = MAX(DELIVER_IN_BUFFER_SIZE, size)) > 0 ++ while ( (len = MIN(DELIVER_IN_BUFFER_SIZE, size)) > 0 + && (len = read(deliver_datafile, deliver_in_buffer, len)) > 0) + { + if (!write_chunk(tctx, deliver_in_buffer, len)) +-- +2.20.1 + diff --git a/debian/patches/75_03-Fix-info-on-using-local_scan-in-the-default-Makefile.patch b/debian/patches/75_03-Fix-info-on-using-local_scan-in-the-default-Makefile.patch new file mode 100644 index 0000000..6db6f83 --- /dev/null +++ b/debian/patches/75_03-Fix-info-on-using-local_scan-in-the-default-Makefile.patch @@ -0,0 +1,42 @@ +From cb25b75af850d664fc005d24fbad0e58bf79d4c7 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris <jgh146exb@wizmail.org> +Date: Thu, 14 Feb 2019 17:14:34 +0000 +Subject: [PATCH 5/7] Fix info on using local_scan() in the default Makefile + +Broken-by: 9723f96673 +(cherry picked from commit 882bc1704d33aa34873e3a0f72e657b0cc2985e5) +--- + OS/Makefile-Default | 10 ++++++++-- + 1 file changed, 8 insertions(+), 2 deletions(-) + +diff --git a/OS/Makefile-Default b/OS/Makefile-Default +index b3990fe8..41a4dbbd 100644 +--- a/OS/Makefile-Default ++++ b/OS/Makefile-Default +@@ -232,6 +232,11 @@ RANLIB=ranlib + EXIM_CHMOD=@true + + ++# If you want to use local_scan() at all, the support code must be included ++# by uncommenting this line. ++ ++# HAVE_LOCAL_SCAN=yes ++ + # LOCAL_SCAN_SOURCE defines the file in which the function local_scan() is + # defined. This provides the administrator with a hook for including C code + # for scanning incoming mails. The path that is defined must be relative to +@@ -239,8 +244,9 @@ EXIM_CHMOD=@true + + # LOCAL_SCAN_SOURCE=Local/local_scan.c + +-# The default setting points to a template function that doesn't actually do +-# any scanning, but just accepts the message. ++# A very simple example points to a template function that doesn't actually do ++# any scanning, but just accepts the message. A compilable file must be ++# included in the build even if HAVE_LOCAL_SCAN is not defined. + + LOCAL_SCAN_SOURCE=src/local_scan.c + +-- +2.20.1 + diff --git a/debian/patches/75_04-GnuTLS-Fix-client-detection-of-server-reject-of-clie.patch b/debian/patches/75_04-GnuTLS-Fix-client-detection-of-server-reject-of-clie.patch new file mode 100644 index 0000000..c45f6aa --- /dev/null +++ b/debian/patches/75_04-GnuTLS-Fix-client-detection-of-server-reject-of-clie.patch @@ -0,0 +1,420 @@ +From c15523829ba17cce5829e2976aa1ff928965d948 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris <jgh146exb@wizmail.org> +Date: Sat, 16 Feb 2019 12:59:23 +0000 +Subject: [PATCH 7/7] GnuTLS: Fix client detection of server reject of client + cert under TLS1.3 + +(cherry picked from commit fc243e944ec00b59b75f41d07494116f925d58b4) +--- + doc/ChangeLog | 7 +++ + src/deliver.c | 2 +- + src/smtp_out.c | 10 +++-- + src/tls-gnu.c | 23 +++------- + src/transports/lmtp.c | 3 +- + src/transports/smtp.c | 81 +++++++++++++++++++++++++++-------- + test/confs/2027 | 8 ++-- + test/confs/5652 | 1 + + test/confs/5821 | 2 +- + test/log/2027 | 2 +- + test/runtest | 14 ++++++ + test/scripts/2000-GnuTLS/2027 | 2 + + 12 files changed, 111 insertions(+), 44 deletions(-) + +diff --git a/doc/ChangeLog b/doc/ChangeLog +index 66c8a7a1..867a1d8a 100644 +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -11,6 +11,13 @@ Since version 4.92 + JH/06 Fix buggy handling of autoreply bounce_return_size_limit, and a possible + buffer overrun for (non-chunking) other transports. + ++JH/07 GnuTLS: Our use of late (post-handshake) certificate verification, under ++ TLS1.3, means that a server rejecting a client certificate is not visible ++ to the client until the first read of encrypted data (typically the ++ response to EHLO). Add detection for that case and treat it as a failed ++ TLS connection attempt, so that the normal retry-in-clear can work (if ++ suitably configured). ++ + + Exim version 4.92 + ----------------- +diff --git a/src/deliver.c b/src/deliver.c +index 664d0045..e1799411 100644 +--- a/src/deliver.c ++++ b/src/deliver.c +@@ -7433,7 +7433,7 @@ if (addr_senddsn) + + tctx.u.fd = fd; + tctx.options = topt_add_return_path | topt_no_body; +- /*XXX hmm, retval ignored. ++ /*XXX hmm, FALSE(fail) retval ignored. + Could error for any number of reasons, and they are not handled. */ + transport_write_message(&tctx, 0); + fflush(f); +diff --git a/src/smtp_out.c b/src/smtp_out.c +index 9bd90c77..b194e804 100644 +--- a/src/smtp_out.c ++++ b/src/smtp_out.c +@@ -688,20 +688,22 @@ Returns: TRUE if a valid, non-error response was received; else FALSE + /*XXX could move to smtp transport; no other users */ + + BOOL +-smtp_read_response(void * sx0, uschar *buffer, int size, int okdigit, ++smtp_read_response(void * sx0, uschar * buffer, int size, int okdigit, + int timeout) + { + smtp_context * sx = sx0; +-uschar *ptr = buffer; +-int count = 0; ++uschar * ptr = buffer; ++int count = 0, rc; + + errno = 0; /* Ensure errno starts out zero */ + + #ifdef EXPERIMENTAL_PIPE_CONNECT + if (sx->pending_BANNER || sx->pending_EHLO) +- if (smtp_reap_early_pipe(sx, &count) != OK) ++ if ((rc = smtp_reap_early_pipe(sx, &count)) != OK) + { + DEBUG(D_transport) debug_printf("failed reaping pipelined cmd responsess\n"); ++ buffer[0] = '\0'; ++ if (rc == DEFER) errno = ERRNO_TLSFAILURE; + return FALSE; + } + #endif +diff --git a/src/tls-gnu.c b/src/tls-gnu.c +index c404dc29..de2d70c0 100644 +--- a/src/tls-gnu.c ++++ b/src/tls-gnu.c +@@ -229,7 +229,7 @@ static gnutls_dh_params_t dh_server_params = NULL; + + static const int ssl_session_timeout = 200; + +-static const char * const exim_default_gnutls_priority = "NORMAL"; ++static const uschar * const exim_default_gnutls_priority = US"NORMAL"; + + /* Guard library core initialisation */ + +@@ -1278,7 +1278,6 @@ int rc; + size_t sz; + const char *errpos; + uschar *p; +-BOOL want_default_priorities; + + if (!exim_gnutls_base_init_done) + { +@@ -1387,32 +1386,24 @@ and replaces gnutls_require_kx, gnutls_require_mac & gnutls_require_protocols. + This was backwards incompatible, but means Exim no longer needs to track + all algorithms and provide string forms for them. */ + +-want_default_priorities = TRUE; +- ++p = NULL; + if (state->tls_require_ciphers && *state->tls_require_ciphers) + { + if (!expand_check_tlsvar(tls_require_ciphers, errstr)) + return DEFER; + if (state->exp_tls_require_ciphers && *state->exp_tls_require_ciphers) + { +- DEBUG(D_tls) debug_printf("GnuTLS session cipher/priority \"%s\"\n", +- state->exp_tls_require_ciphers); +- +- rc = gnutls_priority_init(&state->priority_cache, +- CS state->exp_tls_require_ciphers, &errpos); +- want_default_priorities = FALSE; + p = state->exp_tls_require_ciphers; ++ DEBUG(D_tls) debug_printf("GnuTLS session cipher/priority \"%s\"\n", p); + } + } +-if (want_default_priorities) ++if (!p) + { ++ p = exim_default_gnutls_priority; + DEBUG(D_tls) +- debug_printf("GnuTLS using default session cipher/priority \"%s\"\n", +- exim_default_gnutls_priority); +- rc = gnutls_priority_init(&state->priority_cache, +- exim_default_gnutls_priority, &errpos); +- p = US exim_default_gnutls_priority; ++ debug_printf("GnuTLS using default session cipher/priority \"%s\"\n", p); + } ++rc = gnutls_priority_init(&state->priority_cache, CCS p, &errpos); + + exim_gnutls_err_check(rc, string_sprintf( + "gnutls_priority_init(%s) failed at offset %ld, \"%.6s..\"", +diff --git a/src/transports/lmtp.c b/src/transports/lmtp.c +index 240d78b2..57b346d4 100644 +--- a/src/transports/lmtp.c ++++ b/src/transports/lmtp.c +@@ -122,7 +122,8 @@ Arguments: + Returns: TRUE if a "QUIT" command should be sent, else FALSE + */ + +-static BOOL check_response(int *errno_value, int more_errno, uschar *buffer, ++static BOOL ++check_response(int *errno_value, int more_errno, uschar *buffer, + int *yield, uschar **message) + { + *yield = '4'; /* Default setting is to give a temporary error */ +diff --git a/src/transports/smtp.c b/src/transports/smtp.c +index a351da84..bfd6018d 100644 +--- a/src/transports/smtp.c ++++ b/src/transports/smtp.c +@@ -594,6 +594,11 @@ switch(*errno_value) + pl, smtp_command, s); + return FALSE; + ++ case ERRNO_TLSFAILURE: /* Handle bad first read; can happen with ++ GnuTLS and TLS1.3 */ ++ *message = US"bad first read from TLS conn"; ++ return TRUE; ++ + case ERRNO_FILTER_FAIL: /* Handle a failed filter process error; + can't send QUIT as we mustn't end the DATA. */ + *message = string_sprintf("transport filter process failed (%d)%s", +@@ -942,6 +947,7 @@ Arguments: + + Return: + OK all well ++ DEFER error on first read of TLS'd conn + FAIL SMTP error in response + */ + int +@@ -949,6 +955,7 @@ smtp_reap_early_pipe(smtp_context * sx, int * countp) + { + BOOL pending_BANNER = sx->pending_BANNER; + BOOL pending_EHLO = sx->pending_EHLO; ++int rc = FAIL; + + sx->pending_BANNER = FALSE; /* clear early to avoid recursion */ + sx->pending_EHLO = FALSE; +@@ -960,6 +967,7 @@ if (pending_BANNER) + if (!smtp_reap_banner(sx)) + { + DEBUG(D_transport) debug_printf("bad banner\n"); ++ if (tls_out.active.sock >= 0) rc = DEFER; + goto fail; + } + } +@@ -974,6 +982,7 @@ if (pending_EHLO) + if (!smtp_reap_ehlo(sx)) + { + DEBUG(D_transport) debug_printf("bad response for EHLO\n"); ++ if (tls_out.active.sock >= 0) rc = DEFER; + goto fail; + } + +@@ -1011,7 +1020,7 @@ return OK; + fail: + invalidate_ehlo_cache_entry(sx); + (void) smtp_discard_responses(sx, sx->conn_args.ob, *countp); +- return FAIL; ++ return rc; + } + #endif + +@@ -1056,6 +1065,7 @@ Returns: 3 if at least one address had 2xx and one had 5xx + -2 I/O or other non-response error for RCPT + -3 DATA or MAIL failed - errno and buffer set + -4 banner or EHLO failed (early-pipelining) ++ -5 banner or EHLO failed (early-pipelining, TLS) + */ + + static int +@@ -1064,10 +1074,11 @@ sync_responses(smtp_context * sx, int count, int pending_DATA) + address_item * addr = sx->sync_addr; + smtp_transport_options_block * ob = sx->conn_args.ob; + int yield = 0; ++int rc; + + #ifdef EXPERIMENTAL_PIPE_CONNECT +-if (smtp_reap_early_pipe(sx, &count) != OK) +- return -4; ++if ((rc = smtp_reap_early_pipe(sx, &count)) != OK) ++ return rc == FAIL ? -4 : -5; + #endif + + /* Handle the response for a MAIL command. On error, reinstate the original +@@ -1083,6 +1094,8 @@ if (sx->pending_MAIL) + { + DEBUG(D_transport) debug_printf("bad response for MAIL\n"); + Ustrcpy(big_buffer, mail_command); /* Fits, because it came from there! */ ++ if (errno == ERRNO_TLSFAILURE) ++ return -5; + if (errno == 0 && sx->buffer[0] != 0) + { + int save_errno = 0; +@@ -1141,6 +1154,11 @@ while (count-- > 0) + } + } + ++ /* Error on first TLS read */ ++ ++ else if (errno == ERRNO_TLSFAILURE) ++ return -5; ++ + /* Timeout while reading the response */ + + else if (errno == ETIMEDOUT) +@@ -1253,6 +1271,10 @@ if (pending_DATA != 0) + int code; + uschar *msg; + BOOL pass_message; ++ ++ if (errno == ERRNO_TLSFAILURE) /* Error on first TLS read */ ++ return -5; ++ + if (pending_DATA > 0 || (yield & 1) != 0) + { + if (errno == 0 && sx->buffer[0] == '4') +@@ -1802,7 +1824,9 @@ Args: + tc_chunk_last add LAST option to SMTP BDAT command + tc_reap_prev reap response to previous SMTP commands + +-Returns: OK or ERROR ++Returns: ++ OK or ERROR ++ DEFER TLS error on first read (EHLO-resp); errno set + */ + + static int +@@ -1859,10 +1883,12 @@ if (flags & tc_reap_prev && prev_cmd_count > 0) + case 2: sx->completed_addr = TRUE; /* 5xx (only) => progress made */ + case 0: break; /* No 2xx or 5xx, but no probs */ + +- case -1: /* Timeout on RCPT */ ++ case -5: errno = ERRNO_TLSFAILURE; ++ return DEFER; + #ifdef EXPERIMENTAL_PIPE_CONNECT + case -4: /* non-2xx for pipelined banner or EHLO */ + #endif ++ case -1: /* Timeout on RCPT */ + default: return ERROR; /* I/O error, or any MAIL/DATA error */ + } + cmd_count = 1; +@@ -1933,6 +1959,9 @@ BOOL pass_message = FALSE; + uschar * message = NULL; + int yield = OK; + int rc; ++#ifdef SUPPORT_TLS ++uschar * tls_errstr; ++#endif + + sx->conn_args.ob = ob; + +@@ -2474,27 +2503,27 @@ if ( smtp_peer_options & OPTION_TLS + TLS_NEGOTIATE: + { + address_item * addr; +- uschar * errstr; + sx->cctx.tls_ctx = tls_client_start(sx->cctx.sock, sx->conn_args.host, + sx->addrlist, sx->conn_args.tblock, + # ifdef SUPPORT_DANE + sx->dane ? &tlsa_dnsa : NULL, + # endif +- &tls_out, &errstr); ++ &tls_out, &tls_errstr); + + if (!sx->cctx.tls_ctx) + { + /* TLS negotiation failed; give an error. From outside, this function may + be called again to try in clear on a new connection, if the options permit + it for this host. */ +- DEBUG(D_tls) debug_printf("TLS session fail: %s\n", errstr); ++GNUTLS_CONN_FAILED: ++ DEBUG(D_tls) debug_printf("TLS session fail: %s\n", tls_errstr); + + # ifdef SUPPORT_DANE + if (sx->dane) + { + log_write(0, LOG_MAIN, + "DANE attempt failed; TLS connection to %s [%s]: %s", +- sx->conn_args.host->name, sx->conn_args.host->address, errstr); ++ sx->conn_args.host->name, sx->conn_args.host->address, tls_errstr); + # ifndef DISABLE_EVENT + (void) event_raise(sx->conn_args.tblock->event_action, + US"dane:fail", US"validation-failure"); /* could do with better detail */ +@@ -2503,7 +2532,7 @@ if ( smtp_peer_options & OPTION_TLS + # endif + + errno = ERRNO_TLSFAILURE; +- message = string_sprintf("TLS session: %s", errstr); ++ message = string_sprintf("TLS session: %s", tls_errstr); + sx->send_quit = FALSE; + goto TLS_FAILED; + } +@@ -2601,7 +2630,22 @@ if (tls_out.active.sock >= 0) + #endif + { + if (!smtp_reap_ehlo(sx)) ++#ifdef USE_GNUTLS ++ { ++ /* The GnuTLS layer in Exim only spots a server-rejection of a client ++ cert late, under TLS1.3 - which means here; the first time we try to ++ receive crypted data. Treat it as if it was a connect-time failure. ++ See also the early-pipe equivalent... which will be hard; every call ++ to sync_responses will need to check the result. ++ It would be nicer to have GnuTLS check the cert during the handshake. ++ Can it do that, with all the flexibility we need? */ ++ ++ tls_errstr = US"error on first read"; ++ goto GNUTLS_CONN_FAILED; ++ } ++#else + goto RESPONSE_FAILED; ++#endif + smtp_peer_options = 0; + } + } +@@ -3261,6 +3305,7 @@ for (addr = sx->first_addr, address_count = 0; + + #ifdef EXPERIMENTAL_PIPE_CONNECT + case -4: return -1; /* non-2xx for pipelined banner or EHLO */ ++ case -5: return -1; /* TLS first-read error */ + #endif + } + sx->pending_MAIL = FALSE; /* Dealt with MAIL */ +@@ -3589,11 +3634,12 @@ if ( !(sx.peer_offered & OPTION_CHUNKING) + + case 1: sx.ok = TRUE; /* 2xx (only) => OK, but if LMTP, */ + if (!sx.lmtp) sx.completed_addr = TRUE; /* can't tell about progress yet */ +- case 0: break; /* No 2xx or 5xx, but no probs */ ++ case 0: break; /* No 2xx or 5xx, but no probs */ + +- case -1: goto END_OFF; /* Timeout on RCPT */ ++ case -1: goto END_OFF; /* Timeout on RCPT */ + + #ifdef EXPERIMENTAL_PIPE_CONNECT ++ case -5: /* TLS first-read error */ + case -4: HDEBUG(D_transport) + debug_printf("failed reaping pipelined cmd responses\n"); + #endif +@@ -3730,19 +3776,20 @@ else + { + case 3: sx.ok = TRUE; /* 2xx & 5xx => OK & progress made */ + case 2: sx.completed_addr = TRUE; /* 5xx (only) => progress made */ +- break; ++ break; + +- case 1: sx.ok = TRUE; /* 2xx (only) => OK, but if LMTP, */ ++ case 1: sx.ok = TRUE; /* 2xx (only) => OK, but if LMTP, */ + if (!sx.lmtp) sx.completed_addr = TRUE; /* can't tell about progress yet */ +- case 0: break; /* No 2xx or 5xx, but no probs */ ++ case 0: break; /* No 2xx or 5xx, but no probs */ + +- case -1: goto END_OFF; /* Timeout on RCPT */ ++ case -1: goto END_OFF; /* Timeout on RCPT */ + + #ifdef EXPERIMENTAL_PIPE_CONNECT ++ case -5: /* TLS first-read error */ + case -4: HDEBUG(D_transport) + debug_printf("failed reaping pipelined cmd responses\n"); + #endif +- default: goto RESPONSE_FAILED; /* I/O error, or any MAIL/DATA error */ ++ default: goto RESPONSE_FAILED; /* I/O error, or any MAIL/DATA error */ + } + } + +-- +2.20.1 + diff --git a/debian/patches/75_05-Fix-expansions-for-RFC-822-addresses-having-comments.patch b/debian/patches/75_05-Fix-expansions-for-RFC-822-addresses-having-comments.patch new file mode 100644 index 0000000..517eb1c --- /dev/null +++ b/debian/patches/75_05-Fix-expansions-for-RFC-822-addresses-having-comments.patch @@ -0,0 +1,91 @@ +From f634b80846cc7ffcab65c9855bcb35312f0232e8 Mon Sep 17 00:00:00 2001 +From: Jasen Betts <jasen@xnet.co.nz> +Date: Mon, 18 Feb 2019 13:52:16 +0000 +Subject: [PATCH 1/5] Fix expansions for RFC 822 addresses having comments in + local-part and/or domain. Bug 2375 + +(cherry picked from commit e2ff8e24f41caca3623228b1ec66a3f3961ecad6) +--- + doc/ChangeLog | 3 +++ + src/expand.c | 19 +++++++------------ + test/scripts/0000-Basic/0002 | 7 +++++++ + test/stdout/0002 | 7 +++++++ + 4 files changed, 24 insertions(+), 12 deletions(-) + +diff --git a/doc/ChangeLog b/doc/ChangeLog +index 867a1d8a..9659da32 100644 +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -16,10 +16,13 @@ JH/07 GnuTLS: Our use of late (post-handshake) certificate verification, under + to the client until the first read of encrypted data (typically the + response to EHLO). Add detection for that case and treat it as a failed + TLS connection attempt, so that the normal retry-in-clear can work (if + suitably configured). + ++JB/01 BZg 2375: fix expansions of 822 addresses having comments in local-part ++ and/or domain. Found and fixed by Jason Betts. ++ + + Exim version 4.92 + ----------------- + + JH/01 Remove code calling the customisable local_scan function, unless a new +diff --git a/src/expand.c b/src/expand.c +index 2c290251..35ede718 100644 +--- a/src/expand.c ++++ b/src/expand.c +@@ -7071,20 +7071,15 @@ while (*s != 0) + uschar * error; + int start, end, domain; + uschar * t = parse_extract_address(sub, &error, &start, &end, &domain, + FALSE); + if (t) +- if (c != EOP_DOMAIN) +- { +- if (c == EOP_LOCAL_PART && domain != 0) end = start + domain - 1; +- yield = string_catn(yield, sub+start, end-start); +- } +- else if (domain != 0) +- { +- domain += start; +- yield = string_catn(yield, sub+domain, end-domain); +- } ++ yield = c == EOP_DOMAIN ++ ? string_cat(yield, t + domain) ++ : c == EOP_LOCAL_PART && domain > 0 ++ ? string_catn(yield, t, domain - 1 ) ++ : string_cat(yield, t); + continue; + } + + case EOP_ADDRESSES: + { +@@ -7104,11 +7099,11 @@ while (*s != 0) + } + f.parse_allow_group = TRUE; + + for (;;) + { +- uschar *p = parse_find_address_end(sub, FALSE); ++ uschar * p = parse_find_address_end(sub, FALSE); + uschar saveend = *p; + *p = '\0'; + address = parse_extract_address(sub, &error, &start, &end, &domain, + FALSE); + *p = saveend; +@@ -7117,11 +7112,11 @@ while (*s != 0) + done in chunks by searching for the separator character. At the + start, unless we are dealing with the first address of the output + list, add in a space if the new address begins with the separator + character, or is an empty string. */ + +- if (address != NULL) ++ if (address) + { + if (yield->ptr != save_ptr && address[0] == *outsep) + yield = string_catn(yield, US" ", 1); + + for (;;) +-- +2.20.1 + diff --git a/debian/patches/75_06-Docs-Add-note-on-lsearch-for-IPv4-mapped-IPv6-addres.patch b/debian/patches/75_06-Docs-Add-note-on-lsearch-for-IPv4-mapped-IPv6-addres.patch new file mode 100644 index 0000000..a7863ef --- /dev/null +++ b/debian/patches/75_06-Docs-Add-note-on-lsearch-for-IPv4-mapped-IPv6-addres.patch @@ -0,0 +1,48 @@ +From 8dde16b89efe2138f92cbfa6c59fb31dc80ec22a Mon Sep 17 00:00:00 2001 +From: Jeremy Harris <jgh146exb@wizmail.org> +Date: Tue, 19 Feb 2019 14:45:27 +0000 +Subject: [PATCH 2/5] Docs: Add note on lsearch for IPv4-mapped IPv6 addresses + +Cherry-picked from: 52af443324, c77d3d85fe +--- + doc/doc-docbook/spec.xfpt | 11 ++++++++++- + doc/ChangeLog | 2 +- + 2 files changed, 11 insertions(+), 2 deletions(-) + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -18,7 +18,7 @@ JH/07 GnuTLS: Our use of late (post-hand + TLS connection attempt, so that the normal retry-in-clear can work (if + suitably configured). + +-JB/01 BZg 2375: fix expansions of 822 addresses having comments in local-part ++JB/01 Bug 2375: fix expansions of 822 addresses having comments in local-part + and/or domain. Found and fixed by Jason Betts. + + +--- a/doc/spec.txt ++++ b/doc/spec.txt +@@ -6302,6 +6302,10 @@ The following single-key lookup types ar + implicit key is the host's IP address rather than its name (see section + 10.12). + ++ Warning 3: Do not use an IPv4-mapped IPv6 address for a key; use the ++ IPv4, in dotted-quad form. (Exim converts IPv4-mapped IPv6 addresses to ++ this notation before executing the lookup.) ++ + * lsearch: The given file is a text file that is searched linearly for a line + beginning with the search key, terminated by a colon or white space or the + end of the line. The search is case-insensitive; that is, upper and lower +@@ -8003,7 +8007,11 @@ quote keys was made available in lsearch + implemented iplsearch files do require colons in IPv6 keys (notated using the + quoting facility) so as to distinguish them from IPv4 keys. For this reason, + when the lookup type is iplsearch, IPv6 addresses are converted using colons +-and not dots. In all cases, full, unabbreviated IPv6 addresses are always used. ++and not dots. ++ ++In all cases except IPv4-mapped IPv6, full, unabbreviated IPv6 addresses ++are always used. The latter are converted to IPv4 addresses, in dotted-quad ++form. + + Ideally, it would be nice to tidy up this anomalous situation by changing to + colons in all cases, given that quoting is now available for lsearch. However, diff --git a/debian/patches/75_07-Fix-crash-from-SRV-lookup-hitting-a-CNAME.patch b/debian/patches/75_07-Fix-crash-from-SRV-lookup-hitting-a-CNAME.patch new file mode 100644 index 0000000..cfdbe51 --- /dev/null +++ b/debian/patches/75_07-Fix-crash-from-SRV-lookup-hitting-a-CNAME.patch @@ -0,0 +1,69 @@ +From 09720dd9506176294154dad7152f5f40554046a4 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris <jgh146exb@wizmail.org> +Date: Thu, 14 Mar 2019 12:26:34 +0000 +Subject: [PATCH 3/5] Fix crash from SRV lookup hitting a CNAME + +(cherry picked from commit 14bc9cf085aff7bd5147881e5b7068769a29b026) +--- + doc/ChangeLog | 4 ++++ + src/dns.c | 10 +++++++--- + 2 files changed, 11 insertions(+), 3 deletions(-) + +diff --git a/doc/ChangeLog b/doc/ChangeLog +index 419c1061..0f8d05b2 100644 +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -19,10 +19,14 @@ JH/07 GnuTLS: Our use of late (post-handshake) certificate verification, under + suitably configured). + + JB/01 Bug 2375: fix expansions of 822 addresses having comments in local-part + and/or domain. Found and fixed by Jason Betts. + ++JH/08 Add hardening against SRV & TLSA lookups the hit CNAMEs (a nonvalid ++ configuration). If a CNAME target was not a wellformed name pattern, a ++ crash could result. ++ + + Exim version 4.92 + ----------------- + + JH/01 Remove code calling the customisable local_scan function, unless a new +diff --git a/src/dns.c b/src/dns.c +index 0f0b435d..b7978c52 100644 +--- a/src/dns.c ++++ b/src/dns.c +@@ -714,11 +714,15 @@ regex has substrings that are used - the default uses a conditional. + This test is omitted for PTR records. These occur only in calls from the dnsdb + lookup, which constructs the names itself, so they should be OK. Besides, + bitstring labels don't conform to normal name syntax. (But the aren't used any + more.) + +-For SRV records, we omit the initial _smtp._tcp. components at the start. */ ++For SRV records, we omit the initial _smtp._tcp. components at the start. ++The check has been seen to bite on the destination of a SRV lookup that ++initiall hit a CNAME, for which the next name had only two components. ++RFC2782 makes no mention of the possibiility of CNAMES, but the Wikipedia ++article on SRV says they are not a valid configuration. */ + + #ifndef STAND_ALONE /* Omit this for stand-alone tests */ + + if (check_dns_names_pattern[0] != 0 && type != T_PTR && type != T_TXT) + { +@@ -730,12 +734,12 @@ if (check_dns_names_pattern[0] != 0 && type != T_PTR && type != T_TXT) + /* For an SRV lookup, skip over the first two components (the service and + protocol names, which both start with an underscore). */ + + if (type == T_SRV || type == T_TLSA) + { +- while (*checkname++ != '.'); +- while (*checkname++ != '.'); ++ while (*checkname && *checkname++ != '.') ; ++ while (*checkname && *checkname++ != '.') ; + } + + if (pcre_exec(regex_check_dns_names, NULL, CCS checkname, Ustrlen(checkname), + 0, PCRE_EOPT, ovector, nelem(ovector)) < 0) + { +-- +2.20.1 + diff --git a/debian/patches/75_08-Logging-fix-initial-listening-on-log-line.patch b/debian/patches/75_08-Logging-fix-initial-listening-on-log-line.patch new file mode 100644 index 0000000..4af2972 --- /dev/null +++ b/debian/patches/75_08-Logging-fix-initial-listening-on-log-line.patch @@ -0,0 +1,206 @@ +From e5be948a65fe601024e5d4256f64efbfed3dd72e Mon Sep 17 00:00:00 2001 +From: Jeremy Harris <jgh146exb@wizmail.org> +Date: Mon, 18 Mar 2019 00:31:43 +0000 +Subject: [PATCH 4/5] Logging: fix initial listening-on log line + +(cherry picked from commit 254f38d1c5ada5e4df0bccb385dc466549620c71) +--- + doc/ChangeLog | 4 +++ + src/daemon.c | 73 +++++++++++++++++++++++++++---------------- + src/host.c | 1 + + src/structs.h | 1 + + test/confs/0282 | 2 +- + test/log/0282 | 2 +- + 6 files changed, 54 insertions(+), 29 deletions(-) + +diff --git a/doc/ChangeLog b/doc/ChangeLog +index 0f8d05b2..3c0ffbf0 100644 +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -23,10 +23,14 @@ JB/01 Bug 2375: fix expansions of 822 addresses having comments in local-part + + JH/08 Add hardening against SRV & TLSA lookups the hit CNAMEs (a nonvalid + configuration). If a CNAME target was not a wellformed name pattern, a + crash could result. + ++JH/09 Logging: Fix initial listening-on line for multiple ports for an IP when ++ the OS reports them interleaved with other addresses. ++ ++ + + Exim version 4.92 + ----------------- + + JH/01 Remove code calling the customisable local_scan function, unless a new +diff --git a/src/daemon.c b/src/daemon.c +index a852192e..01da3936 100644 +--- a/src/daemon.c ++++ b/src/daemon.c +@@ -1625,12 +1625,12 @@ if (f.inetd_wait_mode) + else if (f.daemon_listen) + { + int i, j; + int smtp_ports = 0; + int smtps_ports = 0; +- ip_address_item * ipa, * i2; +- uschar * p = big_buffer; ++ ip_address_item * ipa; ++ uschar * p; + uschar * qinfo = queue_interval > 0 + ? string_sprintf("-q%s", readconf_printtime(queue_interval)) + : US"no queue runs"; + + /* Build a list of listening addresses in big_buffer, but limit it to 10 +@@ -1638,73 +1638,92 @@ else if (f.daemon_listen) + + It is now possible to have some ports listening for SMTPS (the old, + deprecated protocol that starts TLS without using STARTTLS), and others + listening for standard SMTP. Keep their listings separate. */ + +- for (j = 0; j < 2; j++) ++ for (int j = 0, i; j < 2; j++) + { + for (i = 0, ipa = addresses; i < 10 && ipa; i++, ipa = ipa->next) + { + /* First time round, look for SMTP ports; second time round, look for +- SMTPS ports. For the first one of each, insert leading text. */ ++ SMTPS ports. Build IP+port strings. */ + + if (host_is_tls_on_connect_port(ipa->port) == (j > 0)) + { + if (j == 0) +- { +- if (smtp_ports++ == 0) +- { +- memcpy(p, "SMTP on", 8); +- p += 7; +- } +- } ++ smtp_ports++; + else +- if (smtps_ports++ == 0) +- p += sprintf(CS p, "%sSMTPS on", +- smtp_ports == 0 ? "" : " and for "); ++ smtps_ports++; + + /* Now the information about the port (and sometimes interface) */ + + if (ipa->address[0] == ':' && ipa->address[1] == 0) + { /* v6 wildcard */ + if (ipa->next && ipa->next->address[0] == 0 && + ipa->next->port == ipa->port) + { +- p += sprintf(CS p, " port %d (IPv6 and IPv4)", ipa->port); +- ipa = ipa->next; ++ ipa->log = string_sprintf(" port %d (IPv6 and IPv4)", ipa->port); ++ (ipa = ipa->next)->log = NULL; + } + else if (ipa->v6_include_v4) +- p += sprintf(CS p, " port %d (IPv6 with IPv4)", ipa->port); ++ ipa->log = string_sprintf(" port %d (IPv6 with IPv4)", ipa->port); + else +- p += sprintf(CS p, " port %d (IPv6)", ipa->port); ++ ipa->log = string_sprintf(" port %d (IPv6)", ipa->port); + } + else if (ipa->address[0] == 0) /* v4 wildcard */ +- p += sprintf(CS p, " port %d (IPv4)", ipa->port); ++ ipa->log = string_sprintf(" port %d (IPv4)", ipa->port); + else /* check for previously-seen IP */ + { ++ ip_address_item * i2; + for (i2 = addresses; i2 != ipa; i2 = i2->next) + if ( host_is_tls_on_connect_port(i2->port) == (j > 0) + && Ustrcmp(ipa->address, i2->address) == 0 + ) + { /* found; append port to list */ +- if (p[-1] == '}') p--; +- while (isdigit(*--p)) ; +- p += 1 + sprintf(CS p+1, "%s%d,%d}", *p == ',' ? "" : "{", +- i2->port, ipa->port); ++ for (p = i2->log; *p; ) p++; /* end of existing string */ ++ if (*--p == '}') *p = '\0'; /* drop EOL */ ++ while (isdigit(*--p)) ; /* char before port */ ++ ++ i2->log = *p == ':' /* no list yet? */ ++ ? string_sprintf("%.*s{%s,%d}", ++ (int)(p - i2->log + 1), i2->log, p+1, ipa->port) ++ : string_sprintf("%s,%d}", i2->log, ipa->port); ++ ipa->log = NULL; + break; + } + if (i2 == ipa) /* first-time IP */ +- p += sprintf(CS p, " [%s]:%d", ipa->address, ipa->port); ++ ipa->log = string_sprintf(" [%s]:%d", ipa->address, ipa->port); + } + } + } ++ } + +- if (ipa) ++ p = big_buffer; ++ for (int j = 0, i; j < 2; j++) ++ { ++ /* First time round, look for SMTP ports; second time round, look for ++ SMTPS ports. For the first one of each, insert leading text. */ ++ ++ if (j == 0) + { +- memcpy(p, " ...", 5); +- p += 4; ++ if (smtp_ports > 0) ++ p += sprintf(CS p, "SMTP on"); + } ++ else ++ if (smtps_ports > 0) ++ p += sprintf(CS p, "%sSMTPS on", ++ smtp_ports == 0 ? "" : " and for "); ++ ++ /* Now the information about the port (and sometimes interface) */ ++ ++ for (i = 0, ipa = addresses; i < 10 && ipa; i++, ipa = ipa->next) ++ if (host_is_tls_on_connect_port(ipa->port) == (j > 0)) ++ if (ipa->log) ++ p += sprintf(CS p, "%s", ipa->log); ++ ++ if (ipa) ++ p += sprintf(CS p, " ..."); + } + + log_write(0, LOG_MAIN, + "exim %s daemon started: pid=%d, %s, listening for %s", + version_string, getpid(), qinfo, big_buffer); +diff --git a/src/host.c b/src/host.c +index 29c977fe..a3b0977b 100644 +--- a/src/host.c ++++ b/src/host.c +@@ -757,10 +757,11 @@ while ((s = string_nextinlist(&list, &sep, NULL, 0))) + next = store_get(sizeof(ip_address_item)); + next->next = NULL; + Ustrcpy(next->address, s); + next->port = port; + next->v6_include_v4 = FALSE; ++ next->log = NULL; + + if (!yield) + yield = last = next; + else + { +diff --git a/src/structs.h b/src/structs.h +index 20db0e5f..1e63d752 100644 +--- a/src/structs.h ++++ b/src/structs.h +@@ -442,10 +442,11 @@ hold an IPv6 address. */ + typedef struct ip_address_item { + struct ip_address_item *next; + int port; + BOOL v6_include_v4; /* Used in the daemon */ + uschar address[46]; ++ uschar * log; /* portion of "listening on" log line */ + } ip_address_item; + + /* Structure for chaining together arbitrary strings. */ + + typedef struct string_item { +-- +2.20.1 + diff --git a/debian/patches/75_09-OpenSSL-Fix-aggregation-of-messages.patch b/debian/patches/75_09-OpenSSL-Fix-aggregation-of-messages.patch new file mode 100644 index 0000000..b82891d --- /dev/null +++ b/debian/patches/75_09-OpenSSL-Fix-aggregation-of-messages.patch @@ -0,0 +1,127 @@ +From 332ebeaf8139b2b75f475880fc14b63c7c45c706 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris <jgh146exb@wizmail.org> +Date: Tue, 19 Mar 2019 15:33:31 +0000 +Subject: [PATCH 5/5] OpenSSL: Fix aggregation of messages. + +Broken-by: a5ffa9b475 +(cherry picked from commit c09dbcfb71f4b9a42cbfd8a20e0be6bfa1b12488) +--- + doc/ChangeLog | 5 +++ + src/tls-openssl.c | 24 ++++++++++---- + test/confs/2152 | 76 +++++++++++++++++++++++++++++++++++++++++++ + test/log/2152 | 9 +++++ + 4 files changed, 108 insertions(+), 6 deletions(-) + create mode 100644 test/confs/2152 + create mode 100644 test/log/2152 + +diff --git a/doc/ChangeLog b/doc/ChangeLog +index 3c0ffbf0..3d63725f 100644 +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -26,10 +26,15 @@ JH/08 Add hardening against SRV & TLSA lookups the hit CNAMEs (a nonvalid + crash could result. + + JH/09 Logging: Fix initial listening-on line for multiple ports for an IP when + the OS reports them interleaved with other addresses. + ++JH/10 OpenSSL: Fix aggregation of messages. Previously, when PIPELINING was ++ used both for input and for a verify callout, both encrypted, SMTP ++ responses being sent by the server could be lost. This resulted in ++ dropped connections and sometimes bounces generated by a peer sending ++ to this system. + + + Exim version 4.92 + ----------------- + +diff --git a/src/tls-openssl.c b/src/tls-openssl.c +index 8f4cf4d8..cc0ead02 100644 +--- a/src/tls-openssl.c ++++ b/src/tls-openssl.c +@@ -272,10 +272,11 @@ Server: + */ + + typedef struct { + SSL_CTX * ctx; + SSL * ssl; ++ gstring * corked; + } exim_openssl_client_tls_ctx; + + static SSL_CTX *server_ctx = NULL; + static SSL *server_ssl = NULL; + +@@ -2471,10 +2472,11 @@ BOOL require_ocsp = FALSE; + #endif + + rc = store_pool; + store_pool = POOL_PERM; + exim_client_ctx = store_get(sizeof(exim_openssl_client_tls_ctx)); ++exim_client_ctx->corked = NULL; + store_pool = rc; + + #ifdef SUPPORT_DANE + tlsp->tlsa_usage = 0; + #endif +@@ -2906,22 +2908,29 @@ Used by both server-side and client-side TLS. + + int + tls_write(void * ct_ctx, const uschar *buff, size_t len, BOOL more) + { + int outbytes, error, left; +-SSL * ssl = ct_ctx ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl : server_ssl; +-static gstring * corked = NULL; ++SSL * ssl = ct_ctx ++ ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl : server_ssl; ++static gstring * server_corked = NULL; ++gstring ** corkedp = ct_ctx ++ ? &((exim_openssl_client_tls_ctx *)ct_ctx)->corked : &server_corked; ++gstring * corked = *corkedp; + + DEBUG(D_tls) debug_printf("%s(%p, %lu%s)\n", __FUNCTION__, + buff, (unsigned long)len, more ? ", more" : ""); + + /* Lacking a CORK or MSG_MORE facility (such as GnuTLS has) we copy data when + "more" is notified. This hack is only ok if small amounts are involved AND only + one stream does it, in one context (i.e. no store reset). Currently it is used +-for the responses to the received SMTP MAIL , RCPT, DATA sequence, only. */ +-/*XXX + if PIPE_COMMAND, banner & ehlo-resp for smmtp-on-connect. Suspect there's +-a store reset there. */ ++for the responses to the received SMTP MAIL , RCPT, DATA sequence, only. ++We support callouts done by the server process by using a separate client ++context for the stashed information. */ ++/* + if PIPE_COMMAND, banner & ehlo-resp for smmtp-on-connect. Suspect there's ++a store reset there, so use POOL_PERM. */ ++/* + if CHUNKING, cmds EHLO,MAIL,RCPT(s),BDAT */ + + if (!ct_ctx && (more || corked)) + { + #ifdef EXPERIMENTAL_PIPE_CONNECT + int save_pool = store_pool; +@@ -2933,14 +2942,17 @@ if (!ct_ctx && (more || corked)) + #ifdef EXPERIMENTAL_PIPE_CONNECT + store_pool = save_pool; + #endif + + if (more) ++ { ++ *corkedp = corked; + return len; ++ } + buff = CUS corked->s; + len = corked->ptr; +- corked = NULL; ++ *corkedp = NULL; + } + + for (left = len; left > 0;) + { + DEBUG(D_tls) debug_printf("SSL_write(%p, %p, %d)\n", ssl, buff, left); +diff --git a/test/confs/2152 b/test/confs/2152 +new file mode 100644 +index 00000000..f783192b +diff --git a/test/log/2152 b/test/log/2152 +new file mode 100644 +index 00000000..720200be +-- +2.20.1 + diff --git a/debian/patches/75_10-Harden-plaintext-authenticator.patch b/debian/patches/75_10-Harden-plaintext-authenticator.patch new file mode 100644 index 0000000..9dcfd47 --- /dev/null +++ b/debian/patches/75_10-Harden-plaintext-authenticator.patch @@ -0,0 +1,55 @@ +From e5b942ae007d0533fbd599c64d550f3a8355b940 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris <jgh146exb@wizmail.org> +Date: Thu, 21 Mar 2019 20:01:03 +0000 +Subject: [PATCH] Harden plaintext authenticator + +Cherry-picked from: f9fc942757 +--- + doc/ChangeLog | 5 +++++ + src/auths/plaintext.c | 6 +----- + 2 files changed, 6 insertions(+), 5 deletions(-) + +diff --git a/doc/ChangeLog b/doc/ChangeLog +index 3d63725f..c34e60d1 100644 +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -32,10 +32,15 @@ JH/10 OpenSSL: Fix aggregation of messages. Previously, when PIPELINING was + used both for input and for a verify callout, both encrypted, SMTP + responses being sent by the server could be lost. This resulted in + dropped connections and sometimes bounces generated by a peer sending + to this system. + ++JH/11 Harden plaintext authenticator against a badly misconfigured client-send ++ string. Previously it was possible to cause undefined behaviour in a ++ library routine (usually a crash). Found by "zerons". ++ ++ + + Exim version 4.92 + ----------------- + + JH/01 Remove code calling the customisable local_scan function, unless a new +diff --git a/src/auths/plaintext.c b/src/auths/plaintext.c +index 7a0f7885..fa05b0ad 100644 +--- a/src/auths/plaintext.c ++++ b/src/auths/plaintext.c +@@ -221,15 +221,11 @@ while ((s = string_nextinlist(&text, &sep, big_buffer, big_buffer_size))) + for (i = 0; i < len; i++) + if (ss[i] == '^') + if (ss[i+1] != '^') + ss[i] = 0; + else +- { +- i++; +- len--; +- memmove(ss + i, ss + i + 1, len - i); +- } ++ if (--len > ++i) memmove(ss + i, ss + i + 1, len - i); + + /* The first string is attached to the AUTH command; others are sent + unembellished. */ + + if (first) +-- +2.20.1 + diff --git a/debian/patches/75_11-GnuTLS-fix-tls_out_ocsp-under-hosts_request_ocsp.patch b/debian/patches/75_11-GnuTLS-fix-tls_out_ocsp-under-hosts_request_ocsp.patch new file mode 100644 index 0000000..8322d93 --- /dev/null +++ b/debian/patches/75_11-GnuTLS-fix-tls_out_ocsp-under-hosts_request_ocsp.patch @@ -0,0 +1,54 @@ +From 5e64b73ef7cdaf20b998b3345a588b462fd30bfb Mon Sep 17 00:00:00 2001 +From: Jeremy Harris <jgh146exb@wizmail.org> +Date: Tue, 7 May 2019 22:55:41 +0100 +Subject: [PATCH] GnuTLS: fix $tls_out_ocsp under hosts_request_ocsp + +(cherry picked from commit 7a501c874f028f689c44999ab05bb0d39da46941) +--- + doc/ChangeLog | 3 +++ + src/tls-gnu.c | 12 ++++++++---- + test/log/5651 | 2 +- + test/log/5730 | 8 ++++---- + 4 files changed, 16 insertions(+), 9 deletions(-) + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -39,6 +39,9 @@ JH/11 Harden plaintext authenticator aga + library routine (usually a crash). Found by "zerons". + + ++JH/18 GnuTLS: fix $tls_out_ocsp under hosts_request_ocsp. Previously the ++ verification result was not updated unless hosts_require_ocsp applied. ++ + + Exim version 4.92 + ----------------- +--- a/src/tls-gnu.c ++++ b/src/tls-gnu.c +@@ -2450,7 +2450,7 @@ if (!verify_certificate(state, errstr)) + } + + #ifndef DISABLE_OCSP +-if (require_ocsp) ++if (request_ocsp) + { + DEBUG(D_tls) + { +@@ -2474,10 +2474,14 @@ if (require_ocsp) + { + tlsp->ocsp = OCSP_FAILED; + tls_error(US"certificate status check failed", NULL, state->host, errstr); +- return NULL; ++ if (require_ocsp) ++ return FALSE; ++ } ++ else ++ { ++ DEBUG(D_tls) debug_printf("Passed OCSP checking\n"); ++ tlsp->ocsp = OCSP_VFIED; + } +- DEBUG(D_tls) debug_printf("Passed OCSP checking\n"); +- tlsp->ocsp = OCSP_VFIED; + } + #endif + diff --git a/debian/patches/75_12-GnuTLS-fix-the-advertising-of-acceptable-certs-by-th.patch b/debian/patches/75_12-GnuTLS-fix-the-advertising-of-acceptable-certs-by-th.patch new file mode 100644 index 0000000..5b98faa --- /dev/null +++ b/debian/patches/75_12-GnuTLS-fix-the-advertising-of-acceptable-certs-by-th.patch @@ -0,0 +1,42 @@ +From 44893ba5249c6c6d5a0d62a1cc57ba3fbf7185b4 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris <jgh146exb@wizmail.org> +Date: Sun, 19 May 2019 12:12:36 +0100 +Subject: [PATCH 1/2] GnuTLS: fix the advertising of acceptable certs by the + server. Bug 2389 + +(cherry picked from commit 12d95aa62042377fc9f603245a17a43142972447) +--- + doc/ChangeLog | 4 ++++ + src/tls-gnu.c | 8 ++++++++ + 2 files changed, 12 insertions(+) + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -42,6 +42,10 @@ JH/11 Harden plaintext authenticator aga + JH/18 GnuTLS: fix $tls_out_ocsp under hosts_request_ocsp. Previously the + verification result was not updated unless hosts_require_ocsp applied. + ++JH/20 Bug 2389: fix server advertising of usable certificates, under GnuTLS in ++ directory-of-certs mode. Previously they were advertised despite the ++ documentation. ++ + + Exim version 4.92 + ----------------- +--- a/src/tls-gnu.c ++++ b/src/tls-gnu.c +@@ -1133,6 +1133,14 @@ else + #endif + gnutls_certificate_set_x509_trust_file(state->x509_cred, + CS state->exp_tls_verify_certificates, GNUTLS_X509_FMT_PEM); ++ ++#ifdef SUPPORT_CA_DIR ++ /* Mimic the behaviour with OpenSSL of not advertising a usable-cert list ++ when using the directory-of-certs config model. */ ++ ++ if ((statbuf.st_mode & S_IFMT) == S_IFDIR) ++ gnutls_certificate_send_x509_rdn_sequence(state->session, 1); ++#endif + } + + if (cert_count < 0) diff --git a/debian/patches/75_13-Use-dsn_from-for-success-DSN-messages.-Bug-2404.patch b/debian/patches/75_13-Use-dsn_from-for-success-DSN-messages.-Bug-2404.patch new file mode 100644 index 0000000..0eb4d4b --- /dev/null +++ b/debian/patches/75_13-Use-dsn_from-for-success-DSN-messages.-Bug-2404.patch @@ -0,0 +1,52 @@ +From 454bab46ae6812e29652d10c390451c962a6f806 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris <jgh146exb@wizmail.org> +Date: Tue, 4 Jun 2019 18:13:21 +0100 +Subject: [PATCH 2/2] Use dsn_from for success-DSN messages. Bug 2404 + +(cherry picked from commit 87abcb247b4444bab5fd0bcb212ddb26d5fd9191) +--- + doc/ChangeLog | 4 ++++ + src/deliver.c | 4 ++-- + 2 files changed, 6 insertions(+), 2 deletions(-) + +diff --git a/doc/ChangeLog b/doc/ChangeLog +index 5a3e453d..1a12c014 100644 +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -65,6 +65,10 @@ JH/20 Bug 2389: fix server advertising of usable certificates, under GnuTLS in + directory-of-certs mode. Previously they were advertised despite the + documentation. + ++JH/27 Bug 2404: Use the main-section configuration option "dsn_from" for ++ success-DSN messages. Previously the From: header was always the default ++ one for these; the option was ignored. ++ + + Exim version 4.92 + ----------------- +diff --git a/src/deliver.c b/src/deliver.c +index e1799411..4720f596 100644 +--- a/src/deliver.c ++++ b/src/deliver.c +@@ -7365,8 +7365,8 @@ if (addr_senddsn) + if (errors_reply_to) + fprintf(f, "Reply-To: %s\n", errors_reply_to); + ++ moan_write_from(f); + fprintf(f, "Auto-Submitted: auto-generated\n" +- "From: Mail Delivery System <Mailer-Daemon@%s>\n" + "To: %s\n" + "Subject: Delivery Status Notification\n" + "Content-Type: multipart/report; report-type=delivery-status; boundary=%s\n" +@@ -7377,7 +7377,7 @@ if (addr_senddsn) + + "This message was created automatically by mail delivery software.\n" + " ----- The following addresses had successful delivery notifications -----\n", +- qualify_domain_sender, sender_address, bound, bound); ++ sender_address, bound, bound); + + for (addr_dsntmp = addr_senddsn; addr_dsntmp; + addr_dsntmp = addr_dsntmp->next) +-- +2.20.1 + diff --git a/debian/patches/75_14-Fix-smtp-response-timeout.patch b/debian/patches/75_14-Fix-smtp-response-timeout.patch new file mode 100644 index 0000000..abf2da4 --- /dev/null +++ b/debian/patches/75_14-Fix-smtp-response-timeout.patch @@ -0,0 +1,325 @@ +From 0a5441fcd93ae4145c07b3ed138dfe0e107174e0 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris <jgh146exb@wizmail.org> +Date: Mon, 27 May 2019 23:44:31 +0100 +Subject: [PATCH 1/2] Fix smtp response timeout + +--- + doc/ChangeLog | 6 ++++++ + src/functions.h | 4 ++-- + src/ip.c | 16 +++++++--------- + src/malware.c | 26 +++++++++++++------------- + src/routers/iplookup.c | 2 +- + src/smtp_out.c | 9 +++++---- + src/spam.c | 2 +- + src/transports/smtp_socks.c | 6 +++--- + src/verify.c | 2 +- + 9 files changed, 39 insertions(+), 34 deletions(-) + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -50,6 +50,13 @@ JH/27 Bug 2404: Use the main-section con + success-DSN messages. Previously the From: header was always the default + one for these; the option was ignored. + ++JH/28 Fix the timeout on smtp response to apply to the whole response. ++ Previously it was reset for every read, so a teergrubing peer sending ++ single bytes within the time limit could extend the connection for a ++ long time. Credit to Qualsys Security Advisory Team for the discovery. ++[from GIT master] ++ ++ + + Exim version 4.92 + ----------------- +--- a/src/functions.h ++++ b/src/functions.h +@@ -225,7 +225,7 @@ extern uschar *expand_string_copy(const + extern int_eximarith_t expand_string_integer(uschar *, BOOL); + extern void modify_variable(uschar *, void *); + +-extern BOOL fd_ready(int, int); ++extern BOOL fd_ready(int, time_t); + + extern int filter_interpret(uschar *, int, address_item **, uschar **); + extern BOOL filter_personal(string_item *, BOOL); +@@ -271,7 +271,7 @@ extern int ip_connectedsocket(int, c + int, host_item *, uschar **, const blob *); + extern int ip_get_address_family(int); + extern void ip_keepalive(int, const uschar *, BOOL); +-extern int ip_recv(client_conn_ctx *, uschar *, int, int); ++extern int ip_recv(client_conn_ctx *, uschar *, int, time_t); + extern int ip_socket(int, int); + + extern int ip_tcpsocket(const uschar *, uschar **, int); +--- a/src/ip.c ++++ b/src/ip.c +@@ -566,16 +566,15 @@ if (setsockopt(sock, SOL_SOCKET, SO_KEEP + /* + Arguments: + fd the file descriptor +- timeout the timeout, seconds ++ timelimit the timeout endpoint, seconds-since-epoch + Returns: TRUE => ready for i/o + FALSE => timed out, or other error + */ + BOOL +-fd_ready(int fd, int timeout) ++fd_ready(int fd, time_t timelimit) + { + fd_set select_inset; +-time_t start_recv = time(NULL); +-int time_left = timeout; ++int time_left = timelimit - time(NULL); + int rc; + + if (time_left <= 0) +@@ -609,8 +608,7 @@ do + DEBUG(D_transport) debug_printf("EINTR while waiting for socket data\n"); + + /* Watch out, 'continue' jumps to the condition, not to the loops top */ +- time_left = timeout - (time(NULL) - start_recv); +- if (time_left > 0) continue; ++ if ((time_left = timelimit - time(NULL)) > 0) continue; + } + + if (rc <= 0) +@@ -634,18 +632,18 @@ Arguments: + cctx the connection context (socket fd, possibly TLS context) + buffer to read into + bufsize the buffer size +- timeout the timeout ++ timelimit the timeout endpoint, seconds-since-epoch + + Returns: > 0 => that much data read + <= 0 on error or EOF; errno set - zero for EOF + */ + + int +-ip_recv(client_conn_ctx * cctx, uschar * buffer, int buffsize, int timeout) ++ip_recv(client_conn_ctx * cctx, uschar * buffer, int buffsize, time_t timelimit) + { + int rc; + +-if (!fd_ready(cctx->sock, timeout)) ++if (!fd_ready(cctx->sock, timelimit)) + return -1; + + /* The socket is ready, read from it (via TLS if it's active). On EOF (i.e. +--- a/src/malware.c ++++ b/src/malware.c +@@ -349,13 +349,13 @@ return cre; + -2 on timeout or error + */ + static int +-recv_line(int fd, uschar * buffer, int bsize, int tmo) ++recv_line(int fd, uschar * buffer, int bsize, time_t tmo) + { + uschar * p = buffer; + ssize_t rcv; + BOOL ok = FALSE; + +-if (!fd_ready(fd, tmo-time(NULL))) ++if (!fd_ready(fd, tmo)) + return -2; + + /*XXX tmo handling assumes we always get a whole line */ +@@ -382,9 +382,9 @@ return p - buffer; + + /* return TRUE iff size as requested */ + static BOOL +-recv_len(int sock, void * buf, int size, int tmo) ++recv_len(int sock, void * buf, int size, time_t tmo) + { +-return fd_ready(sock, tmo-time(NULL)) ++return fd_ready(sock, tmo) + ? recv(sock, buf, size, 0) == size + : FALSE; + } +@@ -430,7 +430,7 @@ for (;;) + } + + static inline int +-mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size, int tmo) ++mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size, time_t tmo) + { + client_conn_ctx cctx = {.sock = sock}; + int offset = 0; +@@ -438,7 +438,7 @@ int i; + + do + { +- i = ip_recv(&cctx, av_buffer+offset, av_buffer_size-offset, tmo-time(NULL)); ++ i = ip_recv(&cctx, av_buffer+offset, av_buffer_size-offset, tmo); + if (i <= 0) + { + (void) malware_panic_defer(US"unable to read from mksd UNIX socket (/var/run/mksd/socket)"); +@@ -497,7 +497,7 @@ switch (*line) + + static int + mksd_scan_packed(struct scan * scanent, int sock, const uschar * scan_filename, +- int tmo) ++ time_t tmo) + { + struct iovec iov[3]; + const char *cmd = "MSQ\n"; +@@ -746,7 +746,7 @@ if (!malware_ok) + if (m_sock_send(malware_daemon_ctx.sock, scanrequest, Ustrlen(scanrequest), &errstr) < 0) + return m_panic_defer(scanent, CUS callout_address, errstr); + +- bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL)); ++ bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo); + + if (bread <= 0) + return m_panic_defer_3(scanent, CUS callout_address, +@@ -1064,7 +1064,7 @@ badseek: err = errno; + if (m_sock_send(malware_daemon_ctx.sock, cmdopt[i], Ustrlen(cmdopt[i]), &errstr) < 0) + return m_panic_defer(scanent, CUS callout_address, errstr); + +- bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL)); ++ bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo); + if (bread > 0) av_buffer[bread]='\0'; + if (bread < 0) + return m_panic_defer_3(scanent, CUS callout_address, +@@ -1096,7 +1096,7 @@ badseek: err = errno; + { + errno = ETIMEDOUT; + i = av_buffer+sizeof(av_buffer)-p; +- if ((bread= ip_recv(&malware_daemon_ctx, p, i-1, tmo-time(NULL))) < 0) ++ if ((bread= ip_recv(&malware_daemon_ctx, p, i-1, tmo)) < 0) + return m_panic_defer_3(scanent, CUS callout_address, + string_sprintf("unable to read result (%s)", strerror(errno)), + malware_daemon_ctx.sock); +@@ -1401,7 +1401,7 @@ badseek: err = errno; + + /* wait for result */ + memset(av_buffer, 0, sizeof(av_buffer)); +- if ((bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL))) <= 0) ++ if ((bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo)) <= 0) + return m_panic_defer_3(scanent, CUS callout_address, + string_sprintf("unable to read from UNIX socket (%s)", scanner_options), + malware_daemon_ctx.sock); +@@ -1737,7 +1737,7 @@ b_seek: err = errno; + + /* Read the result */ + memset(av_buffer, 0, sizeof(av_buffer)); +- bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL)); ++ bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo); + (void)close(malware_daemon_ctx.sock); + malware_daemon_ctx.sock = -1; + malware_daemon_ctx.tls_ctx = NULL; +@@ -1895,7 +1895,7 @@ b_seek: err = errno; + return m_panic_defer(scanent, CUS callout_address, errstr); + + /* Read the result */ +- bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL)); ++ bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo); + + if (bread <= 0) + return m_panic_defer_3(scanent, CUS callout_address, +--- a/src/routers/iplookup.c ++++ b/src/routers/iplookup.c +@@ -279,7 +279,7 @@ while ((hostname = string_nextinlist(&li + /* Read the response and close the socket. If the read fails, try the + next IP address. */ + +- count = ip_recv(&query_cctx, reply, sizeof(reply) - 1, ob->timeout); ++ count = ip_recv(&query_cctx, reply, sizeof(reply) - 1, time(NULL) + ob->timeout); + (void)close(query_cctx.sock); + if (count <= 0) + { +--- a/src/smtp_out.c ++++ b/src/smtp_out.c +@@ -587,14 +587,14 @@ Arguments: + inblock the SMTP input block (contains holding buffer, socket, etc.) + buffer where to put the line + size space available for the line +- timeout the timeout to use when reading a packet ++ timelimit deadline for reading the lime, seconds past epoch + + Returns: length of a line that has been put in the buffer + -1 otherwise, with errno set + */ + + static int +-read_response_line(smtp_inblock *inblock, uschar *buffer, int size, int timeout) ++read_response_line(smtp_inblock *inblock, uschar *buffer, int size, time_t timelimit) + { + uschar *p = buffer; + uschar *ptr = inblock->ptr; +@@ -637,7 +637,7 @@ for (;;) + + /* Need to read a new input packet. */ + +- if((rc = ip_recv(cctx, inblock->buffer, inblock->buffersize, timeout)) <= 0) ++ if((rc = ip_recv(cctx, inblock->buffer, inblock->buffersize, timelimit)) <= 0) + { + DEBUG(D_deliver|D_transport|D_acl) + debug_printf_indent(errno ? " SMTP(%s)<<\n" : " SMTP(closed)<<\n", +@@ -694,6 +694,7 @@ smtp_read_response(void * sx0, uschar * + smtp_context * sx = sx0; + uschar * ptr = buffer; + int count = 0, rc; ++time_t timelimit = time(NULL) + timeout; + + errno = 0; /* Ensure errno starts out zero */ + +@@ -713,7 +714,7 @@ response. */ + + for (;;) + { +- if ((count = read_response_line(&sx->inblock, ptr, size, timeout)) < 0) ++ if ((count = read_response_line(&sx->inblock, ptr, size, timelimit)) < 0) + return FALSE; + + HDEBUG(D_transport|D_acl|D_v) +--- a/src/spam.c ++++ b/src/spam.c +@@ -503,7 +503,7 @@ offset = 0; + while ((i = ip_recv(&spamd_cctx, + spamd_buffer + offset, + sizeof(spamd_buffer) - offset - 1, +- sd->timeout - time(NULL) + start)) > 0) ++ sd->timeout + start)) > 0) + offset += i; + spamd_buffer[offset] = '\0'; /* guard byte */ + +--- a/src/transports/smtp_socks.c ++++ b/src/transports/smtp_socks.c +@@ -129,7 +129,7 @@ switch(method) + #ifdef TCP_QUICKACK + (void) setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off)); + #endif +- if (!fd_ready(fd, tmo-time(NULL)) || read(fd, s, 2) != 2) ++ if (!fd_ready(fd, tmo) || read(fd, s, 2) != 2) + return FAIL; + HDEBUG(D_transport|D_acl|D_v) + debug_printf_indent(" SOCKS<< %02x %02x\n", s[0], s[1]); +@@ -320,7 +320,7 @@ HDEBUG(D_transport|D_acl|D_v) debug_prin + (void) setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off)); + #endif + +-if ( !fd_ready(fd, tmo-time(NULL)) ++if ( !fd_ready(fd, tmo) + || read(fd, buf, 2) != 2 + ) + goto rcv_err; +@@ -370,7 +370,7 @@ if (send(fd, buf, size, 0) < 0) + /* expect conn-reply (success, local(ipver, addr, port)) + of same length as conn-request, or non-success fail code */ + +-if ( !fd_ready(fd, tmo-time(NULL)) ++if ( !fd_ready(fd, tmo) + || (size = read(fd, buf, size)) < 2 + ) + goto rcv_err; +--- a/src/verify.c ++++ b/src/verify.c +@@ -2770,7 +2770,7 @@ for (;;) + int size = sizeof(buffer) - (p - buffer); + + if (size <= 0) goto END_OFF; /* Buffer filled without seeing \n. */ +- count = ip_recv(&ident_conn_ctx, p, size, rfc1413_query_timeout); ++ count = ip_recv(&ident_conn_ctx, p, size, time(NULL) + rfc1413_query_timeout); + if (count <= 0) goto END_OFF; /* Read error or EOF */ + + /* Scan what we just read, to see if we have reached the terminating \r\n. Be diff --git a/debian/patches/75_15-Fix-detection-of-32b-platform-at-build-time.-Bug-240.patch b/debian/patches/75_15-Fix-detection-of-32b-platform-at-build-time.-Bug-240.patch new file mode 100644 index 0000000..039ed5f --- /dev/null +++ b/debian/patches/75_15-Fix-detection-of-32b-platform-at-build-time.-Bug-240.patch @@ -0,0 +1,48 @@ +From 26dd3aa007b3b77969610c031f59388e0953bd00 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris <jgh146exb@wizmail.org> +Date: Fri, 7 Jun 2019 11:54:10 +0100 +Subject: [PATCH 2/2] Fix detection of 32b platform at build time. Bug 2405 + +--- + src/buildconfig.c | 12 +++--- + test/scripts/0000-Basic/0002 | 72 +++++++++++++++++++----------------- + test/stdout/0002 | 72 +++++++++++++++++++----------------- + 3 files changed, 83 insertions(+), 73 deletions(-) + +diff --git a/src/buildconfig.c b/src/buildconfig.c +index 71cf97b1..a680b344 100644 +--- a/src/buildconfig.c ++++ b/src/buildconfig.c +@@ -111,6 +111,7 @@ unsigned long test_ulong_t = 0L; + unsigned int test_uint_t = 0; + #endif + long test_long_t = 0; ++long long test_longlong_t = 0; + int test_int_t = 0; + FILE *base; + FILE *new; +@@ -155,15 +156,16 @@ This assumption is known to be OK for the common operating systems. */ + + fprintf(new, "#ifndef OFF_T_FMT\n"); + if (sizeof(test_off_t) > sizeof(test_long_t)) +- { + fprintf(new, "# define OFF_T_FMT \"%%lld\"\n"); +- fprintf(new, "# define LONGLONG_T long long int\n"); +- } + else +- { + fprintf(new, "# define OFF_T_FMT \"%%ld\"\n"); ++fprintf(new, "#endif\n\n"); ++ ++fprintf(new, "#ifndef LONGLONG_T\n"); ++if (sizeof(test_longlong_t) > sizeof(test_long_t)) ++ fprintf(new, "# define LONGLONG_T long long int\n"); ++else + fprintf(new, "# define LONGLONG_T long int\n"); +- } + fprintf(new, "#endif\n\n"); + + /* Now do the same thing for time_t variables. If the length is greater than +-- +2.20.1 + diff --git a/debian/patches/77_Avoid-re-expansion-in-sort-CVE-2019-13917-OVE-201907.patch b/debian/patches/77_Avoid-re-expansion-in-sort-CVE-2019-13917-OVE-201907.patch new file mode 100644 index 0000000..50c63ed --- /dev/null +++ b/debian/patches/77_Avoid-re-expansion-in-sort-CVE-2019-13917-OVE-201907.patch @@ -0,0 +1,394 @@ +From cf84d126bc1f04746eb7c8e8b3468f7e70add3ec Mon Sep 17 00:00:00 2001 +From: Jeremy Harris <jgh146exb@wizmail.org> +Date: Fri, 5 Jul 2019 15:38:15 +0100 +Subject: [PATCH] Avoid re-expansion in ${sort } CVE-2019-13917 + OVE-20190718-0006 + +(cherry picked from commit 5c887f836e4d8e3f79da1c15565b56b40d9bd0dd) +--- + doc/ChangeLog | 6 ++ + doc/doc-txt/cve-2019-13917 | 46 ++++++++ + src/expand.c | 214 +++++++++++++++++++++++++------------ + 3 files changed, 199 insertions(+), 67 deletions(-) + create mode 100644 doc/doc-txt/cve-2019-13917 + +--- a/src/expand.c ++++ b/src/expand.c +@@ -2147,6 +2147,55 @@ return ret; + + + ++/************************************************/ ++/* Return offset in ops table, or -1 if not found. ++Repoint to just after the operator in the string. ++ ++Argument: ++ ss string representation of operator ++ opname split-out operator name ++*/ ++ ++static int ++identify_operator(const uschar ** ss, uschar ** opname) ++{ ++const uschar * s = *ss; ++uschar name[256]; ++ ++/* Numeric comparisons are symbolic */ ++ ++if (*s == '=' || *s == '>' || *s == '<') ++ { ++ int p = 0; ++ name[p++] = *s++; ++ if (*s == '=') ++ { ++ name[p++] = '='; ++ s++; ++ } ++ name[p] = 0; ++ } ++ ++/* All other conditions are named */ ++ ++else ++ s = read_name(name, sizeof(name), s, US"_"); ++*ss = s; ++ ++/* If we haven't read a name, it means some non-alpha character is first. */ ++ ++if (!name[0]) ++ { ++ expand_string_message = string_sprintf("condition name expected, " ++ "but found \"%.16s\"", s); ++ return -1; ++ } ++if (opname) ++ *opname = string_copy(name); ++ ++return chop_match(name, cond_table, nelem(cond_table)); ++} ++ + + /************************************************* + * Read and evaluate a condition * +@@ -2177,6 +2226,7 @@ BOOL sub2_honour_dollar = TRUE; + int i, rc, cond_type, roffset; + int_eximarith_t num[2]; + struct stat statbuf; ++uschar * opname; + uschar name[256]; + const uschar *sub[10]; + +@@ -2189,37 +2239,7 @@ for (;;) + if (*s == '!') { testfor = !testfor; s++; } else break; + } + +-/* Numeric comparisons are symbolic */ +- +-if (*s == '=' || *s == '>' || *s == '<') +- { +- int p = 0; +- name[p++] = *s++; +- if (*s == '=') +- { +- name[p++] = '='; +- s++; +- } +- name[p] = 0; +- } +- +-/* All other conditions are named */ +- +-else s = read_name(name, 256, s, US"_"); +- +-/* If we haven't read a name, it means some non-alpha character is first. */ +- +-if (name[0] == 0) +- { +- expand_string_message = string_sprintf("condition name expected, " +- "but found \"%.16s\"", s); +- return NULL; +- } +- +-/* Find which condition we are dealing with, and switch on it */ +- +-cond_type = chop_match(name, cond_table, nelem(cond_table)); +-switch(cond_type) ++switch(cond_type = identify_operator(&s, &opname)) + { + /* def: tests for a non-empty variable, or for the existence of a header. If + yield == NULL we are in a skipping state, and don't care about the answer. */ +@@ -2538,7 +2558,7 @@ switch(cond_type) + { + if (i == 0) goto COND_FAILED_CURLY_START; + expand_string_message = string_sprintf("missing 2nd string in {} " +- "after \"%s\"", name); ++ "after \"%s\"", opname); + return NULL; + } + if (!(sub[i] = expand_string_internal(s+1, TRUE, &s, yield == NULL, +@@ -2553,7 +2573,7 @@ switch(cond_type) + conditions that compare numbers do not start with a letter. This just saves + checking for them individually. */ + +- if (!isalpha(name[0]) && yield != NULL) ++ if (!isalpha(opname[0]) && yield != NULL) + if (sub[i][0] == 0) + { + num[i] = 0; +@@ -2867,7 +2887,7 @@ switch(cond_type) + uschar *save_iterate_item = iterate_item; + int (*compare)(const uschar *, const uschar *); + +- DEBUG(D_expand) debug_printf_indent("condition: %s item: %s\n", name, sub[0]); ++ DEBUG(D_expand) debug_printf_indent("condition: %s item: %s\n", opname, sub[0]); + + tempcond = FALSE; + compare = cond_type == ECOND_INLISTI +@@ -2909,14 +2929,14 @@ switch(cond_type) + if (*s != '{') /* }-for-text-editors */ + { + expand_string_message = string_sprintf("each subcondition " +- "inside an \"%s{...}\" condition must be in its own {}", name); ++ "inside an \"%s{...}\" condition must be in its own {}", opname); + return NULL; + } + + if (!(s = eval_condition(s+1, resetok, subcondptr))) + { + expand_string_message = string_sprintf("%s inside \"%s{...}\" condition", +- expand_string_message, name); ++ expand_string_message, opname); + return NULL; + } + while (isspace(*s)) s++; +@@ -2926,7 +2946,7 @@ switch(cond_type) + { + /* {-for-text-editors */ + expand_string_message = string_sprintf("missing } at end of condition " +- "inside \"%s\" group", name); ++ "inside \"%s\" group", opname); + return NULL; + } + +@@ -2958,7 +2978,7 @@ switch(cond_type) + int sep = 0; + uschar *save_iterate_item = iterate_item; + +- DEBUG(D_expand) debug_printf_indent("condition: %s\n", name); ++ DEBUG(D_expand) debug_printf_indent("condition: %s\n", opname); + + while (isspace(*s)) s++; + if (*s++ != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */ +@@ -2979,7 +2999,7 @@ switch(cond_type) + if (!(s = eval_condition(sub[1], resetok, NULL))) + { + expand_string_message = string_sprintf("%s inside \"%s\" condition", +- expand_string_message, name); ++ expand_string_message, opname); + return NULL; + } + while (isspace(*s)) s++; +@@ -2989,7 +3009,7 @@ switch(cond_type) + { + /* {-for-text-editors */ + expand_string_message = string_sprintf("missing } at end of condition " +- "inside \"%s\"", name); ++ "inside \"%s\"", opname); + return NULL; + } + +@@ -3001,11 +3021,11 @@ switch(cond_type) + if (!eval_condition(sub[1], resetok, &tempcond)) + { + expand_string_message = string_sprintf("%s inside \"%s\" condition", +- expand_string_message, name); ++ expand_string_message, opname); + iterate_item = save_iterate_item; + return NULL; + } +- DEBUG(D_expand) debug_printf_indent("%s: condition evaluated to %s\n", name, ++ DEBUG(D_expand) debug_printf_indent("%s: condition evaluated to %s\n", opname, + tempcond? "true":"false"); + + if (yield != NULL) *yield = (tempcond == testfor); +@@ -3098,19 +3118,20 @@ switch(cond_type) + /* Unknown condition */ + + default: +- expand_string_message = string_sprintf("unknown condition \"%s\"", name); +- return NULL; ++ if (!expand_string_message || !*expand_string_message) ++ expand_string_message = string_sprintf("unknown condition \"%s\"", opname); ++ return NULL; + } /* End switch on condition type */ + + /* Missing braces at start and end of data */ + + COND_FAILED_CURLY_START: +-expand_string_message = string_sprintf("missing { after \"%s\"", name); ++expand_string_message = string_sprintf("missing { after \"%s\"", opname); + return NULL; + + COND_FAILED_CURLY_END: + expand_string_message = string_sprintf("missing } at end of \"%s\" condition", +- name); ++ opname); + return NULL; + + /* A condition requires code that is not compiled */ +@@ -3120,7 +3141,7 @@ return NULL; + !defined(SUPPORT_CRYPTEQ) || !defined(CYRUS_SASLAUTHD_SOCKET) + COND_FAILED_NOT_COMPILED: + expand_string_message = string_sprintf("support for \"%s\" not compiled", +- name); ++ opname); + return NULL; + #endif + } +@@ -3849,6 +3870,56 @@ return x; + + + ++/************************************************/ ++/* Comparison operation for sort expansion. We need to avoid ++re-expanding the fields being compared, so need a custom routine. ++ ++Arguments: ++ cond_type Comparison operator code ++ leftarg, rightarg Arguments for comparison ++ ++Return true iff (leftarg compare rightarg) ++*/ ++ ++static BOOL ++sortsbefore(int cond_type, BOOL alpha_cond, ++ const uschar * leftarg, const uschar * rightarg) ++{ ++int_eximarith_t l_num, r_num; ++ ++if (!alpha_cond) ++ { ++ l_num = expanded_string_integer(leftarg, FALSE); ++ if (expand_string_message) return FALSE; ++ r_num = expanded_string_integer(rightarg, FALSE); ++ if (expand_string_message) return FALSE; ++ ++ switch (cond_type) ++ { ++ case ECOND_NUM_G: return l_num > r_num; ++ case ECOND_NUM_GE: return l_num >= r_num; ++ case ECOND_NUM_L: return l_num < r_num; ++ case ECOND_NUM_LE: return l_num <= r_num; ++ default: break; ++ } ++ } ++else ++ switch (cond_type) ++ { ++ case ECOND_STR_LT: return Ustrcmp (leftarg, rightarg) < 0; ++ case ECOND_STR_LTI: return strcmpic(leftarg, rightarg) < 0; ++ case ECOND_STR_LE: return Ustrcmp (leftarg, rightarg) <= 0; ++ case ECOND_STR_LEI: return strcmpic(leftarg, rightarg) <= 0; ++ case ECOND_STR_GT: return Ustrcmp (leftarg, rightarg) > 0; ++ case ECOND_STR_GTI: return strcmpic(leftarg, rightarg) > 0; ++ case ECOND_STR_GE: return Ustrcmp (leftarg, rightarg) >= 0; ++ case ECOND_STR_GEI: return strcmpic(leftarg, rightarg) >= 0; ++ default: break; ++ } ++return FALSE; /* should not happen */ ++} ++ ++ + /* Return pointer to dewrapped string, with enclosing specified chars removed. + The given string is modified on return. Leading whitespace is skipped while + looking for the opening wrap character, then the rest is scanned for the trailing +@@ -3905,7 +3976,7 @@ The element may itself be an object or a + Return NULL when the list is empty. + */ + +-uschar * ++static uschar * + json_nextinlist(const uschar ** list) + { + unsigned array_depth = 0, object_depth = 0; +@@ -6243,9 +6314,10 @@ while (*s != 0) + + case EITEM_SORT: + { ++ int cond_type; + int sep = 0; + const uschar *srclist, *cmp, *xtract; +- uschar *srcitem; ++ uschar * opname, * srcitem; + const uschar *dstlist = NULL, *dstkeylist = NULL; + uschar * tmp; + uschar *save_iterate_item = iterate_item; +@@ -6280,6 +6352,25 @@ while (*s != 0) + goto EXPAND_FAILED_CURLY; + } + ++ if ((cond_type = identify_operator(&cmp, &opname)) == -1) ++ { ++ if (!expand_string_message) ++ expand_string_message = string_sprintf("unknown condition \"%s\"", s); ++ goto EXPAND_FAILED; ++ } ++ switch(cond_type) ++ { ++ case ECOND_NUM_L: case ECOND_NUM_LE: ++ case ECOND_NUM_G: case ECOND_NUM_GE: ++ case ECOND_STR_GE: case ECOND_STR_GEI: case ECOND_STR_GT: case ECOND_STR_GTI: ++ case ECOND_STR_LE: case ECOND_STR_LEI: case ECOND_STR_LT: case ECOND_STR_LTI: ++ break; ++ ++ default: ++ expand_string_message = US"comparator not handled for sort"; ++ goto EXPAND_FAILED; ++ } ++ + while (isspace(*s)) s++; + if (*s++ != '{') + { +@@ -6307,11 +6398,10 @@ while (*s != 0) + if (skipping) continue; + + while ((srcitem = string_nextinlist(&srclist, &sep, NULL, 0))) +- { +- uschar * dstitem; ++ { ++ uschar * srcfield, * dstitem; + gstring * newlist = NULL; + gstring * newkeylist = NULL; +- uschar * srcfield; + + DEBUG(D_expand) debug_printf_indent("%s: $item = \"%s\"\n", name, srcitem); + +@@ -6332,25 +6422,15 @@ while (*s != 0) + while ((dstitem = string_nextinlist(&dstlist, &sep, NULL, 0))) + { + uschar * dstfield; +- uschar * expr; +- BOOL before; + + /* field for comparison */ + if (!(dstfield = string_nextinlist(&dstkeylist, &sep, NULL, 0))) + goto sort_mismatch; + +- /* build and run condition string */ +- expr = string_sprintf("%s{%s}{%s}", cmp, srcfield, dstfield); +- +- DEBUG(D_expand) debug_printf_indent("%s: cond = \"%s\"\n", name, expr); +- if (!eval_condition(expr, &resetok, &before)) +- { +- expand_string_message = string_sprintf("comparison in sort: %s", +- expr); +- goto EXPAND_FAILED; +- } ++ /* String-comparator names start with a letter; numeric names do not */ + +- if (before) ++ if (sortsbefore(cond_type, isalpha(opname[0]), ++ srcfield, dstfield)) + { + /* New-item sorts before this dst-item. Append new-item, + then dst-item, then remainder of dst list. */ diff --git a/debian/patches/78_01-string.c-do-not-interpret-before-0-CVE-2019-15846.patch b/debian/patches/78_01-string.c-do-not-interpret-before-0-CVE-2019-15846.patch new file mode 100644 index 0000000..38ba939 --- /dev/null +++ b/debian/patches/78_01-string.c-do-not-interpret-before-0-CVE-2019-15846.patch @@ -0,0 +1,50 @@ +From 2600301ba6dbac5c9d640c87007a07ee6dcea1f4 Mon Sep 17 00:00:00 2001 +From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de> +Date: Mon, 19 Aug 2019 14:45:48 +0200 +Subject: [PATCH] string.c: do not interpret '\\' before '\0' (CVE-2019-15846) + + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -4,6 +4,11 @@ This document describes *changes* to pre + affect Exim's operation, with an unchanged configuration file. For new + options, and new features, see the NewStuff file next to this ChangeLog. + ++Exim version 4.92.2 ++------------------- ++ ++HS/01 Handle trailing backslash gracefully. (CVE-2019-15846) ++ + + Since version 4.92 + ------------------ +--- a/src/string.c ++++ b/src/string.c +@@ -224,6 +224,8 @@ interpreted in strings. + Arguments: + pp points a pointer to the initiating "\" in the string; + the pointer gets updated to point to the final character ++ If the backslash is the last character in the string, it ++ is not interpreted. + Returns: the value of the character escape + */ + +@@ -236,6 +238,7 @@ const uschar *hex_digits= CUS"0123456789 + int ch; + const uschar *p = *pp; + ch = *(++p); ++if (ch == '\0') return **pp; + if (isdigit(ch) && ch != '8' && ch != '9') + { + ch -= '0'; +@@ -1210,8 +1213,8 @@ memcpy(g->s + p, s, count); + g->ptr = p + count; + return g; + } +- +- ++ ++ + gstring * + string_cat(gstring *string, const uschar *s) + { diff --git a/debian/patches/78_02-Fix-buffer-overflow-in-string_vformat.-Bug-2449.patch b/debian/patches/78_02-Fix-buffer-overflow-in-string_vformat.-Bug-2449.patch new file mode 100644 index 0000000..6c27517 --- /dev/null +++ b/debian/patches/78_02-Fix-buffer-overflow-in-string_vformat.-Bug-2449.patch @@ -0,0 +1,36 @@ +From 478effbfd9c3cc5a627fc671d4bf94d13670d65f Mon Sep 17 00:00:00 2001 +From: Jeremy Harris <jgh146exb@wizmail.org> +Date: Fri, 27 Sep 2019 12:21:49 +0100 +Subject: [PATCH] Fix buffer overflow in string_vformat. Bug 2449 + +--- + src/string.c | 4 ++-- + test/scripts/0000-Basic/0214 | 11 +++++++++++ + test/stdout/0214 | 7 +++++++ + 3 files changed, 20 insertions(+), 2 deletions(-) + +diff --git a/src/string.c b/src/string.c +index c6549bf93..3445f8a42 100644 +--- a/src/string.c ++++ b/src/string.c +@@ -1132,7 +1132,7 @@ store_reset(g->s + (g->size = g->ptr + 1)); + Arguments: + g the growable-string + p current end of data +- count amount to grow by ++ count amount to grow by, offset from p + */ + + static void +@@ -1590,7 +1590,7 @@ while (*fp) + } + else if (g->ptr >= lim - width) + { +- gstring_grow(g, g->ptr, width - (lim - g->ptr)); ++ gstring_grow(g, g->ptr, width); + lim = g->size - 1; + gp = CS g->s + g->ptr; + } +-- +2.23.0 + diff --git a/debian/patches/79_01-Fix-SPA-authenticator-checking-client-supplied-data-.patch b/debian/patches/79_01-Fix-SPA-authenticator-checking-client-supplied-data-.patch new file mode 100644 index 0000000..f0688bb --- /dev/null +++ b/debian/patches/79_01-Fix-SPA-authenticator-checking-client-supplied-data-.patch @@ -0,0 +1,74 @@ +From 6a7edbf6608d10ef0c707c426511e667849518d7 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris <jgh146exb@wizmail.org> +Date: Tue, 5 May 2020 21:15:34 +0100 +Subject: [PATCH 1/2] Fix SPA authenticator, checking client-supplied data + before using it. Bug 2571 + +(cherry picked from commit 57aa14b216432be381b6295c312065b2fd034f86) +--- + doc/ChangeLog | 5 +++++ + src/auths/spa.c | 22 ++++++++++++++++------ + 2 files changed, 21 insertions(+), 6 deletions(-) + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -62,6 +62,11 @@ JH/28 Fix the timeout on smtp response t + [from GIT master] + + ++JH/41 Bug 2571: Fix SPA authenticator. Running as a server, an offset supplied ++ by the client was not checked as pointing within response data before ++ being used. A malicious client could thus cause an out-of-bounds read and ++ possibly gain authentication. Fix by adding the check. ++ + + Exim version 4.92 + ----------------- +--- a/src/auths/spa.c ++++ b/src/auths/spa.c +@@ -139,7 +139,7 @@ SPAAuthChallenge challenge; + SPAAuthResponse response; + SPAAuthResponse *responseptr = &response; + uschar msgbuf[2048]; +-uschar *clearpass; ++uschar *clearpass, *s; + + /* send a 334, MS Exchange style, and grab the client's request, + unless we already have it via an initial response. */ +@@ -197,6 +197,13 @@ that causes failure if the size of msgbu + char *p = ((char*)responseptr) + IVAL(&responseptr->uUser.offset,0); + int len = SVAL(&responseptr->uUser.len,0)/2; + ++ if (p + len*2 >= CS (responseptr+1)) ++ { ++ DEBUG(D_auth) ++ debug_printf("auth_spa_server(): bad uUser spec in response\n"); ++ return FAIL; ++ } ++ + if (len + 1 >= sizeof(msgbuf)) return FAIL; + for (i = 0; i < len; ++i) + { +@@ -245,14 +252,17 @@ spa_smb_nt_encrypt (clearpass, challenge + + /* compare NT hash (LM may not be available) */ + +-if (memcmp(ntRespData, +- ((unsigned char*)responseptr)+IVAL(&responseptr->ntResponse.offset,0), +- 24) == 0) +- /* success. we have a winner. */ ++s = (US responseptr) + IVAL(&responseptr->ntResponse.offset,0); ++if (s + 24 >= US (responseptr+1)) + { +- return auth_check_serv_cond(ablock); ++ DEBUG(D_auth) ++ debug_printf("auth_spa_server(): bad ntRespData spec in response\n"); ++ return FAIL; + } + ++if (memcmp(ntRespData, s, 24) == 0) ++ return auth_check_serv_cond(ablock); /* success. we have a winner. */ ++ + /* Expand server_condition as an authorization check (PH) */ + + return FAIL; diff --git a/debian/patches/79_02-Rework-SPA-fix-to-avoid-overflows.-Bug-2571.patch b/debian/patches/79_02-Rework-SPA-fix-to-avoid-overflows.-Bug-2571.patch new file mode 100644 index 0000000..5bc18e3 --- /dev/null +++ b/debian/patches/79_02-Rework-SPA-fix-to-avoid-overflows.-Bug-2571.patch @@ -0,0 +1,59 @@ +From 5a41d2c2cd2b28a0d1aea21edeaea02bd6db4984 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris <jgh146exb@wizmail.org> +Date: Wed, 6 May 2020 22:31:25 +0100 +Subject: [PATCH 2/2] Rework SPA fix to avoid overflows. Bug 2571 + +Amends: 6a7edbf660 +(cherry picked from commit a04174dc2a84ae1008c23b6a7109e7fa3fb7b8b0) +--- + src/auths/spa.c | 13 +++++++++---- + 1 file changed, 9 insertions(+), 4 deletions(-) + +diff --git a/src/auths/spa.c b/src/auths/spa.c +index ed9aff23b..4e3aef808 100644 +--- a/src/auths/spa.c ++++ b/src/auths/spa.c +@@ -140,6 +140,7 @@ SPAAuthResponse response; + SPAAuthResponse *responseptr = &response; + uschar msgbuf[2048]; + uschar *clearpass, *s; ++unsigned off; + + /* send a 334, MS Exchange style, and grab the client's request, + unless we already have it via an initial response. */ +@@ -194,10 +195,13 @@ that causes failure if the size of msgbuf is exceeded. ****/ + + { + int i; +- char *p = ((char*)responseptr) + IVAL(&responseptr->uUser.offset,0); ++ char * p; + int len = SVAL(&responseptr->uUser.len,0)/2; + +- if (p + len*2 >= CS (responseptr+1)) ++ if ( (off = IVAL(&responseptr->uUser.offset,0)) >= sizeof(SPAAuthResponse) ++ || len >= sizeof(responseptr->buffer)/2 ++ || (p = (CS responseptr) + off) + len*2 >= CS (responseptr+1) ++ ) + { + DEBUG(D_auth) + debug_printf("auth_spa_server(): bad uUser spec in response\n"); +@@ -252,13 +256,14 @@ spa_smb_nt_encrypt (clearpass, challenge.challengeData, ntRespData); + + /* compare NT hash (LM may not be available) */ + +-s = (US responseptr) + IVAL(&responseptr->ntResponse.offset,0); +-if (s + 24 >= US (responseptr+1)) ++off = IVAL(&responseptr->ntResponse.offset,0); ++if (off >= sizeof(SPAAuthResponse) - 24) + { + DEBUG(D_auth) + debug_printf("auth_spa_server(): bad ntRespData spec in response\n"); + return FAIL; + } ++s = (US responseptr) + off; + + if (memcmp(ntRespData, s, 24) == 0) + return auth_check_serv_cond(ablock); /* success. we have a winner. */ +-- +2.26.2 + diff --git a/debian/patches/80_01-GnuTLS-fix-hanging-callout-connections.patch b/debian/patches/80_01-GnuTLS-fix-hanging-callout-connections.patch new file mode 100644 index 0000000..0f36d73 --- /dev/null +++ b/debian/patches/80_01-GnuTLS-fix-hanging-callout-connections.patch @@ -0,0 +1,83 @@ +From 97c5e07c220b55d1c506a1798c9ce3ae3105adea Mon Sep 17 00:00:00 2001 +From: Jeremy Harris <jgh146exb@wizmail.org> +Date: Thu, 13 Feb 2020 16:45:38 +0000 +Subject: [PATCH 4/6] GnuTLS: fix hanging callout connections + +Broken-by: 925ac8e4f1 +(cherry picked from commit bd95ffc2ba87fbd3c752df17bc8fd9c01586d45a) +--- + doc/ChangeLog | 81 ++++--------------------------------------- + src/tls-gnu.c | 24 +++++++------ + 2 files changed, 20 insertions(+), 85 deletions(-) + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -67,6 +67,11 @@ JH/41 Bug 2571: Fix SPA authenticator. + being used. A malicious client could thus cause an out-of-bounds read and + possibly gain authentication. Fix by adding the check. + ++JH/25 Fix use of concurrent TLS connections under GnuTLS. When a callout was ++ done during a receiving connection, and both used TLS, global info was ++ used rather than per-connection info for tracking the state of data ++ queued for transmission. This could result in a connection hang. ++ + + Exim version 4.92 + ----------------- +--- a/src/tls-gnu.c ++++ b/src/tls-gnu.c +@@ -124,10 +124,17 @@ typedef struct exim_gnutls_state { + enum peer_verify_requirement verify_requirement; + int fd_in; + int fd_out; +- BOOL peer_cert_verified; +- BOOL peer_dane_verified; +- BOOL trigger_sni_changes; +- BOOL have_set_peerdn; ++ ++ BOOL peer_cert_verified:1; ++ BOOL peer_dane_verified:1; ++ BOOL trigger_sni_changes:1; ++ BOOL have_set_peerdn:1; ++ BOOL xfer_eof:1; /*XXX never gets set! */ ++ BOOL xfer_error:1; ++#ifdef SUPPORT_CORK ++ BOOL corked:1; ++#endif ++ + const struct host_item *host; /* NULL if server */ + gnutls_x509_crt_t peercert; + uschar *peerdn; +@@ -160,8 +167,6 @@ typedef struct exim_gnutls_state { + uschar *xfer_buffer; + int xfer_buffer_lwm; + int xfer_buffer_hwm; +- BOOL xfer_eof; /*XXX never gets set! */ +- BOOL xfer_error; + } exim_gnutls_state_st; + + static const exim_gnutls_state_st exim_gnutls_state_init = { +@@ -2790,9 +2795,8 @@ ssize_t outbytes; + size_t left = len; + exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server; + #ifdef SUPPORT_CORK +-static BOOL corked = FALSE; + +-if (more && !corked) gnutls_record_cork(state->session); ++if (more && !state->corked) gnutls_record_cork(state->session); + #endif + + DEBUG(D_tls) debug_printf("%s(%p, " SIZE_T_FMT "%s)\n", __FUNCTION__, +@@ -2833,10 +2837,10 @@ if (len > INT_MAX) + } + + #ifdef SUPPORT_CORK +-if (more != corked) ++if (more != state->corked) + { + if (!more) (void) gnutls_record_uncork(state->session, 0); +- corked = more; ++ state->corked = more; + } + #endif + diff --git a/debian/patches/80_02-GnuTLS-tls_write-wait-after-uncorking-the-session.patch b/debian/patches/80_02-GnuTLS-tls_write-wait-after-uncorking-the-session.patch new file mode 100644 index 0000000..9998618 --- /dev/null +++ b/debian/patches/80_02-GnuTLS-tls_write-wait-after-uncorking-the-session.patch @@ -0,0 +1,73 @@ +From 783cb0301d9ceef2748956c3f91762275b7b45e5 Mon Sep 17 00:00:00 2001 +From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de> +Date: Tue, 18 Feb 2020 18:59:49 +0100 +Subject: [PATCH 5/6] GnuTLS: tls_write(): wait after uncorking the session + +(cherry picked from commit 8f9adfd36222d4e9e730734e00dffe874073e5b4) +--- + src/tls-gnu.c | 34 ++++++++++++++++++++++++++++------ + 1 file changed, 28 insertions(+), 6 deletions(-) + +diff --git a/src/tls-gnu.c b/src/tls-gnu.c +index 822ad89c6..94a718673 100644 +--- a/src/tls-gnu.c ++++ b/src/tls-gnu.c +@@ -2835,9 +2835,14 @@ tls_write(void * ct_ctx, const uschar * buff, size_t len, BOOL more) + ssize_t outbytes; + size_t left = len; + exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server; +-#ifdef SUPPORT_CORK + +-if (more && !state->corked) gnutls_record_cork(state->session); ++#ifdef SUPPORT_CORK ++if (more && !state->corked) ++ { ++ DEBUG(D_tls) debug_printf("gnutls_record_cork(session=%p)\n", state->session); ++ gnutls_record_cork(state->session); ++ state->corked = TRUE; ++ } + #endif + + DEBUG(D_tls) debug_printf("%s(%p, " SIZE_T_FMT "%s)\n", __FUNCTION__, +@@ -2853,6 +2858,7 @@ while (left > 0) + while (outbytes == GNUTLS_E_AGAIN); + + DEBUG(D_tls) debug_printf("outbytes=" SSIZE_T_FMT "\n", outbytes); ++ + if (outbytes < 0) + { + DEBUG(D_tls) debug_printf("%s: gnutls_record_send err\n", __FUNCTION__); +@@ -2878,10 +2884,26 @@ if (len > INT_MAX) + } + + #ifdef SUPPORT_CORK +-if (more != state->corked) +- { +- if (!more) (void) gnutls_record_uncork(state->session, 0); +- state->corked = more; ++if (!more && state->corked) ++ { ++ DEBUG(D_tls) debug_printf("gnutls_record_uncork(session=%p)\n", state->session); ++ do { ++ do ++ /* We can't use GNUTLS_RECORD_WAIT here, as it retries on ++ GNUTLS_E_AGAIN || GNUTLS_E_INTR, which would break our timeout set by alarm(). ++ The GNUTLS_E_AGAIN should not happen ever, as our sockets are blocking anyway. ++ But who knows. (That all relies on the fact that GNUTLS_E_INTR and GNUTLS_E_AGAIN ++ match the EINTR and EAGAIN errno values.) */ ++ outbytes = gnutls_record_uncork(state->session, 0); ++ while (outbytes == GNUTLS_E_AGAIN); ++ ++ if (outbytes < 0) ++ { ++ record_io_error(state, len, US"uncork", NULL); ++ return -1; ++ } ++ } while (gnutls_record_check_corked(state->session) > 0); ++ state->corked = FALSE; + } + #endif + +-- +2.28.0 + diff --git a/debian/patches/80_03-GnuTLS-Do-not-care-about-corked-data-when-uncorking.patch b/debian/patches/80_03-GnuTLS-Do-not-care-about-corked-data-when-uncorking.patch new file mode 100644 index 0000000..da8748f --- /dev/null +++ b/debian/patches/80_03-GnuTLS-Do-not-care-about-corked-data-when-uncorking.patch @@ -0,0 +1,55 @@ +From 3afb07f2c63fb6dc3983b28e7cdaf11fceb741d1 Mon Sep 17 00:00:00 2001 +From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de> +Date: Mon, 2 Mar 2020 22:56:32 +0100 +Subject: [PATCH 6/6] GnuTLS: Do not care about corked data when uncorking + +(cherry picked from commit d8d7e3a4162b52382daf8319f221c085c76c5b8f) +--- + src/tls-gnu.c | 31 +++++++++++++++---------------- + 1 file changed, 15 insertions(+), 16 deletions(-) + +diff --git a/src/tls-gnu.c b/src/tls-gnu.c +index 94a718673..2091e44db 100644 +--- a/src/tls-gnu.c ++++ b/src/tls-gnu.c +@@ -2887,22 +2887,21 @@ if (len > INT_MAX) + if (!more && state->corked) + { + DEBUG(D_tls) debug_printf("gnutls_record_uncork(session=%p)\n", state->session); +- do { +- do +- /* We can't use GNUTLS_RECORD_WAIT here, as it retries on +- GNUTLS_E_AGAIN || GNUTLS_E_INTR, which would break our timeout set by alarm(). +- The GNUTLS_E_AGAIN should not happen ever, as our sockets are blocking anyway. +- But who knows. (That all relies on the fact that GNUTLS_E_INTR and GNUTLS_E_AGAIN +- match the EINTR and EAGAIN errno values.) */ +- outbytes = gnutls_record_uncork(state->session, 0); +- while (outbytes == GNUTLS_E_AGAIN); +- +- if (outbytes < 0) +- { +- record_io_error(state, len, US"uncork", NULL); +- return -1; +- } +- } while (gnutls_record_check_corked(state->session) > 0); ++ do ++ /* We can't use GNUTLS_RECORD_WAIT here, as it retries on ++ GNUTLS_E_AGAIN || GNUTLS_E_INTR, which would break our timeout set by alarm(). ++ The GNUTLS_E_AGAIN should not happen ever, as our sockets are blocking anyway. ++ But who knows. (That all relies on the fact that GNUTLS_E_INTR and GNUTLS_E_AGAIN ++ match the EINTR and EAGAIN errno values.) */ ++ outbytes = gnutls_record_uncork(state->session, 0); ++ while (outbytes == GNUTLS_E_AGAIN); ++ ++ if (outbytes < 0) ++ { ++ record_io_error(state, len, US"uncork", NULL); ++ return -1; ++ } ++ + state->corked = FALSE; + } + #endif +-- +2.28.0 + diff --git a/debian/patches/82_TLS-use-RFC-6125-rules-for-certifucate-name-checks-w.patch b/debian/patches/82_TLS-use-RFC-6125-rules-for-certifucate-name-checks-w.patch new file mode 100644 index 0000000..10e54e3 --- /dev/null +++ b/debian/patches/82_TLS-use-RFC-6125-rules-for-certifucate-name-checks-w.patch @@ -0,0 +1,188 @@ +Description: TLS: use RFC 6125 rules for certificate name checks when + CNAMES are present. Bug 2594 +Origin: upstream https://git.exim.org/exim.git/commit/0851a3bbf4667081d47f5d85b6b3a5cb33cbdba6 +Bug: https://bugs.exim.org/show_bug.cgi?id=2594 +Forwarded: not-needed +Last-Update: 2021-03-02 + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -41,10 +41,15 @@ JH/10 OpenSSL: Fix aggregation of messag + + JH/11 Harden plaintext authenticator against a badly misconfigured client-send + string. Previously it was possible to cause undefined behaviour in a + library routine (usually a crash). Found by "zerons". + ++JH/06 Bug 2594: Change the name used for certificate name checks in the smtp ++ transport. Previously it was the name on the DNS A-record; use instead ++ the head of the CNAME chain leading there (if there is one). This seems ++ to align better with RFC 6125. ++ + + JH/18 GnuTLS: fix $tls_out_ocsp under hosts_request_ocsp. Previously the + verification result was not updated unless hosts_require_ocsp applied. + + JH/20 Bug 2389: fix server advertising of usable certificates, under GnuTLS in +--- a/src/host.c ++++ b/src/host.c +@@ -1966,10 +1966,17 @@ host_item *last = NULL; + BOOL temp_error = FALSE; + #if HAVE_IPV6 + int af; + #endif + ++#ifndef DISABLE_TLS ++/* Copy the host name at this point to the value which is used for ++TLS certificate name checking, before anything modifies it. */ ++ ++host->certname = host->name; ++#endif ++ + /* Make sure DNS options are set as required. This appears to be necessary in + some circumstances when the get..byname() function actually calls the DNS. */ + + dns_init((flags & HOST_FIND_QUALIFY_SINGLE) != 0, + (flags & HOST_FIND_SEARCH_PARENTS) != 0, +@@ -2132,10 +2139,13 @@ for (i = 1; i <= times; + + else + { + host_item *next = store_get(sizeof(host_item)); + next->name = host->name; ++#ifndef DISABLE_TLS ++ next->certname = host->certname; ++#endif + next->mx = host->mx; + next->address = text_address; + next->port = PORT_NONE; + next->status = hstatus_unknown; + next->why = hwhy_unknown; +@@ -2150,16 +2160,16 @@ for (i = 1; i <= times; + + /* If no hosts were found, the address field in the original host block will be + NULL. If temp_error is set, at least one of the lookups gave a temporary error, + so we pass that back. */ + +-if (host->address == NULL) ++if (!host->address) + { + uschar *msg = + #ifndef STAND_ALONE +- (message_id[0] == 0 && smtp_in != NULL)? +- string_sprintf("no IP address found for host %s (during %s)", host->name, ++ message_id[0] == 0 && smtp_in ++ ? string_sprintf("no IP address found for host %s (during %s)", host->name, + smtp_get_connection_info()) : + #endif + string_sprintf("no IP address found for host %s", host->name); + + HDEBUG(D_host_lookup) debug_printf("%s\n", msg); +@@ -2277,10 +2287,17 @@ dns_record *rr; + host_item *thishostlast = NULL; /* Indicates not yet filled in anything */ + BOOL v6_find_again = FALSE; + BOOL dnssec_fail = FALSE; + int i; + ++#ifndef DISABLE_TLS ++/* Copy the host name at this point to the value which is used for ++TLS certificate name checking, before any CNAME-following modifies it. */ ++ ++host->certname = host->name; ++#endif ++ + /* If allow_ip is set, a name which is an IP address returns that value + as its address. This is used for MX records when allow_mx_to_ip is set, for + those sites that feel they have to flaunt the RFC rules. */ + + if (allow_ip && string_is_ip_address(host->name, NULL) != 0) +--- a/src/structs.h ++++ b/src/structs.h +@@ -77,18 +77,21 @@ host addresses is done using this struct + + typedef enum {DS_UNK=-1, DS_NO, DS_YES} dnssec_status_t; + + typedef struct host_item { + struct host_item *next; +- const uschar *name; /* Host name */ +- const uschar *address; /* IP address in text form */ +- int port; /* port value in host order (if SRV lookup) */ +- int mx; /* MX value if found via MX records */ +- int sort_key; /* MX*1000 plus random "fraction" */ +- int status; /* Usable, unusable, or unknown */ +- int why; /* Why host is unusable */ +- int last_try; /* Time of last try if known */ ++ const uschar *name; /* Host name */ ++#ifndef DISABLE_TLS ++ const uschar *certname; /* Name used for certificate checks */ ++#endif ++ const uschar *address; /* IP address in text form */ ++ int port; /* port value in host order (if SRV lookup) */ ++ int mx; /* MX value if found via MX records */ ++ int sort_key; /* MX*1000 plus random "fraction" */ ++ int status; /* Usable, unusable, or unknown */ ++ int why; /* Why host is unusable */ ++ int last_try; /* Time of last try if known */ + dnssec_status_t dnssec; + } host_item; + + /* Chain of rewrite rules, read from the rewrite config, or parsed from the + rewrite_headers field of a transport. */ +--- a/src/tls-gnu.c ++++ b/src/tls-gnu.c +@@ -2191,13 +2191,13 @@ tls_client_setup_hostname_checks(host_it + { + if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK) + { + state->exp_tls_verify_cert_hostnames = + #ifdef SUPPORT_I18N +- string_domain_utf8_to_alabel(host->name, NULL); ++ string_domain_utf8_to_alabel(host->certname, NULL); + #else +- host->name; ++ host->certname; + #endif + DEBUG(D_tls) + debug_printf("TLS: server cert verification includes hostname: \"%s\".\n", + state->exp_tls_verify_cert_hostnames); + } +--- a/src/tls-openssl.c ++++ b/src/tls-openssl.c +@@ -309,18 +309,18 @@ typedef struct tls_ext_ctx_cb { + X509_STORE *verify_store; /* non-null if status requested */ + BOOL verify_required; + } client; + } u_ocsp; + #endif +- uschar *dhparam; ++ uschar * dhparam; + /* these are cached from first expand */ +- uschar *server_cipher_list; ++ uschar * server_cipher_list; + /* only passed down to tls_error: */ +- host_item *host; ++ host_item * host; + const uschar * verify_cert_hostnames; + #ifndef DISABLE_EVENT +- uschar * event_action; ++ uschar * event_action; + #endif + } tls_ext_ctx_cb; + + /* should figure out a cleanup of API to handle state preserved per + implementation, for various reasons, which can be void * in the APIs. +@@ -2359,13 +2359,13 @@ if ((rc = setup_certs(ctx, ob->tls_verif + + if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK) + { + cbinfo->verify_cert_hostnames = + #ifdef SUPPORT_I18N +- string_domain_utf8_to_alabel(host->name, NULL); ++ string_domain_utf8_to_alabel(host->certname, NULL); + #else +- host->name; ++ host->certname; + #endif + DEBUG(D_tls) debug_printf("Cert hostname to check: \"%s\"\n", + cbinfo->verify_cert_hostnames); + } + return OK; diff --git a/debian/patches/84_01-CVE-2020-28025-Heap-out-of-bounds-read-in-pdkim_fini.patch b/debian/patches/84_01-CVE-2020-28025-Heap-out-of-bounds-read-in-pdkim_fini.patch new file mode 100644 index 0000000..7c79753 --- /dev/null +++ b/debian/patches/84_01-CVE-2020-28025-Heap-out-of-bounds-read-in-pdkim_fini.patch @@ -0,0 +1,44 @@ +From 9db12ffa00aa1dcbe60eec543307f405e35cfe15 Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory <qsa@qualys.com> +Date: Sun, 21 Feb 2021 18:54:16 -0800 +Subject: [PATCH 01/29] CVE-2020-28025: Heap out-of-bounds read in + pdkim_finish_bodyhash() + +--- + src/pdkim/pdkim.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/src/pdkim/pdkim.c b/src/pdkim/pdkim.c +index 594af03c5..e203311da 100644 +--- a/src/pdkim/pdkim.c ++++ b/src/pdkim/pdkim.c +@@ -825,7 +825,7 @@ for (sig = ctx->sig; sig; sig = sig->next) + /* VERIFICATION --------------------------------------------------------- */ + /* Be careful that the header sig included a bodyash */ + +- if ( sig->bodyhash.data ++ if (sig->bodyhash.data && sig->bodyhash.len == b->bh.len + && memcmp(b->bh.data, sig->bodyhash.data, b->bh.len) == 0) + { + DEBUG(D_acl) debug_printf("PDKIM [%s] Body hash compared OK\n", sig->domain); +@@ -1524,7 +1524,7 @@ for (sig = ctx->sig; sig; sig = sig->next) + do this hash incrementally. + We don't need the hash we're calculating here for the GnuTLS and OpenSSL + cases of RSA signing, since those library routines can do hash-and-sign. +- ++ + Some time in the future we could easily avoid doing the hash here for those + cases (which will be common for a long while. We could also change from + the current copy-all-the-headers-into-one-block, then call the hash-and-sign +@@ -1779,7 +1779,7 @@ for (sig = ctx->sig; sig; sig = sig->next) + ); + goto NEXT_VERIFY; + } +- ++ + /* Make sure sig uses supported DKIM version (only v1) */ + if (sig->version != 1) + { +-- +2.30.2 + diff --git a/debian/patches/84_02-CVE-2020-28018-Use-after-free-in-tls-openssl.c.patch b/debian/patches/84_02-CVE-2020-28018-Use-after-free-in-tls-openssl.c.patch new file mode 100644 index 0000000..3a488b3 --- /dev/null +++ b/debian/patches/84_02-CVE-2020-28018-Use-after-free-in-tls-openssl.c.patch @@ -0,0 +1,33 @@ +From 86cafc842feb6223476568921c2d3e06c706cc31 Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory <qsa@qualys.com> +Date: Sun, 21 Feb 2021 19:05:56 -0800 +Subject: [PATCH 02/29] CVE-2020-28018: Use-after-free in tls-openssl.c + +--- + src/tls-openssl.c | 4 ---- + 1 file changed, 4 deletions(-) + +diff --git a/src/tls-openssl.c b/src/tls-openssl.c +index e751edd9a..2a8d4cabd 100644 +--- a/src/tls-openssl.c ++++ b/src/tls-openssl.c +@@ -2910,16 +2910,12 @@ a store reset there, so use POOL_PERM. */ + + if (!ct_ctx && (more || corked)) + { +-#ifdef EXPERIMENTAL_PIPE_CONNECT + int save_pool = store_pool; + store_pool = POOL_PERM; +-#endif + + corked = string_catn(corked, buff, len); + +-#ifdef EXPERIMENTAL_PIPE_CONNECT + store_pool = save_pool; +-#endif + + if (more) + { +-- +2.30.2 + diff --git a/debian/patches/84_03-CVE-2020-28023-Out-of-bounds-read-in-smtp_setup_msg.patch b/debian/patches/84_03-CVE-2020-28023-Out-of-bounds-read-in-smtp_setup_msg.patch new file mode 100644 index 0000000..28af9cc --- /dev/null +++ b/debian/patches/84_03-CVE-2020-28023-Out-of-bounds-read-in-smtp_setup_msg.patch @@ -0,0 +1,58 @@ +From 4cfadd994e5ab6e57cc43164d1e3198bb4faedbb Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory <qsa@qualys.com> +Date: Sun, 21 Feb 2021 19:11:55 -0800 +Subject: [PATCH 03/29] CVE-2020-28023: Out-of-bounds read in smtp_setup_msg() + +Extracted from Jeremy Harris's commit afaf5a50. +--- + src/acl.c | 3 ++- + src/macros.h | 1 + + src/smtp_in.c | 4 ++-- + 3 files changed, 5 insertions(+), 3 deletions(-) + +diff --git a/src/acl.c b/src/acl.c +index f3b860e4a..49f6fe79c 100644 +--- a/src/acl.c ++++ b/src/acl.c +@@ -4464,7 +4464,8 @@ switch (where) + /* Drop cutthrough conns, and drop heldopen verify conns if + the previous was not DATA */ + { +- uschar prev = smtp_connection_had[smtp_ch_index-2]; ++ uschar prev = ++ smtp_connection_had[SMTP_HBUFF_PREV(SMTP_HBUFF_PREV(smtp_ch_index))]; + BOOL dropverify = !(prev == SCH_DATA || prev == SCH_BDAT); + + cancel_cutthrough_connection(dropverify, US"quit or conndrop"); +diff --git a/src/macros.h b/src/macros.h +index 0f93543ce..b3896b736 100644 +--- a/src/macros.h ++++ b/src/macros.h +@@ -154,6 +154,7 @@ enough to hold all the headers from a normal kind of message. */ + /* The size of the circular buffer that remembers recent SMTP commands */ + + #define SMTP_HBUFF_SIZE 20 ++#define SMTP_HBUFF_PREV(n) ((n) ? (n)-1 : SMTP_HBUFF_SIZE-1) + + /* The initial size of a big buffer for use in various places. It gets put + into big_buffer_size and in some circumstances increased. It should be at least +diff --git a/src/smtp_in.c b/src/smtp_in.c +index 86f87eae1..4265d77b7 100644 +--- a/src/smtp_in.c ++++ b/src/smtp_in.c +@@ -5322,10 +5322,10 @@ while (done <= 0) + } + if (f.smtp_in_pipelining_advertised && last_was_rcpt) + smtp_printf("503 Valid RCPT command must precede %s\r\n", FALSE, +- smtp_names[smtp_connection_had[smtp_ch_index-1]]); ++ smtp_names[smtp_connection_had[SMTP_HBUFF_PREV(smtp_ch_index)]]); + else + done = synprot_error(L_smtp_protocol_error, 503, NULL, +- smtp_connection_had[smtp_ch_index-1] == SCH_DATA ++ smtp_connection_had[SMTP_HBUFF_PREV(smtp_ch_index)] == SCH_DATA + ? US"valid RCPT command must precede DATA" + : US"valid RCPT command must precede BDAT"); + +-- +2.30.2 + diff --git a/debian/patches/84_04-CVE-2020-28010-Heap-out-of-bounds-write-in-main.patch b/debian/patches/84_04-CVE-2020-28010-Heap-out-of-bounds-write-in-main.patch new file mode 100644 index 0000000..9e3d368 --- /dev/null +++ b/debian/patches/84_04-CVE-2020-28010-Heap-out-of-bounds-write-in-main.patch @@ -0,0 +1,42 @@ +From 5987d0dfe88ee6081b72857bc8085c7d2afd53a3 Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory <qsa@qualys.com> +Date: Sun, 21 Feb 2021 19:17:32 -0800 +Subject: [PATCH 04/29] CVE-2020-28010: Heap out-of-bounds write in main() + +Based on Phil Pennock's commit 0f57feb4. +--- + src/exim.c | 11 ++++++----- + 1 file changed, 6 insertions(+), 5 deletions(-) + +diff --git a/src/exim.c b/src/exim.c +index 83b5ef51f..a7dc48c4e 100644 +--- a/src/exim.c ++++ b/src/exim.c +@@ -3664,6 +3664,9 @@ during readconf_main() some expansion takes place already. */ + /* Store the initial cwd before we change directories. Can be NULL if the + dir has already been unlinked. */ + initial_cwd = os_getcwd(NULL, 0); ++if (initial_cwd && strlen(CCS initial_cwd) >= BIG_BUFFER_SIZE) { ++ exim_fail("exim: initial cwd is far too long\n"); ++} + + /* checking: + -be[m] expansion test - +@@ -3950,11 +3953,9 @@ if ( (debug_selector & D_any || LOGGING(arguments)) + p += 13; + else + { +- Ustrncpy(p + 4, initial_cwd, big_buffer_size-5); +- p += 4 + Ustrlen(initial_cwd); +- /* in case p is near the end and we don't provide enough space for +- * string_format to be willing to write. */ +- *p = '\0'; ++ p += 4; ++ snprintf(CS p, big_buffer_size - (p - big_buffer), "%s", CCS initial_cwd); ++ p += strlen(CCS p); + } + + (void)string_format(p, big_buffer_size - (p - big_buffer), " %d args:", argc); +-- +2.30.2 + diff --git a/debian/patches/84_05-CVE-2020-28011-Heap-buffer-overflow-in-queue_run.patch b/debian/patches/84_05-CVE-2020-28011-Heap-buffer-overflow-in-queue_run.patch new file mode 100644 index 0000000..086644b --- /dev/null +++ b/debian/patches/84_05-CVE-2020-28011-Heap-buffer-overflow-in-queue_run.patch @@ -0,0 +1,39 @@ +From 9970ba4d8b9477d98c722221b6b7b97f03104b9f Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory <qsa@qualys.com> +Date: Sun, 21 Feb 2021 19:22:33 -0800 +Subject: [PATCH 05/29] CVE-2020-28011: Heap buffer overflow in queue_run() + +--- + src/queue.c | 14 ++++++++++---- + 1 file changed, 10 insertions(+), 4 deletions(-) + +diff --git a/src/queue.c b/src/queue.c +index 92109ef92..41af5b85e 100644 +--- a/src/queue.c ++++ b/src/queue.c +@@ -416,12 +416,18 @@ if (!recurse) + p += sprintf(CS p, " -q%s", extras); + + if (deliver_selectstring) +- p += sprintf(CS p, " -R%s %s", f.deliver_selectstring_regex? "r" : "", +- deliver_selectstring); ++ { ++ snprintf(CS p, big_buffer_size - (p - big_buffer), " -R%s %s", ++ f.deliver_selectstring_regex? "r" : "", deliver_selectstring); ++ p += strlen(CCS p); ++ } + + if (deliver_selectstring_sender) +- p += sprintf(CS p, " -S%s %s", f.deliver_selectstring_sender_regex? "r" : "", +- deliver_selectstring_sender); ++ { ++ snprintf(CS p, big_buffer_size - (p - big_buffer), " -S%s %s", ++ f.deliver_selectstring_sender_regex? "r" : "", deliver_selectstring_sender); ++ p += strlen(CCS p); ++ } + + log_detail = string_copy(big_buffer); + if (*queue_name) +-- +2.30.2 + diff --git a/debian/patches/84_06-CVE-2020-28013-Heap-buffer-overflow-in-parse_fix_phr.patch b/debian/patches/84_06-CVE-2020-28013-Heap-buffer-overflow-in-parse_fix_phr.patch new file mode 100644 index 0000000..6acdecc --- /dev/null +++ b/debian/patches/84_06-CVE-2020-28013-Heap-buffer-overflow-in-parse_fix_phr.patch @@ -0,0 +1,34 @@ +From 0f6c3d3f7efb5d66dabf69c36e06912d89ff96fc Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory <qsa@qualys.com> +Date: Sun, 21 Feb 2021 19:28:28 -0800 +Subject: [PATCH 06/29] CVE-2020-28013: Heap buffer overflow in + parse_fix_phrase() + +Based on Phil Pennock's commit 8a50c88a. +--- + src/parse.c | 9 ++++++--- + 1 file changed, 6 insertions(+), 3 deletions(-) + +diff --git a/src/parse.c b/src/parse.c +index 4b0efa0e1..e1e2e7358 100644 +--- a/src/parse.c ++++ b/src/parse.c +@@ -1149,9 +1149,12 @@ while (s < end) + { + if (ss >= end) ss--; + *t++ = '('; +- Ustrncpy(t, s, ss-s); +- t += ss-s; +- s = ss; ++ if (ss > s) ++ { ++ Ustrncpy(t, s, ss-s); ++ t += ss-s; ++ s = ss; ++ } + } + } + +-- +2.30.2 + diff --git a/debian/patches/84_07-Security-Refuse-negative-and-large-store-allocations.patch b/debian/patches/84_07-Security-Refuse-negative-and-large-store-allocations.patch new file mode 100644 index 0000000..53b4492 --- /dev/null +++ b/debian/patches/84_07-Security-Refuse-negative-and-large-store-allocations.patch @@ -0,0 +1,74 @@ +From 5fd6af5815401dc60e8fe4309258911aa41d3013 Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory <qsa@qualys.com> +Date: Sun, 21 Feb 2021 19:40:21 -0800 +Subject: [PATCH 07/29] Security: Refuse negative and large store allocations + +Based on Phil Pennock's commits b34d3046 and e6c1606a. +--- + src/store.c | 29 ++++++++++++++++++++++++++++- + 1 file changed, 28 insertions(+), 1 deletion(-) + +diff --git a/src/store.c b/src/store.c +index b52799132..a2a80f631 100644 +--- a/src/store.c ++++ b/src/store.c +@@ -128,6 +128,12 @@ Returns: pointer to store (panic on malloc failure) + void * + store_get_3(int size, const char *filename, int linenumber) + { ++if (size < 0 || size > INT_MAX/2) ++ { ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, ++ "bad memory allocation requested (%d bytes)", ++ size); ++ } + /* Round up the size to a multiple of the alignment. Although this looks a + messy statement, because "alignment" is a constant expression, the compiler can + do a reasonable job of optimizing, especially if the value of "alignment" is a +@@ -270,6 +276,13 @@ store_extend_3(void *ptr, int oldsize, int newsize, const char *filename, + int inc = newsize - oldsize; + int rounded_oldsize = oldsize; + ++if (oldsize < 0 || newsize < oldsize || newsize >= INT_MAX/2) ++ { ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, ++ "bad memory extension requested (%d -> %d bytes)", ++ oldsize, newsize); ++ } ++ + if (rounded_oldsize % alignment != 0) + rounded_oldsize += alignment - (rounded_oldsize % alignment); + +@@ -508,7 +521,16 @@ store_newblock_3(void * block, int newsize, int len, + const char * filename, int linenumber) + { + BOOL release_ok = store_last_get[store_pool] == block; +-uschar * newtext = store_get(newsize); ++uschar * newtext; ++ ++if (len < 0 || len > newsize) ++ { ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, ++ "bad memory extension requested (%d -> %d bytes)", ++ len, newsize); ++ } ++ ++newtext = store_get(newsize); + + memcpy(newtext, block, len); + if (release_ok) store_release_3(block, filename, linenumber); +@@ -539,6 +561,11 @@ store_malloc_3(int size, const char *filename, int linenumber) + { + void *yield; + ++if (size < 0 || size >= INT_MAX/2) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, ++ "bad memory allocation requested (%d bytes)", ++ size); ++ + if (size < 16) size = 16; + + if (!(yield = malloc((size_t)size))) +-- +2.30.2 + diff --git a/debian/patches/84_08-CVE-2020-28017-Integer-overflow-in-receive_add_recip.patch b/debian/patches/84_08-CVE-2020-28017-Integer-overflow-in-receive_add_recip.patch new file mode 100644 index 0000000..d621b70 --- /dev/null +++ b/debian/patches/84_08-CVE-2020-28017-Integer-overflow-in-receive_add_recip.patch @@ -0,0 +1,49 @@ +From b5052a65ed1ba81269ac5a03b4505aa9d55ce084 Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory <qsa@qualys.com> +Date: Sun, 21 Feb 2021 19:46:55 -0800 +Subject: [PATCH 08/29] CVE-2020-28017: Integer overflow in + receive_add_recipient() + +Based on Phil Pennock's commit e3b441f7. +--- + src/receive.c | 10 ++++++++-- + 1 file changed, 8 insertions(+), 2 deletions(-) + +diff --git a/src/receive.c b/src/receive.c +index a0467e8c8..227ace084 100644 +--- a/src/receive.c ++++ b/src/receive.c +@@ -488,6 +488,12 @@ if (recipients_count >= recipients_list_max) + { + recipient_item *oldlist = recipients_list; + int oldmax = recipients_list_max; ++ ++ const int safe_recipients_limit = INT_MAX / 2 / sizeof(recipient_item); ++ if (recipients_list_max < 0 || recipients_list_max >= safe_recipients_limit) ++ { ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Too many recipients: %d", recipients_list_max); ++ } + recipients_list_max = recipients_list_max ? 2*recipients_list_max : 50; + recipients_list = store_get(recipients_list_max * sizeof(recipient_item)); + if (oldlist != NULL) +@@ -4070,7 +4076,7 @@ if (message_logs && !blackholed_by) + { + int fd; + uschar * m_name = spool_fname(US"msglog", message_subdir, message_id, US""); +- ++ + if ( (fd = Uopen(m_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE)) < 0 + && errno == ENOENT + ) +@@ -4229,7 +4235,7 @@ if(!smtp_reply) + if (f.deliver_freeze) log_write(0, LOG_MAIN, "frozen by %s", frozen_by); + if (f.queue_only_policy) log_write(L_delay_delivery, LOG_MAIN, + "no immediate delivery: queued%s%s by %s", +- *queue_name ? " in " : "", *queue_name ? CS queue_name : "", ++ *queue_name ? " in " : "", *queue_name ? CS queue_name : "", + queued_by); + } + f.receive_call_bombout = FALSE; +-- +2.30.2 + diff --git a/debian/patches/84_09-CVE-2020-28022-Heap-out-of-bounds-read-and-write-in-.patch b/debian/patches/84_09-CVE-2020-28022-Heap-out-of-bounds-read-and-write-in-.patch new file mode 100644 index 0000000..1ace416 --- /dev/null +++ b/debian/patches/84_09-CVE-2020-28022-Heap-out-of-bounds-read-and-write-in-.patch @@ -0,0 +1,61 @@ +From f46455c848def70d686d7b164df75b27f8dae04d Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory <qsa@qualys.com> +Date: Sun, 21 Feb 2021 19:53:43 -0800 +Subject: [PATCH 09/29] CVE-2020-28022: Heap out-of-bounds read and write in + extract_option() + +Based on Phil Pennock's commit c5017adf. +--- + src/smtp_in.c | 20 +++++++++++++------- + 1 file changed, 13 insertions(+), 7 deletions(-) + +diff --git a/src/smtp_in.c b/src/smtp_in.c +index 4265d77b7..16c3a3e33 100644 +--- a/src/smtp_in.c ++++ b/src/smtp_in.c +@@ -1984,29 +1984,35 @@ static BOOL + extract_option(uschar **name, uschar **value) + { + uschar *n; +-uschar *v = smtp_cmd_data + Ustrlen(smtp_cmd_data) - 1; +-while (isspace(*v)) v--; ++uschar *v; ++if (Ustrlen(smtp_cmd_data) <= 0) return FALSE; ++v = smtp_cmd_data + Ustrlen(smtp_cmd_data) - 1; ++while (v > smtp_cmd_data && isspace(*v)) v--; + v[1] = 0; ++ + while (v > smtp_cmd_data && *v != '=' && !isspace(*v)) + { + /* Take care to not stop at a space embedded in a quoted local-part */ +- +- if (*v == '"') do v--; while (*v != '"' && v > smtp_cmd_data+1); ++ if (*v == '"') ++ { ++ do v--; while (v > smtp_cmd_data && *v != '"'); ++ if (v <= smtp_cmd_data) return FALSE; ++ } + v--; + } ++if (v <= smtp_cmd_data) return FALSE; + + n = v; + if (*v == '=') + { +- while(isalpha(n[-1])) n--; ++ while (n > smtp_cmd_data && isalpha(n[-1])) n--; + /* RFC says SP, but TAB seen in wild and other major MTAs accept it */ +- if (!isspace(n[-1])) return FALSE; ++ if (n <= smtp_cmd_data || !isspace(n[-1])) return FALSE; + n[-1] = 0; + } + else + { + n++; +- if (v == smtp_cmd_data) return FALSE; + } + *v++ = 0; + *name = n; +-- +2.30.2 + diff --git a/debian/patches/84_10-CVE-2020-28026-Line-truncation-and-injection-in-spoo.patch b/debian/patches/84_10-CVE-2020-28026-Line-truncation-and-injection-in-spoo.patch new file mode 100644 index 0000000..3864de2 --- /dev/null +++ b/debian/patches/84_10-CVE-2020-28026-Line-truncation-and-injection-in-spoo.patch @@ -0,0 +1,102 @@ +From 327f647a849c3974e7107b5386421b0058c15b29 Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory <qsa@qualys.com> +Date: Sun, 21 Feb 2021 21:17:31 -0800 +Subject: [PATCH 10/29] CVE-2020-28026: Line truncation and injection in + spool_read_header() + +This also fixes: + +2/ In src/spool_in.c: + + 462 while ( (len = Ustrlen(big_buffer)) == big_buffer_size-1 + 463 && big_buffer[len-1] != '\n' + 464 ) + 465 { /* buffer not big enough for line; certs make this possible */ + 466 uschar * buf; + 467 if (big_buffer_size >= BIG_BUFFER_SIZE*4) goto SPOOL_READ_ERROR; + 468 buf = store_get_perm(big_buffer_size *= 2, FALSE); + 469 memcpy(buf, big_buffer, --len); + +The --len in memcpy() chops off a useful byte (we know for sure that +big_buffer[len-1] is not a '\n' because we entered the while loop). +--- + src/spool_in.c | 48 +++++++++++++++++++++++++++++++--------------- + 1 file changed, 33 insertions(+), 15 deletions(-) + +diff --git a/src/spool_in.c b/src/spool_in.c +index 2d349778c..dbbcf23ee 100644 +--- a/src/spool_in.c ++++ b/src/spool_in.c +@@ -307,6 +307,36 @@ dsn_ret = 0; + dsn_envid = NULL; + } + ++static void * ++fgets_big_buffer(FILE *fp) ++{ ++int len = 0; ++ ++big_buffer[0] = 0; ++if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) return NULL; ++ ++while ((len = Ustrlen(big_buffer)) == big_buffer_size-1 ++ && big_buffer[len-1] != '\n') ++ { ++ uschar *newbuffer; ++ int newsize; ++ ++ if (big_buffer_size >= BIG_BUFFER_SIZE * 4) return NULL; ++ newsize = big_buffer_size * 2; ++ newbuffer = store_get_perm(newsize); ++ memcpy(newbuffer, big_buffer, len); ++ ++ big_buffer = newbuffer; ++ big_buffer_size = newsize; ++ if (Ufgets(big_buffer + len, big_buffer_size - len, fp) == NULL) return NULL; ++ } ++ ++if (len <= 0 || big_buffer[len-1] != '\n') return NULL; ++return big_buffer; ++} ++ ++ ++ + + /************************************************* + * Read spool header file * +@@ -450,21 +480,9 @@ p = big_buffer + 2; + for (;;) + { + int len; +- if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR; ++ if (fgets_big_buffer(fp) == NULL) goto SPOOL_READ_ERROR; + if (big_buffer[0] != '-') break; +- while ( (len = Ustrlen(big_buffer)) == big_buffer_size-1 +- && big_buffer[len-1] != '\n' +- ) +- { /* buffer not big enough for line; certs make this possible */ +- uschar * buf; +- if (big_buffer_size >= BIG_BUFFER_SIZE*4) goto SPOOL_READ_ERROR; +- buf = store_get_perm(big_buffer_size *= 2); +- memcpy(buf, big_buffer, --len); +- big_buffer = buf; +- if (Ufgets(big_buffer+len, big_buffer_size-len, fp) == NULL) +- goto SPOOL_READ_ERROR; +- } +- big_buffer[len-1] = 0; ++ big_buffer[Ustrlen(big_buffer)-1] = 0; + + switch(big_buffer[1]) + { +@@ -724,7 +742,7 @@ DEBUG(D_deliver) + buffer. It contains the count of recipients which follow on separate lines. + Apply an arbitrary sanity check.*/ + +-if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR; ++if (fgets_big_buffer(fp) == NULL) goto SPOOL_READ_ERROR; + if (sscanf(CS big_buffer, "%d", &rcount) != 1 || rcount > 16384) + goto SPOOL_FORMAT_ERROR; + +-- +2.30.2 + diff --git a/debian/patches/84_11-CVE-2020-28015-28021-New-line-injection-into-spool-h.patch b/debian/patches/84_11-CVE-2020-28015-28021-New-line-injection-into-spool-h.patch new file mode 100644 index 0000000..1d2cc7e --- /dev/null +++ b/debian/patches/84_11-CVE-2020-28015-28021-New-line-injection-into-spool-h.patch @@ -0,0 +1,69 @@ +From ac8f49ef90e768a63ed3dca50e2b2c6e8d333bfd Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory <qsa@qualys.com> +Date: Sun, 21 Feb 2021 21:26:53 -0800 +Subject: [PATCH 11/29] CVE-2020-28015+28021: New-line injection into spool + header file + +--- + src/spool_out.c | 21 +++++++++++++++++---- + 1 file changed, 17 insertions(+), 4 deletions(-) + +diff --git a/src/spool_out.c b/src/spool_out.c +index d55895202..9394393d5 100644 +--- a/src/spool_out.c ++++ b/src/spool_out.c +@@ -108,6 +108,18 @@ return fd; + * Write the header spool file * + *************************************************/ + ++static const uschar * ++zap_newlines(const uschar *s) ++{ ++uschar *z, *p; ++ ++if (Ustrchr(s, '\n') == NULL) return s; ++ ++p = z = string_copy(s); ++while ((p = Ustrchr(p, '\n')) != NULL) *p++ = ' '; ++return z; ++} ++ + /* Returns the size of the file for success; zero for failure. The file is + written under a temporary name, and then renamed. It's done this way so that it + works with re-writing the file on message deferral as well as for the initial +@@ -210,7 +222,7 @@ if (body_zerocount > 0) fprintf(fp, "-body_zerocount %d\n", body_zerocount); + if (authenticated_id) + fprintf(fp, "-auth_id %s\n", authenticated_id); + if (authenticated_sender) +- fprintf(fp, "-auth_sender %s\n", authenticated_sender); ++ fprintf(fp, "-auth_sender %s\n", zap_newlines(authenticated_sender)); + + if (f.allow_unqualified_recipient) fprintf(fp, "-allow_unqualified_recipient\n"); + if (f.allow_unqualified_sender) fprintf(fp, "-allow_unqualified_sender\n"); +@@ -283,19 +295,20 @@ fprintf(fp, "%d\n", recipients_count); + for (i = 0; i < recipients_count; i++) + { + recipient_item *r = recipients_list + i; ++ const uschar *address = zap_newlines(r->address); + + DEBUG(D_deliver) debug_printf("DSN: Flags :%d\n", r->dsn_flags); + + if (r->pno < 0 && r->errors_to == NULL && r->dsn_flags == 0) +- fprintf(fp, "%s\n", r->address); ++ fprintf(fp, "%s\n", address); + else + { +- uschar * errors_to = r->errors_to ? r->errors_to : US""; ++ const uschar * errors_to = r->errors_to ? zap_newlines(r->errors_to) : US""; + /* for DSN SUPPORT extend exim 4 spool in a compatible way by + adding new values upfront and add flag 0x02 */ + uschar * orcpt = r->orcpt ? r->orcpt : US""; + +- fprintf(fp, "%s %s %d,%d %s %d,%d#3\n", r->address, orcpt, Ustrlen(orcpt), ++ fprintf(fp, "%s %s %d,%d %s %d,%d#3\n", address, orcpt, Ustrlen(orcpt), + r->dsn_flags, errors_to, Ustrlen(errors_to), r->pno); + } + +-- +2.30.2 + diff --git a/debian/patches/84_12-CVE-2020-28009-Integer-overflow-in-get_stdinput.patch b/debian/patches/84_12-CVE-2020-28009-Integer-overflow-in-get_stdinput.patch new file mode 100644 index 0000000..acde64a --- /dev/null +++ b/debian/patches/84_12-CVE-2020-28009-Integer-overflow-in-get_stdinput.patch @@ -0,0 +1,61 @@ +From 2cb94a53eb9186bd405120543301e1240b895d86 Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory <qsa@qualys.com> +Date: Sun, 21 Feb 2021 21:45:19 -0800 +Subject: [PATCH 12/29] CVE-2020-28009: Integer overflow in get_stdinput() + +--- + src/string.c | 23 ++++++++++++++++++++++- + 1 file changed, 22 insertions(+), 1 deletion(-) + +diff --git a/src/string.c b/src/string.c +index 3445f8a42..2cdbe7c75 100644 +--- a/src/string.c ++++ b/src/string.c +@@ -1147,6 +1147,18 @@ To try to keep things reasonable, we use increments whose size depends on the + existing length of the string. */ + + unsigned inc = oldsize < 4096 ? 127 : 1023; ++ ++if (g->ptr < 0 || g->ptr > g->size || g->size >= INT_MAX/2) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, ++ "internal error in gstring_grow (ptr %d size %d)", g->ptr, g->size); ++ ++if (count <= 0) return; ++ ++if (count >= INT_MAX/2 - g->ptr) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, ++ "internal error in gstring_grow (ptr %d count %d)", g->ptr, count); ++ ++ + g->size = ((p + count + inc) & ~inc) + 1; + + /* Try to extend an existing allocation. If the result of calling +@@ -1194,6 +1206,10 @@ string_catn(gstring * g, const uschar *s, int count) + { + int p; + ++if (count < 0) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, ++ "internal error in string_catn (count %d)", count); ++ + if (!g) + { + unsigned inc = count < 4096 ? 127 : 1023; +@@ -1201,8 +1217,13 @@ if (!g) + g = string_get(size); + } + ++if (g->ptr < 0 || g->ptr > g->size) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, ++ "internal error in string_catn (ptr %d size %d)", g->ptr, g->size); ++ + p = g->ptr; +-if (p + count >= g->size) ++ ++if (count >= g->size - p) + gstring_grow(g, p, count); + + /* Because we always specify the exact number of characters to copy, we can +-- +2.30.2 + diff --git a/debian/patches/84_13-CVE-2020-28024-Heap-buffer-underflow-in-smtp_ungetc.patch b/debian/patches/84_13-CVE-2020-28024-Heap-buffer-underflow-in-smtp_ungetc.patch new file mode 100644 index 0000000..4545ff3 --- /dev/null +++ b/debian/patches/84_13-CVE-2020-28024-Heap-buffer-underflow-in-smtp_ungetc.patch @@ -0,0 +1,41 @@ +From 7ea481a6471cdad3a674b767f808357b3c7fc721 Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory <qsa@qualys.com> +Date: Sun, 21 Feb 2021 21:49:30 -0800 +Subject: [PATCH 13/29] CVE-2020-28024: Heap buffer underflow in smtp_ungetc() + +--- + src/smtp_in.c | 3 +++ + src/tls.c | 3 +++ + 2 files changed, 6 insertions(+) + +diff --git a/src/smtp_in.c b/src/smtp_in.c +index 16c3a3e33..bdcfde65f 100644 +--- a/src/smtp_in.c ++++ b/src/smtp_in.c +@@ -805,6 +805,9 @@ Returns: the character + int + smtp_ungetc(int ch) + { ++if (smtp_inptr <= smtp_inbuffer) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "buffer underflow in smtp_ungetc"); ++ + *--smtp_inptr = ch; + return ch; + } +diff --git a/src/tls.c b/src/tls.c +index f79bc3193..2a316fe59 100644 +--- a/src/tls.c ++++ b/src/tls.c +@@ -151,6 +151,9 @@ Returns: the character + int + tls_ungetc(int ch) + { ++if (ssl_xfer_buffer_lwm <= 0) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "buffer underflow in tls_ungetc"); ++ + ssl_xfer_buffer[--ssl_xfer_buffer_lwm] = ch; + return ch; + } +-- +2.30.2 + diff --git a/debian/patches/84_14-CVE-2020-28012-Missing-close-on-exec-flag-for-privil.patch b/debian/patches/84_14-CVE-2020-28012-Missing-close-on-exec-flag-for-privil.patch new file mode 100644 index 0000000..c9b2f65 --- /dev/null +++ b/debian/patches/84_14-CVE-2020-28012-Missing-close-on-exec-flag-for-privil.patch @@ -0,0 +1,31 @@ +From a1f36d86760def10138c1053eb3b1882b281fcd9 Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory <qsa@qualys.com> +Date: Sun, 21 Feb 2021 21:53:55 -0800 +Subject: [PATCH 14/29] CVE-2020-28012: Missing close-on-exec flag for + privileged pipe + +--- + src/rda.c | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/src/rda.c b/src/rda.c +index 13f570928..c27e073a3 100644 +--- a/src/rda.c ++++ b/src/rda.c +@@ -623,9 +623,13 @@ search_tidyup(); + if ((pid = fork()) == 0) + { + header_line *waslast = header_last; /* Save last header */ ++ int fd_flags = -1; + + fd = pfd[pipe_write]; + (void)close(pfd[pipe_read]); ++ ++ if ((fd_flags = fcntl(fd, F_GETFD)) == -1) goto bad; ++ if (fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC) == -1) goto bad; + exim_setugid(ugid->uid, ugid->gid, FALSE, rname); + + /* Addresses can get rewritten in filters; if we are not root or the exim +-- +2.30.2 + diff --git a/debian/patches/84_15-Security-Safeguard-against-relative-names-for-msglog.patch b/debian/patches/84_15-Security-Safeguard-against-relative-names-for-msglog.patch new file mode 100644 index 0000000..7b2607a --- /dev/null +++ b/debian/patches/84_15-Security-Safeguard-against-relative-names-for-msglog.patch @@ -0,0 +1,41 @@ +From 0d5d8fc918c4b999a2d5b025d94e25e43680377d Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory <qsa@qualys.com> +Date: Sun, 21 Feb 2021 22:00:31 -0800 +Subject: [PATCH 15/29] Security: Safeguard against relative names for msglog + files. + +Based on Heiko Schlittermann's commit 4f0ac4ad. This fixes: + +3/ In src/deliver.c: + + 333 static int + 334 open_msglog_file(uschar *filename, int mode, uschar **error) + 335 { + 336 if (Ustrstr(filename, US"/../")) + 337 log_write(0, LOG_MAIN|LOG_PANIC, + 338 "Attempt to open msglog file path with upward-traversal: '%s'\n", filename); + +Should this be LOG_PANIC_DIE instead of LOG_PANIC? Right now it will log +the /../ attempt but will open the file anyway. +--- + src/deliver.c | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/src/deliver.c b/src/deliver.c +index d4ed8af08..279672ce0 100644 +--- a/src/deliver.c ++++ b/src/deliver.c +@@ -331,6 +331,10 @@ open_msglog_file(uschar *filename, int mode, uschar **error) + { + int fd, i; + ++if (Ustrstr(filename, US"/../")) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, ++ "Attempt to open msglog file path with upward-traversal: '%s'", filename); ++ + for (i = 2; i > 0; i--) + { + fd = Uopen(filename, +-- +2.30.2 + diff --git a/debian/patches/84_16-Security-Check-overrun-rcpt_count-integer.patch b/debian/patches/84_16-Security-Check-overrun-rcpt_count-integer.patch new file mode 100644 index 0000000..f8bda54 --- /dev/null +++ b/debian/patches/84_16-Security-Check-overrun-rcpt_count-integer.patch @@ -0,0 +1,39 @@ +From 56aadff97bc4e45e6a2ce25cfb9a98a4ae4bec79 Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory <qsa@qualys.com> +Date: Sun, 21 Feb 2021 22:05:37 -0800 +Subject: [PATCH 16/29] Security: Check overrun rcpt_count integer + +Based on Heiko Schlittermann's commit e5cb5e61. This fixes: + +4/ In src/smtp_in.c: + +4966 case RCPT_CMD: +4967 HAD(SCH_RCPT); +4968 rcpt_count++; +.... +5123 if (rcpt_count > recipients_max && recipients_max > 0) + +In theory this recipients_max check can be bypassed, because the int +rcpt_count can overflow (become negative). In practice this would either +consume too much memory or generate too much network traffic, but maybe +it should be fixed anyway. +--- + src/smtp_in.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/src/smtp_in.c b/src/smtp_in.c +index bdcfde65f..1a5fbfea3 100644 +--- a/src/smtp_in.c ++++ b/src/smtp_in.c +@@ -4993,6 +4993,8 @@ while (done <= 0) + + case RCPT_CMD: + HAD(SCH_RCPT); ++ if (rcpt_count < 0 || rcpt_count >= INT_MAX/2) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Too many recipients: %d", rcpt_count); + rcpt_count++; + was_rcpt = fl.rcpt_in_progress = TRUE; + +-- +2.30.2 + diff --git a/debian/patches/84_17-Security-Always-exit-when-LOG_PANIC_DIE-is-set.patch b/debian/patches/84_17-Security-Always-exit-when-LOG_PANIC_DIE-is-set.patch new file mode 100644 index 0000000..a9eee56 --- /dev/null +++ b/debian/patches/84_17-Security-Always-exit-when-LOG_PANIC_DIE-is-set.patch @@ -0,0 +1,24 @@ +From 9b1ba71e66d18b1ae185e4d83788dc913f903a56 Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory <qsa@qualys.com> +Date: Sun, 21 Feb 2021 22:09:06 -0800 +Subject: [PATCH 17/29] Security: Always exit when LOG_PANIC_DIE is set + +--- + src/log.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/log.c b/src/log.c +index d08200044..c8313890e 100644 +--- a/src/log.c ++++ b/src/log.c +@@ -894,6 +894,7 @@ if (!(flags & (LOG_MAIN|LOG_PANIC|LOG_REJECT))) + if (f.disable_logging) + { + DEBUG(D_any) debug_printf("log writing disabled\n"); ++ if ((flags & LOG_PANIC_DIE) == LOG_PANIC_DIE) exim_exit(EXIT_FAILURE, NULL); + return; + } + +-- +2.30.2 + diff --git a/debian/patches/84_18-Security-Fix-off-by-one-in-smtp-transport-read-respo.patch b/debian/patches/84_18-Security-Fix-off-by-one-in-smtp-transport-read-respo.patch new file mode 100644 index 0000000..47d67d2 --- /dev/null +++ b/debian/patches/84_18-Security-Fix-off-by-one-in-smtp-transport-read-respo.patch @@ -0,0 +1,47 @@ +From 28335a4704d8d615fd61e05ea6e435a4cd24e4df Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory <qsa@qualys.com> +Date: Sun, 21 Feb 2021 22:13:18 -0800 +Subject: [PATCH 18/29] Security: Fix off-by-one in smtp transport (read + response) + +Based on Heiko Schlittermann's commit 1887a160. This fixes: + +1/ In src/transports/smtp.c: + +2281 int n = sizeof(sx->buffer); +2282 uschar * rsp = sx->buffer; +2283 +2284 if (sx->esmtp_sent && (n = Ustrlen(sx->buffer)) < sizeof(sx->buffer)/2) +2285 { rsp = sx->buffer + n + 1; n = sizeof(sx->buffer) - n; } + +This should probably be either: + +rsp = sx->buffer + n + 1; n = sizeof(sx->buffer) - n - 1; + +or: + +rsp = sx->buffer + n; n = sizeof(sx->buffer) - n; + +(not sure which) to avoid an off-by-one. +--- + src/transports/smtp.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/transports/smtp.c b/src/transports/smtp.c +index cc37e73f3..07b63a2aa 100644 +--- a/src/transports/smtp.c ++++ b/src/transports/smtp.c +@@ -2328,8 +2328,8 @@ goto SEND_QUIT; + int n = sizeof(sx->buffer); + uschar * rsp = sx->buffer; + +- if (sx->esmtp_sent && (n = Ustrlen(sx->buffer)) < sizeof(sx->buffer)/2) +- { rsp = sx->buffer + n + 1; n = sizeof(sx->buffer) - n; } ++ if (sx->esmtp_sent && (n = Ustrlen(sx->buffer) + 1) < sizeof(sx->buffer)/2) ++ { rsp = sx->buffer + n; n = sizeof(sx->buffer) - n; } + + if (smtp_write_command(sx, SCMD_FLUSH, "HELO %s\r\n", sx->helo_data) < 0) + goto SEND_FAILED; +-- +2.30.2 + diff --git a/debian/patches/84_19-Security-Avoid-decrement-of-dkim_collect_input-if-al.patch b/debian/patches/84_19-Security-Avoid-decrement-of-dkim_collect_input-if-al.patch new file mode 100644 index 0000000..a2b52fe --- /dev/null +++ b/debian/patches/84_19-Security-Avoid-decrement-of-dkim_collect_input-if-al.patch @@ -0,0 +1,59 @@ +From 031ae594f6e68511117f6d39ce238b0c5215d8d1 Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory <qsa@qualys.com> +Date: Sun, 21 Feb 2021 22:19:42 -0800 +Subject: [PATCH 19/29] Security: Avoid decrement of dkim_collect_input if + already at 0 + +Based on Heiko Schlittermann's commit bf2d6e58. This fixes: + +5/ receive_msg() calls dkim_exim_verify_finish(), which sets +dkim_collect_input to 0 and calls pdkim_feed_finish(), which calls +pdkim_header_complete(), which decreases dkim_collect_input to UINT_MAX, +which reactivates the DKIM code. + +As a result, pdkim_feed() is called again (through receive_getc at the +end of receive_msg()), but functions like pdkim_finish_bodyhash() and +exim_sha_finish() have already been called (in pdkim_feed_finish()). +This suggests a use-after-free. + +But it seems that a use-after-free would happen only with +EVP_DigestFinal() (in exim_sha_finish()), which does not seem to be +reachable via DKIM (no SHA3). But we checked OpenSSL only, not GnuTLS. + +Here is a proof of concept that triggers the bug (which came very close +to a security vulnerability): + +(sleep 10; echo 'EHLO test'; sleep 3; echo 'MAIL FROM:<>'; sleep 3; echo 'RCPT TO:postmaster'; sleep 3; echo 'BDAT 42 LAST'; date >&2; sleep 30; printf 'not a valid header line\r\n +DKIM-Signature:\r\nXXX'; sleep 30) | nc -n -v 192.168.56.102 25 + +(gdb) print &dkim_collect_input +$2 = (unsigned int *) 0x55e180386d90 <dkim_collect_input> +(gdb) watch *(unsigned int *) 0x55e180386d90 + +Hardware watchpoint 1: *(unsigned int *) 0x55e180386d90 +Old value = 0 +New value = 4294967295 +#0 0x000055e18031f805 in pdkim_header_complete (ctx=ctx@entry=0x55e181b9e8e0) at pdkim.c:1006 +#1 0x000055e18032106c in pdkim_feed_finish (ctx=0x55e181b9e8e0, return_signatures=0x55e180386d78 <dkim_signatures>, err=err@entry=0x7ffe443e1d00) at pdkim.c:1490 +#2 0x000055e1802a3280 in dkim_exim_verify_finish () at dkim.c:328 +#3 0x000055e1802c9d1d in receive_msg (extract_recip=extract_recip@entry=0) at receive.c:3409 +--- + src/pdkim/pdkim.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/pdkim/pdkim.c b/src/pdkim/pdkim.c +index e203311da..e3233e9f0 100644 +--- a/src/pdkim/pdkim.c ++++ b/src/pdkim/pdkim.c +@@ -1010,7 +1010,7 @@ else + last_sig->next = sig; + } + +- if (--dkim_collect_input == 0) ++ if (dkim_collect_input && --dkim_collect_input == 0) + { + ctx->headers = pdkim_prepend_stringlist(ctx->headers, ctx->cur_header->s); + ctx->cur_header->s[ctx->cur_header->ptr = 0] = '\0'; +-- +2.30.2 + diff --git a/debian/patches/84_20-Security-Leave-a-clean-smtp_out-input-buffer-even-in.patch b/debian/patches/84_20-Security-Leave-a-clean-smtp_out-input-buffer-even-in.patch new file mode 100644 index 0000000..acf17d3 --- /dev/null +++ b/debian/patches/84_20-Security-Leave-a-clean-smtp_out-input-buffer-even-in.patch @@ -0,0 +1,67 @@ +From 6b647c508aced6961f00e139f0337e2c8aba9eb7 Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory <qsa@qualys.com> +Date: Sun, 21 Feb 2021 22:24:13 -0800 +Subject: [PATCH 20/29] Security: Leave a clean smtp_out input buffer even in + case of read error + +Based on Heiko Schlittermann's commit 54895bc3. This fixes: + +7/ In src/smtp_out.c, read_response_line(), inblock->ptr is not updated +when -1 is returned. This does not seem to have bad consequences, but is +maybe not the intended behavior. +--- + src/smtp_out.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +--- a/src/smtp_out.c ++++ b/src/smtp_out.c +@@ -387,11 +387,11 @@ HDEBUG(D_transport|D_acl|D_v) + #ifdef SUPPORT_SOCKS + if (ob->socks_proxy) + { + int sock = socks_sock_connect(sc->host, sc->host_af, port, sc->interface, + sc->tblock, ob->connect_timeout); +- ++ + if (sock >= 0) + { + if (early_data && early_data->data && early_data->len) + if (send(sock, early_data->data, early_data->len, 0) < 0) + { +@@ -588,11 +588,11 @@ Arguments: + buffer where to put the line + size space available for the line + timelimit deadline for reading the lime, seconds past epoch + + Returns: length of a line that has been put in the buffer +- -1 otherwise, with errno set ++ -1 otherwise, with errno set, and inblock->ptr adjusted + */ + + static int + read_response_line(smtp_inblock *inblock, uschar *buffer, int size, time_t timelimit) + { +@@ -629,10 +629,11 @@ for (;;) + *p++ = c; + if (--size < 4) + { + *p = 0; /* Leave malformed line for error message */ + errno = ERRNO_SMTPFORMAT; ++ inblock->ptr = ptr; + return -1; + } + } + + /* Need to read a new input packet. */ +@@ -654,10 +655,11 @@ for (;;) + } + + /* Get here if there has been some kind of recv() error; errno is set, but we + ensure that the result buffer is empty before returning. */ + ++inblock->ptr = inblock->ptrend = inblock->buffer; + *buffer = 0; + return -1; + } + + diff --git a/debian/patches/84_21-Security-Avoid-modification-of-constant-data-in-dkim.patch b/debian/patches/84_21-Security-Avoid-modification-of-constant-data-in-dkim.patch new file mode 100644 index 0000000..b723d0f --- /dev/null +++ b/debian/patches/84_21-Security-Avoid-modification-of-constant-data-in-dkim.patch @@ -0,0 +1,89 @@ +From a4e1b7755ebbdee2689d40683ba69f09e38a8d7f Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory <qsa@qualys.com> +Date: Sun, 21 Feb 2021 22:30:03 -0800 +Subject: [PATCH 21/29] Security: Avoid modification of constant data in dkim + handling + +Based on Heiko Schlittermann's commits f880c7f3 and c118c7f4. This +fixes: + +6/ In src/pdkim/pdkim.c, pdkim_update_ctx_bodyhash() is sometimes called +with a global orig_data and hence canon_data, and the following line can +therefore modify data that should be constant: + + 773 canon_data->len = b->bodylength - b->signed_body_bytes; + +For example, the following proof of concept sets lineending.len to 0 +(this should not be possible): + +(sleep 10; echo 'EHLO test'; sleep 3; echo 'MAIL FROM:<>'; sleep 3; echo 'RCPT TO:postmaster'; sleep 3; echo 'DATA'; date >&2; sleep 30; printf 'DKIM-Signature:a=rsa-sha1;c=simple/simple;l=0\r\n\r\n\r\nXXX\r\n.\r\n'; sleep 30) | nc -n -v 192.168.56.102 25 + +(gdb) print lineending +$1 = {data = 0x55e18035b2ad "\r\n", len = 2} +(gdb) print &lineending.len +$3 = (size_t *) 0x55e180385948 <lineending+8> +(gdb) watch *(size_t *) 0x55e180385948 + +Hardware watchpoint 1: *(size_t *) 0x55e180385948 +Old value = 2 +New value = 0 +(gdb) print lineending +$5 = {data = 0x55e18035b2ad "\r\n", len = 0} +--- + src/pdkim/pdkim.c | 21 ++++++++++++--------- + 1 file changed, 12 insertions(+), 9 deletions(-) + +diff --git a/src/pdkim/pdkim.c b/src/pdkim/pdkim.c +index e3233e9f0..512a3e352 100644 +--- a/src/pdkim/pdkim.c ++++ b/src/pdkim/pdkim.c +@@ -107,7 +107,7 @@ pdkim_combined_canon_entry pdkim_combined_canons[] = { + }; + + +-static blob lineending = {.data = US"\r\n", .len = 2}; ++static const blob lineending = {.data = US"\r\n", .len = 2}; + + /* -------------------------------------------------------------------------- */ + uschar * +@@ -719,9 +719,11 @@ return NULL; + If we have to relax the data for this sig, return our copy of it. */ + + static blob * +-pdkim_update_ctx_bodyhash(pdkim_bodyhash * b, blob * orig_data, blob * relaxed_data) ++pdkim_update_ctx_bodyhash(pdkim_bodyhash * b, const blob * orig_data, blob * relaxed_data) + { +-blob * canon_data = orig_data; ++const blob * canon_data = orig_data; ++size_t left; ++ + /* Defaults to simple canon (no further treatment necessary) */ + + if (b->canon_method == PDKIM_CANON_RELAXED) +@@ -767,16 +769,17 @@ if (b->canon_method == PDKIM_CANON_RELAXED) + } + + /* Make sure we don't exceed the to-be-signed body length */ ++left = canon_data->len; + if ( b->bodylength >= 0 +- && b->signed_body_bytes + (unsigned long)canon_data->len > b->bodylength ++ && left > (unsigned long)b->bodylength - b->signed_body_bytes + ) +- canon_data->len = b->bodylength - b->signed_body_bytes; ++ left = (unsigned long)b->bodylength - b->signed_body_bytes; + +-if (canon_data->len > 0) ++if (left > 0) + { +- exim_sha_update(&b->body_hash_ctx, CUS canon_data->data, canon_data->len); +- b->signed_body_bytes += canon_data->len; +- DEBUG(D_acl) pdkim_quoteprint(canon_data->data, canon_data->len); ++ exim_sha_update(&b->body_hash_ctx, CUS canon_data->data, left); ++ b->signed_body_bytes += left; ++ DEBUG(D_acl) pdkim_quoteprint(canon_data->data, left); + } + + return relaxed_data; +-- +2.30.2 + diff --git a/debian/patches/84_22-CVE-2020-28019-Failure-to-reset-function-pointer-aft.patch b/debian/patches/84_22-CVE-2020-28019-Failure-to-reset-function-pointer-aft.patch new file mode 100644 index 0000000..0d44293 --- /dev/null +++ b/debian/patches/84_22-CVE-2020-28019-Failure-to-reset-function-pointer-aft.patch @@ -0,0 +1,135 @@ +From 1663ab541b37675dec5bcf235605568ff36ac65a Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory <qsa@qualys.com> +Date: Sun, 21 Feb 2021 22:36:10 -0800 +Subject: [PATCH 22/29] CVE-2020-28019: Failure to reset function pointer after + BDAT error + +Based on Phil Pennock's commits 4715403e and 151ffd72, and Jeremy +Harris's commits aa171254 and 9aceb5c2. +--- + src/globals.c | 1 + + src/globals.h | 1 + + src/smtp_in.c | 55 +++++++++++++++++++++++++++++++++++++++-------- + 3 files changed, 48 insertions(+), 9 deletions(-) + +diff --git a/src/globals.c b/src/globals.c +index b3362a34c..894b8487b 100644 +--- a/src/globals.c ++++ b/src/globals.c +@@ -247,6 +247,7 @@ struct global_flags f = + .authentication_local = FALSE, + + .background_daemon = TRUE, ++ .bdat_readers_wanted = FALSE, + + .chunking_offered = FALSE, + .config_changed = FALSE, +diff --git a/src/globals.h b/src/globals.h +index f71f104e2..58f7ae55f 100644 +--- a/src/globals.h ++++ b/src/globals.h +@@ -173,6 +173,7 @@ extern struct global_flags { + BOOL authentication_local :1; /* TRUE if non-smtp (implicit authentication) */ + + BOOL background_daemon :1; /* Set FALSE to keep in foreground */ ++ BOOL bdat_readers_wanted :1; /* BDAT-handling to be pushed on readfunc stack */ + + BOOL chunking_offered :1; + BOOL config_changed :1; /* True if -C used */ +diff --git a/src/smtp_in.c b/src/smtp_in.c +index 1a5fbfea3..016c44c0f 100644 +--- a/src/smtp_in.c ++++ b/src/smtp_in.c +@@ -602,6 +602,10 @@ if (n > 0) + #endif + } + ++/* Forward declarations */ ++static inline void bdat_push_receive_functions(void); ++static inline void bdat_pop_receive_functions(void); ++ + + /* Get a byte from the smtp input, in CHUNKING mode. Handle ack of the + previous BDAT chunk and getting new ones when we run out. Uses the +@@ -634,9 +638,7 @@ for(;;) + if (chunking_data_left > 0) + return lwr_receive_getc(chunking_data_left--); + +- receive_getc = lwr_receive_getc; +- receive_getbuf = lwr_receive_getbuf; +- receive_ungetc = lwr_receive_ungetc; ++ bdat_pop_receive_functions(); + #ifndef DISABLE_DKIM + dkim_save = dkim_collect_input; + dkim_collect_input = 0; +@@ -740,9 +742,7 @@ next_cmd: + goto repeat_until_rset; + } + +- receive_getc = bdat_getc; +- receive_getbuf = bdat_getbuf; /* r~getbuf is never actually used */ +- receive_ungetc = bdat_ungetc; ++ bdat_push_receive_functions(); + #ifndef DISABLE_DKIM + dkim_collect_input = dkim_save; + #endif +@@ -775,9 +775,7 @@ while (chunking_data_left) + if (!bdat_getbuf(&n)) break; + } + +-receive_getc = lwr_receive_getc; +-receive_getbuf = lwr_receive_getbuf; +-receive_ungetc = lwr_receive_ungetc; ++bdat_pop_receive_functions(); + + if (chunking_state != CHUNKING_LAST) + { +@@ -787,6 +785,45 @@ if (chunking_state != CHUNKING_LAST) + } + + ++static inline void ++bdat_push_receive_functions(void) ++{ ++/* push the current receive_* function on the "stack", and ++replace them by bdat_getc(), which in turn will use the lwr_receive_* ++functions to do the dirty work. */ ++if (lwr_receive_getc == NULL) ++ { ++ lwr_receive_getc = receive_getc; ++ lwr_receive_getbuf = receive_getbuf; ++ lwr_receive_ungetc = receive_ungetc; ++ } ++else ++ { ++ DEBUG(D_receive) debug_printf("chunking double-push receive functions\n"); ++ } ++ ++receive_getc = bdat_getc; ++receive_getbuf = bdat_getbuf; ++receive_ungetc = bdat_ungetc; ++} ++ ++static inline void ++bdat_pop_receive_functions(void) ++{ ++if (lwr_receive_getc == NULL) ++ { ++ DEBUG(D_receive) debug_printf("chunking double-pop receive functions\n"); ++ return; ++ } ++ ++receive_getc = lwr_receive_getc; ++receive_getbuf = lwr_receive_getbuf; ++receive_ungetc = lwr_receive_ungetc; ++ ++lwr_receive_getc = NULL; ++lwr_receive_getbuf = NULL; ++lwr_receive_ungetc = NULL; ++} + + + /************************************************* +-- +2.30.2 + diff --git a/debian/patches/84_23-CVE-2020-28007-Link-attack-in-Exim-s-log-directory.patch b/debian/patches/84_23-CVE-2020-28007-Link-attack-in-Exim-s-log-directory.patch new file mode 100644 index 0000000..211abe3 --- /dev/null +++ b/debian/patches/84_23-CVE-2020-28007-Link-attack-in-Exim-s-log-directory.patch @@ -0,0 +1,542 @@ +From 99ae249e9857e80ff4d65b2388bc68c624dcb739 Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory <qsa@qualys.com> +Date: Tue, 23 Feb 2021 08:33:03 -0800 +Subject: [PATCH 23/29] CVE-2020-28007: Link attack in Exim's log directory + +We patch this vulnerability by opening (instead of just creating) the +log file in an unprivileged (exim) child process, and by passing this +file descriptor back to the privileged (root) parent process. The two +functions log_send_fd() and log_recv_fd() are inspired by OpenSSH's +functions mm_send_fd() and mm_receive_fd(); thanks! + +This patch also fixes: + +- a NULL-pointer dereference in usr1_handler() (this signal handler is + installed before process_log_path is initialized); + +- a file-descriptor leak in dmarc_write_history_file() (two return paths + did not close history_file_fd). + +Note: the use of log_open_as_exim() in dmarc_write_history_file() should +be fine because the documentation explicitly states "Make sure the +directory of this file is writable by the user exim runs as." + +(cherry picked from commit 2502cc41d1d92c1413eca6a4ba035c21162662bd) +(cherry picked from commit 93e9a18fbf09deb59bd133986f4c89aeb2d2d86a) +--- + src/dmarc.c | 179 ++++++++++++++++++------------------ + src/exim.c | 14 +-- + src/functions.h | 3 +- + src/log.c | 214 ++++++++++++++++++++++++++++---------------- + test/stderr/0397 | 6 +- + 5 files changed, 234 insertions(+), 182 deletions(-) + +diff --git a/src/dmarc.c b/src/dmarc.c +index f29f7eba6..c5e01c7ee 100644 +--- a/src/dmarc.c ++++ b/src/dmarc.c +@@ -204,6 +204,97 @@ if ( dmarc_policy == DMARC_POLICY_REJECT && action == DMARC_RESULT_REJECT + } + } + ++ ++static int ++dmarc_write_history_file() ++{ ++int tmp_ans; ++u_char **rua; /* aggregate report addressees */ ++uschar *history_buffer = NULL; ++ ++if (!dmarc_history_file) ++ { ++ DEBUG(D_receive) debug_printf("DMARC history file not set\n"); ++ return DMARC_HIST_DISABLED; ++ } ++ ++/* Generate the contents of the history file */ ++history_buffer = string_sprintf( ++ "job %s\nreporter %s\nreceived %ld\nipaddr %s\nfrom %s\nmfrom %s\n", ++ message_id, primary_hostname, time(NULL), sender_host_address, ++ header_from_sender, expand_string(US"$sender_address_domain")); ++ ++if (spf_response) ++ history_buffer = string_sprintf("%sspf %d\n", history_buffer, dmarc_spf_ares_result); ++ /* history_buffer = string_sprintf("%sspf -1\n", history_buffer); */ ++ ++history_buffer = string_sprintf( ++ "%s%spdomain %s\npolicy %d\n", ++ history_buffer, dkim_history_buffer, dmarc_used_domain, dmarc_policy); ++ ++if ((rua = opendmarc_policy_fetch_rua(dmarc_pctx, NULL, 0, 1))) ++ for (tmp_ans = 0; rua[tmp_ans]; tmp_ans++) ++ history_buffer = string_sprintf("%srua %s\n", history_buffer, rua[tmp_ans]); ++else ++ history_buffer = string_sprintf("%srua -\n", history_buffer); ++ ++opendmarc_policy_fetch_pct(dmarc_pctx, &tmp_ans); ++history_buffer = string_sprintf("%spct %d\n", history_buffer, tmp_ans); ++ ++opendmarc_policy_fetch_adkim(dmarc_pctx, &tmp_ans); ++history_buffer = string_sprintf("%sadkim %d\n", history_buffer, tmp_ans); ++ ++opendmarc_policy_fetch_aspf(dmarc_pctx, &tmp_ans); ++history_buffer = string_sprintf("%saspf %d\n", history_buffer, tmp_ans); ++ ++opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans); ++history_buffer = string_sprintf("%sp %d\n", history_buffer, tmp_ans); ++ ++opendmarc_policy_fetch_sp(dmarc_pctx, &tmp_ans); ++history_buffer = string_sprintf("%ssp %d\n", history_buffer, tmp_ans); ++ ++history_buffer = string_sprintf( ++ "%salign_dkim %d\nalign_spf %d\naction %d\n", ++ history_buffer, da, sa, action); ++ ++/* Write the contents to the history file */ ++DEBUG(D_receive) ++ debug_printf("DMARC logging history data for opendmarc reporting%s\n", ++ (host_checking || f.running_in_test_harness) ? " (not really)" : ""); ++if (host_checking || f.running_in_test_harness) ++ { ++ DEBUG(D_receive) ++ debug_printf("DMARC history data for debugging:\n%s", history_buffer); ++ } ++else ++ { ++ ssize_t written_len; ++ const int history_file_fd = log_open_as_exim(dmarc_history_file); ++ ++ if (history_file_fd < 0) ++ { ++ log_write(0, LOG_MAIN|LOG_PANIC, "failure to create DMARC history file: %s", ++ dmarc_history_file); ++ return DMARC_HIST_FILE_ERR; ++ } ++ ++ written_len = write_to_fd_buf(history_file_fd, ++ history_buffer, ++ Ustrlen(history_buffer)); ++ ++ (void)close(history_file_fd); ++ ++ if (written_len <= 0) ++ { ++ log_write(0, LOG_MAIN|LOG_PANIC, "failure to write to DMARC history file: %s", ++ dmarc_history_file); ++ return DMARC_HIST_WRITE_ERR; ++ } ++ } ++return DMARC_HIST_OK; ++} ++ ++ + /* dmarc_process adds the envelope sender address to the existing + context (if any), retrieves the result, sets up expansion + strings and evaluates the condition outcome. */ +@@ -486,94 +577,6 @@ if (!f.dmarc_disable_verify) + return OK; + } + +-static int +-dmarc_write_history_file() +-{ +-int history_file_fd; +-ssize_t written_len; +-int tmp_ans; +-u_char **rua; /* aggregate report addressees */ +-uschar *history_buffer = NULL; +- +-if (!dmarc_history_file) +- { +- DEBUG(D_receive) debug_printf("DMARC history file not set\n"); +- return DMARC_HIST_DISABLED; +- } +-history_file_fd = log_create(dmarc_history_file); +- +-if (history_file_fd < 0) +- { +- log_write(0, LOG_MAIN|LOG_PANIC, "failure to create DMARC history file: %s", +- dmarc_history_file); +- return DMARC_HIST_FILE_ERR; +- } +- +-/* Generate the contents of the history file */ +-history_buffer = string_sprintf( +- "job %s\nreporter %s\nreceived %ld\nipaddr %s\nfrom %s\nmfrom %s\n", +- message_id, primary_hostname, time(NULL), sender_host_address, +- header_from_sender, expand_string(US"$sender_address_domain")); +- +-if (spf_response) +- history_buffer = string_sprintf("%sspf %d\n", history_buffer, dmarc_spf_ares_result); +- /* history_buffer = string_sprintf("%sspf -1\n", history_buffer); */ +- +-history_buffer = string_sprintf( +- "%s%spdomain %s\npolicy %d\n", +- history_buffer, dkim_history_buffer, dmarc_used_domain, dmarc_policy); +- +-if ((rua = opendmarc_policy_fetch_rua(dmarc_pctx, NULL, 0, 1))) +- for (tmp_ans = 0; rua[tmp_ans]; tmp_ans++) +- history_buffer = string_sprintf("%srua %s\n", history_buffer, rua[tmp_ans]); +-else +- history_buffer = string_sprintf("%srua -\n", history_buffer); +- +-opendmarc_policy_fetch_pct(dmarc_pctx, &tmp_ans); +-history_buffer = string_sprintf("%spct %d\n", history_buffer, tmp_ans); +- +-opendmarc_policy_fetch_adkim(dmarc_pctx, &tmp_ans); +-history_buffer = string_sprintf("%sadkim %d\n", history_buffer, tmp_ans); +- +-opendmarc_policy_fetch_aspf(dmarc_pctx, &tmp_ans); +-history_buffer = string_sprintf("%saspf %d\n", history_buffer, tmp_ans); +- +-opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans); +-history_buffer = string_sprintf("%sp %d\n", history_buffer, tmp_ans); +- +-opendmarc_policy_fetch_sp(dmarc_pctx, &tmp_ans); +-history_buffer = string_sprintf("%ssp %d\n", history_buffer, tmp_ans); +- +-history_buffer = string_sprintf( +- "%salign_dkim %d\nalign_spf %d\naction %d\n", +- history_buffer, da, sa, action); +- +-/* Write the contents to the history file */ +-DEBUG(D_receive) +- debug_printf("DMARC logging history data for opendmarc reporting%s\n", +- (host_checking || f.running_in_test_harness) ? " (not really)" : ""); +-if (host_checking || f.running_in_test_harness) +- { +- DEBUG(D_receive) +- debug_printf("DMARC history data for debugging:\n%s", history_buffer); +- } +-else +- { +- written_len = write_to_fd_buf(history_file_fd, +- history_buffer, +- Ustrlen(history_buffer)); +- if (written_len == 0) +- { +- log_write(0, LOG_MAIN|LOG_PANIC, "failure to write to DMARC history file: %s", +- dmarc_history_file); +- return DMARC_HIST_WRITE_ERR; +- } +- (void)close(history_file_fd); +- } +-return DMARC_HIST_OK; +-} +- +- + uschar * + dmarc_exim_expand_query(int what) + { +diff --git a/src/exim.c b/src/exim.c +index a7dc48c4e..f0a168983 100644 +--- a/src/exim.c ++++ b/src/exim.c +@@ -227,18 +227,8 @@ int fd; + + os_restarting_signal(sig, usr1_handler); + +-if ((fd = Uopen(process_log_path, O_APPEND|O_WRONLY, LOG_MODE)) < 0) +- { +- /* If we are already running as the Exim user, try to create it in the +- current process (assuming spool_directory exists). Otherwise, if we are +- root, do the creation in an exim:exim subprocess. */ +- +- int euid = geteuid(); +- if (euid == exim_uid) +- fd = Uopen(process_log_path, O_CREAT|O_APPEND|O_WRONLY, LOG_MODE); +- else if (euid == root_uid) +- fd = log_create_as_exim(process_log_path); +- } ++if (!process_log_path) return; ++fd = log_open_as_exim(process_log_path); + + /* If we are neither exim nor root, or if we failed to create the log file, + give up. There is not much useful we can do with errors, since we don't want +diff --git a/src/functions.h b/src/functions.h +index cab7a7363..366cb2f26 100644 +--- a/src/functions.h ++++ b/src/functions.h +@@ -281,8 +281,7 @@ extern int ip_streamsocket(const uschar *, uschar **, int); + extern int ipv6_nmtoa(int *, uschar *); + + extern uschar *local_part_quote(uschar *); +-extern int log_create(uschar *); +-extern int log_create_as_exim(uschar *); ++extern int log_open_as_exim(uschar *); + extern void log_close_all(void); + + extern macro_item * macro_create(const uschar *, const uschar *, BOOL); +diff --git a/src/log.c b/src/log.c +index c8313890e..15c88c13e 100644 +--- a/src/log.c ++++ b/src/log.c +@@ -264,14 +264,19 @@ overwrite it temporarily if it is necessary to create the directory. + Returns: a file descriptor, or < 0 on failure (errno set) + */ + +-int +-log_create(uschar *name) ++static int ++log_open_already_exim(uschar * const name) + { +-int fd = Uopen(name, +-#ifdef O_CLOEXEC +- O_CLOEXEC | +-#endif +- O_CREAT|O_APPEND|O_WRONLY, LOG_MODE); ++int fd = -1; ++const int flags = O_WRONLY | O_APPEND | O_CREAT | O_NONBLOCK; ++ ++if (geteuid() != exim_uid) ++ { ++ errno = EACCES; ++ return -1; ++ } ++ ++fd = Uopen(name, flags, LOG_MODE); + + /* If creation failed, attempt to build a log directory in case that is the + problem. */ +@@ -285,11 +290,7 @@ if (fd < 0 && errno == ENOENT) + DEBUG(D_any) debug_printf("%s log directory %s\n", + created ? "created" : "failed to create", name); + *lastslash = '/'; +- if (created) fd = Uopen(name, +-#ifdef O_CLOEXEC +- O_CLOEXEC | +-#endif +- O_CREAT|O_APPEND|O_WRONLY, LOG_MODE); ++ if (created) fd = Uopen(name, flags, LOG_MODE); + } + + return fd; +@@ -297,6 +298,81 @@ return fd; + + + ++/* Inspired by OpenSSH's mm_send_fd(). Thanks! */ ++ ++static int ++log_send_fd(const int sock, const int fd) ++{ ++struct msghdr msg; ++union { ++ struct cmsghdr hdr; ++ char buf[CMSG_SPACE(sizeof(int))]; ++} cmsgbuf; ++struct cmsghdr *cmsg; ++struct iovec vec; ++char ch = 'A'; ++ssize_t n; ++ ++memset(&msg, 0, sizeof(msg)); ++memset(&cmsgbuf, 0, sizeof(cmsgbuf)); ++msg.msg_control = &cmsgbuf.buf; ++msg.msg_controllen = sizeof(cmsgbuf.buf); ++ ++cmsg = CMSG_FIRSTHDR(&msg); ++cmsg->cmsg_len = CMSG_LEN(sizeof(int)); ++cmsg->cmsg_level = SOL_SOCKET; ++cmsg->cmsg_type = SCM_RIGHTS; ++*(int *)CMSG_DATA(cmsg) = fd; ++ ++vec.iov_base = &ch; ++vec.iov_len = 1; ++msg.msg_iov = &vec; ++msg.msg_iovlen = 1; ++ ++while ((n = sendmsg(sock, &msg, 0)) == -1 && errno == EINTR); ++if (n != 1) return -1; ++return 0; ++} ++ ++/* Inspired by OpenSSH's mm_receive_fd(). Thanks! */ ++ ++static int ++log_recv_fd(const int sock) ++{ ++struct msghdr msg; ++union { ++ struct cmsghdr hdr; ++ char buf[CMSG_SPACE(sizeof(int))]; ++} cmsgbuf; ++struct cmsghdr *cmsg; ++struct iovec vec; ++ssize_t n; ++char ch = '\0'; ++int fd = -1; ++ ++memset(&msg, 0, sizeof(msg)); ++vec.iov_base = &ch; ++vec.iov_len = 1; ++msg.msg_iov = &vec; ++msg.msg_iovlen = 1; ++ ++memset(&cmsgbuf, 0, sizeof(cmsgbuf)); ++msg.msg_control = &cmsgbuf.buf; ++msg.msg_controllen = sizeof(cmsgbuf.buf); ++ ++while ((n = recvmsg(sock, &msg, 0)) == -1 && errno == EINTR); ++if (n != 1 || ch != 'A') return -1; ++ ++cmsg = CMSG_FIRSTHDR(&msg); ++if (cmsg == NULL) return -1; ++if (cmsg->cmsg_type != SCM_RIGHTS) return -1; ++fd = *(const int *)CMSG_DATA(cmsg); ++if (fd < 0) return -1; ++return fd; ++} ++ ++ ++ + /************************************************* + * Create a log file as the exim user * + *************************************************/ +@@ -312,41 +388,60 @@ Returns: a file descriptor, or < 0 on failure (errno set) + */ + + int +-log_create_as_exim(uschar *name) ++log_open_as_exim(uschar * const name) + { +-pid_t pid = fork(); +-int status = 1; + int fd = -1; ++const uid_t euid = geteuid(); + +-/* In the subprocess, change uid/gid and do the creation. Return 0 from the +-subprocess on success. If we don't check for setuid failures, then the file +-can be created as root, so vulnerabilities which cause setuid to fail mean +-that the Exim user can use symlinks to cause a file to be opened/created as +-root. We always open for append, so can't nuke existing content but it would +-still be Rather Bad. */ +- +-if (pid == 0) ++if (euid == exim_uid) + { +- if (setgid(exim_gid) < 0) +- die(US"exim: setgid for log-file creation failed, aborting", +- US"Unexpected log failure, please try later"); +- if (setuid(exim_uid) < 0) +- die(US"exim: setuid for log-file creation failed, aborting", +- US"Unexpected log failure, please try later"); +- _exit((log_create(name) < 0)? 1 : 0); ++ fd = log_open_already_exim(name); + } ++else if (euid == root_uid) ++ { ++ int sock[2]; ++ if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock) == 0) ++ { ++ const pid_t pid = fork(); ++ if (pid == 0) ++ { ++ (void)close(sock[0]); ++ if (setgroups(1, &exim_gid) != 0) _exit(EXIT_FAILURE); ++ if (setgid(exim_gid) != 0) _exit(EXIT_FAILURE); ++ if (setuid(exim_uid) != 0) _exit(EXIT_FAILURE); ++ ++ if (getuid() != exim_uid || geteuid() != exim_uid) _exit(EXIT_FAILURE); ++ if (getgid() != exim_gid || getegid() != exim_gid) _exit(EXIT_FAILURE); ++ ++ fd = log_open_already_exim(name); ++ if (fd < 0) _exit(EXIT_FAILURE); ++ if (log_send_fd(sock[1], fd) != 0) _exit(EXIT_FAILURE); ++ (void)close(sock[1]); ++ _exit(EXIT_SUCCESS); ++ } + +-/* If we created a subprocess, wait for it. If it succeeded, try the open. */ +- +-while (pid > 0 && waitpid(pid, &status, 0) != pid); +-if (status == 0) fd = Uopen(name, +-#ifdef O_CLOEXEC +- O_CLOEXEC | +-#endif +- O_APPEND|O_WRONLY, LOG_MODE); ++ (void)close(sock[1]); ++ if (pid > 0) ++ { ++ fd = log_recv_fd(sock[0]); ++ while (waitpid(pid, NULL, 0) == -1 && errno == EINTR); ++ } ++ (void)close(sock[0]); ++ } ++ } + +-/* If we failed to create a subprocess, we are in a bad way. We return +-with fd still < 0, and errno set, letting the caller handle the error. */ ++if (fd >= 0) ++ { ++ int flags; ++ flags = fcntl(fd, F_GETFD); ++ if (flags != -1) (void)fcntl(fd, F_SETFD, flags | FD_CLOEXEC); ++ flags = fcntl(fd, F_GETFL); ++ if (flags != -1) (void)fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); ++ } ++else ++ { ++ errno = EACCES; ++ } + + return fd; + } +@@ -459,52 +554,17 @@ if (!ok) + die(US"exim: log file path too long: aborting", + US"Logging failure; please try later"); + +-/* We now have the file name. Try to open an existing file. After a successful +-open, arrange for automatic closure on exec(), and then return. */ ++/* We now have the file name. After a successful open, return. */ + +-*fd = Uopen(buffer, +-#ifdef O_CLOEXEC +- O_CLOEXEC | +-#endif +- O_APPEND|O_WRONLY, LOG_MODE); ++*fd = log_open_as_exim(buffer); + + if (*fd >= 0) + { +-#ifndef O_CLOEXEC +- (void)fcntl(*fd, F_SETFD, fcntl(*fd, F_GETFD) | FD_CLOEXEC); +-#endif + return; + } + +-/* Open was not successful: try creating the file. If this is a root process, +-we must do the creating in a subprocess set to exim:exim in order to ensure +-that the file is created with the right ownership. Otherwise, there can be a +-race if another Exim process is trying to write to the log at the same time. +-The use of SIGUSR1 by the exiwhat utility can provoke a lot of simultaneous +-writing. */ +- + euid = geteuid(); + +-/* If we are already running as the Exim user (even if that user is root), +-we can go ahead and create in the current process. */ +- +-if (euid == exim_uid) *fd = log_create(buffer); +- +-/* Otherwise, if we are root, do the creation in an exim:exim subprocess. If we +-are neither exim nor root, creation is not attempted. */ +- +-else if (euid == root_uid) *fd = log_create_as_exim(buffer); +- +-/* If we now have an open file, set the close-on-exec flag and return. */ +- +-if (*fd >= 0) +- { +-#ifndef O_CLOEXEC +- (void)fcntl(*fd, F_SETFD, fcntl(*fd, F_GETFD) | FD_CLOEXEC); +-#endif +- return; +- } +- + /* Creation failed. There are some circumstances in which we get here when + the effective uid is not root or exim, which is the problem. (For example, a + non-setuid binary with log_arguments set, called in certain ways.) Rather than +-- +2.30.2 + diff --git a/debian/patches/84_24-CVE-2020-28008-Assorted-attacks-in-Exim-s-spool-dire.patch b/debian/patches/84_24-CVE-2020-28008-Assorted-attacks-in-Exim-s-spool-dire.patch new file mode 100644 index 0000000..2bda99c --- /dev/null +++ b/debian/patches/84_24-CVE-2020-28008-Assorted-attacks-in-Exim-s-spool-dire.patch @@ -0,0 +1,205 @@ +From 5fec3406547fd1e46838a76f000102beb6bfe468 Mon Sep 17 00:00:00 2001 +From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de> +Date: Sun, 14 Mar 2021 12:16:57 +0100 +Subject: [PATCH 24/29] CVE-2020-28008: Assorted attacks in Exim's spool + directory + +We patch dbfn_open() by introducing two functions priv_drop_temp() and +priv_restore() (inspired by OpenSSH's functions temporarily_use_uid() +and restore_uid()), which temporarily drop and restore root privileges +thanks to seteuid(). This goes against Exim's developers' wishes ("Exim +(the project) doesn't trust seteuid to work reliably") but, to the best +of our knowledge, seteuid() works everywhere and is the only way to +securely fix dbfn_open(). + +(cherry picked from commit 18da59151dbafa89be61c63580bdb295db36e374) +(cherry picked from commit b05dc3573f4cd476482374b0ac0393153d344338) +--- + doc/ChangeLog | 6 +++ + src/dbfn.c | 110 +++++++++++++++++++++++++----------------- + test/stderr/0275 | 2 +- + test/stderr/0278 | 2 +- + test/stderr/0386 | 2 +- + test/stderr/0388 | 2 +- + test/stderr/0402 | 2 +- + test/stderr/0403 | 2 +- + test/stderr/0404 | 2 +- + test/stderr/0408 | 2 +- + test/stderr/0487 | 2 +- + 11 files changed, 80 insertions(+), 54 deletions(-) + +diff --git a/doc/ChangeLog b/doc/ChangeLog +index 5741fb212..b32347c5b 100644 +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -14,6 +14,12 @@ JH/42 Bug 2545: Fix CHUNKING for all RCPT commands rejected. Previously we + + Exim version 4.92.2 + ------------------- ++QS/02 PID file creation/deletion: only possible if uid=0 or uid is the Exim ++ runtime user. ++ ++QS/01 Creation of (database) files in $spool_dir: only uid=0 or the euid of ++ the Exim runtime user are allowed to create files. ++ + + HS/01 Handle trailing backslash gracefully. (CVE-2019-15846) + +diff --git a/src/dbfn.c b/src/dbfn.c +index 336cfe73e..902756508 100644 +--- a/src/dbfn.c ++++ b/src/dbfn.c +@@ -59,6 +59,66 @@ log_write(0, LOG_MAIN, "Berkeley DB error: %s", msg); + + + ++static enum { ++ PRIV_DROPPING, PRIV_DROPPED, ++ PRIV_RESTORING, PRIV_RESTORED ++} priv_state = PRIV_RESTORED; ++ ++static uid_t priv_euid; ++static gid_t priv_egid; ++static gid_t priv_groups[EXIM_GROUPLIST_SIZE + 1]; ++static int priv_ngroups; ++ ++/* Inspired by OpenSSH's temporarily_use_uid(). Thanks! */ ++ ++static void ++priv_drop_temp(const uid_t temp_uid, const gid_t temp_gid) ++{ ++if (priv_state != PRIV_RESTORED) _exit(EXIT_FAILURE); ++priv_state = PRIV_DROPPING; ++ ++priv_euid = geteuid(); ++if (priv_euid == root_uid) ++ { ++ priv_egid = getegid(); ++ priv_ngroups = getgroups(nelem(priv_groups), priv_groups); ++ if (priv_ngroups < 0) _exit(EXIT_FAILURE); ++ ++ if (priv_ngroups > 0 && setgroups(1, &temp_gid) != 0) _exit(EXIT_FAILURE); ++ if (setegid(temp_gid) != 0) _exit(EXIT_FAILURE); ++ if (seteuid(temp_uid) != 0) _exit(EXIT_FAILURE); ++ ++ if (geteuid() != temp_uid) _exit(EXIT_FAILURE); ++ if (getegid() != temp_gid) _exit(EXIT_FAILURE); ++ } ++ ++priv_state = PRIV_DROPPED; ++} ++ ++/* Inspired by OpenSSH's restore_uid(). Thanks! */ ++ ++static void ++priv_restore(void) ++{ ++if (priv_state != PRIV_DROPPED) _exit(EXIT_FAILURE); ++priv_state = PRIV_RESTORING; ++ ++if (priv_euid == root_uid) ++ { ++ if (seteuid(priv_euid) != 0) _exit(EXIT_FAILURE); ++ if (setegid(priv_egid) != 0) _exit(EXIT_FAILURE); ++ if (priv_ngroups > 0 && setgroups(priv_ngroups, priv_groups) != 0) _exit(EXIT_FAILURE); ++ ++ if (geteuid() != priv_euid) _exit(EXIT_FAILURE); ++ if (getegid() != priv_egid) _exit(EXIT_FAILURE); ++ } ++ ++priv_state = PRIV_RESTORED; ++} ++ ++ ++ ++ + /************************************************* + * Open and lock a database file * + *************************************************/ +@@ -89,7 +149,6 @@ dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof) + { + int rc, save_errno; + BOOL read_only = flags == O_RDONLY; +-BOOL created = FALSE; + flock_t lock_data; + uschar dirname[256], filename[256]; + +@@ -111,12 +170,13 @@ exists, there is no error. */ + snprintf(CS dirname, sizeof(dirname), "%s/db", spool_directory); + snprintf(CS filename, sizeof(filename), "%s/%s.lockfile", dirname, name); + ++priv_drop_temp(exim_uid, exim_gid); + if ((dbblock->lockfd = Uopen(filename, O_RDWR, EXIMDB_LOCKFILE_MODE)) < 0) + { +- created = TRUE; + (void)directory_make(spool_directory, US"db", EXIMDB_DIRECTORY_MODE, TRUE); + dbblock->lockfd = Uopen(filename, O_RDWR|O_CREAT, EXIMDB_LOCKFILE_MODE); + } ++priv_restore(); + + if (dbblock->lockfd < 0) + { +@@ -165,57 +225,17 @@ it easy to pin this down, there are now debug statements on either side of the + open call. */ + + snprintf(CS filename, sizeof(filename), "%s/%s", dirname, name); +-EXIM_DBOPEN(filename, dirname, flags, EXIMDB_MODE, &(dbblock->dbptr)); + ++priv_drop_temp(exim_uid, exim_gid); ++EXIM_DBOPEN(filename, dirname, flags, EXIMDB_MODE, &(dbblock->dbptr)); + if (!dbblock->dbptr && errno == ENOENT && flags == O_RDWR) + { + DEBUG(D_hints_lookup) + debug_printf_indent("%s appears not to exist: trying to create\n", filename); +- created = TRUE; + EXIM_DBOPEN(filename, dirname, flags|O_CREAT, EXIMDB_MODE, &(dbblock->dbptr)); + } +- + save_errno = errno; +- +-/* If we are running as root and this is the first access to the database, its +-files will be owned by root. We want them to be owned by exim. We detect this +-situation by noting above when we had to create the lock file or the database +-itself. Because the different dbm libraries use different extensions for their +-files, I don't know of any easier way of arranging this than scanning the +-directory for files with the appropriate base name. At least this deals with +-the lock file at the same time. Also, the directory will typically have only +-half a dozen files, so the scan will be quick. +- +-This code is placed here, before the test for successful opening, because there +-was a case when a file was created, but the DBM library still returned NULL +-because of some problem. It also sorts out the lock file if that was created +-but creation of the database file failed. */ +- +-if (created && geteuid() == root_uid) +- { +- DIR *dd; +- struct dirent *ent; +- uschar *lastname = Ustrrchr(filename, '/') + 1; +- int namelen = Ustrlen(name); +- +- *lastname = 0; +- dd = opendir(CS filename); +- +- while ((ent = readdir(dd))) +- if (Ustrncmp(ent->d_name, name, namelen) == 0) +- { +- struct stat statbuf; +- Ustrcpy(lastname, ent->d_name); +- if (Ustat(filename, &statbuf) >= 0 && statbuf.st_uid != exim_uid) +- { +- DEBUG(D_hints_lookup) debug_printf_indent("ensuring %s is owned by exim\n", filename); +- if (Uchown(filename, exim_uid, exim_gid)) +- DEBUG(D_hints_lookup) debug_printf_indent("failed setting %s to owned by exim\n", filename); +- } +- } +- +- closedir(dd); +- } ++priv_restore(); + + /* If the open has failed, return NULL, leaving errno set. If lof is TRUE, + log the event - also for debugging - but debug only if the file just doesn't +-- +2.30.2 + diff --git a/debian/patches/84_26-CVE-2020-28014-CVE-2021-27216-Arbitrary-PID-file-cre.patch b/debian/patches/84_26-CVE-2020-28014-CVE-2021-27216-Arbitrary-PID-file-cre.patch new file mode 100644 index 0000000..3e73ae1 --- /dev/null +++ b/debian/patches/84_26-CVE-2020-28014-CVE-2021-27216-Arbitrary-PID-file-cre.patch @@ -0,0 +1,303 @@ +From c166890023f56388cb3482cff3def04171a488c4 Mon Sep 17 00:00:00 2001 +From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de> +Date: Thu, 25 Mar 2021 22:48:09 +0100 +Subject: [PATCH 26/29] CVE-2020-28014, CVE-2021-27216: Arbitrary PID file + creation, clobbering, and deletion + +Arbitrary PID file creation, clobbering, and deletion. +Patch provided by Qualys. + +(cherry picked from commit 974f32939a922512b27d9f0a8a1cb5dec60e7d37) +(cherry picked from commit 43c6f0b83200b7082353c50187ef75de3704580a) +--- + doc/ChangeLog | 5 + + src/daemon.c | 212 ++++++++++++++++++++++++++++++++++++++---- + src/exim.c | 12 ++- + test/stderr/0433 | 24 +++++ + 4 files changed, 232 insertions(+), 21 deletions(-) + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -10,13 +10,18 @@ QS/02 PID file creation/deletion: only p + runtime user. + + QS/01 Creation of (database) files in $spool_dir: only uid=0 or the euid of + the Exim runtime user are allowed to create files. + ++QS/01 Creation of (database) files in $spool_dir: only uid=0 or the uid of ++ the Exim runtime user are allowed to create files. + + HS/01 Handle trailing backslash gracefully. (CVE-2019-15846) + ++QS/02 PID file creation/deletion: only possible if uid=0 or uid is the Exim ++ runtime user. ++ + + Since version 4.92 + ------------------ + + JH/06 Fix buggy handling of autoreply bounce_return_size_limit, and a possible +--- a/src/daemon.c ++++ b/src/daemon.c +@@ -886,10 +886,198 @@ while ((pid = waitpid(-1, &status, WNOHA + } + } + } + + ++static void ++set_pid_file_path(void) ++{ ++if (override_pid_file_path) ++ pid_file_path = override_pid_file_path; ++ ++if (!*pid_file_path) ++ pid_file_path = string_sprintf("%s/exim-daemon.pid", spool_directory); ++ ++if (pid_file_path[0] != '/') ++ log_write(0, LOG_PANIC_DIE, "pid file path %s must be absolute\n", pid_file_path); ++} ++ ++ ++enum pid_op { PID_WRITE, PID_CHECK, PID_DELETE }; ++ ++/* Do various pid file operations as safe as possible. Ideally we'd just ++drop the privileges for creation of the pid file and not care at all about removal of ++the file. FIXME. ++Returns: true on success, false + errno==EACCES otherwise ++*/ ++static BOOL ++operate_on_pid_file(const enum pid_op operation, const pid_t pid) ++{ ++char pid_line[sizeof(int) * 3 + 2]; ++const int pid_len = snprintf(pid_line, sizeof(pid_line), "%d\n", (int)pid); ++BOOL lines_match = FALSE; ++ ++char * path = NULL; ++char * base = NULL; ++char * dir = NULL; ++ ++const int dir_flags = O_RDONLY | O_NONBLOCK; ++const int base_flags = O_NOFOLLOW | O_NONBLOCK; ++const mode_t base_mode = 0644; ++struct stat sb; ++ ++int cwd_fd = -1; ++int dir_fd = -1; ++int base_fd = -1; ++ ++BOOL success = FALSE; ++errno = EACCES; ++ ++set_pid_file_path(); ++if (!f.running_in_test_harness && real_uid != root_uid && real_uid != exim_uid) goto cleanup; ++if (pid_len < 2 || pid_len >= (int)sizeof(pid_line)) goto cleanup; ++ ++path = CS string_copy(pid_file_path); ++if ((base = Ustrrchr(path, '/')) == NULL) /* should not happen, but who knows */ ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "pid file path \"%s\" does not contain a '/'", pid_file_path); ++ ++dir = (base != path) ? path : "/"; ++*base++ = '\0'; ++ ++if (!dir || !*dir || *dir != '/') goto cleanup; ++if (!base || !*base || strchr(base, '/') != NULL) goto cleanup; ++ ++cwd_fd = open(".", dir_flags); ++if (cwd_fd < 0 || fstat(cwd_fd, &sb) != 0 || !S_ISDIR(sb.st_mode)) goto cleanup; ++dir_fd = open(dir, dir_flags); ++if (dir_fd < 0 || fstat(dir_fd, &sb) != 0 || !S_ISDIR(sb.st_mode)) goto cleanup; ++ ++/* emulate openat */ ++if (fchdir(dir_fd) != 0) goto cleanup; ++base_fd = open(base, O_RDONLY | base_flags); ++if (fchdir(cwd_fd) != 0) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno)); ++ ++if (base_fd >= 0) ++ { ++ char line[sizeof(pid_line)]; ++ ssize_t len = -1; ++ ++ if (fstat(base_fd, &sb) != 0 || !S_ISREG(sb.st_mode)) goto cleanup; ++ if ((sb.st_mode & 07777) != base_mode || sb.st_nlink != 1) goto cleanup; ++ if (sb.st_size < 2 || sb.st_size >= (off_t)sizeof(line)) goto cleanup; ++ ++ len = read(base_fd, line, sizeof(line)); ++ if (len != (ssize_t)sb.st_size) goto cleanup; ++ line[len] = '\0'; ++ ++ if (strspn(line, "0123456789") != (size_t)len-1) goto cleanup; ++ if (line[len-1] != '\n') goto cleanup; ++ lines_match = (len == pid_len && strcmp(line, pid_line) == 0); ++ } ++ ++if (operation == PID_WRITE) ++ { ++ if (!lines_match) ++ { ++ if (base_fd >= 0) ++ { ++ int error = -1; ++ /* emulate unlinkat */ ++ if (fchdir(dir_fd) != 0) goto cleanup; ++ error = unlink(base); ++ if (fchdir(cwd_fd) != 0) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno)); ++ if (error) goto cleanup; ++ (void)close(base_fd); ++ base_fd = -1; ++ } ++ /* emulate openat */ ++ if (fchdir(dir_fd) != 0) goto cleanup; ++ base_fd = open(base, O_WRONLY | O_CREAT | O_EXCL | base_flags, base_mode); ++ if (fchdir(cwd_fd) != 0) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno)); ++ if (base_fd < 0) goto cleanup; ++ if (fchmod(base_fd, base_mode) != 0) goto cleanup; ++ if (write(base_fd, pid_line, pid_len) != pid_len) goto cleanup; ++ DEBUG(D_any) debug_printf("pid written to %s\n", pid_file_path); ++ } ++ } ++else ++ { ++ if (!lines_match) goto cleanup; ++ if (operation == PID_DELETE) ++ { ++ int error = -1; ++ /* emulate unlinkat */ ++ if (fchdir(dir_fd) != 0) goto cleanup; ++ error = unlink(base); ++ if (fchdir(cwd_fd) != 0) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno)); ++ if (error) goto cleanup; ++ } ++ } ++ ++success = TRUE; ++errno = 0; ++ ++cleanup: ++if (cwd_fd >= 0) (void)close(cwd_fd); ++if (dir_fd >= 0) (void)close(dir_fd); ++if (base_fd >= 0) (void)close(base_fd); ++return success; ++} ++ ++ ++/* Remove the daemon's pidfile. Note: runs with root privilege, ++as a direct child of the daemon. Does not return. */ ++ ++void ++delete_pid_file(void) ++{ ++const BOOL success = operate_on_pid_file(PID_DELETE, getppid()); ++ ++DEBUG(D_any) ++ debug_printf("delete pid file %s %s: %s\n", pid_file_path, ++ success ? "success" : "failure", strerror(errno)); ++ ++exim_exit(EXIT_SUCCESS, US""); ++} ++ ++ ++/* Called by the daemon; exec a child to get the pid file deleted ++since we may require privs for the containing directory */ ++ ++static void ++daemon_die(void) ++{ ++int pid; ++ ++DEBUG(D_any) debug_printf("SIGTERM/SIGINT seen\n"); ++#if defined(SUPPORT_TLS) && (defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)) ++tls_watch_invalidate(); ++#endif ++ ++if (f.running_in_test_harness || write_pid) ++ { ++ if ((pid = fork()) == 0) ++ { ++ if (override_pid_file_path) ++ (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, FALSE, 3, ++ "-oP", override_pid_file_path, "-oPX"); ++ else ++ (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, FALSE, 1, "-oPX"); ++ ++ /* Control never returns here. */ ++ } ++ if (pid > 0) ++ child_close(pid, 1); ++ } ++exim_exit(EXIT_SUCCESS, US""); ++} ++ ++ + + /************************************************* + * Exim Daemon Mainline * + *************************************************/ + +@@ -1538,32 +1726,18 @@ automatically. Consequently, Exim 4 writ + + The variable daemon_write_pid is used to control this. */ + + if (f.running_in_test_harness || write_pid) + { +- FILE *f; +- +- if (override_pid_file_path) +- pid_file_path = override_pid_file_path; +- +- if (pid_file_path[0] == 0) +- pid_file_path = string_sprintf("%s/exim-daemon.pid", spool_directory); +- +- if ((f = modefopen(pid_file_path, "wb", 0644))) +- { +- (void)fprintf(f, "%d\n", (int)getpid()); +- (void)fclose(f); +- DEBUG(D_any) debug_printf("pid written to %s\n", pid_file_path); +- } +- else +- DEBUG(D_any) +- debug_printf("%s\n", string_open_failed(errno, "pid file %s", +- pid_file_path)); ++ const enum pid_op operation = (f.running_in_test_harness ++ || real_uid == root_uid ++ || (real_uid == exim_uid && !override_pid_file_path)) ? PID_WRITE : PID_CHECK; ++ if (!operate_on_pid_file(operation, getpid())) ++ DEBUG(D_any) debug_printf("%s pid file %s: %s\n", (operation == PID_WRITE) ? "write" : "check", pid_file_path, strerror(errno)); + } + + /* Set up the handler for SIGHUP, which causes a restart of the daemon. */ +- + sighup_seen = FALSE; + signal(SIGHUP, sighup_handler); + + /* Give up root privilege at this point (assuming that exim_uid and exim_gid + are not root). The third argument controls the running of initgroups(). +--- a/src/exim.c ++++ b/src/exim.c +@@ -3042,12 +3042,20 @@ for (i = 1; i < argc; i++) + + else if (Ustrcmp(argrest, "o") == 0) {} + + /* -oP <name>: set pid file path for daemon */ + +- else if (Ustrcmp(argrest, "P") == 0) +- override_pid_file_path = argv[++i]; ++ else if (*argrest == 'P') ++ { ++ if (!f.running_in_test_harness && real_uid != root_uid && real_uid != exim_uid) ++ exim_fail("exim: only uid=%d or uid=%d can use -oP and -oPX " ++ "(uid=%d euid=%d | %d)\n", ++ root_uid, exim_uid, getuid(), geteuid(), real_uid); ++ if (Ustrcmp(argrest, "P") == 0) override_pid_file_path = argv[++i]; ++ else if (Ustrcmp(argrest, "PX") == 0) delete_pid_file(); ++ else badarg = TRUE; ++ } + + /* -or <n>: set timeout for non-SMTP acceptance + -os <n>: set timeout for SMTP acceptance */ + + else if (*argrest == 'r' || *argrest == 's') diff --git a/debian/patches/84_27-testsuite-adjustments-for-CVE-2020-28014-CVE-2021-27.patch b/debian/patches/84_27-testsuite-adjustments-for-CVE-2020-28014-CVE-2021-27.patch new file mode 100644 index 0000000..d0dc071 --- /dev/null +++ b/debian/patches/84_27-testsuite-adjustments-for-CVE-2020-28014-CVE-2021-27.patch @@ -0,0 +1,57 @@ +From 47a48ed569503d8730bafcfd0f96d27cb72c9454 Mon Sep 17 00:00:00 2001 +From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de> +Date: Sat, 1 May 2021 11:21:22 +0200 +Subject: [PATCH 27/29] testsuite: adjustments for CVE-2020-28014, + CVE-2021-27216 (Arbitrary PID file creation) + +--- + src/daemon.c | 32 -------------------------------- + test/stderr/0433 | 24 ------------------------ + 2 files changed, 56 deletions(-) + +diff --git a/src/daemon.c b/src/daemon.c +index 9403472f3..7c15d148c 100644 +--- a/src/daemon.c ++++ b/src/daemon.c +@@ -1044,38 +1044,6 @@ exim_exit(EXIT_SUCCESS, US""); + } + + +-/* Called by the daemon; exec a child to get the pid file deleted +-since we may require privs for the containing directory */ +- +-static void +-daemon_die(void) +-{ +-int pid; +- +-DEBUG(D_any) debug_printf("SIGTERM/SIGINT seen\n"); +-#if defined(SUPPORT_TLS) && (defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)) +-tls_watch_invalidate(); +-#endif +- +-if (f.running_in_test_harness || write_pid) +- { +- if ((pid = fork()) == 0) +- { +- if (override_pid_file_path) +- (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, FALSE, 3, +- "-oP", override_pid_file_path, "-oPX"); +- else +- (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, FALSE, 1, "-oPX"); +- +- /* Control never returns here. */ +- } +- if (pid > 0) +- child_close(pid, 1); +- } +-exim_exit(EXIT_SUCCESS, US""); +-} +- +- + + /************************************************* + * Exim Daemon Mainline * +-- +2.30.2 + diff --git a/debian/patches/84_29-Fix-BDAT-issue-for-body-w-o-trailing-CRLF-again-Bug-.patch b/debian/patches/84_29-Fix-BDAT-issue-for-body-w-o-trailing-CRLF-again-Bug-.patch new file mode 100644 index 0000000..f5965ab --- /dev/null +++ b/debian/patches/84_29-Fix-BDAT-issue-for-body-w-o-trailing-CRLF-again-Bug-.patch @@ -0,0 +1,26 @@ +From 5220dc30120bd79319d465bd7a6e4b21a0881f9a Mon Sep 17 00:00:00 2001 +From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de> +Date: Fri, 30 Apr 2021 10:47:45 +0200 +Subject: [PATCH 29/29] Fix BDAT issue for body w/o trailing CRLF (again Bug + 1974) + +(cherry picked from commit 919111edac911ba9c15422eafd7c5bf14d416d26) +--- + src/smtp_in.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/smtp_in.c b/src/smtp_in.c +index 016c44c0f..76784c15f 100644 +--- a/src/smtp_in.c ++++ b/src/smtp_in.c +@@ -854,6 +854,7 @@ int + bdat_ungetc(int ch) + { + chunking_data_left++; ++bdat_push_receive_functions(); /* we're not done yet, calling push is safe, because it checks the state before pushing anything */ + return lwr_receive_ungetc(ch); + } + +-- +2.30.2 + diff --git a/debian/patches/90_localscan_dlopen.dpatch b/debian/patches/90_localscan_dlopen.dpatch new file mode 100644 index 0000000..ce71bae --- /dev/null +++ b/debian/patches/90_localscan_dlopen.dpatch @@ -0,0 +1,281 @@ +Description: Allow one to use and switch between different local_scan functions + without recompiling exim. + http://marc.merlins.org/linux/exim/files/sa-exim-current/ Original patch from + David Woodhouse, modified first by Derrick 'dman' Hudson and then by Marc + MERLIN for SA-Exim and minor/major API version tracking +Author: David Woodhouse, Derrick 'dman' Hudson, Marc MERLIN +Origin: other, http://marc.merlins.org/linux/exim/files/sa-exim-current/ +Forwarded: no +Last-Update: 2018-12-12 + +--- a/src/EDITME ++++ b/src/EDITME +@@ -824,6 +824,21 @@ HEADERS_CHARSET="ISO-8859-1" + + + #------------------------------------------------------------------------------ ++# On systems which support dynamic loading of shared libraries, Exim can ++# load a local_scan function specified in its config file instead of having ++# to be recompiled with the desired local_scan function. For a full ++# description of the API to this function, see the Exim specification. ++ ++DLOPEN_LOCAL_SCAN=yes ++ ++# If you set DLOPEN_LOCAL_SCAN, then you need to include -rdynamic in the ++# linker flags. Without it, the loaded .so won't be able to access any ++# functions from exim. ++ ++LDFLAGS += -rdynamic ++CFLAGS += -fvisibility=hidden ++ ++#------------------------------------------------------------------------------ + # The default distribution of Exim contains only the plain text form of the + # documentation. Other forms are available separately. If you want to install + # the documentation in "info" format, first fetch the Texinfo documentation +--- a/src/config.h.defaults ++++ b/src/config.h.defaults +@@ -32,6 +32,8 @@ Do not put spaces between # and the 'def + + #define AUTH_VARS 3 + ++#define DLOPEN_LOCAL_SCAN ++ + #define BIN_DIRECTORY + + #define CONFIGURE_FILE +--- a/src/globals.c ++++ b/src/globals.c +@@ -141,6 +141,10 @@ int dsn_ret = 0; + const pcre *regex_DSN = NULL; + uschar *dsn_advertise_hosts = NULL; + ++#ifdef DLOPEN_LOCAL_SCAN ++uschar *local_scan_path = NULL; ++#endif ++ + #ifdef SUPPORT_TLS + BOOL gnutls_compat_mode = FALSE; + BOOL gnutls_allow_auto_pkcs11 = FALSE; +--- a/src/globals.h ++++ b/src/globals.h +@@ -138,6 +138,9 @@ extern int dsn_ret; / + extern const pcre *regex_DSN; /* For recognizing DSN settings */ + extern uschar *dsn_advertise_hosts; /* host for which TLS is advertised */ + ++#ifdef DLOPEN_LOCAL_SCAN ++extern uschar *local_scan_path; /* Path to local_scan() library */ ++#endif + /* Input-reading functions for messages, so we can use special ones for + incoming TCP/IP. */ + +--- a/src/local_scan.c ++++ b/src/local_scan.c +@@ -5,61 +5,131 @@ + /* Copyright (c) University of Cambridge 1995 - 2009 */ + /* See the file NOTICE for conditions of use and distribution. */ + ++#include "exim.h" + +-/****************************************************************************** +-This file contains a template local_scan() function that just returns ACCEPT. +-If you want to implement your own version, you should copy this file to, say +-Local/local_scan.c, and edit the copy. To use your version instead of the +-default, you must set +- +-HAVE_LOCAL_SCAN=yes +-LOCAL_SCAN_SOURCE=Local/local_scan.c +- +-in your Local/Makefile. This makes it easy to copy your version for use with +-subsequent Exim releases. +- +-For a full description of the API to this function, see the Exim specification. +-******************************************************************************/ +- +- +-/* This is the only Exim header that you should include. The effect of +-including any other Exim header is not defined, and may change from release to +-release. Use only the documented interface! */ +- +-#include "local_scan.h" +- +- +-/* This is a "do-nothing" version of a local_scan() function. The arguments +-are: +- +- fd The file descriptor of the open -D file, which contains the +- body of the message. The file is open for reading and +- writing, but modifying it is dangerous and not recommended. +- +- return_text A pointer to an unsigned char* variable which you can set in +- order to return a text string. It is initialized to NULL. +- +-The return values of this function are: +- +- LOCAL_SCAN_ACCEPT +- The message is to be accepted. The return_text argument is +- saved in $local_scan_data. +- +- LOCAL_SCAN_REJECT +- The message is to be rejected. The returned text is used +- in the rejection message. +- +- LOCAL_SCAN_TEMPREJECT +- This specifies a temporary rejection. The returned text +- is used in the rejection message. +-*/ ++#ifdef DLOPEN_LOCAL_SCAN ++#include <dlfcn.h> ++static int (*local_scan_fn)(int fd, uschar **return_text) = NULL; ++static int load_local_scan_library(void); ++#endif + + int + local_scan(int fd, uschar **return_text) + { + fd = fd; /* Keep picky compilers happy */ + return_text = return_text; +-return LOCAL_SCAN_ACCEPT; ++#ifdef DLOPEN_LOCAL_SCAN ++/* local_scan_path is defined AND not the empty string */ ++if (local_scan_path && *local_scan_path) ++ { ++ if (!local_scan_fn) ++ { ++ if (!load_local_scan_library()) ++ { ++ char *base_msg , *error_msg , *final_msg ; ++ int final_length = -1 ; ++ ++ base_msg=US"Local configuration error - local_scan() library failure\n"; ++ error_msg = dlerror() ; ++ ++ final_length = strlen(base_msg) + strlen(error_msg) + 1 ; ++ final_msg = (char*)malloc( final_length*sizeof(char) ) ; ++ *final_msg = '\0' ; ++ ++ strcat( final_msg , base_msg ) ; ++ strcat( final_msg , error_msg ) ; ++ ++ *return_text = final_msg ; ++ return LOCAL_SCAN_TEMPREJECT; ++ } ++ } ++ return local_scan_fn(fd, return_text); ++ } ++else ++#endif ++ return LOCAL_SCAN_ACCEPT; ++} ++ ++#ifdef DLOPEN_LOCAL_SCAN ++ ++static int load_local_scan_library(void) ++{ ++/* No point in keeping local_scan_lib since we'll never dlclose() anyway */ ++void *local_scan_lib = NULL; ++int (*local_scan_version_fn)(void); ++int vers_maj; ++int vers_min; ++ ++local_scan_lib = dlopen(local_scan_path, RTLD_NOW); ++if (!local_scan_lib) ++ { ++ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() library open failed - " ++ "message temporarily rejected"); ++ return FALSE; ++ } ++ ++local_scan_version_fn = dlsym(local_scan_lib, "local_scan_version_major"); ++if (!local_scan_version_fn) ++ { ++ dlclose(local_scan_lib); ++ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() library doesn't contain " ++ "local_scan_version_major() function - message temporarily rejected"); ++ return FALSE; ++ } ++ ++/* The major number is increased when the ABI is changed in a non ++ backward compatible way. */ ++vers_maj = local_scan_version_fn(); ++ ++local_scan_version_fn = dlsym(local_scan_lib, "local_scan_version_minor"); ++if (!local_scan_version_fn) ++ { ++ dlclose(local_scan_lib); ++ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() library doesn't contain " ++ "local_scan_version_minor() function - message temporarily rejected"); ++ return FALSE; ++ } ++ ++/* The minor number is increased each time a new feature is added (in a ++ way that doesn't break backward compatibility) -- Marc */ ++vers_min = local_scan_version_fn(); ++ ++ ++if (vers_maj != LOCAL_SCAN_ABI_VERSION_MAJOR) ++ { ++ dlclose(local_scan_lib); ++ local_scan_lib = NULL; ++ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() has an incompatible major" ++ "version number, you need to recompile your module for this version" ++ "of exim (The module was compiled for version %d.%d and this exim provides" ++ "ABI version %d.%d)", vers_maj, vers_min, LOCAL_SCAN_ABI_VERSION_MAJOR, ++ LOCAL_SCAN_ABI_VERSION_MINOR); ++ return FALSE; ++ } ++else if (vers_min > LOCAL_SCAN_ABI_VERSION_MINOR) ++ { ++ dlclose(local_scan_lib); ++ local_scan_lib = NULL; ++ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() has an incompatible minor" ++ "version number, you need to recompile your module for this version" ++ "of exim (The module was compiled for version %d.%d and this exim provides" ++ "ABI version %d.%d)", vers_maj, vers_min, LOCAL_SCAN_ABI_VERSION_MAJOR, ++ LOCAL_SCAN_ABI_VERSION_MINOR); ++ return FALSE; ++ } ++ ++local_scan_fn = dlsym(local_scan_lib, "local_scan"); ++if (!local_scan_fn) ++ { ++ dlclose(local_scan_lib); ++ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() library doesn't contain " ++ "local_scan() function - message temporarily rejected"); ++ return FALSE; ++ } ++ ++return TRUE; + } + ++#endif /* DLOPEN_LOCAL_SCAN */ ++ + /* End of local_scan.c */ +--- a/src/local_scan.h ++++ b/src/local_scan.h +@@ -17,6 +17,7 @@ settings, and the store functions. */ + + #include <stdarg.h> + #include <sys/types.h> ++#pragma GCC visibility push(default) + #include "config.h" + #include "mytypes.h" + #include "store.h" +@@ -192,4 +193,6 @@ extern uschar *string_copy(const uschar + extern uschar *string_copyn(const uschar *, int); + extern uschar *string_sprintf(const char *, ...) ALMOST_PRINTF(1,2); + ++#pragma GCC visibility pop ++ + /* End of local_scan.h */ +--- a/src/readconf.c ++++ b/src/readconf.c +@@ -199,6 +199,9 @@ static optionlist optionlist_config[] = + { "local_from_prefix", opt_stringptr, &local_from_prefix }, + { "local_from_suffix", opt_stringptr, &local_from_suffix }, + { "local_interfaces", opt_stringptr, &local_interfaces }, ++#ifdef DLOPEN_LOCAL_SCAN ++ { "local_scan_path", opt_stringptr, &local_scan_path }, ++#endif + #ifdef HAVE_LOCAL_SCAN + { "local_scan_timeout", opt_time, &local_scan_timeout }, + #endif diff --git a/debian/patches/series b/debian/patches/series new file mode 100644 index 0000000..e448ccf --- /dev/null +++ b/debian/patches/series @@ -0,0 +1,60 @@ +31_eximmanpage.dpatch +32_exim4.dpatch +33_eximon.binary.dpatch +34_eximstatsmanpage.dpatch +35_install.dpatch +60_convert4r4.dpatch +67_unnecessaryCopt.diff +70_remove_exim-users_references.dpatch +75_01-Fix-json-extract-operator-for-unfound-case.patch +75_02-Fix-transport-buffer-size-handling.patch +75_03-Fix-info-on-using-local_scan-in-the-default-Makefile.patch +75_04-GnuTLS-Fix-client-detection-of-server-reject-of-clie.patch +75_05-Fix-expansions-for-RFC-822-addresses-having-comments.patch +75_06-Docs-Add-note-on-lsearch-for-IPv4-mapped-IPv6-addres.patch +75_07-Fix-crash-from-SRV-lookup-hitting-a-CNAME.patch +75_08-Logging-fix-initial-listening-on-log-line.patch +75_09-OpenSSL-Fix-aggregation-of-messages.patch +75_10-Harden-plaintext-authenticator.patch +75_11-GnuTLS-fix-tls_out_ocsp-under-hosts_request_ocsp.patch +75_12-GnuTLS-fix-the-advertising-of-acceptable-certs-by-th.patch +75_13-Use-dsn_from-for-success-DSN-messages.-Bug-2404.patch +75_14-Fix-smtp-response-timeout.patch +75_15-Fix-detection-of-32b-platform-at-build-time.-Bug-240.patch +77_Avoid-re-expansion-in-sort-CVE-2019-13917-OVE-201907.patch +78_01-string.c-do-not-interpret-before-0-CVE-2019-15846.patch +78_02-Fix-buffer-overflow-in-string_vformat.-Bug-2449.patch +79_01-Fix-SPA-authenticator-checking-client-supplied-data-.patch +79_02-Rework-SPA-fix-to-avoid-overflows.-Bug-2571.patch +80_01-GnuTLS-fix-hanging-callout-connections.patch +80_02-GnuTLS-tls_write-wait-after-uncorking-the-session.patch +80_03-GnuTLS-Do-not-care-about-corked-data-when-uncorking.patch +82_TLS-use-RFC-6125-rules-for-certifucate-name-checks-w.patch +84_01-CVE-2020-28025-Heap-out-of-bounds-read-in-pdkim_fini.patch +84_02-CVE-2020-28018-Use-after-free-in-tls-openssl.c.patch +84_03-CVE-2020-28023-Out-of-bounds-read-in-smtp_setup_msg.patch +84_04-CVE-2020-28010-Heap-out-of-bounds-write-in-main.patch +84_05-CVE-2020-28011-Heap-buffer-overflow-in-queue_run.patch +84_06-CVE-2020-28013-Heap-buffer-overflow-in-parse_fix_phr.patch +84_07-Security-Refuse-negative-and-large-store-allocations.patch +84_08-CVE-2020-28017-Integer-overflow-in-receive_add_recip.patch +84_09-CVE-2020-28022-Heap-out-of-bounds-read-and-write-in-.patch +84_10-CVE-2020-28026-Line-truncation-and-injection-in-spoo.patch +84_11-CVE-2020-28015-28021-New-line-injection-into-spool-h.patch +84_12-CVE-2020-28009-Integer-overflow-in-get_stdinput.patch +84_13-CVE-2020-28024-Heap-buffer-underflow-in-smtp_ungetc.patch +84_14-CVE-2020-28012-Missing-close-on-exec-flag-for-privil.patch +84_15-Security-Safeguard-against-relative-names-for-msglog.patch +84_16-Security-Check-overrun-rcpt_count-integer.patch +84_17-Security-Always-exit-when-LOG_PANIC_DIE-is-set.patch +84_18-Security-Fix-off-by-one-in-smtp-transport-read-respo.patch +84_19-Security-Avoid-decrement-of-dkim_collect_input-if-al.patch +84_20-Security-Leave-a-clean-smtp_out-input-buffer-even-in.patch +84_21-Security-Avoid-modification-of-constant-data-in-dkim.patch +84_22-CVE-2020-28019-Failure-to-reset-function-pointer-aft.patch +84_23-CVE-2020-28007-Link-attack-in-Exim-s-log-directory.patch +84_24-CVE-2020-28008-Assorted-attacks-in-Exim-s-spool-dire.patch +84_26-CVE-2020-28014-CVE-2021-27216-Arbitrary-PID-file-cre.patch +84_27-testsuite-adjustments-for-CVE-2020-28014-CVE-2021-27.patch +84_29-Fix-BDAT-issue-for-body-w-o-trailing-CRLF-again-Bug-.patch +90_localscan_dlopen.dpatch |