diff -Nru exim4-4.82/debian/changelog exim4-4.84/debian/changelog --- exim4-4.82/debian/changelog 2014-02-25 16:33:31.000000000 +0000 +++ exim4-4.84/debian/changelog 2015-02-02 16:55:56.000000000 +0000 @@ -1,22 +1,214 @@ -exim4 (4.82-3ubuntu2) trusty; urgency=medium +exim4 (4.84-6openssl1) trusty; urgency=medium + * Backport/rebuild Debian package for trusty with openssl. + + -- Charles F Peters II (Chuck) Mon, 02 Feb 2015 11:54:33 -0500 + +exim4 (4.84-6) unstable; urgency=medium + + * Revert init script's restart order change in 4.84-4 for the time being. + This needs a slightly more involved change than I want to push into jessie + right now. + + -- Andreas Metzler Sun, 21 Dec 2014 14:07:12 +0100 + +exim4 (4.84-5) unstable; urgency=medium + + * 82_quoted-or-r-2047-encoded.diff pulled from upstream git (sans + testsuite), extends the fix in 4.84-2. + + -- Andreas Metzler Wed, 17 Dec 2014 19:03:39 +0100 + +exim4 (4.84-4) unstable; urgency=medium + + * Unset message_prefix/message_sufix in maildrop_pipe transport. Maildrop + neither expects a mbox-style From nor an empty line add the end. (Thanks, + Edward Betts) Closes: #769396 + * Change the init script's restart order from { regenerate_config; stop; + start ; } to { stop; regenerate_config; start ; }. (Thanks, Jakub Warmuz) + Closes: #768874 + * 81_buffer-overrun-in-spam-acl.diff from upstream git. Fix a buffer overrun + with control characters in argument of spam= acl condition. + + + -- Andreas Metzler Sun, 30 Nov 2014 08:24:04 +0100 + +exim4 (4.84-3) unstable; urgency=medium + + * Apply patch to Italian (it) debconf template translation, thanks to + s3v . Closes: #764925 + * Let virtual package cron-daemon fulfill exim4-base's dependency now that + bcron provides it instead of "cron" and systemd-cron is fixed. + Closes: #765720 + + -- Andreas Metzler Sun, 19 Oct 2014 13:35:56 +0200 + +exim4 (4.84-2) unstable; urgency=high + + * Add 80_mime_empty_charset.diff from upstream GIT (the parts that change + the code, not the testsuite) to handle empty content-type charset. + + -- Andreas Metzler Fri, 29 Aug 2014 19:41:38 +0200 + +exim4 (4.84-1) unstable; urgency=medium + + * New upstream release. + + -- Andreas Metzler Thu, 14 Aug 2014 19:33:01 +0200 + +exim4 (4.84~RC2-1) unstable; urgency=medium + + * New upstream release candidate. + + -- Andreas Metzler Sat, 09 Aug 2014 07:42:00 +0200 + +exim4 (4.84~RC1-3) unstable; urgency=medium + + * Third try. Simply comment *custom* in debian/control. + + -- Andreas Metzler Sat, 02 Aug 2014 09:29:13 +0200 + +exim4 (4.84~RC1-2) unstable; urgency=medium + + * Re-upload, after manually removing *custom* from the changes file to avoid + false detection of NEW packages due to the changes in the archive + infrastructure related source-only uploads. + + -- Andreas Metzler Sat, 02 Aug 2014 08:14:54 +0200 + +exim4 (4.84~RC1-1) unstable; urgency=medium + + * New upstream release candidate, fixing a regression in the MIME handling + code. + + -- Andreas Metzler Sat, 02 Aug 2014 07:45:26 +0200 + +exim4 (4.83-2) unstable; urgency=medium + + * Upload to unstable. + + -- Andreas Metzler Sat, 26 Jul 2014 09:25:15 +0200 + +exim4 (4.83-1) experimental; urgency=medium + + * New upstream release which includes the fix for CVE-2014-2972. + + -- Andreas Metzler Wed, 23 Jul 2014 08:13:22 +0200 + +exim4 (4.83~RC3-1) experimental; urgency=medium + + * New upstream release candidate. + + -- Andreas Metzler Tue, 08 Jul 2014 19:07:52 +0200 + +exim4 (4.83~RC2-1) experimental; urgency=medium + + * New upstream release candidate. + + JH/26 Port service names are now accepted for tls_on_connect_ports, to + align with daemon_smtp_ports. Bug 72. Closes: #316441 + + + -- Andreas Metzler Fri, 06 Jun 2014 19:11:24 +0200 + +exim4 (4.83~RC1-1) experimental; urgency=medium + + * New upstream feature release candidate. + + JH/06 Log outbound-TLS and port details, subject to log selectors, for a + failed delivery. Closes: #712987 + * Unfuzz 31_eximmanpage.dpatch and 50_localscan_dlopen.dpatch. + * Drop superfluous patches: 75_unbind-ldap-connection.diff + 76_fix_ldap_option_setting.diff 77_close-the-server-side-of-TLS.diff + 80_fix_ftbfs_hurd.diff + * Since exim4-base currently only includes daily cronjobs let anacron + fulfill the dependency, too. Systems with missing recommends (anacron + recommends cron) that are *not* restarted regularily will therefore not + run the cron-job regularily. Exim should not break horribly in this case + and we can assume the local system administrator knows what (s)he is doing + by disabling installation of recommends. (Policy: "[...] packages that + would be found together with this one in all but unusual installations") + Closes: #733929 + + -- Andreas Metzler Thu, 29 May 2014 13:09:04 +0200 + +exim4 (4.82.1-2) unstable; urgency=high + + * [87_double_expansion.diff] from upstream. Stop unwanted double expansion + of arguments to mathematical comparison operations. CVE-2014-2972 + + -- Andreas Metzler Sun, 20 Jul 2014 19:05:48 +0200 + +exim4 (4.82.1-1) unstable; urgency=high + + * New upstream security release, fixing CVE-2014-2957. This is a remote + code execution flaw in Exim version 4.82 (only) when built with DMARC + support. Debian's binary packages are not built with DMARC support and + therefore not vulnerable. However we want to fix this for people building + their own binaries based on Debian's packaging. + + -- Andreas Metzler Wed, 28 May 2014 19:01:43 +0200 + +exim4 (4.82-8) unstable; urgency=medium + + * Now that GMP has been relicensed to LGPLv3+/GPLv2+ build exim against + GnuTLS v3. + + -- Andreas Metzler Sat, 12 Apr 2014 16:19:05 +0200 + +exim4 (4.82-7) unstable; urgency=high + + [ Martin Pitt ] + * debian/tests/control: Add missing python test dependency, as + debian/tests/security calls python. Closes: #740092 + + [ Andreas Metzler ] + * 4.82 deprecated $tls_bits, $tls_certificate_verified, $tls_cipher, + $tls_peerdn, $tls_sni and introduced tls_in_*/tls_out_* variants of these + variables which describe the respective status of the current incoming or + outgoing TLS connection. The rationale for this is that a single exim + process can now use both an incoming (message reception) and outgoing + TLS connection (callout or cutthrough delivery) concurrently. With this + change the "old" variables were mapped to tls_in_*, i.e. they expand to + empty values on outgoing connections. (This is not yet documented.) + Outgoing tls-connections can therefore not be detected by nonempty + $tls_cipher anymore. exim4-config << 4.82 used this mechanism to prevent + sending of plaintext AUTH information on unencrypted connections. Force a + lockstep upgrade of exim4-config by bumping the version of exim4-base's + dependency on exim4-config to >= 4.82. + Closes: #742901, #736081 + + -- Andreas Metzler Sun, 06 Apr 2014 08:32:11 +0200 + +exim4 (4.82-6) experimental; urgency=medium + + [ Martin Pitt ] * debian/tests/control: Add missing python test dependency, as - debian/tests/security calls python. + debian/tests/security calls python. Closes: #740092 + + [ Andreas Metzler ] + * Now that GMP has been relicensed to LGPLv3+/GPLv2+ build exim against + GnuTLS v3. - -- Martin Pitt Tue, 25 Feb 2014 17:33:13 +0100 + -- Andreas Metzler Sat, 05 Apr 2014 14:18:11 +0200 -exim4 (4.82-3ubuntu1) trusty; urgency=low +exim4 (4.82-5) unstable; urgency=medium - * Merge from Debian unstable (LP: #1259620). Remaining changes: - - Show Ubuntu distribution on smtp: - + debian/patches/fix_smtp_banner.patch: updated SMTP banner - with Ubuntu distribution - + debian/control: added lsb-release build dependency - - Don't provide default-mta; in Ubuntu, we want postfix to be the - default. - - Build-depend on db5.3. + * Upgrade to libdb5.3-dev. Closes: #738637 Be paranoid and bump BDBVERSION + in exim4-base.postinst from 3.0 (no idea why this did not read 5.1) to + 5.3, therefore purging hints db on upgrades. - -- Yolanda Robla Tue, 10 Dec 2013 17:07:20 +0000 + -- Andreas Metzler Wed, 12 Feb 2014 19:31:55 +0100 + +exim4 (4.82-4) unstable; urgency=medium + + * Correct title/name of exim4-config_files(5). (Thanks, Heiko Schlittermann) + Closes: #734212 + * 80_fix_ftbfs_hurd.diff by Samuel Thibault fixes FTBFS on GNU/hurd due to + missing support for TCLASS. Closes: #738445 + * Add debian/upstream-signing-key.pgp (listed in + debian/source/include-binaries) and update watchfile to check + upstream signature. + + -- Andreas Metzler Sun, 09 Feb 2014 19:41:34 +0100 exim4 (4.82-3) unstable; urgency=low @@ -98,21 +290,6 @@ -- Andreas Metzler Sun, 29 Sep 2013 14:43:25 +0200 -exim4 (4.80-9ubuntu2) trusty; urgency=low - - * Build-depend on libdb5.3-dev, instead of libdb5.1-dev. - - -- Dmitrijs Ledkovs Mon, 04 Nov 2013 12:14:54 +0000 - -exim4 (4.80-9ubuntu1) trusty; urgency=low - - * Resynchronise with Debian. Remaining changes: - - Don't provide default-mta; in Ubuntu, we want postfix to be the - default. - - Add "Ubuntu" to SMTP banner. - - -- Colin Watson Mon, 28 Oct 2013 11:55:21 -0700 - exim4 (4.80-9) unstable; urgency=low * Upload to unstable. @@ -159,34 +336,6 @@ -- Andreas Metzler Sun, 01 Sep 2013 15:58:49 +0200 -exim4 (4.80-7ubuntu4) trusty; urgency=low - - * Rebuild for Perl 5.18. - - -- Colin Watson Wed, 23 Oct 2013 10:24:08 +0100 - -exim4 (4.80-7ubuntu3) saucy; urgency=low - - * debian/patches/fix_smtp_banner.patch: updated SMTP banner - with Ubuntu distribution - * debian/control: added lsb-release build dependency - - -- Yolanda Robla Tue, 18 Jun 2013 19:17:43 +0200 - -exim4 (4.80-7ubuntu2) saucy; urgency=low - - * debian/tests: Add autopkgtest. - - -- Yolanda Mon, 27 May 2013 11:31:35 +0200 - -exim4 (4.80-7ubuntu1) raring; urgency=low - - * Merge from Debian unstable (LP: #1166383). Remaining changes: - - debian/control: Don't declare a Provides: default-mta; in Ubuntu, - we want postfix to be the default. - - -- Robie Basak Mon, 08 Apr 2013 18:13:15 +0100 - exim4 (4.80-7) unstable; urgency=low * Use exim's ${quote:xxx} operator when invoking spfquery to disallow @@ -206,14 +355,6 @@ -- Andreas Metzler Wed, 21 Nov 2012 19:08:53 +0100 -exim4 (4.80-5.1ubuntu1) raring; urgency=low - - * Merge from Debian. Remaining changes: - - debian/control: Don't declare a Provides: default-mta; in Ubuntu, - we want postfix to be the default. - - -- Oussama Bounaim Sun, 11 Nov 2012 07:11:06 +0100 - exim4 (4.80-5.1) unstable; urgency=high * Non-maintainer upload by the Security Team. @@ -241,23 +382,6 @@ -- Andreas Metzler Sat, 23 Jun 2012 18:35:03 +0200 -exim4 (4.80-3ubuntu1.1) quantal-security; urgency=low - - * SECURITY UPDATE: arbitrary code execution via dns decode logic - - debian/patches/CVE-2012-5671.patch: adjust max length and validate - against it in src/pdkim/pdkim.h, src/dkim.c. - - CVE-2012-5671 - - -- Marc Deslauriers Thu, 25 Oct 2012 08:22:46 -0400 - -exim4 (4.80-3ubuntu1) quantal; urgency=low - - * Merge from Debian unstable. Remaining changes: - - debian/control: Don't declare a Provides: default-mta; in Ubuntu, - we want postfix to be the default. - - -- Clint Byrum Thu, 14 Jun 2012 15:28:08 -0700 - exim4 (4.80-3) unstable; urgency=low * Pull 75_openssl_sni.diff from upstream. - Segfault caused by NULL @@ -405,26 +529,6 @@ -- Andreas Metzler Sat, 24 Sep 2011 18:36:08 +0200 -exim4 (4.76-3ubuntu3) precise; urgency=low - - * Rebuild for libmysqlclient transition - - -- Clint Byrum Wed, 23 Nov 2011 23:29:35 -0800 - -exim4 (4.76-3ubuntu2) precise; urgency=low - - * Rebuild for Perl 5.14. - - -- Colin Watson Wed, 16 Nov 2011 01:22:39 +0000 - -exim4 (4.76-3ubuntu1) precise; urgency=low - - * Merge from debian unstable. Remaining changes: - - debian/control: Don't declare a Provides: default-mta; in Ubuntu, - we want postfix to be the default. - - -- Stéphane Graber Thu, 20 Oct 2011 11:29:07 -0400 - exim4 (4.76-3) unstable; urgency=low * [exim4-base.cron.daily] Correct invocation of mail(1), options need to be @@ -445,14 +549,6 @@ -- Andreas Metzler Sun, 18 Sep 2011 11:49:13 +0200 -exim4 (4.76-2ubuntu1) oneiric; urgency=low - - * Merge from debian unstable. Remaining changes: - - debian/control: Don't declare a Provides: default-mta; in Ubuntu, - we want postfix to be the default. - - -- Stéphane Graber Mon, 30 May 2011 17:48:56 -0400 - exim4 (4.76-2) unstable; urgency=low * debian/rules: Remove test/ and test-stamp on clean. @@ -465,14 +561,6 @@ -- Andreas Metzler Sun, 29 May 2011 18:21:03 +0200 -exim4 (4.76-1ubuntu1) oneiric; urgency=low - - * Merge from debian unstable. Remaining changes (LP: #779391): - - debian/control: Don't declare a Provides: default-mta; in Ubuntu, - we want postfix to be the default. - - -- Stéphane Graber Mon, 23 May 2011 12:37:30 -0400 - exim4 (4.76-1) unstable; urgency=low * New upstream version. @@ -519,14 +607,6 @@ -- Andreas Metzler Fri, 06 May 2011 20:08:51 +0200 -exim4 (4.75-2ubuntu1) oneiric; urgency=low - - * Merge from debian unstable. Remaining changes: - - debian/control: Don't declare a Provides: default-mta; in Ubuntu, - we want postfix to be the default. - - -- Stéphane Graber Fri, 06 May 2011 14:51:28 -0400 - exim4 (4.75-2) unstable; urgency=low * clamav socket on Debian is clamd:/var/run/clamav/clamd.ctl, fix @@ -569,24 +649,6 @@ -- Andreas Metzler Thu, 24 Feb 2011 19:02:07 +0100 -exim4 (4.74-1ubuntu1) natty; urgency=low - - * Merge from debian experimental. Remaining changes: (LP: #713855) - - debian/patches/71_exiq_grep_error_on_messages_without_size.patch: - + Improve handling of broken messages when "exim4 -bp" (mailq) - reports lines without size info. (Closes: #528625) - - debian/control: Don't declare a Provides: default-mta; in Ubuntu, - we want postfix to be the default. - - debian/{control,rules}: Add and enable hardened build for PIE. - (Closes: #542726) - * Update 71_exiq_grep_error_on_messages_without_size.patch to get way - which upstream has fixed it. Probably it can be dropped with next - upstream release. - * This upload fixes CVE: (LP: #708023) - - CVE-2011-0017 - - -- Artur Rona Wed, 09 Feb 2011 21:31:35 +0100 - exim4 (4.74-1) experimental; urgency=low * 4.74 release, should build on hurd again. @@ -612,20 +674,6 @@ -- Andreas Metzler Sun, 23 Jan 2011 14:02:36 +0100 -exim4 (4.73~rc1-1ubuntu1) natty; urgency=low - - * Merge from debian unstable. Remaining changes: (LP: #697934) - - debian/patches/71_exiq_grep_error_on_messages_without_size.patch: - + Improve handling of broken messages when "exim4 -bp" (mailq) - reports lines without size info. - - debian/control: Don't declare a Provides: default-mta; in Ubuntu, - we want postfix to be the default. - - debian/{control,rules}: Add and enable hardened build for PIE. - (Closes: #542726) - * Drop B-D on libmysqlclient15-dev, resolved in Debian. - - -- Artur Rona Tue, 28 Dec 2010 22:20:17 +0100 - exim4 (4.73~rc1-1) experimental; urgency=low * New upstream release candidate. @@ -721,20 +769,6 @@ -- Andreas Metzler Sun, 26 Dec 2010 15:13:08 +0100 -exim4 (4.72-2ubuntu1) natty; urgency=low - - * Merge from debian unstable. Remaining changes: (LP: #671615) - - debian/patches/71_exiq_grep_error_on_messages_without_size.dpatch: - Improve handling of broken messages when "exim4 -bp" (mailq) reports - lines without size info. - - Don't declare a Provides: default-mta; in Ubuntu, we want postfix to be - the default. - - debian/control: Change build dependencies to MySQL 5.1. - - debian/{control,rules}: add and enable hardened build for PIE - (Closes: #542726). - - -- Artur Rona Fri, 05 Nov 2010 21:05:47 +0100 - exim4 (4.72-2) unstable; urgency=low [ Marc Haber ] @@ -758,20 +792,6 @@ -- Andreas Metzler Sat, 30 Oct 2010 13:38:26 +0200 -exim4 (4.72-1ubuntu1) maverick; urgency=low - - * Merge with Debian unstable (LP: #609620). Remaining changes: - + debian/patches/71_exiq_grep_error_on_messages_without_size.dpatch: - Improve handling of broken messages when "exim4 -bp" (mailq) reports - lines without size info. - + Don't declare a Provides: default-mta; in Ubuntu, we want postfix to be - the default. - + debian/control: Change build dependencies to MySQL 5.1. - + debian/{control,rules}: add and enable hardened build for PIE - (Closes: #542726). - - -- Artur Rona Sun, 25 Jul 2010 02:00:42 +0200 - exim4 (4.72-1) unstable; urgency=low * New upstream release. (Identical to the git snapshot previously @@ -823,20 +843,6 @@ -- Andreas Metzler Thu, 25 Mar 2010 17:34:30 +0100 -exim4 (4.71-3ubuntu1) lucid; urgency=low - - * Merge with Debian unstable (lp: #501657). Remaining changes: - + debian/patches/71_exiq_grep_error_on_messages_without_size.dpatch: - Improve handling of broken messages when "exim4 -bp" (mailq) reports - lines without size info. - + Don't declare a Provides: default-mta; in Ubuntu, we want postfix to be - the default. - + debian/control: Change build dependencies to MySQL 5.1. - + debian/{control,rules}: add and enable hardened build for PIE - (Debian bug 542726). - - -- Michael Bienia Fri, 01 Jan 2010 16:28:19 +0100 - exim4 (4.71-3) unstable; urgency=low * exim4-base.cron.daily: Do not run exim_tidydb on Berkeley DB logfiles. @@ -951,35 +957,6 @@ -- Andreas Metzler Sat, 17 Oct 2009 14:26:54 +0200 -exim4 (4.69-11ubuntu4) karmic; urgency=low - - * debian/{control,rules}: add and enable hardened build for PIE - (Debian bug 542726). - - -- Kees Cook Thu, 20 Aug 2009 17:33:26 -0700 - -exim4 (4.69-11ubuntu3) karmic; urgency=low - - * debian/control: Change build dependencies to MySQL 5.1. - - -- Mathias Gug Mon, 17 Aug 2009 17:57:26 -0400 - -exim4 (4.69-11ubuntu2) karmic; urgency=low - - * Don't declare a Provides: default-mta; in Ubuntu, we want postfix to be - the default. - - -- Steve Langasek Wed, 03 Jun 2009 15:39:14 +0000 - -exim4 (4.69-11ubuntu1) karmic; urgency=low - - * Merge from debian unstable (LP: #375923), remaining changes: - - debian/patches/71_exiq_grep_error_on_messages_without_size.dpatch: - Improve handling of broken messages when "exim4 -bp" (mailq) reports - lines without size info - - -- Thierry Carrez Wed, 13 May 2009 12:15:29 +0200 - exim4 (4.69-11) unstable; urgency=medium * Build-Depend on lynx-cur|lynx instead of lynx. (lynx is just a dummy @@ -1037,15 +1014,6 @@ -- Andreas Metzler Sat, 02 May 2009 09:05:56 +0200 -exim4 (4.69-9ubuntu1) jaunty; urgency=low - - [ Daniel van Eeden ] - * debian/patches/71_exiq_grep_error_on_messages_without_size.dpatch: - Improve handling of broken messages when "exim4 -bp" (mailq) reports lines - w/o size info, LP: #18194 - - -- Dustin Kirkland Wed, 11 Feb 2009 06:43:52 -0600 - exim4 (4.69-9) unstable; urgency=medium * [update-exim4.conf]: Use POSIX character classes [:alnum:] or explicit diff -Nru exim4-4.82/debian/control exim4-4.84/debian/control --- exim4-4.82/debian/control 2014-01-02 02:59:36.000000000 +0000 +++ exim4-4.84/debian/control 2014-11-18 17:58:37.000000000 +0000 @@ -1,11 +1,10 @@ Source: exim4 Section: mail Priority: standard -Maintainer: Ubuntu Developers -XSBC-Original-Maintainer: Exim4 Maintainers +Maintainer: Exim4 Maintainers Uploaders: Andreas Metzler ,Marc Haber Homepage: http://www.exim.org/ -Standards-Version: 3.9.5 +Standards-Version: 3.9.6 #Vcs-Git: git://git.debian.org/git/pkg-exim4/exim4.git #Vcs-Browser: http://git.debian.org/?p=pkg-exim4/exim4.git Vcs-Git: git://anonscm.debian.org/pkg-exim4/exim4.git @@ -14,7 +13,7 @@ lynx-cur | lynx, docbook-xml, libpcre3-dev, libldap2-dev, libpam0g-dev, libident-dev, libdb5.3-dev, libxmu-dev, libxt-dev, libxext-dev, libx11-dev, libxaw7-dev, libpq-dev, libmysqlclient-dev | libmysqlclient15-dev, - libsqlite3-dev, libperl-dev, libgnutls-dev, libsasl2-dev, lsb-release + libsqlite3-dev, libperl-dev, libgnutls28-dev, libsasl2-dev XS-Testsuite: autopkgtest Package: exim4-base @@ -24,7 +23,9 @@ exim4-daemon-custom (<<${Upstream-Version}) Conflicts: exim, exim-tls Replaces: exim, exim-tls, exim4-daemon-light, exim4-daemon-heavy, exim4-daemon-custom -Depends: ${shlibs:Depends}, ${misc:Depends}, cron | fcron, exim4-config (>=4.30) | exim4-config-2, adduser, netbase, lsb-base (>= 3.0-6) +Depends: ${shlibs:Depends}, ${misc:Depends}, + cron | cron-daemon | anacron | fcron, + exim4-config (>=4.82) | exim4-config-2, adduser, netbase, lsb-base (>= 3.0-6) # psmisc just for exiwhat. Recommends: psmisc, mailx, perl-modules Suggests: mail-reader, eximon4, exim4-doc-html|exim4-doc-info, @@ -92,7 +93,7 @@ Package: exim4-daemon-light Architecture: any -Provides: mail-transport-agent, exim4-localscanapi-1.0, exim4-localscanapi-1.1 +Provides: mail-transport-agent, exim4-localscanapi-1.0, exim4-localscanapi-1.1, default-mta Conflicts: mail-transport-agent Replaces: mail-transport-agent, exim4-base (<= 4.61-1) Depends: exim4-base (>= ${Upstream-Version}), ${shlibs:Depends}, ${misc:Depends} @@ -176,35 +177,35 @@ can find the subscription web page on http://lists.alioth.debian.org/mailman/listinfo/pkg-exim4-users -Package: exim4-daemon-custom -Architecture: any -Priority: optional -Provides: mail-transport-agent, exim4-localscanapi-1.0, exim4-localscanapi-1.1 -Conflicts: mail-transport-agent -Replaces: mail-transport-agent, exim4-base (<= 4.61-1) -Depends: exim4-base (>= ${Upstream-Version}), ${shlibs:Depends}, ${misc:Depends} -Description: custom Exim MTA (v4) daemon with locally set features - Exim (v4) is a mail transport agent. This package contains a - custom-configured exim4 daemon compiled to local needs. This package - is not part of official Debian, but can easily be built from the - Debian source package. For information about the feature set compiled in, - and for bug reports, please find out who built your package. - . - The Debian exim4 packages have their own web page, - http://wiki.debian.org/PkgExim4. There is also a Debian-specific - FAQ list. Information about the way the Debian packages are - configured can be found in - /usr/share/doc/exim4-base/README.Debian.gz, which additionally contains - information about the way the Debian binary packages are built. The - very extensive upstream documentation is shipped in - /usr/share/doc/exim4-base/spec.txt.gz. To repeat the debconf-driven - configuration process in a standard setup, invoke dpkg-reconfigure - exim4-config. 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 +#Package: exim4-daemon-custom +#Architecture: any +#Priority: optional +#Provides: mail-transport-agent, exim4-localscanapi-1.0, exim4-localscanapi-1.1 +#Conflicts: mail-transport-agent +#Replaces: mail-transport-agent, exim4-base (<= 4.61-1) +#Depends: exim4-base (>= ${Upstream-Version}), ${shlibs:Depends}, ${misc:Depends} +#Description: custom Exim MTA (v4) daemon with locally set features +# Exim (v4) is a mail transport agent. This package contains a +# custom-configured exim4 daemon compiled to local needs. This package +# is not part of official Debian, but can easily be built from the +# Debian source package. For information about the feature set compiled in, +# and for bug reports, please find out who built your package. +# . +# The Debian exim4 packages have their own web page, +# http://wiki.debian.org/PkgExim4. There is also a Debian-specific +# FAQ list. Information about the way the Debian packages are +# configured can be found in +# /usr/share/doc/exim4-base/README.Debian.gz, which additionally contains +# information about the way the Debian binary packages are built. The +# very extensive upstream documentation is shipped in +# /usr/share/doc/exim4-base/spec.txt.gz. To repeat the debconf-driven +# configuration process in a standard setup, invoke dpkg-reconfigure +# exim4-config. 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 Package: eximon4 Architecture: any @@ -297,31 +298,31 @@ can find the subscription web page on http://lists.alioth.debian.org/mailman/listinfo/pkg-exim4-users -Package: exim4-daemon-custom-dbg -Architecture: any -Priority: extra -Section: debug -Depends: exim4-daemon-custom, ${misc:Depends} -Description: debugging symbols for the Exim MTA (v4) packages - Exim (v4) is a mail transport agent. This package contains - debugging symbols for the binaries contained in the - exim4-daemon-custom package. - . - The Debian exim4 packages have their own web page, - http://wiki.debian.org/PkgExim4. There is also a Debian-specific - FAQ list. Information about the way the Debian packages are - configured can be found in - /usr/share/doc/exim4-base/README.Debian.gz, which additionally contains - information about the way the Debian binary packages are built. The - very extensive upstream documentation is shipped in - /usr/share/doc/exim4-base/spec.txt.gz. To repeat the debconf-driven - configuration process in a standard setup, invoke dpkg-reconfigure - exim4-config. 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 +#Package: exim4-daemon-custom-dbg +#Architecture: any +#Priority: extra +#Section: debug +#Depends: exim4-daemon-custom, ${misc:Depends} +#Description: debugging symbols for the Exim MTA (v4) packages +# Exim (v4) is a mail transport agent. This package contains +# debugging symbols for the binaries contained in the +# exim4-daemon-custom package. +# . +# The Debian exim4 packages have their own web page, +# http://wiki.debian.org/PkgExim4. There is also a Debian-specific +# FAQ list. Information about the way the Debian packages are +# configured can be found in +# /usr/share/doc/exim4-base/README.Debian.gz, which additionally contains +# information about the way the Debian binary packages are built. The +# very extensive upstream documentation is shipped in +# /usr/share/doc/exim4-base/spec.txt.gz. To repeat the debconf-driven +# configuration process in a standard setup, invoke dpkg-reconfigure +# exim4-config. 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 Package: exim4-dev Architecture: any diff -Nru exim4-4.82/debian/debconf/conf.d/transport/30_exim4-config_maildrop_pipe exim4-4.84/debian/debconf/conf.d/transport/30_exim4-config_maildrop_pipe --- exim4-4.82/debian/debconf/conf.d/transport/30_exim4-config_maildrop_pipe 2012-09-23 10:07:23.000000000 +0000 +++ exim4-4.84/debian/debconf/conf.d/transport/30_exim4-config_maildrop_pipe 2014-11-18 17:58:37.000000000 +0000 @@ -4,6 +4,8 @@ driver = pipe path = "/bin:/usr/bin:/usr/local/bin" command = "/usr/bin/maildrop" + message_prefix = + message_suffix = return_path_add delivery_date_add envelope_to_add diff -Nru exim4-4.82/debian/exim4-base.postinst exim4-4.84/debian/exim4-base.postinst --- exim4-4.82/debian/exim4-base.postinst 2013-08-06 17:19:04.000000000 +0000 +++ exim4-4.84/debian/exim4-base.postinst 2014-07-22 17:16:03.000000000 +0000 @@ -10,7 +10,7 @@ db_version 2.0 -BDBVERSION=3.0 +BDBVERSION=5.3 case "$1" in configure) diff -Nru exim4-4.82/debian/manpages/exim4-config_files.5 exim4-4.84/debian/manpages/exim4-config_files.5 --- exim4-4.82/debian/manpages/exim4-config_files.5 2013-08-08 17:58:23.000000000 +0000 +++ exim4-4.84/debian/manpages/exim4-config_files.5 2014-07-22 17:16:03.000000000 +0000 @@ -2,7 +2,7 @@ .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) -.TH EXIM4_FILES 5 "Jun 21, 2006" EXIM4 +.TH EXIM4-CONFIG_FILES 5 "Jan 5, 2014" EXIM4 .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: @@ -18,7 +18,7 @@ .\" \(oqthis text is enclosed in single quotes\(cq .\" \(lqthis text is enclosed in double quotes\(rq .SH NAME -exim4_files \- Files in use by the Debian exim4 packages +exim4-config_files \- Files in use by the Debian exim4 packages .SH SYNOPSIS .br /etc/aliases diff -Nru exim4-4.82/debian/patches/31_eximmanpage.dpatch exim4-4.84/debian/patches/31_eximmanpage.dpatch --- exim4-4.82/debian/patches/31_eximmanpage.dpatch 2013-11-27 18:50:43.000000000 +0000 +++ exim4-4.84/debian/patches/31_eximmanpage.dpatch 2014-12-01 17:45:56.000000000 +0000 @@ -1,12 +1,12 @@ Description: We ship the binary as exim4 instead of exim, fix manpage accordingly. Author: Marc Haber , - Andreas Metzler -Last-Update: 2013-09-28 + Andreas Metzler +Last-Update: 2014-05-29 Forwarded: not-needed (upstream uses the "exim" name) ---- exim4-4.82~rc1.orig/doc/exim.8 -+++ exim4-4.82~rc1/doc/exim.8 +--- a/doc/exim.8 ++++ b/doc/exim.8 @@ -1,9 +1,9 @@ -.TH EXIM 8 +.TH EXIM4 8 @@ -78,7 +78,7 @@ .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 -@@ -434,7 +434,7 @@ written directly into the spool director +@@ -435,7 +435,7 @@ written directly into the spool director .sp If \fB\-bP\fP is followed by a name preceded by +, for example, .sp @@ -87,7 +87,7 @@ .sp it searches for a matching named list of any type (domain, host, address, or local part) and outputs what it finds. -@@ -443,7 +443,7 @@ If one of the words \fBrouter\fP, \fBtra +@@ -444,7 +444,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 @@ -96,7 +96,7 @@ .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 -@@ -522,7 +522,7 @@ This option is for testing retry rules, +@@ -523,7 +523,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 @@ -105,7 +105,7 @@ Retry rule: *.comp.mus.example F,2h,15m; F,4d,30m; .sp The first -@@ -535,7 +535,7 @@ rule is found that matches the host, one +@@ -536,7 +536,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 @@ -114,7 +114,7 @@ Retry rule: *@haydn.comp.mus.example quota_3d F,1h,15m .TP 10 \fB\-brw\fP -@@ -638,7 +638,7 @@ doing such tests. +@@ -639,7 +639,7 @@ doing such tests. .TP 10 \fB\-bV\fP This option causes Exim to write the current version number, compilation @@ -123,7 +123,7 @@ 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 run time configuration file that is in use. -@@ -666,7 +666,7 @@ If no arguments are given, Exim runs in +@@ -667,7 +667,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 @@ -132,7 +132,7 @@ security issues. .sp Verification differs from address testing (the \fB\-bt\fP option) in that routers -@@ -779,14 +779,14 @@ command line item. \fB\-D\fP can be used +@@ -780,14 +780,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 @@ -150,7 +150,7 @@ .sp \fB\-D\fP may be repeated up to 10 times on a command line. .TP 10 -@@ -915,8 +915,8 @@ never provoke a bounce. An empty sender +@@ -916,8 +916,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 @@ -161,7 +161,7 @@ .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 -@@ -1267,12 +1267,12 @@ other circumstances, they are ignored un +@@ -1271,12 +1271,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 @@ -176,7 +176,7 @@ .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 -@@ -1458,13 +1458,13 @@ When scanning the queue, Exim can be mad +@@ -1474,13 +1474,13 @@ When scanning the queue, Exim can be mad lexically less than a given value by following the \fB\-q\fP option with a starting message id. For example: .sp @@ -192,7 +192,7 @@ .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 -@@ -1480,7 +1480,7 @@ starting a queue runner process at inter +@@ -1496,7 +1496,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 @@ -201,7 +201,7 @@ .sp Such a daemon listens for incoming SMTP calls, and also starts a queue runner process every 30 minutes. -@@ -1511,7 +1511,7 @@ regular expression; otherwise it is a li +@@ -1527,7 +1527,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 @@ -210,8 +210,8 @@ .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 -@@ -1620,6 +1620,26 @@ This option is interpreted by Sendmail t - to the named file. It is ignored by Exim. +@@ -1637,6 +1637,26 @@ to the named file. It is ignored by Exi + .sp . .SH "SEE ALSO" +.BR exicyclog (8), diff -Nru exim4-4.82/debian/patches/50_localscan_dlopen.dpatch exim4-4.84/debian/patches/50_localscan_dlopen.dpatch --- exim4-4.82/debian/patches/50_localscan_dlopen.dpatch 2013-11-27 18:50:43.000000000 +0000 +++ exim4-4.84/debian/patches/50_localscan_dlopen.dpatch 2014-12-21 13:05:01.000000000 +0000 @@ -11,15 +11,15 @@ Forwarded: no Last-Update: 2013-09-28 ---- exim4-4.82~rc1.orig/src/EDITME -+++ exim4-4.82~rc1/src/EDITME -@@ -752,6 +752,21 @@ HEADERS_CHARSET="ISO-8859-1" +--- a/src/EDITME ++++ b/src/EDITME +@@ -783,6 +783,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 ++# 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 @@ -35,8 +35,8 @@ # 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 ---- exim4-4.82~rc1.orig/src/config.h.defaults -+++ exim4-4.82~rc1/src/config.h.defaults +--- a/src/config.h.defaults ++++ b/src/config.h.defaults @@ -27,6 +27,8 @@ it's a default value. */ #define AUTH_VARS 3 @@ -46,32 +46,33 @@ #define BIN_DIRECTORY #define CONFIGURE_FILE ---- exim4-4.82~rc1.orig/src/globals.c -+++ exim4-4.82~rc1/src/globals.c -@@ -116,6 +116,9 @@ tls_support tls_out = { - NULL /* tls_sni */ - }; +--- a/src/globals.c ++++ b/src/globals.c +@@ -134,6 +134,10 @@ BOOL smtp_use_dsn = FALSE; + uschar *dsn_advertise_hosts = NULL; + #endif +#ifdef DLOPEN_LOCAL_SCAN +uschar *local_scan_path = NULL; +#endif - ++ #ifdef SUPPORT_TLS BOOL gnutls_compat_mode = FALSE; ---- exim4-4.82~rc1.orig/src/globals.h -+++ exim4-4.82~rc1/src/globals.h -@@ -113,6 +113,9 @@ extern uschar *tls_verify_certificates;/ - extern uschar *tls_verify_hosts; /* Mandatory client verification */ + BOOL gnutls_allow_auto_pkcs11 = FALSE; +--- a/src/globals.h ++++ b/src/globals.h +@@ -134,6 +134,9 @@ extern BOOL smtp_use_dsn; / + extern uschar *dsn_advertise_hosts; /* host for which TLS is advertised */ #endif +#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. */ ---- exim4-4.82~rc1.orig/src/local_scan.c -+++ exim4-4.82~rc1/src/local_scan.c + +--- a/src/local_scan.c ++++ b/src/local_scan.c @@ -5,60 +5,131 @@ /* Copyright (c) University of Cambridge 1995 - 2009 */ /* See the file NOTICE for conditions of use and distribution. */ @@ -241,7 +242,7 @@ + { + dlclose(local_scan_lib); + log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() library doesn't contain " -+ "local_scan() function - message temporarily rejected"); ++ "local_scan() function - message temporarily rejected"); + return FALSE; + } + @@ -251,8 +252,8 @@ +#endif /* DLOPEN_LOCAL_SCAN */ + /* End of local_scan.c */ ---- exim4-4.82~rc1.orig/src/local_scan.h -+++ exim4-4.82~rc1/src/local_scan.h +--- a/src/local_scan.h ++++ b/src/local_scan.h @@ -17,6 +17,7 @@ settings, and the store functions. */ #include @@ -261,16 +262,16 @@ #include "config.h" #include "mytypes.h" #include "store.h" -@@ -190,4 +191,6 @@ extern uschar *string_copy(const uschar +@@ -194,4 +195,6 @@ extern uschar *string_copy(const uschar extern uschar *string_copyn(uschar *, int); extern uschar *string_sprintf(const char *, ...) ALMOST_PRINTF(1,2); +#pragma GCC visibility pop + /* End of local_scan.h */ ---- exim4-4.82~rc1.orig/src/readconf.c -+++ exim4-4.82~rc1/src/readconf.c -@@ -286,6 +286,9 @@ static optionlist optionlist_config[] = +--- a/src/readconf.c ++++ b/src/readconf.c +@@ -289,6 +289,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 }, diff -Nru exim4-4.82/debian/patches/65_saverandomseed.dpatch exim4-4.84/debian/patches/65_saverandomseed.dpatch --- exim4-4.82/debian/patches/65_saverandomseed.dpatch 2012-09-23 10:07:23.000000000 +0000 +++ exim4-4.84/debian/patches/65_saverandomseed.dpatch 1970-01-01 00:00:00.000000000 +0000 @@ -1,73 +0,0 @@ -#! /bin/sh /usr/share/dpatch/dpatch-run -## 65_saverandomseed.dpatch by -## -## All lines beginning with `## DP:' are a description of the patch. -## DP: Save gcrypt RNG seed. - -diff -NurbBp exim.orig/src/tls-gnu.c exim/src/tls-gnu.c ---- exim.orig/src/tls-gnu.c 2009-11-15 12:17:32.000000000 +0100 -+++ exim/src/tls-gnu.c 2009-11-15 12:38:30.000000000 +0100 -@@ -20,6 +20,7 @@ functions from the GnuTLS library. */ - #include - #include - -+#include - - #define UNKNOWN_NAME "unknown" - #define DH_BITS 2048 -@@ -443,10 +444,35 @@ tls_init(host_item *host, uschar *certif - uschar *crl) - { - int rc; -+uschar filename[200]; - uschar *cert_expanded, *key_expanded, *cas_expanded, *crl_expanded; -+gcry_error_t gcr_rc; - - client_host = host; - -+/* initialize gcrypt explicitely */ -+gcry_check_version (NULL); -+ -+/* Use a random_seed file for gcrypt's RNG */ -+if (host_number_string != NULL) -+ { -+ if (!string_format(filename, sizeof(filename), "%s/random.seed%s", -+ spool_directory, host_number_string)) -+ return tls_error(US"overlong filename spool_directory/random.seedlocalhost_number", host, 0); -+ } -+else -+ { -+ if (!string_format(filename, sizeof(filename), "%s/random.seed", -+ spool_directory)) -+ return tls_error(US"overlong filename spool_directory/random.seed", host, 0); -+ } -+ -+gcr_rc = gcry_control (GCRYCTL_SET_RANDOM_SEED_FILE,filename); -+if (gcr_rc) -+ return tls_error(US"Failure to set random_seed file", host, gcr_rc); -+ -+gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); -+ - rc = gnutls_global_init(); - if (rc < 0) return tls_error(US"tls-init", host, gnutls_strerror(rc)); - -@@ -1295,8 +1321,19 @@ Returns: nothing - void - tls_close(BOOL shutdown) - { -+gcry_error_t gcr_rc; -+ - if (tls_active < 0) return; /* TLS was not active */ - -+gcr_rc = gcry_control (GCRYCTL_UPDATE_RANDOM_SEED_FILE); -+ -+if (gcr_rc) -+ { -+ DEBUG(D_tls) debug_printf( -+ "GCRYCTL_UPDATE_RANDOM_SEED_FILE failed: (%d): (%s)\n", -+ gcr_rc,gcry_strerror(gcr_rc)); -+ } -+ - if (shutdown) - { - DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS\n"); diff -Nru exim4-4.82/debian/patches/75_unbind-ldap-connection.diff exim4-4.84/debian/patches/75_unbind-ldap-connection.diff --- exim4-4.82/debian/patches/75_unbind-ldap-connection.diff 2013-11-27 18:50:44.000000000 +0000 +++ exim4-4.84/debian/patches/75_unbind-ldap-connection.diff 1970-01-01 00:00:00.000000000 +0000 @@ -1,26 +0,0 @@ -From ff2c417d0b970db22a382cb692d066d8fe3c32ae Mon Sep 17 00:00:00 2001 -From: Todd Lyons -Date: Thu, 31 Oct 2013 06:04:27 -0700 -Subject: [PATCH 1/8] Only unbind ldap connection if bind succeeded - ---- - src/lookups/ldap.c | 3 ++- - 1 file changed, 2 insertions(+), 1 deletion(-) - -diff --git a/src/lookups/ldap.c b/src/lookups/ldap.c -index bb29b43..6129b4b 100644 ---- a/src/lookups/ldap.c -+++ b/src/lookups/ldap.c -@@ -1367,7 +1367,8 @@ while ((lcp = ldap_connections) != NULL) - { - DEBUG(D_lookup) debug_printf("unbind LDAP connection to %s:%d\n", lcp->host, - lcp->port); -- ldap_unbind(lcp->ld); -+ if(lcp->bound == TRUE) -+ ldap_unbind(lcp->ld); - ldap_connections = lcp->next; - } - } --- -1.7.10.4 - diff -Nru exim4-4.82/debian/patches/76_fix_ldap_option_setting.diff exim4-4.84/debian/patches/76_fix_ldap_option_setting.diff --- exim4-4.82/debian/patches/76_fix_ldap_option_setting.diff 2013-11-27 18:50:44.000000000 +0000 +++ exim4-4.84/debian/patches/76_fix_ldap_option_setting.diff 1970-01-01 00:00:00.000000000 +0000 @@ -1,106 +0,0 @@ -From f535f98390710c48b0fe2bf3bbe751a3459ca72b Mon Sep 17 00:00:00 2001 -From: Todd Lyons -Date: Thu, 31 Oct 2013 09:42:15 -0700 -Subject: [PATCH] Fix ldap option setting. - -Some client libs set a global context, newer client libs set a global - default which then needs to be reloaded. - -diff --git a/src/lookups/ldap.c b/src/lookups/ldap.c -index 6129b4b..a25868f 100644 ---- a/src/lookups/ldap.c -+++ b/src/lookups/ldap.c -@@ -280,6 +280,13 @@ if (lcp == NULL) - { - LDAP *ld; - -+ #ifdef LDAP_OPT_X_TLS_NEWCTX -+ int am_server = 0; -+ LDAP *ldsetctx; -+ #else -+ LDAP *ldsetctx = NULL; -+ #endif -+ - - /* --------------------------- OpenLDAP ------------------------ */ - -@@ -365,6 +372,10 @@ if (lcp == NULL) - goto RETURN_ERROR; - } - -+ #ifdef LDAP_OPT_X_TLS_NEWCTX -+ ldsetctx = ld; -+ #endif -+ - /* Set the TCP connect time limit if available. This is something that is - in Netscape SDK v4.1; I don't know about other libraries. */ - -@@ -461,31 +472,31 @@ if (lcp == NULL) - #ifdef LDAP_OPT_X_TLS_CACERTFILE - if (eldap_ca_cert_file != NULL) - { -- ldap_set_option(ld, LDAP_OPT_X_TLS_CACERTFILE, eldap_ca_cert_file); -+ ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_CACERTFILE, eldap_ca_cert_file); - } - #endif - #ifdef LDAP_OPT_X_TLS_CACERTDIR - if (eldap_ca_cert_dir != NULL) - { -- ldap_set_option(ld, LDAP_OPT_X_TLS_CACERTDIR, eldap_ca_cert_dir); -+ ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_CACERTDIR, eldap_ca_cert_dir); - } - #endif - #ifdef LDAP_OPT_X_TLS_CERTFILE - if (eldap_cert_file != NULL) - { -- ldap_set_option(ld, LDAP_OPT_X_TLS_CERTFILE, eldap_cert_file); -+ ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_CERTFILE, eldap_cert_file); - } - #endif - #ifdef LDAP_OPT_X_TLS_KEYFILE - if (eldap_cert_key != NULL) - { -- ldap_set_option(ld, LDAP_OPT_X_TLS_KEYFILE, eldap_cert_key); -+ ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_KEYFILE, eldap_cert_key); - } - #endif - #ifdef LDAP_OPT_X_TLS_CIPHER_SUITE - if (eldap_cipher_suite != NULL) - { -- ldap_set_option(ld, LDAP_OPT_X_TLS_CIPHER_SUITE, eldap_cipher_suite); -+ ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_CIPHER_SUITE, eldap_cipher_suite); - } - #endif - #ifdef LDAP_OPT_X_TLS_REQUIRE_CERT -@@ -508,8 +519,26 @@ if (lcp == NULL) - { - cert_option = LDAP_OPT_X_TLS_TRY; - } -- /* Use NULL ldap handle because is a global option */ -- ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &cert_option); -+ /* This ldap handle is set at compile time based on client libs. Older -+ * versions want it to be global and newer versions can force a reload -+ * of the TLS context (to reload these settings we are changing from the -+ * default that loaded at instantiation). */ -+ rc = ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_REQUIRE_CERT, &cert_option); -+ if (rc) -+ { -+ DEBUG(D_lookup) -+ debug_printf("Unable to set TLS require cert_option(%d) globally: %s\n", -+ cert_option, ldap_err2string(rc)); -+ } -+ } -+ #endif -+ #ifdef LDAP_OPT_X_TLS_NEWCTX -+ rc = ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_NEWCTX, &am_server); -+ if (rc) -+ { -+ DEBUG(D_lookup) -+ debug_printf("Unable to reload TLS context %d: %s\n", -+ rc, ldap_err2string(rc)); - } - #endif - --- -1.6.3.2 - diff -Nru exim4-4.82/debian/patches/77_close-the-server-side-of-TLS.diff exim4-4.84/debian/patches/77_close-the-server-side-of-TLS.diff --- exim4-4.82/debian/patches/77_close-the-server-side-of-TLS.diff 2013-11-27 18:50:44.000000000 +0000 +++ exim4-4.84/debian/patches/77_close-the-server-side-of-TLS.diff 1970-01-01 00:00:00.000000000 +0000 @@ -1,40 +0,0 @@ -From a400eccf287c55558ae7197c831828cf10b0a35c Mon Sep 17 00:00:00 2001 -From: Tony Finch -Date: Tue, 5 Nov 2013 12:18:02 +0000 -Subject: [PATCH 2/8] Correctly close the server side of TLS when forking for - delivery. - ---- - src/daemon.c | 2 +- - src/exim.c | 2 +- - 2 files changed, 2 insertions(+), 2 deletions(-) - -diff --git a/src/daemon.c b/src/daemon.c -index 3467f14..8e61dcf 100644 ---- a/src/daemon.c -+++ b/src/daemon.c -@@ -639,7 +639,7 @@ if (pid == 0) - the data structures if necessary. */ - - #ifdef SUPPORT_TLS -- tls_close(FALSE, FALSE); -+ tls_close(TRUE, FALSE); - #endif - - /* Reset SIGHUP and SIGCHLD in the child in both cases. */ -diff --git a/src/exim.c b/src/exim.c -index a715c0b..856e655 100644 ---- a/src/exim.c -+++ b/src/exim.c -@@ -526,7 +526,7 @@ close_unwanted(void) - if (smtp_input) - { - #ifdef SUPPORT_TLS -- tls_close(FALSE, FALSE); /* Shut down the TLS library */ -+ tls_close(TRUE, FALSE); /* Shut down the TLS library */ - #endif - (void)close(fileno(smtp_in)); - (void)close(fileno(smtp_out)); --- -1.7.10.4 - diff -Nru exim4-4.82/debian/patches/80_mime_empty_charset.diff exim4-4.84/debian/patches/80_mime_empty_charset.diff --- exim4-4.82/debian/patches/80_mime_empty_charset.diff 1970-01-01 00:00:00.000000000 +0000 +++ exim4-4.84/debian/patches/80_mime_empty_charset.diff 2014-12-21 13:05:01.000000000 +0000 @@ -0,0 +1,60 @@ +From 93cad488cb2c9a31aea345c8910a9f9c5815071c Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Fri, 29 Aug 2014 14:11:50 +0100 +Subject: [PATCH] Fix crash in mime acl when a parameter is zero-length + + +diff --git a/src/mime.c b/src/mime.c +index 95d3da4..ab701f2 100644 +--- a/src/mime.c ++++ b/src/mime.c +@@ -620,12 +620,18 @@ NEXT_PARAM_SEARCH: + else + param_value = string_cat(param_value, &size, &ptr, q++, 1); + } +- param_value[ptr++] = '\0'; +- param_value_len = ptr; +- +- param_value = rfc2047_decode(param_value, check_rfc2047_length, NULL, 32, ¶m_value_len, &q); +- debug_printf("Found %s MIME parameter in %s header, value is '%s'\n", mp->name, mime_header_list[i].name, param_value); +- *((uschar **)(mp->value)) = param_value; ++ if (param_value) ++ { ++ param_value[ptr++] = '\0'; ++ param_value_len = ptr; ++ ++ param_value = rfc2047_decode(param_value, ++ check_rfc2047_length, NULL, 32, ¶m_value_len, &q); ++ debug_printf("Found %s MIME parameter in %s header, " ++ "value is '%s'\n", mp->name, mime_header_list[i].name, ++ param_value); ++ } ++ *mp->value = param_value; + p += (mp->namelen + param_value_len + 1); + goto NEXT_PARAM_SEARCH; + } +diff --git a/src/mime.h b/src/mime.h +index abf68da..af09f67 100644 +--- a/src/mime.h ++++ b/src/mime.h +@@ -40,15 +40,15 @@ static int mime_header_list_size = sizeof(mime_header_list)/sizeof(mime_header); + + + typedef struct mime_parameter { +- uschar *name; +- int namelen; +- void *value; ++ uschar * name; ++ int namelen; ++ uschar ** value; + } mime_parameter; + + static mime_parameter mime_parameter_list[] = { +- { US"name=", 5, &mime_filename }, ++ { US"name=", 5, &mime_filename }, + { US"filename=", 9, &mime_filename }, +- { US"charset=", 8, &mime_charset }, ++ { US"charset=", 8, &mime_charset }, + { US"boundary=", 9, &mime_boundary } + }; + diff -Nru exim4-4.82/debian/patches/81_buffer-overrun-in-spam-acl.diff exim4-4.84/debian/patches/81_buffer-overrun-in-spam-acl.diff --- exim4-4.82/debian/patches/81_buffer-overrun-in-spam-acl.diff 1970-01-01 00:00:00.000000000 +0000 +++ exim4-4.84/debian/patches/81_buffer-overrun-in-spam-acl.diff 2014-12-21 13:05:01.000000000 +0000 @@ -0,0 +1,26 @@ +From e252eb8c71ea3bddb32bf73bddc8b22cfde2bc3a Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Thu, 27 Nov 2014 16:26:44 +0000 +Subject: [PATCH] Fix buffer overrun in spam= acl condition. Bug 1552 + +--- + src/spam.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/src/spam.c b/src/spam.c +index 7eb6fbf..76bf7d6 100644 +--- a/src/spam.c ++++ b/src/spam.c +@@ -129,7 +129,8 @@ spam(uschar **listptr) + (spamd_address_container *)store_get(sizeof(spamd_address_container)); + + /* grok spamd address and port */ +- if( sscanf(CS address, "%s %u", this_spamd->tcp_addr, &(this_spamd->tcp_port)) != 2 ) { ++ if (sscanf(CS address, "%23s %u", this_spamd->tcp_addr, &(this_spamd->tcp_port)) != 2) ++ { + log_write(0, LOG_MAIN, + "spam acl condition: warning - invalid spamd address: '%s'", address); + continue; +-- +2.1.3 + diff -Nru exim4-4.82/debian/patches/82_quoted-or-r-2047-encoded.diff exim4-4.84/debian/patches/82_quoted-or-r-2047-encoded.diff --- exim4-4.82/debian/patches/82_quoted-or-r-2047-encoded.diff 1970-01-01 00:00:00.000000000 +0000 +++ exim4-4.84/debian/patches/82_quoted-or-r-2047-encoded.diff 2014-12-21 13:05:01.000000000 +0000 @@ -0,0 +1,194 @@ +From 5c6cf6a0d5cb7da39e7fde01dca1ff862c1fa1c8 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Sun, 14 Dec 2014 15:15:34 +0000 +Subject: [PATCH] Account properly for quoted or 2047-encoded MIME parameters + while walking headers. Bug 1558 + +--- + src/mime.c | 103 ++++++++++++++++++++++------------------ + test/log/4000 | 3 ++ + test/mail/4000.userx | 38 +++++++++++++++ + test/scripts/4000-scanning/4000 | 29 +++++++++++ + test/stdout/4000 | 11 +++++ + 5 files changed, 137 insertions(+), 47 deletions(-) + +diff --git a/src/mime.c b/src/mime.c +index ab701f2..a61e9f2 100644 +--- a/src/mime.c ++++ b/src/mime.c +@@ -528,26 +528,24 @@ while(1) + */ + if (context != NULL) + { +- while(fgets(CS header, MIME_MAX_HEADER_SIZE, f) != NULL) ++ while(fgets(CS header, MIME_MAX_HEADER_SIZE, f)) + { + /* boundary line must start with 2 dashes */ +- if (Ustrncmp(header,"--",2) == 0) +- { +- if (Ustrncmp((header+2),context->boundary,Ustrlen(context->boundary)) == 0) ++ if ( Ustrncmp(header, "--", 2) == 0 ++ && Ustrncmp(header+2, context->boundary, Ustrlen(context->boundary)) == 0) ++ { ++ /* found boundary */ ++ if (Ustrncmp((header+2+Ustrlen(context->boundary)), "--", 2) == 0) + { +- /* found boundary */ +- if (Ustrncmp((header+2+Ustrlen(context->boundary)),"--",2) == 0) +- { +- /* END boundary found */ +- debug_printf("End boundary found %s\n", context->boundary); +- return rc; +- } +- else +- debug_printf("Next part with boundary %s\n", context->boundary); +- +- /* can't use break here */ +- goto DECODE_HEADERS; ++ /* END boundary found */ ++ debug_printf("End boundary found %s\n", context->boundary); ++ return rc; + } ++ else ++ debug_printf("Next part with boundary %s\n", context->boundary); ++ ++ /* can't use break here */ ++ goto DECODE_HEADERS; + } + } + /* Hit EOF or read error. Ugh. */ +@@ -557,92 +555,103 @@ while(1) + + DECODE_HEADERS: + /* parse headers, set up expansion variables */ +- while (mime_get_header(f,header)) ++ while (mime_get_header(f, header)) + { + int i; + /* loop through header list */ + for (i = 0; i < mime_header_list_size; i++) +- { +- uschar *header_value = NULL; +- int header_value_len = 0; +- +- /* found an interesting header? */ +- if (strncmpic(mime_header_list[i].name,header,mime_header_list[i].namelen) == 0) +- { +- uschar *p = header + mime_header_list[i].namelen; +- /* yes, grab the value (normalize to lower case) +- and copy to its corresponding expansion variable */ ++ if (strncmpic(mime_header_list[i].name, ++ header, mime_header_list[i].namelen) == 0) ++ { /* found an interesting header */ ++ uschar * header_value; ++ int header_value_len; ++ uschar * p = header + mime_header_list[i].namelen; ++ ++ /* grab the value (normalize to lower case) ++ and copy to its corresponding expansion variable */ + while(*p != ';') + { + *p = tolower(*p); + p++; + } +- header_value_len = (p - (header + mime_header_list[i].namelen)); +- header_value = (uschar *)malloc(header_value_len+1); +- memset(header_value,0,header_value_len+1); ++ header_value_len = p - (header + mime_header_list[i].namelen); + p = header + mime_header_list[i].namelen; +- Ustrncpy(header_value, p, header_value_len); +- debug_printf("Found %s MIME header, value is '%s'\n", mime_header_list[i].name, header_value); ++ header_value = string_copyn(p, header_value_len); ++ debug_printf("Found %s MIME header, value is '%s'\n", ++ mime_header_list[i].name, header_value); + *((uschar **)(mime_header_list[i].value)) = header_value; + + /* make p point to the next character after the closing ';' */ +- p += (header_value_len+1); ++ p += header_value_len+1; + +- /* grab all param=value tags on the remaining line, check if they are interesting */ ++ /* grab all param=value tags on the remaining line, ++ check if they are interesting */ + NEXT_PARAM_SEARCH: +- while (*p != 0) ++ while (*p) + { + mime_parameter * mp; + for (mp = mime_parameter_list; + mp < &mime_parameter_list[mime_parameter_list_size]; + mp++) + { +- uschar *param_value = NULL; +- int param_value_len = 0; ++ uschar * param_value = NULL; + + /* found an interesting parameter? */ + if (strncmpic(mp->name, p, mp->namelen) == 0) + { +- uschar *q = p + mp->namelen; ++ uschar * q = p + mp->namelen; ++ int plen = 0; + int size = 0; + int ptr = 0; + + /* yes, grab the value and copy to its corresponding expansion variable */ + while(*q && *q != ';') /* ; terminates */ +- { + if (*q == '"') + { + q++; /* skip leading " */ +- while(*q && *q != '"') /* which protects ; */ ++ plen++; /* and account for the skip */ ++ while(*q && *q != '"') /* " protects ; */ ++ { + param_value = string_cat(param_value, &size, &ptr, q++, 1); +- if (*q) q++; /* skip trailing " */ ++ plen++; ++ } ++ if (*q) ++ { ++ q++; /* skip trailing " */ ++ plen++; ++ } + } + else ++ { + param_value = string_cat(param_value, &size, &ptr, q++, 1); +- } ++ plen++; ++ } ++ + if (param_value) + { + param_value[ptr++] = '\0'; +- param_value_len = ptr; + + param_value = rfc2047_decode(param_value, +- check_rfc2047_length, NULL, 32, ¶m_value_len, &q); ++ check_rfc2047_length, NULL, 32, NULL, &q); + debug_printf("Found %s MIME parameter in %s header, " + "value is '%s'\n", mp->name, mime_header_list[i].name, + param_value); + } + *mp->value = param_value; +- p += (mp->namelen + param_value_len + 1); ++ p += mp->namelen + plen + 1; /* name=, content, ; */ + goto NEXT_PARAM_SEARCH; + } + } + /* There is something, but not one of our interesting parameters. + Advance to the next semicolon */ +- while(*p != ';') p++; ++ while(*p != ';') ++ { ++ if (*p == '"') while(*++p && *p != '"') ; ++ p++; ++ } + p++; + } + } +- } + } + + /* set additional flag variables (easier access) */ diff -Nru exim4-4.82/debian/patches/fix_smtp_banner.patch exim4-4.84/debian/patches/fix_smtp_banner.patch --- exim4-4.82/debian/patches/fix_smtp_banner.patch 2014-01-02 02:59:36.000000000 +0000 +++ exim4-4.84/debian/patches/fix_smtp_banner.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,60 +0,0 @@ -Description: Add EXIM_DISTRIBUTION var to display it on the SMTP banner -Origin: https://blueprints.launchpad.net/ubuntu/+spec/servercloud-s-server-app-banner-updates -Author: Yolanda Robla -Last-Update: 2013-06-20 - -=== modified file 'src/exim.h' -Index: exim4_ubuntu/src/globals.c -=================================================================== ---- exim4_ubuntu.orig/src/globals.c 2013-12-10 17:06:29.194997355 +0000 -+++ exim4_ubuntu/src/globals.c 2013-12-10 17:06:29.190997355 +0000 -@@ -1175,7 +1175,7 @@ - uschar *smtp_active_hostname = NULL; - BOOL smtp_authenticated = FALSE; - uschar *smtp_banner = US"$smtp_active_hostname ESMTP " -- "Exim $version_number $tod_full" -+ "Exim $version_number " EXIM_DISTRIBUTION " $tod_full" - "\0<---------------Space to patch smtp_banner->"; - BOOL smtp_batched_input = FALSE; - BOOL smtp_check_spool_space = TRUE; -Index: exim4_ubuntu/src/config.h.defaults -=================================================================== ---- exim4_ubuntu.orig/src/config.h.defaults 2013-12-10 17:06:29.194997355 +0000 -+++ exim4_ubuntu/src/config.h.defaults 2013-12-10 17:06:29.190997355 +0000 -@@ -196,4 +196,6 @@ - #define SC_EXIM_ARITH "%" SCNi64 /* scanf incl. 0x prefix */ - #define SC_EXIM_DEC "%" SCNd64 /* scanf decimal */ - -+#define EXIM_DISTRIBUTION -+ - /* End of config.h.defaults */ -Index: exim4_ubuntu/scripts/Configure-config.h -=================================================================== ---- exim4_ubuntu.orig/scripts/Configure-config.h 2013-12-10 17:06:29.194997355 +0000 -+++ exim4_ubuntu/scripts/Configure-config.h 2013-12-10 17:06:29.190997355 +0000 -@@ -23,6 +23,12 @@ - if [ "$1" != "" ] ; then MAKE=$1 ; fi - if [ "$MAKE" = "" ] ; then MAKE=make ; fi - -+# exporting distribution to use it in smtp banner -+if test -x /usr/bin/lsb_release && lsb_release -si; then -+ export EXIM_DISTRIBUTION=\"$(lsb_release -si)\" -+else -+ export EXIM_DISTRIBUTION=\"\" -+fi - $MAKE buildconfig || exit 1 - - # BEWARE: tab characters needed in the following sed command. They have had -Index: exim4_ubuntu/src/exim.h -=================================================================== ---- exim4_ubuntu.orig/src/exim.h 2013-12-10 17:06:29.194997355 +0000 -+++ exim4_ubuntu/src/exim.h 2013-12-10 17:06:29.190997355 +0000 -@@ -580,4 +580,8 @@ - #endif - #endif - -+#ifndef EXIM_DISTRIBUTION -+ #define EXIM_DISTRIBUTION "" -+#endif -+ - /* End of exim.h */ diff -Nru exim4-4.82/debian/patches/series exim4-4.84/debian/patches/series --- exim4-4.82/debian/patches/series 2014-01-02 02:59:36.000000000 +0000 +++ exim4-4.84/debian/patches/series 2014-12-21 13:05:01.000000000 +0000 @@ -8,7 +8,6 @@ 66_enlarge-dh-parameters-size.dpatch 67_unnecessaryCopt.diff 70_remove_exim-users_references.dpatch -75_unbind-ldap-connection.diff -76_fix_ldap_option_setting.diff -77_close-the-server-side-of-TLS.diff -fix_smtp_banner.patch +80_mime_empty_charset.diff +81_buffer-overrun-in-spam-acl.diff +82_quoted-or-r-2047-encoded.diff diff -Nru exim4-4.82/debian/po/it.po exim4-4.84/debian/po/it.po --- exim4-4.82/debian/po/it.po 2012-09-23 10:07:23.000000000 +0000 +++ exim4-4.84/debian/po/it.po 2014-11-18 17:58:37.000000000 +0000 @@ -32,7 +32,7 @@ "remain undelivered until Exim is re-installed." msgstr "" "Ci sono dei messaggi nella directory di «spool» di Exim, /var/spool/exim4/" -"input, che non sono stati ancora consegnati. Rimuovendo Exim i messaggi " +"input che non sono stati ancora consegnati. Rimuovendo Exim i messaggi " "verranno mantenuti lì sinché Exim non verrà reinstallato." #. Type: boolean @@ -163,7 +163,7 @@ "delivery can be disabled entirely (except mail for root and postmaster)." msgstr "" "Un sistema con indirizzo IP dinamico può ricevere la propria posta, " -"altrimenti la posta locale può essere completamente disabilita (eccetto i " +"altrimenti la posta locale può essere completamente disabilitata (eccetto i " "messaggi per root e postmaster)." #. Type: boolean @@ -261,7 +261,7 @@ msgstr "" "Nella configurazione predefinita, tutti i domini vengono trattati allo " "stesso modo. Se sia a.esempio che b.esempio sono domini locali, acc@a." -"esempio e acc@b.ese,pio verranno consegnati alla stessa destinazione. Se si " +"esempio e acc@b.esempio verranno consegnati alla stessa destinazione. Se si " "desidera altrimenti, è necessario modificare i file di configurazione in " "seguito." @@ -326,7 +326,7 @@ "blank." msgstr "" "Se questa macchina non dovrà fare da «smarthost» per nessun'altra, lasciare " -"il campo in bianco." +"il campo vuoto." #. Type: string #. Description @@ -362,9 +362,9 @@ "(for example smarthost.example::587 or 192.168.254.254::2525). Colons in " "IPv6 addresses need to be doubled." msgstr "" -"Inserire l'indirizzo IP o il nome del sel server di posta che dovrà essere " +"Inserire l'indirizzo IP o il nome del server di posta che dovrà essere " "utilizzato come «smarthost» in uscita. Se questo accetta la posta solo su " -"una porta diversa dalla TCP/25, aggiungere due puntie il numero della porta " +"una porta diversa dalla TCP/25, aggiungere due punti e il numero della porta " "(esempio smarthost.esempio::587 o 192.168.254.245::2525). I due punti negli " "indirizzi IPv6 devono essere raddoppiati." @@ -403,7 +403,7 @@ "If this value is left empty, such mail will be saved in /var/mail/mail, " "which is not recommended." msgstr "" -"Se si lascia il campo bianco la posta viene salvata in /var/mail/mail, che " +"Se si lascia il campo vuoto, la posta viene salvata in /var/mail/mail, che " "non è raccomandato." #. Type: string @@ -463,9 +463,9 @@ "when 127.0.0.1 is entered here, as this will disable listening on public " "network interfaces." msgstr "" -"Se questo sistems riceve posta solo da servizi locali (e non altri sistemi), " +"Se questo sistema riceve posta solo da servizi locali (e non altri sistemi), " "è preferibile proibire connessioni dall'esterno verso Exim. Questi servizi " -"includono programma di posta elettronica (MUA) che contattano solo " +"includono programmi di posta elettronica (MUA) che contattano solo " "localhost, oppure fetchmail. Se si inserisce «127.0.0.1» si rende " "impossibile la connessione dall'esterno, disabilitando quindi l'ascolto su " "interfacce di rete pubbliche." @@ -562,7 +562,7 @@ "in the Debian-specific README files in /usr/share/doc/exim4-base." msgstr "" "Una descrizione più dettagliata sulla configurazione divisa o meno in file è " -"disponibile del README specific per Debian in /usr/share/doc/exim4-base/" +"disponibile nel README specifico per Debian in /usr/share/doc/exim4-base/" "README.Debian.gz." #. Type: boolean @@ -581,7 +581,7 @@ "Path are rewritten." msgstr "" "L'intestazione dei messaggi in uscita può essere cambiata in modo da far " -"sembrare che il messaggio arrivi da un'altro sistema. Se viene scelta questa " +"sembrare che il messaggio arrivi da un altro sistema. Se viene scelta questa " "opzione, «${mailname}», «localhost» e «${dc_other_hostnames}» nei campi " "From, Reply-To, Sender e Return-Path saranno riscritti." diff -Nru exim4-4.82/debian/rules exim4-4.84/debian/rules --- exim4-4.82/debian/rules 2013-08-06 17:19:04.000000000 +0000 +++ exim4-4.84/debian/rules 2015-02-02 17:08:34.000000000 +0000 @@ -39,7 +39,8 @@ extradaemonpackages=exim4-daemon-heavy endif # If you want to build a daemon with a configuration tailored to YOUR special -# needs, call "fakeroot debian/rules unpack-configs", copy EDITME.exim4-light +# needs, uncomment the two custom packages in debian/control +# call "fakeroot debian/rules unpack-configs", copy EDITME.exim4-light # to EDITME.exim4-custom and modify it. Please note that you _need_ to # modify EDITME.exim4-custom or your build will fail due to #386188. # @@ -87,7 +88,7 @@ #DEBUGOUT:=$(shell echo >&2 extradaemonpackages $(extradaemonpackages)) # If you want to build with OpenSSL instead of GnuTLS, uncomment this -# OPENSSL:=1 +OPENSSL:=1 # Please note that building exim4-daemon-heavy with OpenSSL is a GPL # violation. diff -Nru exim4-4.82/debian/source/include-binaries exim4-4.84/debian/source/include-binaries --- exim4-4.82/debian/source/include-binaries 1970-01-01 00:00:00.000000000 +0000 +++ exim4-4.84/debian/source/include-binaries 2014-07-22 17:16:03.000000000 +0000 @@ -0,0 +1 @@ +debian/upstream-signing-key.pgp Binary files /tmp/6Yluu_GPeQ/exim4-4.82/debian/upstream-signing-key.pgp and /tmp/f63GYQCCSY/exim4-4.84/debian/upstream-signing-key.pgp differ diff -Nru exim4-4.82/debian/watch exim4-4.84/debian/watch --- exim4-4.82/debian/watch 2013-08-06 17:19:04.000000000 +0000 +++ exim4-4.84/debian/watch 2014-07-22 17:16:03.000000000 +0000 @@ -1,2 +1,3 @@ -version=2 +version=3 +opts=pgpsigurlmangle=s/$/.asc/ \ http://ftp.exim.org/pub/exim/exim4/exim-(\d.*)\.(?:tgz|tar\.(?:gz|bz2|xz)) diff -Nru exim4-4.82/doc/ChangeLog exim4-4.84/doc/ChangeLog --- exim4-4.82/doc/ChangeLog 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/doc/ChangeLog 2014-08-09 12:44:29.000000000 +0000 @@ -1,6 +1,175 @@ Change log file for Exim from version 4.21 ------------------------------------------- + +Exim version 4.84 +----------------- +TL/01 Bugzilla 1506: Re-add a 'return NULL' to silence complaints from static + checkers that were complaining about end of non-void function with no + return. + +JH/01 Bug 1513: Fix parsing of quoted parameter values in MIME headers. + This was a regression intruduced in 4.83 by another bugfix. + +JH/02 Fix broken compilation when EXPERIMENTAL_DSN is enabled. + +TL/02 Bug 1509: Fix exipick for enhanced spoolfile specification used when + EXPERIMENTAL_DNS is enabled. Fix from Wolfgang Breyha. + + +Exim version 4.83 +----------------- + +TF/01 Correctly close the server side of TLS when forking for delivery. + + When a message was received over SMTP with TLS, Exim failed to clear up + the incoming connection properly after forking off the child process to + deliver the message. In some situations the subsequent outgoing + delivery connection happened to have the same fd number as the incoming + connection previously had. Exim would try to use TLS and fail, logging + a "Bad file descriptor" error. + +TF/02 Portability fix for building lookup modules on Solaris when the xpg4 + utilities have not been installed. + +JH/01 Fix memory-handling in use of acl as a conditional; avoid free of + temporary space as the ACL may create new global variables. + +TL/01 LDAP support uses per connection or global context settings, depending + upon the detected version of the libraries at build time. + +TL/02 Experimental Proxy Protocol support: allows a proxied SMTP connection + to extract and use the src ip:port in logging and expansions as if it + were a direct connection from the outside internet. PPv2 support was + updated based on HAProxy spec change in May 2014. + +JH/02 Add ${listextract {number}{list}{success}{fail}}. + +TL/03 Bugzilla 1433: Fix DMARC SEGV with specific From header contents. + Properly escape header and check for NULL return. + +PP/01 Continue incomplete 4.82 PP/19 by fixing docs too: use dns_dnssec_ok + not dns_use_dnssec. + +JH/03 Bugzilla 1157: support log_selector smtp_confirmation for lmtp. + +TL/04 Add verify = header_names_ascii check to reject email with non-ASCII + characters in header names, implemented as a verify condition. + Contributed by Michael Fischer v. Mollard. + +TL/05 Rename SPF condition results err_perm and err_temp to standardized + results permerror and temperror. Previous values are deprecated but + still accepted. In a future release, err_perm and err_temp will be + completely removed, which will be a backward incompatibility if the + ACL tests for either of these two old results. Patch contributed by + user bes-internal on the mailing list. + +JH/04 Add ${utf8clean:} operator. Contributed by Alex Rau. + +JH/05 Bugzilla 305: Log incoming-TLS details on rejects, subject to log + selectors, in both main and reject logs. + +JH/06 Log outbound-TLS and port details, subject to log selectors, for a + failed delivery. + +JH/07 Add malware type "sock" for talking to simple daemon. + +JH/08 Bugzilla 1371: Add tls_{,try_}verify_hosts to smtp transport. + +JH/09 Bugzilla 1431: Support (with limitations) headers_add/headers_remove in + routers/transports under cutthrough routing. + +JH/10 Bugzilla 1005: ACL "condition =" should accept values which are negative + numbers. Touch up "bool" conditional to keep the same definition. + +TL/06 Remove duplicated language in spec file from 4.82 TL/16. + +JH/11 Add dnsdb tlsa lookup. From Todd Lyons. + +JH/12 Expand items in router/transport headers_add or headers_remove lists + individually rather than the list as a whole. Bug 1452. + + Required for reasonable handling of multiple headers_ options when + they may be empty; requires that headers_remove items with embedded + colons must have them doubled (or the list-separator changed). + +TL/07 Add new dmarc expansion variable $dmarc_domain_policy to directly + view the policy declared in the DMARC record. Currently, $dmarc_status + is a combined value of both the record presence and the result of the + analysis. + +JH/13 Fix handling of $tls_cipher et.al. in (non-verify) transport. Bug 1455. + +JH/14 New options dnssec_request_domains, dnssec_require_domains on the + dnslookup router and the smtp transport (applying to the forward + lookup). + +TL/08 Bugzilla 1453: New LDAP "SERVERS=" option allows admin to override list + of ldap servers used for a specific lookup. Patch provided by Heiko + Schlichting. + +JH/18 New options dnssec_lax, dnssec_strict on dnsdb lookups. + New variable $lookup_dnssec_authenticated for observability. + +TL/09 Bugzilla 609: Add -C option to exiqgrep, specify which exim.conf to use. + Patch submitted by Lars Timman. + +JH/19 EXPERIMENTAL_OCSP support under GnuTLS. Bug 1459. + +TL/10 Bugzilla 1454: New -oMm option to pass message reference to Exim. + Requires trusted mode and valid format message id, aborts otherwise. + Patch contributed by Heiko Schlichting. + +JH/20 New expansion variables tls_(in,out)_(our,peer)cert, and expansion item + certextract with support for various fields. Bug 1358. + +JH/21 Observability of OCSP via variables tls_(in,out)_ocsp. Stapling + is requested by default, modifiable by smtp transport option + hosts_request_ocsp. + +JH/22 Expansion operators ${md5:string} and ${sha1:string} can now + operate on certificate variables to give certificate fingerprints + Also new ${sha256:cert_variable}. + +JH/23 The PRDR feature is moved from being Experimental into the mainline. + +TL/11 Bug 1119: fix memory allocation in string_printing2(). Patch from + Christian Aistleitner. + +JH/24 The OCSP stapling feature is moved from Experimental into the mainline. + +TL/12 Bug 1444: Fix improper \r\n sequence handling when writing spool + file. Patch from Wolfgang Breyha. + +JH/25 Expand the coverage of the delivery $host and $host_address to + client authenticators run in verify callout. Bug 1476. + +JH/26 Port service names are now accepted for tls_on_connect_ports, to + align with daemon_smtp_ports. Bug 72. + +TF/03 Fix udpsend. The ip_connectedsocket() function's socket type + support and error reporting did not work properly. + +TL/13 Bug 1495: Exiqgrep check if -C config file specified on cli exists + and is readable. Patch from Andrew Colin Kissa. + +TL/14 Enhance documentation of ${run expansion and how it parses the + commandline after expansion, particularly in the case when an + unquoted variable expansion results in an empty value. + +JH/27 The TLS SNI feature was broken in 4.82. Fix it. + +PP/02 Fix internal collision of T_APL on systems which support RFC3123 + by renaming away from it. Addresses GH issue 15, reported by + Jasper Wallace. + +JH/28 Fix parsing of MIME headers for parameters with quoted semicolons. + +TL/15 SECURITY: prevent double expansion in math comparison functions + (can expand unsanitized data). Not remotely exploitable. + CVE-2014-2972 + + Exim version 4.82 ----------------- @@ -237,7 +406,7 @@ TL/12 Enhanced documentation in the ratelimit.pl script provided in the src/util/ subdirectory. -TL/13 Bug 1301 - Imported transport SQL logging patch from Axel Rau +TL/13 Bug 1031 - Imported transport SQL logging patch from Axel Rau renamed to Transport Post Delivery Action by Jeremy Harris, as EXPERIMENTAL_TPDA. diff -Nru exim4-4.82/doc/exim.8 exim4-4.84/doc/exim.8 --- exim4-4.82/doc/exim.8 2013-10-28 12:57:57.000000000 +0000 +++ exim4-4.84/doc/exim.8 2014-08-11 13:13:29.000000000 +0000 @@ -423,6 +423,7 @@ configuration file is output. If a list of configuration files was supplied, the value that is output here is the name of the file that was actually used. +.sp If the \fB\-n\fP flag is given, then for most modes of \fB\-bP\fP operation the name will not be output. .sp @@ -938,9 +939,11 @@ This option is equivalent to an ACL applying: .sp control = suppress_local_fixups +.sp for every message received. Note that Sendmail will complain about such bad formatting, where Exim silently just does not fix it up. This may change in future. +.sp As this affects audit information, the caller must be a trusted user to use this option. .TP 10 @@ -961,6 +964,7 @@ Its use is restricted to administrators. The configuration file has to be read and parsed, to determine access rights, before this is set and takes effect, so early configuration file errors will not honour this flag. +.sp The tag should not be longer than 32 characters. .TP 10 \fB\-M\fP <\fImessage id\fP> <\fImessage id\fP> ... @@ -1306,6 +1310,18 @@ using the same syntax as for \fB\-oMa\fP. The interface address is placed in \fI$received_ip_address\fP and the port number, if present, in \fI$received_port\fP. .TP 10 +\fB\-oMm\fP <\fImessage reference\fP> +See \fB\-oMa\fP above for general remarks about the \fB\-oM\fP options. The \fB\-oMm\fP +option sets the message reference, e.g. message\-id, and is logged during +delivery. This is useful when some kind of audit trail is required to tie +messages together. The format of the message reference is checked and will +abort if the format is invalid. The option will only be accepted if exim is +running in trusted mode, not as any regular user. +.sp +The best example of a message reference is when Exim sends a bounce message. +The message reference is the message\-id of the original message for which Exim +is sending the bounce. +.TP 10 \fB\-oMr\fP <\fIprotocol name\fP> See \fB\-oMa\fP above for general remarks about the \fB\-oM\fP options. The \fB\-oMr\fP option sets the received protocol value that is stored in @@ -1618,6 +1634,7 @@ \fB\-X\fP <\fIlogfile\fP> This option is interpreted by Sendmail to cause debug information to be sent to the named file. It is ignored by Exim. +.sp . .SH "SEE ALSO" .rs diff -Nru exim4-4.82/doc/experimental-spec.txt exim4-4.84/doc/experimental-spec.txt --- exim4-4.82/doc/experimental-spec.txt 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/doc/experimental-spec.txt 2014-08-09 12:44:29.000000000 +0000 @@ -6,114 +6,6 @@ liable to incompatible change. -PRDR support --------------------------------------------------------------- - -Per-Recipient Data Reponse is an SMTP extension proposed by Eric Hall -in a (now-expired) IETF draft from 2007. It's not hit mainstream -use, but has apparently been implemented in the META1 MTA. - -There is mention at http://mail.aegee.org/intern/sendmail.html -of a patch to sendmail "to make it PRDR capable". - - ref: http://www.eric-a-hall.com/specs/draft-hall-prdr-00.txt - -If Exim is built with EXPERIMENTAL_PRDR there is a new config -boolean "prdr_enable" which controls whether PRDR is advertised -as part of an EHLO response, a new "acl_data_smtp_prdr" ACL -(called for each recipient, after data arrives but before the -data ACL), and a new smtp transport option "hosts_try_prdr". - -PRDR may be used to support per-user content filtering. Without it -one must defer any recipient after the first that has a different -content-filter configuration. With PRDR, the RCPT-time check -for this can be disabled when the MAIL-time $smtp_command included -"PRDR". Any required difference in behaviour of the main DATA-time -ACL should however depend on the PRDR-time ACL having run, as Exim -will avoid doing so in some situations (eg. single-recipient mails). - - - -OCSP Stapling support --------------------------------------------------------------- - -X.509 PKI certificates expire and can be revoked; to handle this, the -clients need some way to determine if a particular certificate, from a -particular Certificate Authority (CA), is still valid. There are three -main ways to do so. - -The simplest way is to serve up a Certificate Revocation List (CRL) with -an ordinary web-server, regenerating the CRL before it expires. The -downside is that clients have to periodically re-download a potentially -huge file from every certificate authority it knows of. - -The way with most moving parts at query time is Online Certificate -Status Protocol (OCSP), where the client verifies the certificate -against an OCSP server run by the CA. This lets the CA track all -usage of the certs. This requires running software with access to the -private key of the CA, to sign the responses to the OCSP queries. OCSP -is based on HTTP and can be proxied accordingly. - -The only widespread OCSP server implementation (known to this writer) -comes as part of OpenSSL and aborts on an invalid request, such as -connecting to the port and then disconnecting. This requires -re-entering the passphrase each time some random client does this. - -The third way is OCSP Stapling; in this, the server using a certificate -issued by the CA periodically requests an OCSP proof of validity from -the OCSP server, then serves it up inline as part of the TLS -negotiation. This approach adds no extra round trips, does not let the -CA track users, scales well with number of certs issued by the CA and is -resilient to temporary OCSP server failures, as long as the server -starts retrying to fetch an OCSP proof some time before its current -proof expires. The downside is that it requires server support. - -If Exim is built with EXPERIMENTAL_OCSP and it was built with OpenSSL, -then it gains a new global option: "tls_ocsp_file". - -The file specified therein is expected to be in DER format, and contain -an OCSP proof. Exim will serve it as part of the TLS handshake. This -option will be re-expanded for SNI, if the tls_certificate option -contains $tls_sni, as per other TLS options. - -Exim does not at this time implement any support for fetching a new OCSP -proof. The burden is on the administrator to handle this, outside of -Exim. The file specified should be replaced atomically, so that the -contents are always valid. Exim will expand the "tls_ocsp_file" option -on each connection, so a new file will be handled transparently on the -next connection. - -Exim will check for a valid next update timestamp in the OCSP proof; -if not present, or if the proof has expired, it will be ignored. - -Also, given EXPERIMENTAL_OCSP and OpenSSL, the smtp transport gains -a "hosts_require_ocsp" option; a host-list for which an OCSP Stapling -is requested and required for the connection to proceed. The host(s) -should also be in "hosts_require_tls", and "tls_verify_certificates" -configured for the transport. - -For the client to be able to verify the stapled OCSP the server must -also supply, in its stapled information, any intermediate -certificates for the chain leading to the OCSP proof from the signer -of the server certificate. There may be zero or one such. These -intermediate certificates should be added to the server OCSP stapling -file (named by tls_ocsp_file). - -At this point in time, we're gathering feedback on use, to determine if -it's worth adding complexity to the Exim daemon to periodically re-fetch -OCSP files and somehow handling multiple files. - - A helper script "ocsp_fetch.pl" for fetching a proof from a CA - OCSP server is supplied. The server URL may be included in the - server certificate, if the CA is helpful. - - One fail mode seen was the OCSP Signer cert expiring before the end - of vailidity of the OCSP proof. The checking done by Exim/OpenSSL - noted this as invalid overall, but the re-fetch script did not. - - - - Brightmail AntiSpam (BMI) suppport -------------------------------------------------------------- @@ -452,15 +344,21 @@ This means the queried domain has published a SPF record, but wants to allow outside servers to send mail under its domain as well. - o err_perm This indicates a syntax error in the SPF - record of the queried domain. This should be - treated like "none". - o err_temp This indicates a temporary error during all + This should be treated like "none". + o permerror This indicates a syntax error in the SPF + record of the queried domain. You may deny + messages when this occurs. (Changed in 4.83) + o temperror This indicates a temporary error during all processing, including Exim's SPF processing. You may defer messages when this occurs. + (Changed in 4.83) + o err_temp Same as permerror, deprecated in 4.83, will be + removed in a future release. + o err_perm Same as temperror, deprecated in 4.83, will be + removed in a future release. You can prefix each string with an exclamation mark to invert -is meaning, for example "!fail" will match all results but +its meaning, for example "!fail" will match all results but "fail". The string list is evaluated left-to-right, in a short-circuit fashion. When a string matches the outcome of the SPF check, the condition succeeds. If none of the listed @@ -510,8 +408,8 @@ $spf_result This contains the outcome of the SPF check in string form, - one of pass, fail, softfail, none, neutral, err_perm or - err_temp. + one of pass, fail, softfail, none, neutral, permerror or + temperror. $spf_smtp_comment This contains a string that can be used in a SMTP response @@ -773,7 +671,7 @@ Of course, you can also use any other lookup method that Exim supports, including LDAP, Postgres, MySQL, etc, as long as the -result is a list of colon-separated strings; +result is a list of colon-separated strings. Several expansion variables are set before the DATA ACL is processed, and you can use them in this ACL. The following @@ -781,7 +679,10 @@ o $dmarc_status This is a one word status indicating what the DMARC library - thinks of the email. + thinks of the email. It is a combination of the results of + DMARC record lookup and the SPF/DKIM/DMARC processing results + (if a DMARC record was found). The actual policy declared + in the DMARC record is in a separate expansion variable. o $dmarc_status_text This is a slightly longer, human readable status. @@ -790,6 +691,11 @@ This is the domain which DMARC used to look up the DMARC policy record. + o $dmarc_domain_policy + This is the policy declared in the DMARC record. Valid values + are "none", "reject" and "quarantine". It is blank when there + is any error, including no DMARC record. + o $dmarc_ar_header This is the entire Authentication-Results header which you can add using an add_header modifier. @@ -825,6 +731,9 @@ warn !domains = +screwed_up_dmarc_records control = dmarc_enable_forensic + warn condition = (lookup if destined to mailing list) + set acl_m_mailing_list = 1 + (DATA ACL) warn dmarc_status = accept : none : off !authenticated = * @@ -840,6 +749,10 @@ set $acl_m_quarantine = 1 # Do something in a transport with this flag variable + deny condition = ${if eq{$dmarc_domain_policy}{reject}} + condition = ${if eq{$acl_m_mailing_list}{1}} + message = Messages from $dmarc_used_domain break mailing lists + deny dmarc_status = reject !authenticated = * message = Message from $domain_used_domain failed sender's DMARC policy, REJECT @@ -1015,6 +928,226 @@ set acl_c_spam_host = ${lookup redis{GET...}} +Proxy Protocol Support +-------------------------------------------------------------- + +Exim now has Experimental "Proxy Protocol" support. It was built on +specifications from: +http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt +Above URL revised May 2014 to change version 2 spec: +http://git.1wt.eu/web?p=haproxy.git;a=commitdiff;h=afb768340c9d7e50d8e + +The purpose of this function is so that an application load balancer, +such as HAProxy, can sit in front of several Exim servers and Exim +will log the IP that is connecting to the proxy server instead of +the IP of the proxy server when it connects to Exim. It resets the +$sender_address_host and $sender_address_port to the IP:port of the +connection to the proxy. It also re-queries the DNS information for +this new IP address so that the original sender's hostname and IP +get logged in the Exim logfile. There is no logging if a host passes or +fails Proxy Protocol negotiation, but it can easily be determined and +recorded in an ACL (example is below). + +1. To compile Exim with Proxy Protocol support, put this in +Local/Makefile: + +EXPERIMENTAL_PROXY=yes + +2. Global configuration settings: + +proxy_required_hosts = HOSTLIST + +The proxy_required_hosts option will require any IP in that hostlist +to use Proxy Protocol. The specification of Proxy Protocol is very +strict, and if proxy negotiation fails, Exim will not allow any SMTP +command other than QUIT. (See end of this section for an example.) +The option is expanded when used, so it can be a hostlist as well as +string of IP addresses. Since it is expanded, specifying an alternate +separator is supported for ease of use with IPv6 addresses. + +To log the IP of the proxy in the incoming logline, add: + log_selector = +proxy + +A default incoming logline (wrapped for appearance) will look like this: + + 2013-11-04 09:25:06 1VdNti-0001OY-1V <= me@example.net + H=mail.example.net [1.2.3.4] P=esmtp S=433 + +With the log selector enabled, an email that was proxied through a +Proxy Protocol server at 192.168.1.2 will look like this: + + 2013-11-04 09:25:06 1VdNti-0001OY-1V <= me@example.net + H=mail.example.net [1.2.3.4] P=esmtp PRX=192.168.1.2 S=433 + +3. In the ACL's the following expansion variables are available. + +proxy_host_address The (internal) src IP of the proxy server + making the connection to the Exim server. +proxy_host_port The (internal) src port the proxy server is + using to connect to the Exim server. +proxy_target_address The dest (public) IP of the remote host to + the proxy server. +proxy_target_port The dest port the remote host is using to + connect to the proxy server. +proxy_session Boolean, yes/no, the connected host is required + to use Proxy Protocol. + +There is no expansion for a failed proxy session, however you can detect +it by checking if $proxy_session is true but $proxy_host is empty. As +an example, in my connect ACL, I have: + + warn condition = ${if and{ {bool{$proxy_session}} \ + {eq{$proxy_host_address}{}} } } + log_message = Failed required proxy protocol negotiation \ + from $sender_host_name [$sender_host_address] + + warn condition = ${if and{ {bool{$proxy_session}} \ + {!eq{$proxy_host_address}{}} } } + # But don't log health probes from the proxy itself + condition = ${if eq{$proxy_host_address}{$sender_host_address} \ + {false}{true}} + log_message = Successfully proxied from $sender_host_name \ + [$sender_host_address] through proxy protocol \ + host $proxy_host_address + + # Possibly more clear + warn logwrite = Remote Source Address: $sender_host_address:$sender_host_port + logwrite = Proxy Target Address: $proxy_target_address:$proxy_target_port + logwrite = Proxy Internal Address: $proxy_host_address:$proxy_host_port + logwrite = Internal Server Address: $received_ip_address:$received_port + + +4. Recommended ACL additions: + - Since the real connections are all coming from your proxy, and the + per host connection tracking is done before Proxy Protocol is + evaluated, smtp_accept_max_per_host must be set high enough to + handle all of the parallel volume you expect per inbound proxy. + - With the smtp_accept_max_per_host set so high, you lose the ability + to protect your server from massive numbers of inbound connections + from one IP. In order to prevent your server from being DOS'd, you + need to add a per connection ratelimit to your connect ACL. I + suggest something like this: + + # Set max number of connections per host + LIMIT = 5 + # Or do some kind of IP lookup in a flat file or database + # LIMIT = ${lookup{$sender_host_address}iplsearch{/etc/exim/proxy_limits}} + + defer message = Too many connections from this IP right now + ratelimit = LIMIT / 5s / per_conn / strict + + +5. Runtime issues to be aware of: + - The proxy has 3 seconds (hard-coded in the source code) to send the + required Proxy Protocol header after it connects. If it does not, + the response to any commands will be: + "503 Command refused, required Proxy negotiation failed" + - If the incoming connection is configured in Exim to be a Proxy + Protocol host, but the proxy is not sending the header, the banner + does not get sent until the timeout occurs. If the sending host + sent any input (before the banner), this causes a standard Exim + synchronization error (i.e. trying to pipeline before PIPELINING + was advertised). + - This is not advised, but is mentioned for completeness if you have + a specific internal configuration that you want this: If the Exim + server only has an internal IP address and no other machines in your + organization will connect to it to try to send email, you may + simply set the hostlist to "*", however, this will prevent local + mail programs from working because that would require mail from + localhost to use Proxy Protocol. Again, not advised! + +6. Example of a refused connection because the Proxy Protocol header was +not sent from a host configured to use Proxy Protocol. In the example, +the 3 second timeout occurred (when a Proxy Protocol banner should have +been sent), the banner was displayed to the user, but all commands are +rejected except for QUIT: + +# nc mail.example.net 25 +220-mail.example.net, ESMTP Exim 4.82+proxy, Mon, 04 Nov 2013 10:45:59 +220 -0800 RFC's enforced +EHLO localhost +503 Command refused, required Proxy negotiation failed +QUIT +221 mail.example.net closing connection + + +DSN Support +-------------------------------------------------------------- + +DSN Support tries to add RFC 3461 support to Exim. It adds support for +*) the additional parameters for MAIL FROM and RCPT TO +*) RFC complient MIME DSN messages for all of + success, failure and delay notifications +*) dsn_advertise_hosts main option to select which hosts are able + to use the extension +*) dsn_lasthop router switch to end DSN processing + +In case of failure reports this means that the last three parts, the message body +intro, size info and final text, of the defined template are ignored since there is no +logical place to put them in the MIME message. + +All the other changes are made without changing any defaults + +Building exim: +-------------- + +Define +EXPERIMENTAL_DSN=YES +in your Local/Makefile. + +Configuration: +-------------- +All DSNs are sent in MIME format if you built exim with EXPERIMENTAL_DSN=YES +No option needed to activate it, and no way to turn it off. + +Failure and delay DSNs are triggered as usual except a sender used NOTIFY=... +to prevent them. + +Support for Success DSNs is added and activated by NOTIFY=SUCCESS by clients. + +Add +dsn_advertise_hosts = * +or a more restrictive host_list to announce DSN in EHLO answers + +Those hosts can then use NOTIFY,ENVID,RET,ORCPT options. + +If a message is relayed to a DSN aware host without changing the envelope +recipient the options are passed along and no success DSN is generated. + +A redirect router will always trigger a success DSN if requested and the DSN +options are not passed any further. + +A success DSN always contains the recipient address as submitted by the +client as required by RFC. Rewritten addresses are never exposed. + +If you used DSN patch up to 1.3 before remove all "dsn_process" switches from +your routers since you don't need them anymore. There is no way to "gag" +success DSNs anymore. Announcing DSN means answering as requested. + +You can prevent Exim from passing DSN options along to other DSN aware hosts by defining +dsn_lasthop +in a router. Exim will then send the success DSN himself if requested as if +the next hop does not support DSN. +Adding it to a redirect router makes no difference. + +Certificate name checking +-------------------------------------------------------------- +The X509 certificates used for TLS are supposed be verified +that they are owned by the expected host. The coding of TLS +support to date has not made these checks. + +If built with EXPERIMENTAL_CERTNAMES defined, code is +included to do so, and a new smtp transport option +"tls_verify_cert_hostname" supported which takes a list of +names for which the checks must be made. The host must +also be in "tls_verify_hosts". + +Both Subject and Subject-Alternate-Name certificate fields +are supported, as are wildcard certificates (limited to +a single wildcard being the initial component of a 3-or-more +component FQDN). + + -------------------------------------------------------------- End of file diff -Nru exim4-4.82/doc/filter.txt exim4-4.84/doc/filter.txt --- exim4-4.82/doc/filter.txt 2013-10-28 12:57:57.000000000 +0000 +++ exim4-4.84/doc/filter.txt 2014-08-11 13:13:29.000000000 +0000 @@ -2,11 +2,11 @@ Philip Hazel -Copyright (c) 2010 University of Cambridge +Copyright (c) 2014 University of Cambridge +-----------------------------------------------------------------------------+ +-------------------------------------+--------------------------------+------+ -|Revision 4.82 |28 Oct 2013 |PH | +|Revision 4.84 |11 Aug 2014 |PH | +-------------------------------------+--------------------------------+------+ ------------------------------------------------------------------------------- @@ -78,8 +78,8 @@ 1. FORWARDING AND FILTERING IN EXIM This document describes the user interfaces to Exim's in-built mail filtering -facilities, and is copyright (c) University of Cambridge 2010. It corresponds -to Exim version 4.82. +facilities, and is copyright (c) University of Cambridge 2014. It corresponds +to Exim version 4.84. 1.1 Introduction diff -Nru exim4-4.82/doc/NewStuff exim4-4.84/doc/NewStuff --- exim4-4.82/doc/NewStuff 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/doc/NewStuff 2014-08-09 12:44:29.000000000 +0000 @@ -6,6 +6,61 @@ test from the snapshots or the CVS before the documentation is updated. Once the documentation is updated, this file is reduced to a short list. +Version 4.84 +------------ + + +Version 4.83 +------------ + + 1. If built with the EXPERIMENTAL_PROXY feature enabled, Exim can be + configured to expect an initial header from a proxy that will make the + actual external source IP:host be used in exim instead of the IP of the + proxy that is connecting to it. + + 2. New verify option header_names_ascii, which will check to make sure + there are no non-ASCII characters in header names. Exim itself handles + those non-ASCII characters, but downstream apps may not, so Exim can + detect and reject if those characters are present. + + 3. New expansion operator ${utf8clean:string} to replace malformed UTF8 + codepoints with valid ones. + + 4. New malware type "sock". Talks over a Unix or TCP socket, sending one + command line and matching a regex against the return data for trigger + and a second regex to extract malware_name. The mail spoofile name can + be included in the command line. + + 5. The smtp transport now supports options "tls_verify_hosts" and + "tls_try_verify_hosts". If either is set the certificate verification + is split from the encryption operation. The default remains that a failed + verification cancels the encryption. + + 6. New SERVERS override of default ldap server list. In the ACLs, an ldap + lookup can now set a list of servers to use that is different from the + default list. + + 7. New command-line option -C for exiqgrep to specify alternate exim.conf + file when searching the queue. + + 8. OCSP now supports GnuTLS also, if you have version 3.1.3 or later of that. + + 9. Support for DNSSEC on outbound connections. + +10. New variables "tls_(in,out)_(our,peer)cert" and expansion item + "certextract" to extract fields from them. Hash operators md5 and sha1 + work over them for generating fingerprints, and a new sha256 operator + for them added. + +11. PRDR is now supported dy default. + +12. OCSP stapling is now supported by default. + +13. If built with the EXPERIMENTAL_DSN feature enabled, Exim will output + Delivery Status Notification messages in MIME format, and negociate + DSN features per RFC 3461. + + Version 4.82 ------------ diff -Nru exim4-4.82/doc/OptionLists.txt exim4-4.84/doc/OptionLists.txt --- exim4-4.82/doc/OptionLists.txt 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/doc/OptionLists.txt 2014-08-09 12:44:29.000000000 +0000 @@ -180,12 +180,12 @@ dns_check_names_pattern string + main 2.11 dns_csa_search_limit integer 5 main 4.60 dns_csa_use_reverse boolean true main 4.60 +dns_dnssec_ok integer -1 main 4.82 dns_ipv4_lookup boolean false main 3.20 dns_qualify_single boolean true smtp dns_retrans time 0s main 1.60 dns_retry integer 0 main 1.60 dns_search_parents boolean false smtp -dns_use_dnssec integer -1 main 4.82 dns_use_edns0 integer -1 main 4.76 domains domain list unset routers 4.00 driver string unset authenticators @@ -714,6 +714,7 @@ -oMai # Supply authenticated id -oMas # Supply authenticated sender -oMi # Supply interface address +-oMm # Supply message reference -oMr # Supply protocol name -oMs # Supply host name -oMt # Supply ident string diff -Nru exim4-4.82/doc/spec.txt exim4-4.84/doc/spec.txt --- exim4-4.82/doc/spec.txt 2013-10-28 12:57:55.000000000 +0000 +++ exim4-4.84/doc/spec.txt 2014-08-11 13:13:27.000000000 +0000 @@ -2,11 +2,11 @@ Exim Maintainers -Copyright (c) 2013 University of Cambridge +Copyright (c) 2014 University of Cambridge +-----------------------------------------------------------------------------+ +-------------------------------------+--------------------------------+------+ -|Revision 4.82 |28 Oct 2013 |EM | +|Revision 4.84 |11 Aug 2014 |EM | +-------------------------------------+--------------------------------+------+ ------------------------------------------------------------------------------- @@ -157,10 +157,10 @@ 10.12. Host list patterns for single-key lookups by host address 10.13. Host list patterns that match by host name 10.14. Behaviour when an IP address or name cannot be found - 10.15. Temporary DNS errors when looking up host information - 10.16. Host list patterns for single-key lookups by host name - 10.17. Host list patterns for query-style lookups - 10.18. Mixing wildcarded host names and addresses in host lists + 10.15. Mixing wildcarded host names and addresses in host lists + 10.16. Temporary DNS errors when looking up host information + 10.17. Host list patterns for single-key lookups by host name + 10.18. Host list patterns for query-style lookups 10.19. Address lists 10.20. Case of letters in address lists 10.21. Local part lists @@ -396,50 +396,51 @@ 42.6. The DATA ACLs 42.7. The SMTP DKIM ACL 42.8. The SMTP MIME ACL - 42.9. The QUIT ACL - 42.10. The not-QUIT ACL - 42.11. Finding an ACL to use - 42.12. ACL return codes - 42.13. Unset ACL options - 42.14. Data for message ACLs - 42.15. Data for non-message ACLs - 42.16. Format of an ACL - 42.17. ACL verbs - 42.18. ACL variables - 42.19. Condition and modifier processing - 42.20. ACL modifiers - 42.21. Use of the control modifier - 42.22. Summary of message fixup control - 42.23. Adding header lines in ACLs - 42.24. Removing header lines in ACLs - 42.25. ACL conditions - 42.26. Using DNS lists - 42.27. Specifying the IP address for a DNS list lookup - 42.28. DNS lists keyed on domain names - 42.29. Multiple explicit keys for a DNS list - 42.30. Data returned by DNS lists - 42.31. Variables set from DNS lists - 42.32. Additional matching conditions for DNS lists - 42.33. Negated DNS matching conditions - 42.34. Handling multiple DNS records from a DNS list - 42.35. Detailed information from merged DNS lists - 42.36. DNS lists and IPv6 - 42.37. Rate limiting incoming messages - 42.38. Ratelimit options for what is being measured - 42.39. Ratelimit update modes - 42.40. Ratelimit options for handling fast clients - 42.41. Limiting the rate of different events - 42.42. Using rate limiting - 42.43. Address verification - 42.44. Callout verification - 42.45. Additional parameters for callouts - 42.46. Callout caching - 42.47. Sender address verification reporting - 42.48. Redirection while verifying - 42.49. Client SMTP authorization (CSA) - 42.50. Bounce address tag validation - 42.51. Using an ACL to control relaying - 42.52. Checking a relay configuration + 42.9. The SMTP PRDR ACL + 42.10. The QUIT ACL + 42.11. The not-QUIT ACL + 42.12. Finding an ACL to use + 42.13. ACL return codes + 42.14. Unset ACL options + 42.15. Data for message ACLs + 42.16. Data for non-message ACLs + 42.17. Format of an ACL + 42.18. ACL verbs + 42.19. ACL variables + 42.20. Condition and modifier processing + 42.21. ACL modifiers + 42.22. Use of the control modifier + 42.23. Summary of message fixup control + 42.24. Adding header lines in ACLs + 42.25. Removing header lines in ACLs + 42.26. ACL conditions + 42.27. Using DNS lists + 42.28. Specifying the IP address for a DNS list lookup + 42.29. DNS lists keyed on domain names + 42.30. Multiple explicit keys for a DNS list + 42.31. Data returned by DNS lists + 42.32. Variables set from DNS lists + 42.33. Additional matching conditions for DNS lists + 42.34. Negated DNS matching conditions + 42.35. Handling multiple DNS records from a DNS list + 42.36. Detailed information from merged DNS lists + 42.37. DNS lists and IPv6 + 42.38. Rate limiting incoming messages + 42.39. Ratelimit options for what is being measured + 42.40. Ratelimit update modes + 42.41. Ratelimit options for handling fast clients + 42.42. Limiting the rate of different events + 42.43. Using rate limiting + 42.44. Address verification + 42.45. Callout verification + 42.46. Additional parameters for callouts + 42.47. Callout caching + 42.48. Sender address verification reporting + 42.49. Redirection while verifying + 42.50. Client SMTP authorization (CSA) + 42.51. Bounce address tag validation + 42.52. Using an ACL to control relaying + 42.53. Checking a relay configuration 43. Content scanning at ACL time @@ -656,8 +657,8 @@ 1.1 Exim documentation ---------------------- -This edition of the Exim specification applies to version 4.82 of Exim. -Substantive changes from the 4.80 edition are marked in some renditions of the +This edition of the Exim specification applies to version 4.84 of Exim. +Substantive changes from the 4.83 edition are marked in some renditions of the document; this paragraph is so marked if the rendition is capable of showing a change indicator. @@ -1545,7 +1546,7 @@ having to simulate the effect of the scanner. * Routers can be designated for use only when verifying an address, as - opposed to routing it for delivery. The verify_only option controls this. + opposed to routing it for delivery. The verify_only option controls this. Again, cutthrough delivery counts as a verification. * Individual routers can be explicitly skipped when running the routers to @@ -1765,7 +1766,7 @@ Exim is distributed as a gzipped or bzipped tar file which, when unpacked, creates a directory with the name of the current release (for example, -exim-4.82) into which the following files are placed: +exim-4.84) into which the following files are placed: ACKNOWLEDGMENTS contains some acknowledgments CHANGES contains a reference to where changes are documented @@ -2375,7 +2376,7 @@ For the utility programs, old versions are renamed by adding the suffix .O to their names. The Exim binary itself, however, is handled differently. It is installed under a name that includes the version number and the compile number, -for example exim-4.82-1. The script then arranges for a symbolic link called +for example exim-4.84-1. The script then arranges for a symbolic link called exim to point to the binary. If you are updating a previous version of Exim, the script takes care to ensure that the name exim is never absent from the directory (as seen by other processes). @@ -2884,7 +2885,7 @@ actually perform an ident callout when testing using -bh because there is no incoming SMTP connection. - Warning 2: Address verification callouts (see section 42.44) are also + Warning 2: Address verification callouts (see section 42.45) are also skipped when testing using -bh. If you want these callouts to occur, use -bhc instead. @@ -4007,6 +4008,19 @@ the same syntax as for -oMa. The interface address is placed in $received_ip_address and the port number, if present, in $received_port. +-oMm + + See -oMa above for general remarks about the -oM options. The -oMm option + sets the message reference, e.g. message-id, and is logged during delivery. + This is useful when some kind of audit trail is required to tie messages + together. The format of the message reference is checked and will abort if + the format is invalid. The option will only be accepted if exim is running + in trusted mode, not as any regular user. + + The best example of a message reference is when Exim sends a bounce + message. The message reference is the message-id of the original message + for which Exim is sending the bounce. + -oMr See -oMa above for general remarks about the -oM options. The -oMr option @@ -5194,15 +5208,18 @@ (hence their names): rfc1413_hosts = * -rfc1413_query_timeout = 5s +rfc1413_query_timeout = 0s + +These settings cause Exim to avoid ident callbacks for all incoming SMTP calls. +Few hosts offer RFC1413 service these days; calls have to be terminated by a +timeout and this needlessly delays the startup of an incoming SMTP connection. +If you have hosts for which you trust RFC1413 and need this information, you +can change this. -These settings cause Exim to make ident callbacks for all incoming SMTP calls. -You can limit the hosts to which these calls are made, or change the timeout -that is used. If you set the timeout to zero, all ident calls are disabled. -Although they are cheap and can provide useful information for tracing problem -messages, some hosts and firewalls have problems with ident calls. This can -result in a timeout instead of an immediate refused connection, leading to -delays on starting up an incoming SMTP session. +This line enables an efficiency SMTP option. It is negociated by clients and +not expected to cause problems but can be disabled if needed. + +prdr_enable = true When Exim receives messages over SMTP connections, it expects all addresses to be fully qualified with a domain, as required by the SMTP definition. However, @@ -5352,7 +5369,7 @@ address is refused. Verification consists of trying to route the address, to see if a bounce message could be delivered to it. In the case of remote addresses, basic verification checks only the domain, but callouts can be used -for more verification if required. Section 42.43 discusses the details of +for more verification if required. Section 42.44 discusses the details of address verification. accept hosts = +relay_from_hosts @@ -5628,9 +5645,13 @@ remote_smtp: driver = smtp + hosts_try_prdr = * -This transport is used for delivering messages over SMTP connections. All its -options are defaulted. The list of remote hosts comes from the router. +This transport is used for delivering messages over SMTP connections. The list +of remote hosts comes from the router. The hosts_try_prdr option enables an +efficiency SMTP option. It is negotiated between client and server and not +expected to cause problems but can be disabled if needed. All other options are +defaulted. local_delivery: driver = appendfile @@ -6363,8 +6384,8 @@ keyword causes a forced expansion failure - see section 11.4 for an explanation of what this means. -The supported DNS record types are A, CNAME, MX, NS, PTR, SPF, SRV, and TXT, -and, when Exim is compiled with IPv6 support, AAAA (and A6 if that is also +The supported DNS record types are A, CNAME, MX, NS, PTR, SPF, SRV, TLSA and +TXT, and, when Exim is compiled with IPv6 support, AAAA (and A6 if that is also configured). If no type is given, TXT is assumed. When the type is PTR, the data can be an IP address, written as normal; inversion and the addition of in-addr.arpa or ip6.arpa happens automatically. For example: @@ -6439,7 +6460,7 @@ list. A third pseudo-type is CSA (Client SMTP Authorization). This looks up SRV -records according to the CSA rules, which are described in section 42.49. +records according to the CSA rules, which are described in section 42.50. Although dnsdb supports SRV lookups directly, this is not sufficient because of the extra parent domain search behaviour of CSA. The result of a successful lookup such as: @@ -6478,16 +6499,18 @@ in the same way that multiple DNS records for a single item are handled. A different separator can be specified, as described above. +Modifiers for dnsdb lookups are givien by optional keywords, each followed by a +comma, that may appear before the record type. + The dnsdb lookup fails only if all the DNS lookups fail. If there is a -temporary DNS error for any of them, the behaviour is controlled by an optional -keyword followed by a comma that may appear before the record type. The -possible keywords are "defer_strict", "defer_never", and "defer_lax". With -"strict" behaviour, any temporary DNS error causes the whole lookup to defer. -With "never" behaviour, a temporary DNS error is ignored, and the behaviour is -as if the DNS lookup failed to find anything. With "lax" behaviour, all the -queries are attempted, but a temporary DNS error causes the whole lookup to -defer only if none of the other lookups succeed. The default is "lax", so the -following lookups are equivalent: +temporary DNS error for any of them, the behaviour is controlled by a +defer-option modifier. The possible keywords are "defer_strict", "defer_never", +and "defer_lax". With "strict" behaviour, any temporary DNS error causes the +whole lookup to defer. With "never" behaviour, a temporary DNS error is +ignored, and the behaviour is as if the DNS lookup failed to find anything. +With "lax" behaviour, all the queries are attempted, but a temporary DNS error +causes the whole lookup to defer only if none of the other lookups succeed. The +default is "lax", so the following lookups are equivalent: ${lookup dnsdb{defer_lax,a=one.host.com:two.host.com}} ${lookup dnsdb{a=one.host.com:two.host.com}} @@ -6495,6 +6518,14 @@ Thus, in the default case, as long as at least one of the DNS lookups yields some data, the lookup succeeds. +Use of DNSSEC is controlled by a dnssec modifier. The possible keywords are +"dnssec_strict", "dnssec_lax", and "dnssec_never". With "strict" or "lax" +DNSSEC information is requested with the lookup. With "strict" a response from +the DNS resolver that is not labelled as authenticated data is treated as +equivalent to a temporary DNS error. The default is "never". + +See also the $lookup_dnssec_authenticated variable. + 9.13 More about LDAP -------------------- @@ -6553,6 +6584,15 @@ LDAP connections, rather than the SSL-on-connect "ldaps". See the ldap_start_tls option. +Starting with Exim 4.83, the initialization of LDAP with TLS is more tightly +controlled. Every part of the TLS configuration can be configured by settings +in exim.conf. Depending on the version of the client libraries installed on +your system, some of the initialization may have required setting options in / +etc/ldap.conf or ~/.ldaprc to get TLS working with self-signed certificates. +This revealed a nuance where the current UID that exim was running as could +affect which config files it read. With Exim 4.83, these methods become +optional, only taking effect if not specifically set in exim.conf. + 9.15 LDAP quoting ----------------- @@ -6697,6 +6737,7 @@ USER set the DN, for authenticating the LDAP bind PASS set the password, likewise REFERRALS set the referrals parameter +SERVERS set alternate server list for this query only SIZE set the limit for the number of entries returned TIME set the maximum waiting time for a query @@ -6718,6 +6759,14 @@ The TIME parameter (also a number of seconds) is passed to the server to set a server-side limit on the time taken to complete a search. +The SERVERS parameter allows you to specify an alternate list of ldap servers +to use for an individual lookup. The global ldap_servers option provides a +default list of ldap servers, and a single lookup can specify a single ldap +server to use. But when you need to do a lookup with a list of servers that is +different than the default list (maybe different order, maybe a completely +different set of servers), the SERVERS parameter allows you to specify this +alternate list. + Here is an example of an LDAP query in an Exim lookup that uses some of these values. This is a single line, folded to fit on the page: @@ -7701,7 +7750,13 @@ Both "+include_unknown" and "+ignore_unknown" may appear in the same list. The effect of each one lasts until the next, or until the end of the list. -To explain the host/ip processing logic a different way for the same ACL: + +10.15 Mixing wildcarded host names and addresses in host lists +-------------------------------------------------------------- + +This section explains the host/ip processing logic with the same concepts as +the previous section, but specifically addresses what happens when a wildcarded +hostname is one of the items in the hostlist. * If you have name lookups or wildcarded host names and IP addresses in the same host list, you should normally put the IP addresses first. For @@ -7727,7 +7782,7 @@ was discussed in depth in the first example in this section. -10.15 Temporary DNS errors when looking up host information +10.16 Temporary DNS errors when looking up host information ----------------------------------------------------------- A temporary DNS lookup failure normally causes a defer action (except when @@ -7738,7 +7793,7 @@ lists such as whitelists. -10.16 Host list patterns for single-key lookups by host name +10.17 Host list patterns for single-key lookups by host name ------------------------------------------------------------ If a pattern is of the form @@ -7760,7 +7815,7 @@ an address lookup and one doing a name lookup, both using the same file. -10.17 Host list patterns for query-style lookups +10.18 Host list patterns for query-style lookups ------------------------------------------------ If a pattern is of the form @@ -7791,32 +7846,6 @@ 10.12.) -10.18 Mixing wildcarded host names and addresses in host lists --------------------------------------------------------------- - -If you have name lookups or wildcarded host names and IP addresses in the same -host list, you should normally put the IP addresses first. For example, in an -ACL you could have: - -accept hosts = 10.9.8.7 : *.friend.example - -The reason for this lies in the left-to-right way that Exim processes lists. It -can test IP addresses without doing any DNS lookups, but when it reaches an -item that requires a host name, it fails if it cannot find a host name to -compare with the pattern. If the above list is given in the opposite order, the -accept statement fails for a host whose name cannot be found, even if its IP -address is 10.9.8.7. - -If you really do want to do the name check first, and still recognize the IP -address, you can rewrite the ACL like this: - -accept hosts = *.friend.example -accept hosts = 10.9.8.7 - -If the first accept fails, Exim goes on to try the second one. See chapter 42 -for details of ACLs. - - 10.19 Address lists ------------------- @@ -8197,6 +8226,59 @@ is an empty string. If the ACL returns defer the result is a forced-fail. Otherwise the expansion fails. +${certextract{}{}{}{}} + + The must be a variable of type certificate. The field name is + expanded and used to retrive the relevant field from the certificate. + Supported fields are: + + version + serial_number + subject RFC4514 DN + issuer RFC4514 DN + notbefore time + notafter time + sig_algorithm + signature + subj_altname tagged list + ocsp_uri list + crl_uri list + + If the field is found, is expanded, and replaces the whole item; + otherwise is used. During the expansion of the variable + $value contains the value that has been extracted. Afterwards, it is + restored to any previous value it might have had. + + If {} is omitted, the item is replaced by an empty string if the + key is not found. If {} is also omitted, the value that was + extracted is used. + + Some field names take optional modifiers, appended and separated by commas. + + The field selectors marked as "RFC4514" above output a Distinguished Name + string which is not quite parseable by Exim as a comma-separated tagged + list (the exceptions being elements containin commas). RDN elements of a + single type may be selected by a modifier of the type label; if so the + expansion result is a list (newline-separated by default). The separator + may be changed by another modifer of a right angle-bracket followed + immediately by the new separator. Recognised RDN type labels include "CN", + "O", "OU" and "DC". + + The field selectors marked as "time" above may output a number of seconds + since epoch if the modifier "int" is used. + + The field selectors marked as "list" above return a list, newline-separated + by default, (embedded separator characters in elements are doubled). The + separator may be changed by a modifier of a right angle-bracket followed + immediately by the new separator. + + The field selectors marked as "tagged" above prefix each list element with + a type string and an equals sign. Elements of only one type may be selected + by a modifier which is one of "dns", "uri" or "mail"; if so the elenment + tags are omitted. + + If not otherwise noted field values are presented in human-readable form. + ${dlfunc{}{}{}{}...} This expansion dynamically loads and then calls a locally-written C @@ -8496,6 +8578,37 @@ , whichever is the shorter. Do not confuse length with strlen, which gives the length of a string. +${listextract{}{}{}{}} + + The argument must consist entirely of decimal digits, apart from + an optional leading minus, and leading and trailing white space (which is + ignored). + + After expansion, is interpreted as a list, colon-separated by + default, but the separator can be changed in the usual way. + + The first field of the list is numbered one. If the number is negative, the + fields are counted from the end of the list, with the rightmost one + numbered -1. The numbered element of the list is extracted and placed in + $value, then is expanded as the result. + + If the modulus of the number is zero or greater than the number of fields + in the string, the result is the expansion of . + + For example: + + ${listextract{2}{x:42:99}} + + yields "42", and + + ${listextract{-3}{<, x,42,99,& Mailer,,/bin/bash}{result: $value}} + + yields "result: 99". + + If {} is omitted, an empty string is used for string3. If {< + string2>} is also omitted, the value that was extracted is used. You can + use "fail" instead of {} as in a string extract. + ${lookup{} {} {} {}} This is the first of one of two different types of lookup item, which are @@ -8615,7 +8728,7 @@ absent, it defaults to 0. The result of the expansion is a prvs-signed email address, to be typically used with the return_path option on an smtp transport as part of a bounce address tag validation (BATV) scheme. For - more discussion and an example, see section 42.50. + more discussion and an example, see section 42.51. ${prvscheck{
}{}{}} @@ -8641,7 +8754,7 @@ All three variables can be used in the expansion of the third argument. However, once the expansion is complete, only $prvscheck_result remains - set. For more discussion and an example, see section 42.50. + set. For more discussion and an example, see section 42.51. ${readfile{}{}} @@ -8749,10 +8862,22 @@ ${run{ }{}{}} - The command and its arguments are first expanded separately, and then the - command is run in a separate process, but under the same uid and gid. As in - other command executions from Exim, a shell is not used by default. If you - want a shell, you must explicitly code it. + The command and its arguments are first expanded as one string. The string + is split apart into individual arguments by spaces, and then the command is + run in a separate process, but under the same uid and gid. As in other + command executions from Exim, a shell is not used by default. If the + command requires a shell, you must explicitly code it. + + Since the arguments are split by spaces, when there is a variable expansion + which has an empty result, it will cause the situation that the argument + will simply be omitted when the program is actually executed by Exim. If + the script/program requires a specific number of arguments and the expanded + variable could possibly result in this empty expansion, the variable must + be quoted. This is more difficult if the expanded variable itself could + result in a string containing quotes, because it would interfere with the + quotes around the command arguments. A possible guard against this is to + wrap the variable in the sg operator to change any quote marks to some + other character. The standard input for the command exists, but is empty. The standard output and standard error are set to the same file descriptor. If the @@ -9264,6 +9389,13 @@ it as a 40-digit hexadecimal number, in which any letters are in upper case. +${sha256:} + + The sha256 operator computes the SHA-256 hash fingerprint of the + certificate, and returns it as a 64-digit hexadecimal number, in which any + letters are in upper case. Only arguments which are a single variable of + certificate type are supported. + ${stat:} The string, after expansion, must be a file path. A call to the stat() @@ -9316,6 +9448,11 @@ This forces the letters in the string into upper-case. +${utf8clean:} + + This replaces any invalid utf-8 sequence in the string by the character "? + ". + 11.7 Expansion conditions ------------------------- @@ -9371,10 +9508,10 @@ This condition turns a string holding a true or false representation into a boolean state. It parses "true", "false", "yes" and "no" - (case-insensitively); also positive integer numbers map to true if - non-zero, false if zero. An empty string is treated as false. Leading and - trailing whitespace is ignored; thus a string consisting only of whitespace - is false. All other string values will result in expansion failure. + (case-insensitively); also integer numbers map to true if non-zero, false + if zero. An empty string is treated as false. Leading and trailing + whitespace is ignored; thus a string consisting only of whitespace is + false. All other string values will result in expansion failure. When combined with ACL variables, this expansion condition will let you make decisions in one place and act on those decisions in another place. @@ -9909,6 +10046,12 @@ commands available in Exim filter files include an if command with its own regular expression matching condition. +$acl_arg1, $acl_arg2, etc + + Within an acl condition, expansion condition or expansion item any + arguments are copied to these variables, any unused variables being made + empty. + $acl_c... Values can be placed in these variables by the set modifier in an ACL. They @@ -9932,6 +10075,11 @@ the message, and can be accessed by filters, routers, and transports during subsequent delivery. +$acl_narg + + Within an acl condition, expansion condition or expansion item this + variable has the number of arguments. + $acl_verify_message After an address verification has failed, this variable contains the @@ -10111,7 +10259,7 @@ When a DNS (black) list lookup succeeds, these variables are set to contain the following data from the lookup: the list's domain name, the key that was looked up, the contents of any associated TXT record, and the value - from the main A record. See section 42.31 for more details. + from the main A record. See section 42.32 for more details. $domain @@ -10200,7 +10348,7 @@ $headers_added Within an ACL this variable contains the headers added so far by the ACL - modifier add_header (section 42.23). The headers are a newline-separated + modifier add_header (section 42.24). The headers are a newline-separated list. $home @@ -10423,6 +10571,13 @@ the ability to find the amount of free space (only true for experimental systems), the space value is -1. See also the check_log_space option. +$lookup_dnssec_authenticated + + This variable is set after a DNS lookup done by a dnsdb lookup expansion, + dnslookup router or smtp transport. It will be empty if DNSSEC was not + requested, "no" if the result was not labelled as authenticated data and + "yes" if it was. + $mailstore_basename This variable is set only when doing deliveries in "mailstore" format in @@ -10632,17 +10787,17 @@ $prvscheck_address This variable is used in conjunction with the prvscheck expansion item, - which is described in sections 11.5 and 42.50. + which is described in sections 11.5 and 42.51. $prvscheck_keynum This variable is used in conjunction with the prvscheck expansion item, - which is described in sections 11.5 and 42.50. + which is described in sections 11.5 and 42.51. $prvscheck_result This variable is used in conjunction with the prvscheck expansion item, - which is described in sections 11.5 and 42.50. + which is described in sections 11.5 and 42.51. $qualify_domain @@ -10921,15 +11076,15 @@ $sender_host_dnssec - If $sender_host_name has been populated (by reference, hosts_lookup or - otherwise) then this boolean will have been set true if, and only if, the - resolver library states that the reverse DNS was authenticated data. At all - other times, this variable is false. + If an attempt to populate $sender_host_name has been made (by reference, + hosts_lookup or otherwise) then this boolean will have been set true if, + and only if, the resolver library states that the reverse DNS was + authenticated data. At all other times, this variable is false. It is likely that you will need to coerce DNSSEC support on in the resolver library, by setting: - dns_use_dnssec = 1 + dns_dnssec_ok = 1 Exim does not perform DNSSEC validation itself, instead leaving that to a validating resolver (eg, unbound, or bind with suitable configuration). @@ -10976,7 +11131,7 @@ unset.) * Exim needs the host name in order to test an item in a host list. The - items that require this are described in sections 10.13 and 10.16. + items that require this are described in sections 10.13 and 10.17. * The calling host matches helo_try_verify_hosts or helo_verify_hosts. In this case, the host name is required to compare with the name quoted in @@ -11005,7 +11160,7 @@ $sender_rate_xxx A number of variables whose names begin $sender_rate_ are set as part of - the ratelimit ACL condition. Details are given in section 42.37. + the ratelimit ACL condition. Details are given in section 42.38. $sender_rcvhost @@ -11150,6 +11305,30 @@ SMTP connection; the meaning of this depends upon the TLS implementation used. If TLS has not been negotiated, the value will be 0. +$tls_in_ourcert + + This variable refers to the certificate presented to the peer of an inbound + connection when the message was received. It is only useful as the argument + of a certextract expansion item, md5 or sha1 operator, or a def condition. + +$tls_in_peercert + + This variable refers to the certificate presented by the peer of an inbound + connection when the message was received. It is only useful as the argument + of a certextract expansion item, md5 or sha1 operator, or a def condition. + +$tls_out_ourcert + + This variable refers to the certificate presented to the peer of an + outbound connection. It is only useful as the argument of a certextract + expansion item, md5 or sha1 operator, or a def condition. + +$tls_out_peercert + + This variable refers to the certificate presented by the peer of an + outbound connection. It is only useful as the argument of a certextract + expansion item, md5 or sha1 operator, or a def condition. + $tls_in_certificate_verified This variable is set to "1" if a TLS certificate was verified when the @@ -11184,6 +11363,22 @@ for details of TLS support and chapter 30 for details of the smtp transport. +$tls_in_ocsp + + When a message is received from a remote client connection the result of + any OCSP request from the client is encoded in this variable: + + 0 OCSP proof was not requested (default value) + 1 No response to request + 2 Response not verified + 3 Verification failed + 4 Verification succeeded + +$tls_out_ocsp + + When a message is sent to a remote host connection the result of any OCSP + request made is encoded in this variable. See $tls_in_ocsp for values. + $tls_in_peerdn When a message is received from a remote host over an encrypted SMTP @@ -11463,8 +11658,8 @@ the interfaces and ports on which it listens are controlled by the following options: - * daemon_smtp_ports contains a list of default ports. (For backward - compatibility, this option can also be specified in the singular.) + * daemon_smtp_ports contains a list of default ports or service names. (For + backward compatibility, this option can also be specified in the singular.) * local_interfaces contains list of interface IP addresses on which to listen. Each item may optionally also specify a port. @@ -11556,8 +11751,8 @@ Exim supports the obsolete SSMTP protocol (also known as SMTPS) that was used before the STARTTLS command was standardized for SMTP. Some legacy clients still use this protocol. If the tls_on_connect_ports option is set to a list of -port numbers, connections to those ports must use SSMTP. The most common use of -this option is expected to be +port numbers or service names, connections to those ports must use SSMTP. The +most common use of this option is expected to be tls_on_connect_ports = 465 @@ -11877,6 +12072,7 @@ acl_smtp_auth ACL for AUTH acl_smtp_connect ACL for connection acl_smtp_data ACL for DATA +acl_smtp_data_prdr ACL for DATA, per-recipient acl_smtp_dkim ACL for DKIM verification acl_smtp_etrn ACL for ETRN acl_smtp_expn ACL for EXPN @@ -11932,6 +12128,7 @@ tls_crl certificate revocation list tls_dh_max_bits clamp D-H bit count suggestion tls_dhparam DH parameters for server +tls_ocsp_file location of server certificate status proof tls_on_connect_ports specify SSMTP (SMTPS) ports tls_privatekey location of server private key tls_remember_esmtp don't reset after starting TLS @@ -12018,6 +12215,7 @@ ignore_fromline_hosts allow "From " from these hosts ignore_fromline_local allow "From " from local SMTP pipelining_advertise_hosts advertise pipelining to these hosts +prdr_enable advertise PRDR to all hosts tls_advertise_hosts advertise TLS to these hosts @@ -12058,10 +12256,10 @@ disable_ipv6 do no IPv6 processing dns_again_means_nonexist for broken domains dns_check_names_pattern pre-DNS syntax check +dns_dnssec_ok parameter for resolver dns_ipv4_lookup only v4 lookup for these domains dns_retrans parameter for resolver dns_retry parameter for resolver -dns_use_dnssec parameter for resolver dns_use_edns0 parameter for resolver hold_domains hold delivery for these domains local_interfaces for routing checks @@ -12168,6 +12366,15 @@ processed and the message itself has been received, but before the final acknowledgment is sent. See chapter 42 for further details. ++------------------+---------+-------------+--------------+ +|acl_smtp_data_prdr|Use: main|Type: string*|Default: unset| ++------------------+---------+-------------+--------------+ + +This option defines the ACL that, if the PRDR feature has been negotiated, is +run for each recipient after an SMTP DATA command has been processed and the +message itself has been received, but before the acknowledgment is sent. See +chapter 42 for further details. + +-------------+---------+-------------+--------------+ |acl_smtp_etrn|Use: main|Type: string*|Default: unset| +-------------+---------+-------------+--------------+ @@ -12450,32 +12657,32 @@ +------------------------------+---------+----------+-----------+ This option specifies the expiry time for negative callout cache data for a -domain. See section 42.44 for details of callout verification, and section -42.46 for details of the caching. +domain. See section 42.45 for details of callout verification, and section +42.47 for details of the caching. +------------------------------+---------+----------+-----------+ |callout_domain_positive_expire|Use: main|Type: time|Default: 7d| +------------------------------+---------+----------+-----------+ This option specifies the expiry time for positive callout cache data for a -domain. See section 42.44 for details of callout verification, and section -42.46 for details of the caching. +domain. See section 42.45 for details of callout verification, and section +42.47 for details of the caching. +-----------------------+---------+----------+-----------+ |callout_negative_expire|Use: main|Type: time|Default: 2h| +-----------------------+---------+----------+-----------+ This option specifies the expiry time for negative callout cache data for an -address. See section 42.44 for details of callout verification, and section -42.46 for details of the caching. +address. See section 42.45 for details of callout verification, and section +42.47 for details of the caching. +-----------------------+---------+----------+------------+ |callout_positive_expire|Use: main|Type: time|Default: 24h| +-----------------------+---------+----------+------------+ This option specifies the expiry time for positive callout cache data for an -address. See section 42.44 for details of callout verification, and section -42.46 for details of the caching. +address. See section 42.45 for details of callout verification, and section +42.47 for details of the caching. +-------------------------+---------+-------------+------------------+ |callout_random_local_part|Use: main|Type: string*|Default: see below| @@ -12486,7 +12693,7 @@ $primary_hostname-$tod_epoch-testing -See section 42.45 for details of how this value is used. +See section 42.46 for details of how this value is used. +----------------+---------+-------------+----------+ |check_log_inodes|Use: main|Type: integer|Default: 0| @@ -12607,6 +12814,10 @@ delay_warning = 2h:12h:99d +Note that the option is only evaluated at the time a delivery attempt fails, +which depends on retry and queue-runner configuration. Typically retries will +be configured more frequently than warning messages. + +-----------------------+---------+-------------+------------------+ |delay_warning_condition|Use: main|Type: string*|Default: see below| +-----------------------+---------+-------------+------------------+ @@ -12733,7 +12944,7 @@ +--------------------+---------+-------------+----------+ This option controls the depth of parental searching for CSA SRV records in the -DNS, as described in more detail in section 42.49. +DNS, as described in more detail in section 42.50. +-------------------+---------+-------------+-------------+ |dns_csa_use_reverse|Use: main|Type: boolean|Default: true| @@ -12741,7 +12952,17 @@ This option controls whether or not an IP address, given as a CSA domain, is reversed and looked up in the reverse DNS, as described in more detail in -section 42.49. +section 42.50. + ++-------------+---------+-------------+-----------+ +|dns_dnssec_ok|Use: main|Type: integer|Default: -1| ++-------------+---------+-------------+-----------+ + +If this option is set to a non-negative number then Exim will initialise the +DNS resolver library to either use or not use DNSSEC, overriding the system +default. A value of 0 coerces DNSSEC off, a value of 1 coerces DNSSEC on. + +If the resolver library does not support DNSSEC then this option has no effect. +---------------+---------+------------------+--------------+ |dns_ipv4_lookup|Use: main|Type: domain list*|Default: unset| @@ -12775,16 +12996,6 @@ See dns_retrans above. -+--------------+---------+-------------+-----------+ -|dns_use_dnssec|Use: main|Type: integer|Default: -1| -+--------------+---------+-------------+-----------+ - -If this option is set to a non-negative number then Exim will initialise the -DNS resolver library to either use or not use DNSSEC, overriding the system -default. A value of 0 coerces DNSSEC off, a value of 1 coerces DNSSEC on. - -If the resolver library does not support DNSSEC then this option has no effect. - +-------------+---------+-------------+-----------+ |dns_use_edns0|Use: main|Type: integer|Default: -1| +-------------+---------+-------------+-----------+ @@ -13849,12 +14060,22 @@ This option can be used to suppress the advertisement of the SMTP PIPELINING extension to specific hosts. See also the no_pipelining control in section -42.21. When PIPELINING is not advertised and smtp_enforce_sync is true, an Exim +42.22. When PIPELINING is not advertised and smtp_enforce_sync is true, an Exim server enforces strict synchronization for each SMTP command and response. When PIPELINING is advertised, Exim assumes that clients will use it; "out of order" commands that are "expected" do not count as protocol errors (see smtp_max_synprot_errors). ++-----------+---------+-------------+--------------+ +|prdr_enable|Use: main|Type: boolean|Default: false| ++-----------+---------+-------------+--------------+ + +This option can be used to enable the Per-Recipient Data Response extension to +SMTP, defined by Eric Hall. If the option is set, PRDR is advertised by Exim +when operating as a server. If the client requests PRDR, and more than one +recipient, for a message an additional ACL is called for each recipient after +the message content is recieved. See section 42.9. + +---------------------+---------+-------------+--------------+ |preserve_message_logs|Use: main|Type: boolean|Default: false| +---------------------+---------+-------------+--------------+ @@ -14509,7 +14730,7 @@ The check can be globally disabled by setting smtp_enforce_sync false. If you want to disable the check selectively (for example, only for certain hosts), you can do so by an appropriate use of a control modifier in an ACL (see -section 42.21). See also pipelining_advertise_hosts. +section 42.22). See also pipelining_advertise_hosts. +-----------------+---------+-------------+--------------+ |smtp_etrn_command|Use: main|Type: string*|Default: unset| @@ -14590,7 +14811,7 @@ Exim has two rate-limiting facilities. This section describes the older facility, which can limit rates within a single connection. The newer ratelimit -ACL condition can limit rates across all connections. See section 42.37 for +ACL condition can limit rates across all connections. See section 42.38 for details of the newer facility. When a host matches smtp_ratelimit_hosts, the values of smtp_ratelimit_mail and @@ -14751,7 +14972,7 @@ This option controls what happens if a syntactically valid but undefined ACL variable is referenced. If it is false (the default), an empty string is -substituted; if it is true, an error is generated. See section 42.18 for +substituted; if it is true, an error is generated. See section 42.19 for details of ACL variables. +---------------------------+---------+-------------+--------------+ @@ -15039,6 +15260,14 @@ to the 4.80 release, as Debian used to patch Exim to raise the minimum acceptable bound from 1024 to 2048. ++-------------+---------+-------------+--------------+ +|tls_ocsp_file|Use: main|Type: string*|Default: unset| ++-------------+---------+-------------+--------------+ + +This option must if set expand to the absolute path to a file which contains a +current status proof for the server's certificate, as obtained from the +Certificate Authority. + +--------------------+---------+-----------------+--------------+ |tls_on_connect_ports|Use: main|Type: string list|Default: unset| +--------------------+---------+-----------------+--------------+ @@ -15380,7 +15609,7 @@ This option applies to the processing of an address by a router. When a recipient address is being processed in an ACL, there is a separate control modifier that can be used to specify case-sensitive processing within the ACL -(see section 42.21). +(see section 42.22). +----------------+--------------+-------------+--------------+ |check_local_user|Use: routers**|Type: boolean|Default: false| @@ -15593,23 +15822,23 @@ check_local_user is set, when the default is taken from the password information. See also initgroups and user and the discussion in chapter 23. -+-----------+------------+-------------+--------------+ -|headers_add|Use: routers|Type: string*|Default: unset| -+-----------+------------+-------------+--------------+ - -This option specifies a string of text that is expanded at routing time, and -associated with any addresses that are accepted by the router. However, this -option has no effect when an address is just being verified. The way in which -the text is used to add header lines at transport time is described in section -46.17. New header lines are not actually added until the message is in the -process of being transported. This means that references to header lines in -string expansions in the transport's configuration do not "see" the added -header lines. ++-----------+------------+-----------+--------------+ +|headers_add|Use: routers|Type: list*|Default: unset| ++-----------+------------+-----------+--------------+ + +This option specifies a list of text headers, newline-separated, that is +associated with any addresses that are accepted by the router. Each item is +separately expanded, at routing time. However, this option has no effect when +an address is just being verified. The way in which the text is used to add +header lines at transport time is described in section 46.17. New header lines +are not actually added until the message is in the process of being +transported. This means that references to header lines in string expansions in +the transport's configuration do not "see" the added header lines. The headers_add option is expanded after errors_to, but before headers_remove -and transport. If the expanded string is empty, or if the expansion is forced -to fail, the option has no effect. Other expansion failures are treated as -configuration errors. +and transport. If an item is empty, or if an item expansion is forced to fail, +the item has no effect. Other expansion failures are treated as configuration +errors. Unlike most options, headers_add can be specified multiple times for a router; all listed headers are added. @@ -15626,22 +15855,22 @@ this ambiguous situation should be avoided. The repeat_use option of the redirect router may be of help. -+--------------+------------+-------------+--------------+ -|headers_remove|Use: routers|Type: string*|Default: unset| -+--------------+------------+-------------+--------------+ - -This option specifies a string of text that is expanded at routing time, and -associated with any addresses that are accepted by the router. However, this -option has no effect when an address is just being verified. The way in which -the text is used to remove header lines at transport time is described in -section 46.17. Header lines are not actually removed until the message is in -the process of being transported. This means that references to header lines in -string expansions in the transport's configuration still "see" the original -header lines. ++--------------+------------+-----------+--------------+ +|headers_remove|Use: routers|Type: list*|Default: unset| ++--------------+------------+-----------+--------------+ + +This option specifies a list of text headers, colon-separated, that is +associated with any addresses that are accepted by the router. Each item is +separately expanded, at routing time. However, this option has no effect when +an address is just being verified. The way in which the text is used to remove +header lines at transport time is described in section 46.17. Header lines are +not actually removed until the message is in the process of being transported. +This means that references to header lines in string expansions in the +transport's configuration still "see" the original header lines. The headers_remove option is expanded after errors_to and headers_add, but -before transport. If the expansion is forced to fail, the option has no effect. -Other expansion failures are treated as configuration errors. +before transport. If an item expansion is forced to fail, the item has no +effect. Other expansion failures are treated as configuration errors. Unlike most options, headers_remove can be specified multiple times for a router; all listed headers are removed. @@ -16242,7 +16471,7 @@ |verify_only|Use: routers**|Type: boolean|Default: false| +-----------+--------------+-------------+--------------+ -If this option is set, the router is used only when verifying an address, +If this option is set, the router is used only when verifying an address, delivering in cutthrough mode or testing with the -bv option, not when actually doing a delivery, testing with the -bt option, or running the SMTP EXPN command. It can be further restricted to verifying only senders or recipients @@ -16425,6 +16654,23 @@ See section 17.1 above for a discussion of Exim's behaviour when there is a DNS lookup error. ++----------------------+--------------+------------------+--------------+ +|dnssec_request_domains|Use: dnslookup|Type: domain list*|Default: unset| ++----------------------+--------------+------------------+--------------+ + +DNS lookups for domains matching dnssec_request_domains will be done with the +dnssec request bit set. This applies to all of the SRV, MX A6, AAAA, A lookup +sequence. + ++----------------------+--------------+------------------+--------------+ +|dnssec_require_domains|Use: dnslookup|Type: domain list*|Default: unset| ++----------------------+--------------+------------------+--------------+ + +DNS lookups for domains matching dnssec_request_domains will be done with the +dnssec request bit set. Any returns not having the Authenticated Data bit (AD +bit) set wil be ignored and logged as a host-lookup failure. This applies to +all of the SRV, MX A6, AAAA, A lookup sequence. + +----------+--------------+------------------+--------------+ |mx_domains|Use: dnslookup|Type: domain list*|Default: unset| +----------+--------------+------------------+--------------+ @@ -18442,10 +18688,9 @@ is provided to help with checking out the values of variables and so on when debugging driver configurations. For example, if a headers_add option is not working properly, debug_print could be used to output the variables it -references. A newline is added to the text if it does not end with one. - -The variables $transport_name and $router_name contain the name of the -transport and the router that called it. +references. A newline is added to the text if it does not end with one. The +variables $transport_name and $router_name contain the name of the transport +and the router that called it. +-----------------+---------------+-------------+--------------+ |delivery_date_add|Use: transports|Type: boolean|Default: false| @@ -18485,16 +18730,16 @@ value that the router supplies, and also overriding any value associated with user (see below). -+-----------+---------------+-------------+--------------+ -|headers_add|Use: transports|Type: string*|Default: unset| -+-----------+---------------+-------------+--------------+ - -This option specifies a string of text that is expanded and added to the header -portion of a message as it is transported, as described in section 46.17. -Additional header lines can also be specified by routers. If the result of the -expansion is an empty string, or if the expansion is forced to fail, no action -is taken. Other expansion failures are treated as errors and cause the delivery -to be deferred. ++-----------+---------------+-----------+--------------+ +|headers_add|Use: transports|Type: list*|Default: unset| ++-----------+---------------+-----------+--------------+ + +This option specifies a list of text headers, newline-separated, which are +(separately) expanded and added to the header portion of a message as it is +transported, as described in section 46.17. Additional header lines can also be +specified by routers. If the result of the expansion is an empty string, or if +the expansion is forced to fail, no action is taken. Other expansion failures +are treated as errors and cause the delivery to be deferred. Unlike most options, headers_add can be specified multiple times for a transport; all listed headers are added. @@ -18508,19 +18753,19 @@ the settings of message_prefix and message_suffix should be checked, since this option does not automatically suppress them. -+--------------+---------------+-------------+--------------+ -|headers_remove|Use: transports|Type: string*|Default: unset| -+--------------+---------------+-------------+--------------+ - -This option specifies a string that is expanded into a list of header names; -these headers are omitted from the message as it is transported, as described -in section 46.17. Header removal can also be specified by routers. If the -result of the expansion is an empty string, or if the expansion is forced to -fail, no action is taken. Other expansion failures are treated as errors and -cause the delivery to be deferred. ++--------------+---------------+-----------+--------------+ +|headers_remove|Use: transports|Type: list*|Default: unset| ++--------------+---------------+-----------+--------------+ + +This option specifies a list of header names, colon-separated; these headers +are omitted from the message as it is transported, as described in section +46.17. Header removal can also be specified by routers. Each list item is +separately expanded. If the result of the expansion is an empty string, or if +the expansion is forced to fail, no action is taken. Other expansion failures +are treated as errors and cause the delivery to be deferred. Unlike most options, headers_remove can be specified multiple times for a -router; all listed headers are added. +router; all listed headers are removed. +---------------+---------------+------------+--------------+ |headers_rewrite|Use: transports|Type: string|Default: unset| @@ -21203,6 +21448,23 @@ option is false, the RES_DNSRCH resolver option is set. See the search_parents option in chapter 17 for more details. ++----------------------+---------+------------------+--------------+ +|dnssec_request_domains|Use: smtp|Type: domain list*|Default: unset| ++----------------------+---------+------------------+--------------+ + +DNS lookups for domains matching dnssec_request_domains will be done with the +dnssec request bit set. This applies to all of the SRV, MX A6, AAAA, A lookup +sequence. + ++----------------------+---------+------------------+--------------+ +|dnssec_require_domains|Use: smtp|Type: domain list*|Default: unset| ++----------------------+---------+------------------+--------------+ + +DNS lookups for domains matching dnssec_request_domains will be done with the +dnssec request bit set. Any returns not having the Authenticated Data bit (AD +bit) set wil be ignored and logged as a host-lookup failure. This applies to +all of the SRV, MX A6, AAAA, A lookup sequence. + +----+---------+-------------+--------------+ |dscp|Use: smtp|Type: string*|Default: unset| +----+---------+-------------+--------------+ @@ -21432,6 +21694,22 @@ hard failure if required. See also hosts_try_auth, and chapter 33 for details of authentication. ++------------------+---------+----------------+----------+ +|hosts_request_ocsp|Use: smtp|Type: host list*|Default: *| ++------------------+---------+----------------+----------+ + +Exim will request a Certificate Status on a TLS session for any host that +matches this list. tls_verify_certificates should also be set for the +transport. + ++------------------+---------+----------------+--------------+ +|hosts_require_ocsp|Use: smtp|Type: host list*|Default: unset| ++------------------+---------+----------------+--------------+ + +Exim will request, and check for a valid Certificate Status being given, on a +TLS session for any host that matches this list. tls_verify_certificates should +also be set for the transport. + +-----------------+---------+----------------+--------------+ |hosts_require_tls|Use: smtp|Type: host list*|Default: unset| +-----------------+---------+----------------+--------------+ @@ -21451,6 +21729,13 @@ unauthenticated. See also hosts_require_auth, and chapter 33 for details of authentication. ++--------------+---------+----------------+--------------+ +|hosts_try_prdr|Use: smtp|Type: host list*|Default: unset| ++--------------+---------+----------------+--------------+ + +This option provides a list of servers to which, provided they announce PRDR +support, Exim will attempt to negotiate PRDR for multi-recipient messages. + +---------+---------+------------------+--------------+ |interface|Use: smtp|Type: string list*|Default: unset| +---------+---------+------------------+--------------+ @@ -21687,6 +21972,18 @@ fails, Exim closes the current connection (because it is in an unknown state), opens a new one to the same host, and then tries the delivery in clear. ++--------------------+---------+----------------------+--------+ +|tls_try_verify_hosts|Use: smtp|Type: host list* unset|Default:| ++--------------------+---------+----------------------+--------+ + +This option gives a list of hosts for which, on encrypted connections, +certificate verification will be tried but need not succeed. The +tls_verify_certificates option must also be set. Note that unless the host is +in this list TLS connections will be denied to hosts using self-signed +certificates when tls_verify_certificates is set. The +$tls_out_certificate_verified variable is set when certificate verification +succeeds. + +-----------------------+---------+-------------+--------------+ |tls_verify_certificates|Use: smtp|Type: string*|Default: unset| +-----------------------+---------+-------------+--------------+ @@ -21700,6 +21997,18 @@ of the server during the expansion of this option. See chapter 41 for details of TLS. +For back-compatability, if neither tls_verify_hosts nor tls_try_verify_hosts +are set and certificate verification fails the TLS connection is closed. + ++----------------+---------+----------------------+--------+ +|tls_verify_hosts|Use: smtp|Type: host list* unset|Default:| ++----------------+---------+----------------------+--------+ + +This option gives a list of hosts for which. on encrypted connections, +certificate verification must succeed. The tls_verify_certificates option must +also be set. If both this option and tls_try_verify_hosts are unset operation +is as if this option selected all hosts. + 30.5 How the limits for the number of hosts to try are used ----------------------------------------------------------- @@ -23223,8 +23532,7 @@ authentication fails. If the result of the expansion is "1", "yes", or "true", authentication succeeds and the generic server_set_id option is expanded and saved in $authenticated_id. For any other result, a temporary error code is -returned, with the expanded string as the error text , and the failed id saved -in $authenticated_fail_id. +returned, with the expanded string as the error text Warning: If you use a lookup in the expansion to find the user's password, be sure to make the authentication fail if the user is unknown. There are good and @@ -23646,6 +23954,9 @@ This authenticator is an interface to the authentication facility of the Dovecot POP/IMAP server, which can support a number of authentication methods. + +Note that Dovecot must be configured to use auth-client not auth-userdb. + If you are using Dovecot to authenticate POP/IMAP clients, it might be helpful to use the same mechanisms for SMTP authentication. This is a server authenticator only. There is only one option: @@ -23663,7 +23974,7 @@ driver = dovecot public_name = PLAIN server_socket = /var/run/dovecot/auth-client - server_set_id = $auth2 + server_set_id = $auth1 dovecot_ntlm: driver = dovecot @@ -24387,7 +24698,65 @@ server using the global option called tls_crl and to an Exim client using an identically named option for the smtp transport. In each case, the value of the option is expanded and must then be the name of a file that contains a CRL in -PEM format. +PEM format. The downside is that clients have to periodically re-download a +potentially huge file from every certificate authority the know of. + +The way with most moving parts at query time is Online Certificate Status +Protocol (OCSP), where the client verifies the certificate against an OCSP +server run by the CA. This lets the CA track all usage of the certs. It +requires running software with access to the private key of the CA, to sign the +responses to the OCSP queries. OCSP is based on HTTP and can be proxied +accordingly. + +The only widespread OCSP server implementation (known to this writer) comes as +part of OpenSSL and aborts on an invalid request, such as connecting to the +port and then disconnecting. This requires re-entering the passphrase each time +some random client does this. + +The third way is OCSP Stapling; in this, the server using a certificate issued +by the CA periodically requests an OCSP proof of validity from the OCSP server, +then serves it up inline as part of the TLS negotiation. This approach adds no +extra round trips, does not let the CA track users, scales well with number of +certs issued by the CA and is resilient to temporary OCSP server failures, as +long as the server starts retrying to fetch an OCSP proof some time before its +current proof expires. The downside is that it requires server support. + +Unless Exim is built with the support disabled, or with GnuTLS earlier than +version 3.1.3, support for OCSP stapling is included. + +There is a global option called tls_ocsp_file. The file specified therein is +expected to be in DER format, and contain an OCSP proof. Exim will serve it as +part of the TLS handshake. This option will be re-expanded for SNI, if the +tls_certificate option contains "tls_in_sni", as per other TLS options. + +Exim does not at this time implement any support for fetching a new OCSP proof. +The burden is on the administrator to handle this, outside of Exim. The file +specified should be replaced atomically, so that the contents are always valid. +Exim will expand the tls_ocsp_file option on each connection, so a new file +will be handled transparently on the next connection. + +When built with OpenSSL Exim will check for a valid next update timestamp in +the OCSP proof; if not present, or if the proof has expired, it will be +ignored. + +For the client to be able to verify the stapled OCSP the server must also +supply, in its stapled information, any intermediate certificates for the chain +leading to the OCSP proof from the signer of the server certificate. There may +be zero or one such. These intermediate certificates should be added to the +server OCSP stapling file named by tls_ocsp_file. + +Note that the proof only covers the terminal server certificate, not any of the +chain from CA to it. + +There is no current way to staple a proof for a client certificate. + + A helper script "ocsp_fetch.pl" for fetching a proof from a CA + OCSP server is supplied. The server URL may be included in the + server certificate, if the CA is helpful. + + One failure mode seen was the OCSP Signer cert expiring before the end + of validity of the OCSP proof. The checking done by Exim/OpenSSL + noted this as invalid overall, but the re-fetch script did not. 41.9 Configuring an Exim client to use TLS @@ -24431,7 +24800,23 @@ name a file or, for OpenSSL only (not GnuTLS), a directory, that contains a collection of expected server certificates. The client verifies the server's certificate against this collection, taking into account any revoked -certificates that are in the list defined by tls_crl. +certificates that are in the list defined by tls_crl. Failure to verify fails +the TLS connection unless either of the tls_verify_hosts or +tls_try_verify_hosts options are set. + +The tls_verify_hosts and tls_try_verify_hosts options restrict certificate +verification to the listed servers. Verification either must or need not +succeed respectively. + +The smtp transport has two OCSP-related options: hosts_require_ocsp; a +host-list for which a Certificate Status is requested and required for the +connection to proceed. The default value is empty. hosts_request_ocsp; a +host-list for which (additionally) a Certificate Status is requested (but not +necessarily verified). The default value is "*" meaning that requests are made +unless configured otherwise. + +The host(s) should also be in hosts_require_tls, and tls_verify_certificates +configured for the transport, for OCSP to be relevant. If tls_require_ciphers is set on the smtp transport, it must contain a list of permitted cipher suites. If either of these checks fails, delivery to the @@ -24501,6 +24886,8 @@ * tls_verify_certificates + * tls_verify_certificates + Great care should be taken to deal with matters of case, various injection attacks in the string ("../" or SQL), and ensuring that a valid filename can always be referenced; it is important to remember that $tls_sni is arbitrary @@ -24587,6 +24974,11 @@ available for the user to install if the receiving end is a client MUA that can interact with a user. +Note that certificates using MD5 are unlikely to work on today's Internet; even +if your libraries allow loading them for use in Exim when acting as a server, +increasingly clients will not accept such certificates. The error diagnostics +in such a case can be frustratingly vague. + 41.14 Self-signed certificates ------------------------------ @@ -24661,7 +25053,7 @@ The -bh command line option provides a way of testing your ACL configuration locally by running a fake SMTP session with which you interact. The host relay-test.mail-abuse.org provides a service for checking your relaying -configuration (see section 42.52 for more details). +configuration (see section 42.53 for more details). 42.2 Specifying when ACLs are used @@ -24676,6 +25068,7 @@ acl_smtp_auth ACL for AUTH acl_smtp_connect ACL for start of SMTP connection acl_smtp_data ACL after DATA is complete + acl_smtp_data_prdr ACL for each recipient, after DATA is complete acl_smtp_etrn ACL for ETRN acl_smtp_expn ACL for EXPN acl_smtp_helo ACL for HELO or EHLO @@ -24788,8 +25181,8 @@ after the data) correctly - they keep the message on their queues and try again later, but that is their problem, though it does waste some of your resources. -The acl_smtp_data ACL is run after both the acl_smtp_dkim and the acl_smtp_mime -ACLs. +The acl_smtp_data ACL is run after the acl_smtp_data_prdr, the acl_smtp_dkim +and the acl_smtp_mime ACLs. 42.7 The SMTP DKIM ACL @@ -24816,8 +25209,38 @@ This ACL is evaluated after acl_smtp_dkim but before acl_smtp_data. -42.9 The QUIT ACL ------------------ +42.9 The SMTP PRDR ACL +---------------------- + +The acl_smtp_data_prdr ACL is available only when Exim is compiled with PRDR +support enabled (which is the default). It becomes active only when the PRDR +feature is negotiated between client and server for a message, and more than +one recipient has been accepted. + +The ACL test specfied by acl_smtp_data_prdr happens after a message has been +recieved, and is executed for each recipient of the message. The test may +accept or deny for inividual recipients. The acl_smtp_data will still be called +after this ACL and can reject the message overall, even if this ACL has +accepted it for some or all recipients. + +PRDR may be used to support per-user content filtering. Without it one must +defer any recipient after the first that has a different content-filter +configuration. With PRDR, the RCPT-time check for this can be disabled when the +MAIL-time $smtp_command included "PRDR". Any required difference in behaviour +of the main DATA-time ACL should however depend on the PRDR-time ACL having +run, as Exim will avoid doing so in some situations (eg. single-recipient +mails). + +See also the prdr_enable global option and the hosts_try_prdr smtp transport +option. + +This ACL is evaluated after acl_smtp_dkim but before acl_smtp_data. If the ACL +is not defined, processing completes as if the feature was not requested by the +client. + + +42.10 The QUIT ACL +------------------ The ACL for the SMTP QUIT command is anomalous, in that the outcome of the ACL does not affect the response code to QUIT, which is always 221. Thus, the ACL @@ -24843,7 +25266,7 @@ connection is closed. In these special cases, the QUIT ACL does not run. -42.10 The not-QUIT ACL +42.11 The not-QUIT ACL ---------------------- The not-QUIT ACL, specified by acl_smtp_notquit, is run in most cases when an @@ -24878,7 +25301,7 @@ verb in another ACL, it is the message from the other ACL that is used. -42.11 Finding an ACL to use +42.12 Finding an ACL to use --------------------------- The value of an acl_smtp_xxx option is expanded before use, so you can use @@ -24929,15 +25352,16 @@ file. -42.12 ACL return codes +42.13 ACL return codes ---------------------- Except for the QUIT ACL, which does not affect the SMTP return code (see -section 42.9 above), the result of running an ACL is either "accept" or "deny", -or, if some test cannot be completed (for example, if a database is down), -"defer". These results cause 2xx, 5xx, and 4xx return codes, respectively, to -be used in the SMTP dialogue. A fourth return, "error", occurs when there is an -error such as invalid syntax in the ACL. This also causes a 4xx return code. +section 42.10 above), the result of running an ACL is either "accept" or +"deny", or, if some test cannot be completed (for example, if a database is +down), "defer". These results cause 2xx, 5xx, and 4xx return codes, +respectively, to be used in the SMTP dialogue. A fourth return, "error", occurs +when there is an error such as invalid syntax in the ACL. This also causes a 4 +xx return code. For the non-SMTP ACL, "defer" and "error" are treated in the same way as "deny", because there is no mechanism for passing temporary errors to the @@ -24959,7 +25383,7 @@ recipients; it may create new recipients. -42.13 Unset ACL options +42.14 Unset ACL options ----------------------- The default actions when any of the acl_xxx options are unset are not all the @@ -24981,7 +25405,7 @@ connection. For an example, see the ACL in the default configuration file. -42.14 Data for message ACLs +42.15 Data for message ACLs --------------------------- When a MAIL or RCPT ACL, or either of the DATA ACLs, is running, the variables @@ -25008,7 +25432,7 @@ contains the total number of accepted recipients. -42.15 Data for non-message ACLs +42.16 Data for non-message ACLs ------------------------------- When an ACL is being run for AUTH, EHLO, ETRN, EXPN, HELO, STARTTLS, or VRFY, @@ -25031,7 +25455,7 @@ option to do this.) -42.16 Format of an ACL +42.17 Format of an ACL ---------------------- An individual ACL consists of a number of statements. Each statement starts @@ -25054,7 +25478,7 @@ test a sender address in the ACL that is run for a VRFY command. -42.17 ACL verbs +42.18 ACL verbs --------------- The ACL verbs are as follows: @@ -25147,7 +25571,7 @@ passes control to subsequent statements only if the message's sender can be verified. Otherwise, it rejects the command. Note the positioning of the message modifier, before the verify condition. The reason for this is - discussed in section 42.19. + discussed in section 42.20. * warn: If all the conditions are true, a line specified by the log_message modifier is written to Exim's main log. Control always passes to the next @@ -25159,7 +25583,7 @@ If log_message is not present, a warn verb just checks its conditions and obeys any "immediate" modifiers (such as control, set, logwrite, add_header , and remove_header) that appear before the first failing condition. There - is more about adding header lines in section 42.23. + is more about adding header lines in section 42.24. If any condition on a warn statement cannot be completed (that is, there is some sort of defer), the log line specified by log_message is not written. @@ -25185,7 +25609,7 @@ mechanism. It is conventional to align the conditions vertically. -42.18 ACL variables +42.19 ACL variables ------------------- There are some special variables that can be set during ACL processing. They @@ -25227,7 +25651,7 @@ their names are compatible, so there is no problem with upgrading. -42.19 Condition and modifier processing +42.20 Condition and modifier processing --------------------------------------- An exclamation mark preceding a condition negates its result. For example: @@ -25300,7 +25724,7 @@ which time Exim has set up the message. -42.20 ACL modifiers +42.21 ACL modifiers ------------------- The ACL modifiers are as follows: @@ -25309,7 +25733,7 @@ This modifier specifies one or more header lines that are to be added to an incoming message, assuming, of course, that the message is ultimately - accepted. For details, see section 42.23. + accepted. For details, see section 42.24. continue = @@ -25335,7 +25759,7 @@ individual recipients, even if the control modifier appears in a RCPT ACL. As there are now quite a few controls that can be applied, they are - described separately in section 42.21. The control modifier can be used in + described separately in section 42.22. The control modifier can be used in several different ways. For example: * It can be at the end of an accept statement: @@ -25568,11 +25992,11 @@ This modifier specifies one or more header names in a colon-separated list that are to be removed from an incoming message, assuming, of course, that - the message is ultimately accepted. For details, see section 42.24. + the message is ultimately accepted. For details, see section 42.25. set = - This modifier puts a value into one of the ACL variables (see section 42.18 + This modifier puts a value into one of the ACL variables (see section 42.19 ). udpsend = @@ -25589,7 +26013,7 @@ $tod_zulu $sender_host_address -42.21 Use of the control modifier +42.22 Use of the control modifier --------------------------------- The control modifier supports the following settings: @@ -25649,8 +26073,16 @@ received. It is usable in the RCPT ACL and valid only for single-recipient mails forwarded from one SMTP connection to another. If a recipient-verify callout connection is requested in the same ACL it is held open and used - for the data, otherwise one is made after the ACL completes. Note that - routers are used in verify mode. + for the data, otherwise one is made after the ACL completes. + + Note that routers are used in verify mode, and cannot depend on content of + received headers. Note also that headers cannot be modified by any of the + post-data ACLs (DATA, MIME and DKIM). Headers may be modified by routers + (subject to the above) and transports. + + Cutthrough delivery is not supported via transport-filters or when DKIM + signing of outgoing messages is done, because it sends data to the ultimate + destination before the entire message has been received from the source. Should the ultimate destination system positively accept or reject the mail, a corresponding indication is given to the source system and nothing @@ -25862,7 +26294,7 @@ that are being submitted at the same time using -bs or -bS. -42.22 Summary of message fixup control +42.23 Summary of message fixup control -------------------------------------- All four possibilities for message fixups can be specified: @@ -25877,7 +26309,7 @@ * Remotely submitted, fixups applied: use "control = submission". -42.23 Adding header lines in ACLs +42.24 Adding header lines in ACLs --------------------------------- The add_header modifier can be used to add one or more extra header lines to an @@ -25888,10 +26320,13 @@ add_header = X-blacklisted-at: $dnslist_domain The add_header modifier is permitted in the MAIL, RCPT, PREDATA, DATA, MIME, -and non-SMTP ACLs (in other words, those that are concerned with receiving a -message). The message must ultimately be accepted for add_header to have any -significant effect. You can use add_header with any ACL verb, including deny -(though this is potentially useful only in a RCPT ACL). +DKIM, and non-SMTP ACLs (in other words, those that are concerned with +receiving a message). The message must ultimately be accepted for add_header to +have any significant effect. You can use add_header with any ACL verb, +including deny (though this is potentially useful only in a RCPT ACL). + +Headers will not be added to the message if the modifier is used in DATA, MIME +or DKIM ACLs for messages delivered by cutthrough routing. Leading and trailing newlines are removed from the data for the add_header modifier; if it then contains one or more newlines that are not followed by a @@ -25917,7 +26352,7 @@ run. Similarly, header lines that are added by the DATA or MIME ACLs are not visible in those ACLs. Because of this restriction, you cannot use header lines as a way of passing data between (for example) the MAIL and RCPT ACLs. If you -want to do this, you can use ACL variables, as described in section 42.18. +want to do this, you can use ACL variables, as described in section 42.19. The list of headers yet to be added is given by the $headers_added variable. @@ -25967,7 +26402,7 @@ in a router or transport. -42.24 Removing header lines in ACLs +42.25 Removing header lines in ACLs ----------------------------------- The remove_header modifier can be used to remove one or more header lines from @@ -25977,11 +26412,14 @@ remove_header = x-route-mail1 : x-route-mail2 The remove_header modifier is permitted in the MAIL, RCPT, PREDATA, DATA, MIME, -and non-SMTP ACLs (in other words, those that are concerned with receiving a -message). The message must ultimately be accepted for remove_header to have any -significant effect. You can use remove_header with any ACL verb, including deny -, though this is really not useful for any verb that doesn't result in a -delivered message. +DKIM, and non-SMTP ACLs (in other words, those that are concerned with +receiving a message). The message must ultimately be accepted for remove_header +to have any significant effect. You can use remove_header with any ACL verb, +including deny, though this is really not useful for any verb that doesn't +result in a delivered message. + +Headers will not be removed to the message if the modifier is used in DATA, +MIME or DKIM ACLs for messages delivered by cutthrough routing. More than one header can be removed at the same time by using a colon separated list of header names. The header matching is case insensitive. Wildcards are @@ -26011,7 +26449,7 @@ removed by the DATA or MIME ACLs are still visible in those ACLs. Because of this restriction, you cannot use header lines as a way of controlling data passed between (for example) the MAIL and RCPT ACLs. If you want to do this, -you should instead use ACL variables, as described in section 42.18. +you should instead use ACL variables, as described in section 42.19. The remove_header modifier acts immediately as it is encountered during the processing of an ACL. Notice the difference between these two cases: @@ -26033,7 +26471,7 @@ system filter or in a router or transport. -42.25 ACL conditions +42.26 ACL conditions -------------------- Some of the conditions listed in this section are available only when Exim is @@ -26114,7 +26552,7 @@ as "RBL lists", after the original Realtime Blackhole List, but note that the use of the lists at mail-abuse.org now carries a charge. There are too many different variants of this condition to describe briefly here. See - sections 42.26-42.36 for details. + sections 42.27-42.37 for details. domains = @@ -26199,7 +26637,7 @@ ratelimit = This condition can be used to limit the rate at which a user or host - submits messages. Details are given in section 42.37. + submits messages. Details are given in section 42.38. recipients =
@@ -26252,7 +26690,19 @@ verify = csa This condition checks whether the sending host (the client) is authorized - to send email. Details of how this works are given in section 42.49. + to send email. Details of how this works are given in section 42.50. + +verify = header_names_ascii + + This condition is relevant only in an ACL that is run after a message has + been received, that is, in an ACL specified by acl_smtp_data or + acl_not_smtp. It checks all header names (not the content) to make sure + there are no non-ASCII characters, also excluding control characters. The + allowable characters are decimal ASCII values 33 through 126. + + Exim itself will handle headers with non-ASCII characters, but it can cause + problems for downstream applications, so this option will allow their + detection and rejection in the DATA ACL's. verify = header_sender/ @@ -26268,7 +26718,7 @@ command. Details of address verification and the options are given later, starting - at section 42.43 (callouts are described in section 42.44). You can combine + at section 42.44 (callouts are described in section 42.45). You can combine this condition with the senders condition to restrict it to bounce messages only: @@ -26322,7 +26772,7 @@ This condition is relevant only after a RCPT command. It verifies the current recipient. Details of address verification are given later, - starting at section 42.43. After a recipient has been verified, the value + starting at section 42.44. After a recipient has been verified, the value of $address_data is the last value that was set while routing the address. This applies even if the verification fails. When an address that is being verified is redirected to a single address, verification continues with the @@ -26355,7 +26805,7 @@ you want to preserve the value for longer, you can save it in an ACL variable. - Details of verification are given later, starting at section 42.43. Exim + Details of verification are given later, starting at section 42.44. Exim caches the result of sender verification, to avoid doing it more than once per message. @@ -26365,7 +26815,7 @@ verified as a sender. -42.26 Using DNS lists +42.27 Using DNS lists --------------------- In its simplest form, the dnslists condition tests whether the calling host is @@ -26422,7 +26872,7 @@ connections (but your local name server cache should be active). -42.27 Specifying the IP address for a DNS list lookup +42.28 Specifying the IP address for a DNS list lookup ----------------------------------------------------- By default, the IP address that is used in a DNS list lookup is the IP address @@ -26434,10 +26884,10 @@ This feature is not very helpful with explicit IP addresses; it is intended for use with IP addresses that are looked up, for example, the IP addresses of the MX hosts or nameservers of an email sender address. For an example, see section -42.29 below. +42.30 below. -42.28 DNS lists keyed on domain names +42.29 DNS lists keyed on domain names ------------------------------------- There are some lists that are keyed on domain names rather than inverted IP @@ -26466,7 +26916,7 @@ name. The whole condition is true if either of the DNS lookups succeeds. -42.29 Multiple explicit keys for a DNS list +42.30 Multiple explicit keys for a DNS list ------------------------------------------- The syntax described above for looking up explicitly-defined values (either @@ -26494,7 +26944,7 @@ a.domain.black.list.tld Once a DNS record has been found (that matches a specific IP return address, if -specified - see section 42.32), no further lookups are done. If there is a +specified - see section 42.33), no further lookups are done. If there is a temporary DNS error, the rest of the sublist of domains or IP addresses is tried. A temporary error for the whole dnslists item occurs only if no other DNS lookup in this sublist succeeds. In other words, a successful lookup for @@ -26529,10 +26979,10 @@ domain's mail servers are on the Spamhaus black list. The key that was used for a successful DNS list lookup is put into the variable -$dnslist_matched (see section 42.31). +$dnslist_matched (see section 42.32). -42.30 Data returned by DNS lists +42.31 Data returned by DNS lists -------------------------------- DNS lists are constructed using address records in the DNS. The original RBL @@ -26548,12 +26998,12 @@ 127.1.0.6 RSS and DUL 127.1.0.7 RSS and DUL and RBL -Section 42.32 below describes how you can distinguish between different values. -Some DNS lists may return more than one address record; see section 42.34 for +Section 42.33 below describes how you can distinguish between different values. +Some DNS lists may return more than one address record; see section 42.35 for details of how they are checked. -42.31 Variables set from DNS lists +42.32 Variables set from DNS lists ---------------------------------- When an entry is found in a DNS list, the variable $dnslist_domain contains the @@ -26567,7 +27017,7 @@ the key is also available in another variable (in this case, $sender_host_address). In more complicated cases, however, this is not true. -For example, using a data lookup (as described in section 42.29) might generate +For example, using a data lookup (as described in section 42.30) might generate a dnslists lookup like this: deny dnslists = spamhaus.example/<|192.168.1.2|192.168.6.7|... @@ -26579,7 +27029,7 @@ addresses are included in $dnslist_value, separated by commas and spaces. The variable $dnslist_text contains the contents of any associated TXT record. For lists such as RBL+ the TXT record for a merged entry is often not very -meaningful. See section 42.35 for a way of obtaining more information. +meaningful. See section 42.36 for a way of obtaining more information. You can use the DNS list variables in message or log_message modifiers - although these appear before the condition in the ACL, they are not expanded @@ -26591,7 +27041,7 @@ dnslists = rbl-plus.mail-abuse.example -42.32 Additional matching conditions for DNS lists +42.33 Additional matching conditions for DNS lists -------------------------------------------------- You can add an equals sign and an IP address after a dnslists domain name in @@ -26602,7 +27052,7 @@ rejects only those hosts that yield 127.0.0.2. Without this additional data, any address record is considered to be a match. For the moment, we assume that -the DNS lookup returns just one record. Section 42.34 describes how multiple +the DNS lookup returns just one record. Section 42.35 describes how multiple records are handled. More than one IP address may be given for checking, using a comma as a @@ -26636,7 +27086,7 @@ odd number. -42.33 Negated DNS matching conditions +42.34 Negated DNS matching conditions ------------------------------------- You can supply a negative list of IP addresses as part of a dnslists condition. @@ -26682,7 +27132,7 @@ which is less clear, and harder to maintain. -42.34 Handling multiple DNS records from a DNS list +42.35 Handling multiple DNS records from a DNS list --------------------------------------------------- A DNS lookup for a dnslists condition may return more than one DNS record, @@ -26744,7 +27194,7 @@ between "=" and "==" and between "&" and "=&". -42.35 Detailed information from merged DNS lists +42.36 Detailed information from merged DNS lists ------------------------------------------------ When the facility for restricting the matching IP values in a DNS list is used, @@ -26793,7 +27243,7 @@ done. Only if there is a match is one of the more specific lists consulted. -42.36 DNS lists and IPv6 +42.37 DNS lists and IPv6 ------------------------ If Exim is asked to do a dnslist lookup for an IPv6 address, it inverts it @@ -26818,8 +27268,14 @@ deny condition = ${if isip4{$sender_host_address}} dnslists = some.list.example +If an explicit key is being used for a DNS lookup and it may be an IPv6 address +you should specify alternate list separators for both the outer (DNS list name) +list and inner (lookup keys) list: + + dnslists = <; dnsbl.example.com/<|$acl_m_addrslist -42.37 Rate limiting incoming messages + +42.38 Rate limiting incoming messages ------------------------------------- The ratelimit ACL condition can be used to measure and control the rate at @@ -26883,7 +27339,7 @@ lookup key is not affected by changes to the update mode and the count= option. -42.38 Ratelimit options for what is being measured +42.39 Ratelimit options for what is being measured -------------------------------------------------- The per_conn option limits the client's connection rate. It is not normally @@ -26926,10 +27382,10 @@ rate by one (except for the per_rcpt option in ACLs other than acl_smtp_rcpt). The count does not have to be an integer. -The unique= option is described in section 42.41 below. +The unique= option is described in section 42.42 below. -42.39 Ratelimit update modes +42.40 Ratelimit update modes ---------------------------- You can specify one of three options with the ratelimit condition to control @@ -26968,7 +27424,7 @@ specify the readonly option explicitly. -42.40 Ratelimit options for handling fast clients +42.41 Ratelimit options for handling fast clients ------------------------------------------------- If a client's average rate is greater than the maximum, the rate limiting @@ -26998,7 +27454,7 @@ ln(peakrate/maxrate) -42.41 Limiting the rate of different events +42.42 Limiting the rate of different events ------------------------------------------- The ratelimit unique= option controls a mechanism for counting the rate of @@ -27035,7 +27491,7 @@ intended. -42.42 Using rate limiting +42.43 Using rate limiting ------------------------- Exim's other ACL facilities are used to define what counter-measures are taken @@ -27079,11 +27535,11 @@ ratelimit data). -42.43 Address verification +42.44 Address verification -------------------------- -Several of the verify conditions described in section 42.25 cause addresses to -be verified. Section 42.47 discusses the reporting of sender verification +Several of the verify conditions described in section 42.26 cause addresses to +be verified. Section 42.48 discusses the reporting of sender verification failures. The verification conditions can be followed by options that modify the verification process. The options are separated from the keyword and from each other by slashes, and some of them contain parameters. For example: @@ -27107,13 +27563,13 @@ the condition is forced to be true instead. Note that this is a main verification option as well as a suboption for callouts. - * The no_details option is covered in section 42.47, which discusses the + * The no_details option is covered in section 42.48, which discusses the reporting of sender address verification failures. * The success_on_redirect option causes verification always to succeed immediately after a successful redirection. By default, if a redirection generates just one address, that address is also verified. See further - discussion in section 42.48. + discussion in section 42.49. After an address verification failure, $acl_verify_message contains the error message that is associated with the failure. It can be preserved by coding like @@ -27145,7 +27601,7 @@ rejections of MAIL and rejections of RCPT in callouts. -42.44 Callout verification +42.45 Callout verification -------------------------- For non-local addresses, routing verifies the domain, but is unable to do any @@ -27163,7 +27619,7 @@ described below. This facility should be used with care, because it can add a lot of resource usage to the cost of verifying an address. However, Exim does cache the results of callouts, which helps to reduce the cost. Details of -caching are in section 42.46. +caching are in section 42.47. Recipient callouts are usually used only between hosts that are controlled by the same administration. For example, a corporate gateway host could use @@ -27178,6 +27634,7 @@ that does not set up hosts routes to an smtp transport with a hosts setting, the transport's hosts are used. If an smtp transport has hosts_override set, its hosts are always used, whether or not the router supplies a host list. +Callouts are only supported on smtp transports. The port that is used is taken from the transport, if it is specified and is a remote transport. (For routers that do verification only, no transport need be @@ -27221,7 +27678,7 @@ disabled by using a control modifier to set no_callout_flush. -42.45 Additional parameters for callouts +42.46 Additional parameters for callouts ---------------------------------------- The callout option can be followed by an equals sign and a number of optional @@ -27386,7 +27843,7 @@ callouts are performed than when an empty sender or postmaster is used. -42.46 Callout caching +42.47 Callout caching --------------------- Exim caches the results of callouts in order to reduce the amount of resources @@ -27427,10 +27884,10 @@ behaviour will be the same. -42.47 Sender address verification reporting +42.48 Sender address verification reporting ------------------------------------------- -See section 42.43 for a general discussion of verification. When sender +See section 42.44 for a general discussion of verification. When sender verification fails in an ACL, the details of the failure are given as additional output lines before the 550 response to the relevant SMTP command (RCPT or DATA). For example, if sender callout is in use, you might see: @@ -27452,7 +27909,7 @@ verify = sender/no_details -42.48 Redirection while verifying +42.49 Redirection while verifying --------------------------------- A dilemma arises when a local address is redirected by aliasing or forwarding @@ -27495,7 +27952,7 @@ address and a report is output for each of them. -42.49 Client SMTP authorization (CSA) +42.50 Client SMTP authorization (CSA) ------------------------------------- Client SMTP Authorization is a system that allows a site to advertise which @@ -27565,7 +28022,7 @@ authorization required but absent, or "?" for unknown. -42.50 Bounce address tag validation +42.51 Bounce address tag validation ----------------------------------- Bounce address tag validation (BATV) is a scheme whereby the envelope senders @@ -27648,7 +28105,7 @@ If no key can be found for the existing return path, no signing takes place. -42.51 Using an ACL to control relaying +42.52 Using an ACL to control relaying -------------------------------------- An MTA is said to relay a message if it receives it from some host and delivers @@ -27703,7 +28160,7 @@ in chapter 7. -42.52 Checking a relay configuration +42.53 Checking a relay configuration ------------------------------------ You can check the relay characteristics of your configuration in the same way @@ -27930,6 +28387,20 @@ You can safely omit this option (the default value is 1). +sock + + This is a general-purpose way of talking to simple scanner daemons running + on the local machine. There are four options: an address (which may be an + IP addres and port, or the path of a Unix socket), a commandline to send + (may include a single %s which will be replaced with the path to the mail + file to be scanned), an RE to trigger on from the returned data, an RE to + extract malware_name from the returned data. For example: + + av_scanner = sock:127.0.0.1 6001:%s:(SPAM|VIRUS):(.*)\$ + + Default for the socket specifier is /tmp/malware.sock. Default for the + commandline is %s\n. Both regular-expressions are required. + sophie Sophie is a daemon that uses Sophos' libsavi library to scan for viruses. @@ -29569,8 +30040,8 @@ control = submission -in a MAIL, RCPT, or pre-data ACL for an incoming message (see sections 42.20 -and 42.21). This makes Exim treat the message as a local submission, and is +in a MAIL, RCPT, or pre-data ACL for an incoming message (see sections 42.21 +and 42.22). This makes Exim treat the message as a local submission, and is normally used when the source of the message is known to be an MUA running on a client host (as opposed to an MTA). For example, to set submission mode for messages originating on the IPv4 loopback interface, you could include the @@ -29952,7 +30423,7 @@ specified in a system filter, or on any of the routers and transports that process the message. Section 45.6 contains details about modifying headers in a system filter. Header lines can also be added in an ACL as a message is -received (see section 42.23). +received (see section 42.24). In contrast to what happens in a system filter, header modifications that are specified on routers and transports apply only to the particular recipient @@ -29965,9 +30436,9 @@ transport cannot refer to the modified header lines, because such expansions all occur before the message is actually transported. -For both routers and transports, the result of expanding a headers_add option -must be in the form of one or more RFC 2822 header lines, separated by newlines -(coded as "\n"). For example: +For both routers and transports, the argument of a headers_add option must be +in the form of one or more RFC 2822 header lines, separated by newlines (coded +as "\n"). For example: headers_add = X-added-header: added by $primary_hostname\n\ X-added-second: another added header line @@ -29975,10 +30446,10 @@ Exim does not check the syntax of these added header lines. Multiple headers_add options for a single router or transport can be specified; -the values will be concatenated (with a separating newline added) before -expansion. +the values will append to a single list of header lines. Each header-line is +separately expanded. -The result of expanding headers_remove must consist of a colon-separated list +The argument of a headers_remove option must consist of a colon-separated list of header names. This is confusing, because header names themselves are often terminated by colons. In this case, the colons are the list separators, not part of the names. For example: @@ -29986,14 +30457,14 @@ headers_remove = return-receipt-to:acknowledge-to Multiple headers_remove options for a single router or transport can be -specified; the values will be concatenated (with a separating colon added) -before expansion. +specified; the arguments will append to a single header-names list. Each item +is separately expanded. -When headers_add or headers_remove is specified on a router, its value is -expanded at routing time, and then associated with all addresses that are -accepted by that router, and also with any new addresses that it generates. If -an address passes through several routers as a result of aliasing or -forwarding, the changes are cumulative. +When headers_add or headers_remove is specified on a router, items are expanded +at routing time, and then associated with all addresses that are accepted by +that router, and also with any new addresses that it generates. If an address +passes through several routers as a result of aliasing or forwarding, the +changes are cumulative. However, this does not apply to multiple routers that result from the use of the unseen option. Any header modifications that were specified by the "unseen" @@ -31925,6 +32396,7 @@ R on <= lines: reference for local bounce on => ** and == lines: router name S size of message +SNI server name indication from TLS client hello ST shadow transport name T on <= lines: message subject (topic) on => ** and == lines: transport name @@ -32171,10 +32643,10 @@ queue run because it is frozen or because another process is already delivering it. The message that is written is "spool file is locked". - * smtp_confirmation: The response to the final "." in the SMTP dialogue for - outgoing messages is added to delivery log lines in the form "C=". A - number of MTAs (including Exim) return an identifying string in this - response. + * smtp_confirmation: The response to the final "." in the SMTP or LMTP + dialogue for outgoing messages is added to delivery log lines in the form + "C=". A number of MTAs (including Exim) return an identifying string + in this response. * smtp_connection: A log line is written whenever an SMTP connection is established or closed, unless the connection is from a host that matches @@ -32358,9 +32830,15 @@ exim -bpu -to obtain a queue listing with undelivered recipients only, and then greps the -output to select messages that match given criteria. The following selection -options are available: +or (in case -a switch is specified) + +exim -bp + +The -C option is used to specify an alternate exim.conf which might contain +alternate exim configuration the queue management might be using. + +to obtain a queue listing, and then greps the output to select messages that +match given criteria. The following selection options are available: -f @@ -32418,6 +32896,10 @@ Display messages in reverse order. +-a + + Include delivered recipients in queue listing. + There is one more option, -h, which outputs a list of options. @@ -34072,7 +34554,8 @@ Exim's DKIM implementation allows to 1. Sign outgoing messages: This function is implemented in the SMTP transport. - It can co-exist with all other Exim features, including transport filters. + It can co-exist with all other Exim features (including transport filters) + except cutthrough delivery. 2. Verify signatures in incoming messages: This is implemented by an additional ACL (acl_smtp_dkim), which can be called several times per @@ -34169,7 +34652,10 @@ Verification of DKIM signatures in incoming email is implemented via the acl_smtp_dkim ACL. By default, this ACL is called once for each syntactically -(!) correct signature in the incoming message. +(!) correct signature in the incoming message. A missing ACL definition +defaults to accept. If any ACL call does not acccept, the message is not +accepted. If a cutthrough delivery was in progress for the message it is +summarily dropped (having wasted the transmission effort). To evaluate the signature in the ACL a large number of expansion variables containing the signature status and its details are set up during the runtime diff -Nru exim4-4.82/exim_monitor/em_globals.c exim4-4.84/exim_monitor/em_globals.c --- exim4-4.82/exim_monitor/em_globals.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/exim_monitor/em_globals.c 2014-08-09 12:44:29.000000000 +0000 @@ -130,7 +130,7 @@ BOOL deliver_firsttime = FALSE; BOOL deliver_freeze = FALSE; -int deliver_frozen_at = 0; +time_t deliver_frozen_at = 0; BOOL deliver_manual_thaw = FALSE; #ifndef DISABLE_DKIM @@ -145,6 +145,11 @@ BOOL dont_deliver = FALSE; +#ifdef EXPERIMENTAL_DSN +int dsn_ret = 0; +uschar *dsn_envid = NULL; +#endif + #ifdef WITH_CONTENT_SCAN int fake_response = OK; #endif diff -Nru exim4-4.82/exim_monitor/em_log.c exim4-4.84/exim_monitor/em_log.c --- exim4-4.82/exim_monitor/em_log.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/exim_monitor/em_log.c 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim Monitor * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* This module contains code for scanning the main log, diff -Nru exim4-4.82/.gitignore exim4-4.84/.gitignore --- exim4-4.82/.gitignore 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/.gitignore 2014-08-09 12:44:29.000000000 +0000 @@ -1,3 +1,4 @@ Local build-* tags +cscope.out diff -Nru exim4-4.82/Makefile exim4-4.84/Makefile --- exim4-4.82/Makefile 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/Makefile 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ # appropriate links, and then creating and running the main makefile in that # directory. -# Copyright (c) University of Cambridge, 1995 - 2007 +# Copyright (c) University of Cambridge, 1995 - 2014 # See the file NOTICE for conditions of use and distribution. # IRIX make uses the shell that is in the SHELL variable, which often defaults @@ -90,9 +90,11 @@ cscope.files: FRC echo "-q" > $@ echo "-p3" >> $@ - find src Local -name "*.[cshyl]" -print \ + find src Local OS -name "*.[cshyl]" -print \ + -o -name "os.h*" -print \ -o -name "*akefile*" -print \ -o -name EDITME -print >> $@ + ls OS/* >> $@ FRC: diff -Nru exim4-4.82/OS/Makefile-Base exim4-4.84/OS/Makefile-Base --- exim4-4.82/OS/Makefile-Base 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/OS/Makefile-Base 2014-08-09 12:44:29.000000000 +0000 @@ -32,7 +32,8 @@ # up-to-date. Then the os-specific source files and the C configuration file # are set up, and finally it goes to the main Exim target. -all: $(EDITME) checklocalmake Makefile os.h os.c config.h version.h allexim +all: allexim +config: $(EDITME) checklocalmake Makefile os.h os.c config.h version.h checklocalmake: @if $(SHELL) $(SCRIPTS)/newer $(EDITME)-$(OSTYPE) $(EDITME) || \ @@ -94,20 +95,19 @@ # therefore always be run, even if the files exist. This shouldn't in fact be a # problem, but it does no harm. Other make programs will just ignore this. -.PHONY: all allexim buildauths buildlookups buildpdkim buildrouters \ +.PHONY: all config allexim buildauths buildlookups buildpdkim buildrouters \ buildtransports checklocalmake clean # This is the real default target for all the various exim binaries and # scripts, once the configuring stuff is done. -allexim: config.h $(EXIM_MONITOR) exicyclog exinext exiwhat \ +allexim: $(EXIM_MONITOR) exicyclog exinext exiwhat \ exigrep eximstats exipick exiqgrep exiqsumm \ transport-filter.pl convert4r3 convert4r4 \ exim_checkaccess \ exim_dbmbuild exim_dumpdb exim_fixdb exim_tidydb exim_lock \ - buildlookups buildrouters buildtransports \ - buildauths buildpdkim exim + exim # Targets for special-purpose configuration header builders @@ -117,7 +117,7 @@ # Target for the exicyclog utility script -exicyclog: Makefile config.h ../src/exicyclog.src +exicyclog: config ../src/exicyclog.src @rm -f exicyclog @sed \ -e "s?PROCESSED_FLAG?This file has been so processed.?"\ @@ -142,7 +142,7 @@ @echo ">>> exicyclog script built" # Target for the exinext utility script -exinext: Makefile config.h ../src/exinext.src +exinext: config ../src/exinext.src @rm -f exinext @sed \ -e "s?PROCESSED_FLAG?This file has been so processed.?"\ @@ -157,7 +157,7 @@ @echo ">>> exinext script built" # Target for the exiwhat utility script -exiwhat: Makefile config.h ../src/exiwhat.src +exiwhat: config ../src/exiwhat.src @rm -f exiwhat @sed \ -e "s?PROCESSED_FLAG?This file has been so processed.?"\ @@ -178,7 +178,7 @@ @echo ">>> exiwhat script built" # Target for the exim_checkaccess utility script -exim_checkaccess: Makefile config.h ../src/exim_checkaccess.src +exim_checkaccess: config ../src/exim_checkaccess.src @rm -f exim_checkaccess @sed \ -e "s?PROCESSED_FLAG?This file has been so processed.?"\ @@ -194,7 +194,7 @@ @echo ">>> exim_checkaccess script built"; echo "" # Target for the Exim monitor start-up script -eximon: Makefile config.h ../src/eximon.src ../OS/eximon.conf-Default \ +eximon: config ../src/eximon.src ../OS/eximon.conf-Default \ ../Local/eximon.conf @rm -f eximon $(SHELL) $(SCRIPTS)/Configure-eximon @@ -317,8 +317,8 @@ local_scan.o $(EXIM_PERL) $(OBJ_WITH_CONTENT_SCAN) \ $(OBJ_WITH_OLD_DEMIME) $(OBJ_EXPERIMENTAL) -exim: lookups/lookups.a auths/auths.a pdkim/pdkim.a \ - routers/routers.a transports/transports.a \ +exim: buildlookups buildauths pdkim/pdkim.a \ + buildrouters buildtransports \ $(OBJ_EXIM) version.o @echo "$(LNCC) -o exim" $(FE)$(PURIFY) $(LNCC) -o exim $(LFLAGS) $(OBJ_EXIM) version.o \ @@ -355,7 +355,7 @@ OBJ_FIXDB = exim_fixdb.o util-os.o util-store.o -exim_fixdb: $(OBJ_FIXDB) auths/auths.a +exim_fixdb: $(OBJ_FIXDB) buildauths @echo "$(LNCC) -o exim_fixdb" $(FE)$(LNCC) $(CFLAGS) $(INCLUDE) -o exim_fixdb $(LFLAGS) $(OBJ_FIXDB) \ auths/auths.a $(LIBS) $(EXTRALIBS) $(DBMLIB) @@ -531,7 +531,7 @@ # The local scan module depends only on its own special header, and is compiled # from a source whose location is set by configuration. -local_scan.o: Makefile config.h local_scan.h ../$(LOCAL_SCAN_SOURCE) +local_scan.o: config local_scan.h ../$(LOCAL_SCAN_SOURCE) @echo "$(CC) local_scan.c" $(FE)$(CC) -c $(CFLAGS) -I. $(INCLUDE) -o local_scan.o ../$(LOCAL_SCAN_SOURCE) @@ -578,7 +578,7 @@ std-crypto.o: $(HDRS) std-crypto.c store.o: $(HDRS) store.c string.o: $(HDRS) string.c -tls.o: $(HDRS) tls.c tls-gnu.c tls-openssl.c +tls.o: $(HDRS) tls.c tls-gnu.c tlscert-gnu.c tls-openssl.c tlscert-openssl.c tod.o: $(HDRS) tod.c transport.o: $(HDRS) transport.c tree.o: $(HDRS) tree.c @@ -621,7 +621,7 @@ # When using parallel make, we don't have the dependency to force building # in the sub-directory unless we force that dependency: -$(OBJ_LOOKUPS): lookups/lookups.a +$(OBJ_LOOKUPS): buildlookups # The exim monitor's private modules - the sources live in a private # subdirectory. The final binary combines the private modules with some @@ -649,7 +649,7 @@ # The lookups library. -buildlookups lookups/lookups.a: config.h version.h +buildlookups: config @cd lookups && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) CC="$(CC)" CFLAGS="$(CFLAGS)" \ CFLAGS_DYNAMIC="$(CFLAGS_DYNAMIC)" HDRS="../version.h $(PHDRS)" \ FE="$(FE)" RANLIB="$(RANLIB)" RM_COMMAND="$(RM_COMMAND)" \ @@ -658,7 +658,7 @@ # The routers library. -buildrouters routers/routers.a: config.h +buildrouters: config @cd routers && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) CC="$(CC)" CFLAGS="$(CFLAGS)" \ FE="$(FE)" RANLIB="$(RANLIB)" RM_COMMAND="$(RM_COMMAND)" HDRS="$(PHDRS)" \ INCLUDE="$(INCLUDE) $(IPV6_INCLUDE) $(TLS_INCLUDE)" @@ -666,7 +666,7 @@ # The transports library. -buildtransports transports/transports.a: config.h +buildtransports: config @cd transports && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) CC="$(CC)" CFLAGS="$(CFLAGS)" \ FE="$(FE)" RANLIB="$(RANLIB)" RM_COMMAND="$(RM_COMMAND)" HDRS="$(PHDRS)" \ INCLUDE="$(INCLUDE) $(IPV6_INCLUDE) $(TLS_INCLUDE)" @@ -674,7 +674,7 @@ # The library of authorization modules -buildauths auths/auths.a: config.h +buildauths: config @cd auths && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) CC="$(CC)" CFLAGS="$(CFLAGS)" \ FE="$(FE)" RANLIB="$(RANLIB)" RM_COMMAND="$(RM_COMMAND)" HDRS="$(PHDRS)" \ INCLUDE="$(INCLUDE) $(IPV6_INCLUDE) $(TLS_INCLUDE)" @@ -682,7 +682,8 @@ # The PDKIM library -buildpdkim pdkim/pdkim.a: config.h +buildpdkim: pdkim/pdkim.a +pdkim/pdkim.a: config @cd pdkim && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) CC="$(CC)" CFLAGS="$(CFLAGS)" \ FE="$(FE)" RANLIB="$(RANLIB)" RM_COMMAND="$(RM_COMMAND)" HDRS="$(PHDRS)" \ INCLUDE="$(INCLUDE) $(IPV6_INCLUDE) $(TLS_INCLUDE)" diff -Nru exim4-4.82/OS/os.c-Linux exim4-4.84/OS/os.c-Linux --- exim4-4.82/OS/os.c-Linux 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/OS/os.c-Linux 2014-08-09 12:44:29.000000000 +0000 @@ -94,7 +94,7 @@ ip_address_item *next; char addr6p[8][5]; unsigned int plen, scope, dad_status, if_idx; -char devname[20]; +char devname[20+1]; FILE *f; #endif diff -Nru exim4-4.82/OS/os.h-Darwin exim4-4.84/OS/os.h-Darwin --- exim4-4.82/OS/os.h-Darwin 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/OS/os.h-Darwin 2014-08-09 12:44:29.000000000 +0000 @@ -36,7 +36,10 @@ #define DYNLIB_FN_EXT "dylib" /* We currently need some assistance getting OFF_T_FMT correct on MacOS */ -#define OFF_T_FMT "%llu" +#ifdef OFF_T_FMT +# undef OFF_T_FMT +#endif +#define OFF_T_FMT "%lld" #define LONGLONG_T long int /* End */ diff -Nru exim4-4.82/README.DSN exim4-4.84/README.DSN --- exim4-4.82/README.DSN 1970-01-01 00:00:00.000000000 +0000 +++ exim4-4.84/README.DSN 2014-08-09 12:44:29.000000000 +0000 @@ -0,0 +1,141 @@ +Exim DSN Patch (4.82) +--------------------- + +This patch is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This patch is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this patch; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. + +Installation & Usage +-------------------- +See docs/experimental-spec.txt + +Credits +------- + +The original work for the patch was done by Philip Hazel in Exim 3 + +The extract was taken and re-applied to Exim 4 by the following :- +Phil Bingham (phil.bingham@cwipapps.net) +Steve Falla (steve.falla@cwipapps.net) +Ray Edah (ray.edah@cwipapps.net) +Andrew Johnson (andrew.johnson@cwippaps.net) +Adrian Hungate (adrian.hungate@cwipapps.net) + +Now Primarily maintained by :- +Andrew Johnson (andrew.johnson@cwippaps.net) + +Updated for 4.82, improved and submitted to +http://bugs.exim.org/show_bug.cgi?id=118 +by :- +Wolfgang Breyha (wbreyha@gmx.net) + +Contributions +------------- +Andrey J. Melnikoff (TEMHOTA) (temnota@kmv.ru) + + +ChangeLog +--------- +14-Apr-2006 : Changed subject to "Delivery Status Notification" + +17-May-2006 : debug_printf in spool-in.c were not wrapped with #ifndef COMPILE_UTILITY + thanks to Andrey J. Melnikoff for this information + +12-Sep-2006 : Now supports Exim 4.63 + +12-Sep-2006 : src/EDITME did not include the #define SUPPORT_DSN as stated + in the documentation, this has now been corrected + thanks to Robert Kehl for this information + +28-Jul-2008 : New version for exim 4.69 released. + +02-Jul-2010 : New version for exim 4.72 released. + +25-Apr-2014 : Version 1.4 + *) fix ENVID and ORCPT addition in SMTP transport + *) p was not moved to the end of the string. new content + added afterwards overwrites ENVID and/or ORCPT + *) change spool file format to be compatible with the + extensible format of exim 4 by prepending new values and + setting the extended bitmask accordingly + *) use SUPPORT_DSN_LEGACY=yes in Makefile to be able to read + the legacy format of older patches until all messages are out of queue. + *) change "dsn" boolean toggle to "dsn_advertise_hosts" to + be able to select who actually can use the extension + *) Add all RFC 3461 MUST fields to delivery-status section + *) convert xtext in ENVID + *) add all successful rcpts to ONE message instead of sending several messages + +26-Apr-2014 : Version 1.5 + fixes: + *) fixed wrong order for ENVID + *) fixed wrong Final-Recipient value + *) af_ignore_failure is ignored for success reports + *) fixed DSN_LEGACY switch + improvements: + *) added MIME "failure" reports + *) bounce_return_message is ignored (required by RFC) + *) in case RET= is defined we honor these values + otherwise bounce_return_body is honored. + *) bounce_return_size_limit is always honored. + *) message body intro and final text is ignored + *) do not send report if DSN flags say NO + *) added MIME "delay" reports + *) do not send report if DSN flags say NO + *) changed from SUPPORT_DSN to EXPERIMENTAL_DSN + *) updated documentation + +01-May-2014 : Version 1.6 + fixes: + *) code cleanup + *) use text/rfc822-headers were applicable + *) fix NOTIFY=FAILURE + + improvements: + *) do not truncated MIME messages + *) if bounce_return_size_limit is smaller then the actual message + only the header is returned + *) if bounce_return_body or bounce_return_size_limit prevents Exim + from returning the requested (RET=FULL) body this fact is added + as X-Exim-DSN-Information Header + *) this also means that all of the last three parts of the "failure" + template are not used anymore + + *) dsn_process switch removed + *) every router "processes" DSN by default + *) there is no possibilty to "gag" DSN anymore since this violates RFC + *) dsn_lasthop switch added for routers + *) if dsn_lasthop is set by a router it is handled as relaying to a + non DSN aware relay. success mails are sent if Exim successfully + delivers the message. + *) redirect routers always "act" as if dsn_lasthop is set + + *) address_item.dsn_aware changed from uschar to int for easier handling. + +02-May-2014 : fixes: + *) Reporting-MTA: use smtp_active_hostname instead of qualify_domain from + original patch. + +20-May-2014 : fixes: + *) removed support for EXPERIMENTAL_DSN_LEGACY for codebase inclusion + *) fixed build of exim_monitor tree + *) fixed late declaration of dsn_all_lasthop + +----------------- + +Support for this patch up to 1.3 (limited though it is) will only be provided through the SourceForge +project page (http://sourceforge.net/projects/eximdsn/) + +From 1.4 onward feel free to ask on the exim-users mailinglist or add comments to +http://bugs.exim.org/show_bug.cgi?id=118 + diff -Nru exim4-4.82/README.UPDATING exim4-4.84/README.UPDATING --- exim4-4.82/README.UPDATING 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/README.UPDATING 2014-08-09 12:44:29.000000000 +0000 @@ -26,6 +26,21 @@ that might affect a running system. +Exim version 4.83 +----------------- + + * SPF condition results renamed "permerror" and "temperror". The old + names are still accepted for back-compatability, for this release. + + * TLS details are now logged on rejects, subject to log selectors. + + * Items in headers_remove lists must now have any embedded list-separators + doubled. + + * Attempted use of the deprecated options "gnutls_require_kx" et. al. + now result in logged warning. + + Exim version 4.82 ----------------- diff -Nru exim4-4.82/scripts/Configure-Makefile exim4-4.84/scripts/Configure-Makefile --- exim4-4.82/scripts/Configure-Makefile 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/scripts/Configure-Makefile 2014-08-09 12:44:29.000000000 +0000 @@ -142,6 +142,10 @@ fi if [ ".$need_this" != "." ]; then tls_include=`pkg-config --cflags $pc_value` + if [ $? -ne 0 ]; then + echo >&2 "*** Missing pkg-config for package $pc_value (for Exim $var build option)" + exit 1 + fi tls_libs=`pkg-config --libs $pc_value` echo "TLS_INCLUDE=$tls_include" echo "TLS_LIBS=$tls_libs" @@ -161,6 +165,10 @@ else # main binary cflags=`pkg-config --cflags $pc_value` + if [ $? -ne 0 ]; then + echo >&2 "*** Missing pkg-config for package $pc_value (for Exim $var build option)" + exit 1 + fi libs=`pkg-config --libs $pc_value` if [ "$var" != "${var#LOOKUP_}" ]; then echo "LOOKUP_INCLUDE += $cflags" @@ -178,6 +186,10 @@ case $PCRE_CONFIG in yes|YES|y|Y) cflags=`pcre-config --cflags` + if [ $? -ne 0 ]; then + echo >&2 "*** Missing pcre-config for regular expression support" + exit 1 + fi libs=`pcre-config --libs` if [ ".$cflags" != "." ]; then echo "INCLUDE += $cflags" @@ -196,6 +208,10 @@ echo "# End of pkg-config fixups" echo ) >> $mft + subexit=$? + if [ $subexit -ne 0 ]; then + exit $subexit + fi fi rm -f $mftt diff -Nru exim4-4.82/scripts/lookups-Makefile exim4-4.84/scripts/lookups-Makefile --- exim4-4.82/scripts/lookups-Makefile 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/scripts/lookups-Makefile 2014-08-09 12:44:29.000000000 +0000 @@ -24,6 +24,22 @@ _XPG=1 export _XPG + # We need the _right_ tr, so must do that first; but if a shell which + # we're more confident is sane is available, let's try that. Mostly, + # the problem is that "local" is not actually in "the" standard, it's + # just in every not-insane shell. Though arguably, there are no shells + # with POSIX-ish syntax which qualify as "not insane". + for b in /bin/dash /bin/bash /usr/local/bin/bash + do + if [ -x "$b" ] + then + SHELL="$b" + break + fi + done + # if we get a report of a system with zsh but not bash, we can add that + # to the list, but be sure to enable sh_word_split in that case. + exec "$SHELL" "$0" "$@" fi @@ -41,6 +57,16 @@ LC_ALL=C export LC_ALL +if [ -f "$defs_source" ] +then + : + # we are happy +else + echo >&2 "$0: ERROR: MISSING FILE '${defs_source}'" + echo >&2 "$0: SHOULD HAVE BEEN CALLED FROM scripts/Configure-Makefile" + exit 1 +fi + # nb: do not permit leading whitespace for this, as CFLAGS_DYNAMIC is exported # to the lookups subdir via a line with leading whitespace which otherwise # matches @@ -95,7 +121,10 @@ local mod_name pkgconf if [ "${lookup_name%:*}" = "$lookup_name" ] then - mod_name=$(echo $lookup_name | tr A-Z a-z) + # Square brackets are redundant but benign for POSIX compliant tr, + # however Solaris /usr/bin/tr requires them. Sometimes Solaris + # gets installed without a complete set of xpg4 tools, sigh. + mod_name=$(echo $lookup_name | tr [A-Z] [a-z]) else mod_name="${lookup_name#*:}" lookup_name="${lookup_name%:*}" diff -Nru exim4-4.82/scripts/MakeLinks exim4-4.84/scripts/MakeLinks --- exim4-4.82/scripts/MakeLinks 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/scripts/MakeLinks 2014-08-09 12:44:29.000000000 +0000 @@ -233,6 +233,8 @@ ln -s ../src/store.c store.c ln -s ../src/string.c string.c ln -s ../src/tls.c tls.c +ln -s ../src/tlscert-gnu.c tlscert-gnu.c +ln -s ../src/tlscert-openssl.c tlscert-openssl.c ln -s ../src/tls-gnu.c tls-gnu.c ln -s ../src/tls-openssl.c tls-openssl.c ln -s ../src/tod.c tod.c diff -Nru exim4-4.82/src/acl.c exim4-4.84/src/acl.c --- exim4-4.82/src/acl.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/acl.c 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* Code for handling Access Control Lists (ACLs) */ @@ -397,7 +397,7 @@ (unsigned int) ~((1<address, portnum, arg); -host_af = (Ustrchr(h->address, ':') == NULL)? AF_INET:AF_INET6; -r = s = ip_socket(SOCK_DGRAM, host_af); -if (r < 0) goto defer; -r = ip_connect(s, host_af, h->address, portnum, 1); +r = s = ip_connectedsocket(SOCK_DGRAM, h->address, portnum, portnum, + 1, NULL, &errstr); if (r < 0) goto defer; len = Ustrlen(arg); r = send(s, arg, len, 0); -if (r < 0) goto defer; +if (r < 0) + { + errstr = US strerror(errno); + close(s); + goto defer; + } +close(s); if (r < len) { *log_msgptr = @@ -2916,7 +2932,7 @@ return OK; defer: -*log_msgptr = string_sprintf("\"udpsend\" failed: %s", strerror(errno)); +*log_msgptr = string_sprintf("\"udpsend\" failed: %s", errstr); return DEFER; } @@ -2976,12 +2992,14 @@ if (cb->type == ACLC_MESSAGE) { + HDEBUG(D_acl) debug_printf(" message: %s\n", cb->arg); user_message = cb->arg; continue; } if (cb->type == ACLC_LOG_MESSAGE) { + HDEBUG(D_acl) debug_printf("l_message: %s\n", cb->arg); log_message = cb->arg; continue; } @@ -3088,7 +3106,9 @@ /* The true/false parsing here should be kept in sync with that used in expand.c when dealing with ECOND_BOOL so that we don't have too many different definitions of what can be a boolean. */ - if (Ustrspn(arg, "0123456789") == Ustrlen(arg)) /* Digits, or empty */ + if (*arg == '-' + ? Ustrspn(arg+1, "0123456789") == Ustrlen(arg+1) /* Negative number */ + : Ustrspn(arg, "0123456789") == Ustrlen(arg)) /* Digits, or empty */ rc = (Uatoi(arg) == 0)? FAIL : OK; else rc = (strcmpic(arg, US"no") == 0 || @@ -3228,8 +3248,9 @@ disable_callout_flush = TRUE; break; - case CONTROL_FAKEDEFER: case CONTROL_FAKEREJECT: + cancel_cutthrough_connection("fakereject"); + case CONTROL_FAKEDEFER: fake_response = (control_type == CONTROL_FAKEDEFER) ? DEFER : FAIL; if (*p == '/') { @@ -3259,10 +3280,12 @@ *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg); return ERROR; } + cancel_cutthrough_connection("item frozen"); break; case CONTROL_QUEUE_ONLY: queue_only_policy = TRUE; + cancel_cutthrough_connection("queueing forced"); break; case CONTROL_SUBMISSION: @@ -3329,17 +3352,19 @@ case CONTROL_CUTTHROUGH_DELIVERY: if (deliver_freeze) - { - *log_msgptr = string_sprintf("\"control=%s\" on frozen item", arg); - return ERROR; - } - if (queue_only_policy) - { - *log_msgptr = string_sprintf("\"control=%s\" on queue-only item", arg); - return ERROR; - } - cutthrough_delivery = TRUE; - break; + *log_msgptr = US"frozen"; + else if (queue_only_policy) + *log_msgptr = US"queue-only"; + else if (fake_response == FAIL) + *log_msgptr = US"fakereject"; + else + { + cutthrough_delivery = TRUE; + break; + } + *log_msgptr = string_sprintf("\"control=%s\" on %s item", + arg, *log_msgptr); + return ERROR; } break; @@ -4294,7 +4319,7 @@ ratelimiters_cmd = NULL; log_reject_target = LOG_MAIN|LOG_REJECT; -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR if (where == ACL_WHERE_RCPT || where == ACL_WHERE_PRDR ) #else if (where == ACL_WHERE_RCPT ) @@ -4338,7 +4363,7 @@ switch (where) { case ACL_WHERE_RCPT: -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR case ACL_WHERE_PRDR: #endif if( rcpt_count > 1 ) @@ -4458,4 +4483,6 @@ fprintf(f, "-acl%c %s %d\n%s\n", name[0], name+1, Ustrlen(value), value); } +/* vi: aw ai sw=2 +*/ /* End of acl.c */ diff -Nru exim4-4.82/src/auths/dovecot.c exim4-4.84/src/auths/dovecot.c --- exim4-4.82/src/auths/dovecot.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/auths/dovecot.c 2014-08-09 12:44:29.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (c) 2004 Andrey Panin + * Copyright (c) 2006-2014 The Exim Maintainers * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published diff -Nru exim4-4.82/src/config.h.defaults exim4-4.84/src/config.h.defaults --- exim4-4.82/src/config.h.defaults 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/config.h.defaults 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* The default settings for Exim configuration variables. A #define without @@ -41,6 +41,8 @@ #define DELIVER_IN_BUFFER_SIZE 8192 #define DELIVER_OUT_BUFFER_SIZE 8192 #define DISABLE_DKIM +#define DISABLE_PRDR +#define DISABLE_OCSP #define DISABLE_DNSSEC #define DISABLE_D_OPTION @@ -165,10 +167,11 @@ /* EXPERIMENTAL features */ #define EXPERIMENTAL_BRIGHTMAIL +#define EXPERIMENTAL_CERTNAMES #define EXPERIMENTAL_DCC #define EXPERIMENTAL_DMARC -#define EXPERIMENTAL_OCSP -#define EXPERIMENTAL_PRDR +#define EXPERIMENTAL_DSN +#define EXPERIMENTAL_PROXY #define EXPERIMENTAL_REDIS #define EXPERIMENTAL_SPF #define EXPERIMENTAL_SRS diff -Nru exim4-4.82/src/daemon.c exim4-4.84/src/daemon.c --- exim4-4.82/src/daemon.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/daemon.c 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* Functions concerned with running Exim as a daemon */ @@ -639,7 +639,7 @@ the data structures if necessary. */ #ifdef SUPPORT_TLS - tls_close(FALSE, FALSE); + tls_close(TRUE, FALSE); #endif /* Reset SIGHUP and SIGCHLD in the child in both cases. */ @@ -1127,13 +1127,13 @@ list = daemon_smtp_port; sep = 0; - while ((s = string_nextinlist(&list,&sep,big_buffer,big_buffer_size)) != NULL) + while ((s = string_nextinlist(&list,&sep,big_buffer,big_buffer_size))) pct++; default_smtp_port = store_get((pct+1) * sizeof(int)); list = daemon_smtp_port; sep = 0; for (pct = 0; - (s = string_nextinlist(&list,&sep,big_buffer,big_buffer_size)) != NULL; + (s = string_nextinlist(&list,&sep,big_buffer,big_buffer_size)); pct++) { if (isdigit(*s)) @@ -1146,13 +1146,38 @@ else { struct servent *smtp_service = getservbyname(CS s, "tcp"); - if (smtp_service == NULL) + if (!smtp_service) log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "TCP port \"%s\" not found", s); default_smtp_port[pct] = ntohs(smtp_service->s_port); } } default_smtp_port[pct] = 0; + /* Check the list of TLS-on-connect ports and do name lookups if needed */ + + list = tls_in.on_connect_ports; + sep = 0; + while ((s = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))) + if (!isdigit(*s)) + { + list = tls_in.on_connect_ports; + tls_in.on_connect_ports = NULL; + sep = 0; + while ((s = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))) + { + if (!isdigit(*s)) + { + struct servent *smtp_service = getservbyname(CS s, "tcp"); + if (!smtp_service) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "TCP port \"%s\" not found", s); + s= string_sprintf("%d", (int)ntohs(smtp_service->s_port)); + } + tls_in.on_connect_ports = string_append_listele(tls_in.on_connect_ports, + ':', s); + } + break; + } + /* Create the list of local interfaces, possibly with ports included. This list may contain references to 0.0.0.0 and ::0 as wildcards. These special values are converted below. */ @@ -2065,5 +2090,6 @@ /* Control never reaches here */ } +/* vi: aw ai sw=2 +*/ /* End of exim_daemon.c */ - diff -Nru exim4-4.82/src/deliver.c exim4-4.84/src/deliver.c --- exim4-4.82/src/deliver.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/deliver.c 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2009 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* The main code for delivering a message. */ @@ -63,6 +63,10 @@ static address_item *addr_remote = NULL; static address_item *addr_route = NULL; static address_item *addr_succeed = NULL; +#ifdef EXPERIMENTAL_DSN +static address_item *addr_dsntmp = NULL; +static address_item *addr_senddsn = NULL; +#endif static FILE *message_log = NULL; static BOOL update_spool; @@ -673,8 +677,36 @@ +static uschar * +d_hostlog(uschar * s, int * sizep, int * ptrp, address_item * addr) +{ + s = string_append(s, sizep, ptrp, 5, US" H=", addr->host_used->name, + US" [", addr->host_used->address, US"]"); + if ((log_extra_selector & LX_outgoing_port) != 0) + s = string_append(s, sizep, ptrp, 2, US":", string_sprintf("%d", + addr->host_used->port)); + return s; +} + +#ifdef SUPPORT_TLS +static uschar * +d_tlslog(uschar * s, int * sizep, int * ptrp, address_item * addr) +{ + if ((log_extra_selector & LX_tls_cipher) != 0 && addr->cipher != NULL) + s = string_append(s, sizep, ptrp, 2, US" X=", addr->cipher); + if ((log_extra_selector & LX_tls_certificate_verified) != 0 && + addr->cipher != NULL) + s = string_append(s, sizep, ptrp, 2, US" CV=", + testflag(addr, af_cert_verified)? "yes":"no"); + if ((log_extra_selector & LX_tls_peerdn) != 0 && addr->peerdn != NULL) + s = string_append(s, sizep, ptrp, 3, US" DN=\"", + string_printing(addr->peerdn), US"\""); + return s; +} +#endif + /* If msg is NULL this is a delivery log and logchar is used. Otherwise -this is a nonstandard call; no two-characher delivery flag is written +this is a nonstandard call; no two-character delivery flag is written but sender-host and sender are prefixed and "msg" is inserted in the log line. Arguments: @@ -702,6 +734,7 @@ tpda_delivery_local_part = NULL; tpda_delivery_domain = NULL; tpda_delivery_confirmation = NULL; + lookup_dnssec_authenticated = NULL; #endif s = reset_point = store_get(size); @@ -765,13 +798,9 @@ else { - if (addr->host_used != NULL) + if (addr->host_used) { - s = string_append(s, &size, &ptr, 5, US" H=", addr->host_used->name, - US" [", addr->host_used->address, US"]"); - if ((log_extra_selector & LX_outgoing_port) != 0) - s = string_append(s, &size, &ptr, 2, US":", string_sprintf("%d", - addr->host_used->port)); + s = d_hostlog(s, &size, &ptr, addr); if (continue_sequence > 1) s = string_cat(s, &size, &ptr, US"*", 1); @@ -782,19 +811,16 @@ tpda_delivery_local_part = addr->local_part; tpda_delivery_domain = addr->domain; tpda_delivery_confirmation = addr->message; + + /* DNS lookup status */ + lookup_dnssec_authenticated = addr->host_used->dnssec==DS_YES ? US"yes" + : addr->host_used->dnssec==DS_NO ? US"no" + : NULL; #endif } #ifdef SUPPORT_TLS - if ((log_extra_selector & LX_tls_cipher) != 0 && addr->cipher != NULL) - s = string_append(s, &size, &ptr, 2, US" X=", addr->cipher); - if ((log_extra_selector & LX_tls_certificate_verified) != 0 && - addr->cipher != NULL) - s = string_append(s, &size, &ptr, 2, US" CV=", - testflag(addr, af_cert_verified)? "yes":"no"); - if ((log_extra_selector & LX_tls_peerdn) != 0 && addr->peerdn != NULL) - s = string_append(s, &size, &ptr, 3, US" DN=\"", - string_printing(addr->peerdn), US"\""); + s = d_tlslog(s, &size, &ptr, addr); #endif if (addr->authenticator) @@ -808,42 +834,41 @@ } } - #ifdef EXPERIMENTAL_PRDR + #ifndef DISABLE_PRDR if (addr->flags & af_prdr_used) s = string_append(s, &size, &ptr, 1, US" PRDR"); #endif + } - if ((log_extra_selector & LX_smtp_confirmation) != 0 && - addr->message != NULL) - { - int i; - uschar *p = big_buffer; - uschar *ss = addr->message; - *p++ = '\"'; - for (i = 0; i < 100 && ss[i] != 0; i++) - { - if (ss[i] == '\"' || ss[i] == '\\') *p++ = '\\'; - *p++ = ss[i]; - } - *p++ = '\"'; - *p = 0; - s = string_append(s, &size, &ptr, 2, US" C=", big_buffer); - } +/* confirmation message (SMTP (host_used) and LMTP (driver_name)) */ + +if (log_extra_selector & LX_smtp_confirmation && + addr->message && + (addr->host_used || Ustrcmp(addr->transport->driver_name, "lmtp") == 0)) + { + int i; + uschar *p = big_buffer; + uschar *ss = addr->message; + *p++ = '\"'; + for (i = 0; i < 256 && ss[i] != 0; i++) /* limit logged amount */ + { + if (ss[i] == '\"' || ss[i] == '\\') *p++ = '\\'; /* quote \ and " */ + *p++ = ss[i]; + } + *p++ = '\"'; + *p = 0; + s = string_append(s, &size, &ptr, 2, US" C=", big_buffer); } /* Time on queue and actual time taken to deliver */ if ((log_extra_selector & LX_queue_time) != 0) - { s = string_append(s, &size, &ptr, 2, US" QT=", - readconf_printtime(time(NULL) - received_time)); - } + readconf_printtime( (int) ((long)time(NULL) - (long)received_time)) ); if ((log_extra_selector & LX_deliver_time) != 0) - { s = string_append(s, &size, &ptr, 2, US" DT=", readconf_printtime(addr->more_errno)); - } /* string_cat() always leaves room for the terminator. Release the store we used to build the line after writing it. */ @@ -1038,7 +1063,7 @@ (void)close(addr->return_file); } -/* The sucess case happens only after delivery by a transport. */ +/* The success case happens only after delivery by a transport. */ if (result == OK) { @@ -1054,10 +1079,8 @@ DEBUG(D_deliver) debug_printf("%s delivered\n", addr->address); if (addr->parent == NULL) - { deliver_msglog("%s %s: %s%s succeeded\n", now, addr->address, driver_name, driver_kind); - } else { deliver_msglog("%s %s <%s>: %s%s succeeded\n", now, addr->address, @@ -1065,7 +1088,35 @@ child_done(addr, now); } + /* Certificates for logging (via TPDA) */ + #ifdef SUPPORT_TLS + tls_out.ourcert = addr->ourcert; + addr->ourcert = NULL; + tls_out.peercert = addr->peercert; + addr->peercert = NULL; + + tls_out.cipher = addr->cipher; + tls_out.peerdn = addr->peerdn; + tls_out.ocsp = addr->ocsp; + #endif + delivery_log(LOG_MAIN, addr, logchar, NULL); + + #ifdef SUPPORT_TLS + if (tls_out.ourcert) + { + tls_free_cert(tls_out.ourcert); + tls_out.ourcert = NULL; + } + if (tls_out.peercert) + { + tls_free_cert(tls_out.peercert); + tls_out.peercert = NULL; + } + tls_out.cipher = NULL; + tls_out.peerdn = NULL; + tls_out.ocsp = OCSP_NOT_REQ; + #endif } @@ -1236,9 +1287,7 @@ if (used_return_path != NULL && (log_extra_selector & LX_return_path_on_delivery) != 0) - { s = string_append(s, &size, &ptr, 3, US" P=<", used_return_path, US">"); - } if (addr->router != NULL) s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name); @@ -1246,8 +1295,11 @@ s = string_append(s, &size, &ptr, 2, US" T=", addr->transport->name); if (addr->host_used != NULL) - s = string_append(s, &size, &ptr, 5, US" H=", addr->host_used->name, - US" [", addr->host_used->address, US"]"); + s = d_hostlog(s, &size, &ptr, addr); + + #ifdef SUPPORT_TLS + s = d_tlslog(s, &size, &ptr, addr); + #endif if (addr->basic_errno > 0) s = string_append(s, &size, &ptr, 2, US": ", @@ -2937,35 +2989,75 @@ #ifdef SUPPORT_TLS case 'X': - if (addr == NULL) goto ADDR_MISMATCH; /* Below, in 'A' handler */ - addr->cipher = (*ptr)? string_copy(ptr) : NULL; - while (*ptr++); - addr->peerdn = (*ptr)? string_copy(ptr) : NULL; + if (addr == NULL) goto ADDR_MISMATCH; /* Below, in 'A' handler */ + switch (*ptr++) + { + case '1': + addr->cipher = NULL; + addr->peerdn = NULL; + + if (*ptr) + addr->cipher = string_copy(ptr); + while (*ptr++); + if (*ptr) + addr->peerdn = string_copy(ptr); + break; + + case '2': + addr->peercert = NULL; + if (*ptr) + (void) tls_import_cert(ptr, &addr->peercert); + break; + + case '3': + addr->ourcert = NULL; + if (*ptr) + (void) tls_import_cert(ptr, &addr->ourcert); + break; + + #ifndef DISABLE_OCSP + case '4': + addr->ocsp = OCSP_NOT_REQ; + if (*ptr) + addr->ocsp = *ptr - '0'; + break; + #endif + } while (*ptr++); break; - #endif + #endif /*SUPPORT_TLS*/ case 'C': /* client authenticator information */ switch (*ptr++) - { - case '1': - addr->authenticator = (*ptr)? string_copy(ptr) : NULL; - break; - case '2': - addr->auth_id = (*ptr)? string_copy(ptr) : NULL; - break; - case '3': - addr->auth_sndr = (*ptr)? string_copy(ptr) : NULL; - break; - } + { + case '1': + addr->authenticator = (*ptr)? string_copy(ptr) : NULL; + break; + case '2': + addr->auth_id = (*ptr)? string_copy(ptr) : NULL; + break; + case '3': + addr->auth_sndr = (*ptr)? string_copy(ptr) : NULL; + break; + } while (*ptr++); break; -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR case 'P': - addr->flags |= af_prdr_used; break; + addr->flags |= af_prdr_used; + break; #endif + #ifdef EXPERIMENTAL_DSN + case 'D': + if (addr == NULL) break; + memcpy(&(addr->dsn_aware), ptr, sizeof(addr->dsn_aware)); + ptr += sizeof(addr->dsn_aware); + DEBUG(D_deliver) debug_printf("DSN read: addr->dsn_aware = %d\n", addr->dsn_aware); + break; + #endif + case 'A': if (addr == NULL) { @@ -2990,7 +3082,7 @@ addr->user_message = (*ptr)? string_copy(ptr) : NULL; while(*ptr++); - /* Always two strings for host information, followed by the port number */ + /* Always two strings for host information, followed by the port number and DNSSEC mark */ if (*ptr != 0) { @@ -3001,6 +3093,10 @@ while(*ptr++); memcpy(&(h->port), ptr, sizeof(h->port)); ptr += sizeof(h->port); + h->dnssec = *ptr == '2' ? DS_YES + : *ptr == '1' ? DS_NO + : DS_UNK; + ptr++; addr->host_used = h; } else ptr++; @@ -4028,25 +4124,55 @@ retry_item *r; /* The certificate verification status goes into the flags */ - if (tls_out.certificate_verified) setflag(addr, af_cert_verified); /* Use an X item only if there's something to send */ - #ifdef SUPPORT_TLS - if (addr->cipher != NULL) + if (addr->cipher) { ptr = big_buffer; - sprintf(CS ptr, "X%.128s", addr->cipher); + sprintf(CS ptr, "X1%.128s", addr->cipher); while(*ptr++); - if (addr->peerdn == NULL) *ptr++ = 0; else + if (!addr->peerdn) + *ptr++ = 0; + else { sprintf(CS ptr, "%.512s", addr->peerdn); while(*ptr++); } + rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer); } - #endif + if (addr->peercert) + { + ptr = big_buffer; + *ptr++ = 'X'; *ptr++ = '2'; + if (!tls_export_cert(ptr, big_buffer_size-2, addr->peercert)) + while(*ptr++); + else + *ptr++ = 0; + rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer); + } + if (addr->ourcert) + { + ptr = big_buffer; + *ptr++ = 'X'; *ptr++ = '3'; + if (!tls_export_cert(ptr, big_buffer_size-2, addr->ourcert)) + while(*ptr++); + else + *ptr++ = 0; + rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer); + } + #ifndef DISABLE_OCSP + if (addr->ocsp > OCSP_NOT_REQ) + { + ptr = big_buffer; + sprintf(CS ptr, "X4%c", addr->ocsp + '0'); + while(*ptr++); + rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer); + } + # endif + #endif /*SUPPORT_TLS*/ if (client_authenticator) { @@ -4070,8 +4196,16 @@ rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer); } - #ifdef EXPERIMENTAL_PRDR - if (addr->flags & af_prdr_used) rmt_dlv_checked_write(fd, "P", 1); + #ifndef DISABLE_PRDR + if (addr->flags & af_prdr_used) + rmt_dlv_checked_write(fd, "P", 1); + #endif + + #ifdef EXPERIMENTAL_DSN + big_buffer[0] = 'D'; + memcpy(big_buffer+1, &addr->dsn_aware, sizeof(addr->dsn_aware)); + rmt_dlv_checked_write(fd, big_buffer, sizeof(addr->dsn_aware) + 1); + DEBUG(D_deliver) debug_printf("DSN write: addr->dsn_aware = %d\n", addr->dsn_aware); #endif /* Retry information: for most success cases this will be null. */ @@ -4125,6 +4259,11 @@ while(*ptr++); memcpy(ptr, &(addr->host_used->port), sizeof(addr->host_used->port)); ptr += sizeof(addr->host_used->port); + + /* DNS lookup status */ + *ptr++ = addr->host_used->dnssec==DS_YES ? '2' + : addr->host_used->dnssec==DS_NO ? '1' : '0'; + } rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer); } @@ -5219,6 +5358,14 @@ if (r->pno >= 0) new->onetime_parent = recipients_list[r->pno].address; + #ifdef EXPERIMENTAL_DSN + /* If DSN support is enabled, set the dsn flags and the original receipt + to be passed on to other DSN enabled MTAs */ + new->dsn_flags = r->dsn_flags & rf_dsnflags; + new->dsn_orcpt = r->orcpt; + DEBUG(D_deliver) debug_printf("DSN: set orcpt: %s flags: %d\n", new->dsn_orcpt, new->dsn_flags); + #endif + switch (process_recipients) { /* RECIP_DEFER is set when a system filter freezes a message. */ @@ -6158,11 +6305,17 @@ regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE); #endif - #ifdef EXPERIMENTAL_PRDR + #ifndef DISABLE_PRDR if (regex_PRDR == NULL) regex_PRDR = regex_must_compile(US"\\n250[\\s\\-]PRDR(\\s|\\n|$)", FALSE, TRUE); #endif + #ifdef EXPERIMENTAL_DSN + /* Set the regex to check for DSN support on remote MTA */ + if (regex_DSN == NULL) regex_DSN = + regex_must_compile(US"\\n250[\\s\\-]DSN(\\s|\\n|$)", FALSE, TRUE); + #endif + /* Now sort the addresses if required, and do the deliveries. The yield of do_remote_deliveries is FALSE when mua_wrapper is set and all addresses cannot be delivered in one transaction. */ @@ -6267,6 +6420,169 @@ else if (!dont_deliver) retry_update(&addr_defer, &addr_failed, &addr_succeed); +#ifdef EXPERIMENTAL_DSN +/* Send DSN for successful messages */ +addr_dsntmp = addr_succeed; +addr_senddsn = NULL; + +while(addr_dsntmp != NULL) + { + DEBUG(D_deliver) + debug_printf("DSN: processing router : %s\n", addr_dsntmp->router->name); + + DEBUG(D_deliver) + debug_printf("DSN: processing successful delivery address: %s\n", addr_dsntmp->address); + + /* af_ignore_error not honored here. it's not an error */ + + DEBUG(D_deliver) debug_printf("DSN: Sender_address: %s\n", sender_address); + DEBUG(D_deliver) debug_printf("DSN: orcpt: %s flags: %d\n", addr_dsntmp->dsn_orcpt, addr_dsntmp->dsn_flags); + DEBUG(D_deliver) debug_printf("DSN: envid: %s ret: %d\n", dsn_envid, dsn_ret); + DEBUG(D_deliver) debug_printf("DSN: Final recipient: %s\n", addr_dsntmp->address); + DEBUG(D_deliver) debug_printf("DSN: Remote SMTP server supports DSN: %d\n", addr_dsntmp->dsn_aware); + + /* send report if next hop not DSN aware or a router flagged "last DSN hop" + and a report was requested */ + if (((addr_dsntmp->dsn_aware != dsn_support_yes) || + ((addr_dsntmp->dsn_flags & rf_dsnlasthop) != 0)) + && + (((addr_dsntmp->dsn_flags & rf_dsnflags) != 0) && + ((addr_dsntmp->dsn_flags & rf_notify_success) != 0))) + { + /* copy and relink address_item and send report with all of them at once later */ + address_item *addr_next; + addr_next = addr_senddsn; + addr_senddsn = store_get(sizeof(address_item)); + memcpy(addr_senddsn, addr_dsntmp, sizeof(address_item)); + addr_senddsn->next = addr_next; + } + else + { + DEBUG(D_deliver) debug_printf("DSN: *** NOT SENDING DSN SUCCESS Message ***\n"); + } + + addr_dsntmp = addr_dsntmp->next; + } + +if (addr_senddsn != NULL) + { + pid_t pid; + int fd; + + /* create exim process to send message */ + pid = child_open_exim(&fd); + + DEBUG(D_deliver) debug_printf("DSN: child_open_exim returns: %d\n", pid); + + if (pid < 0) /* Creation of child failed */ + { + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Process %d (parent %d) failed to " + "create child process to send failure message: %s", getpid(), + getppid(), strerror(errno)); + + DEBUG(D_deliver) debug_printf("DSN: child_open_exim failed\n"); + + } + else /* Creation of child succeeded */ + { + FILE *f = fdopen(fd, "wb"); + /* header only as required by RFC. only failure DSN needs to honor RET=FULL */ + int topt = topt_add_return_path | topt_no_body; + uschar boundaryStr[64]; + + DEBUG(D_deliver) debug_printf("sending error message to: %s\n", sender_address); + + /* build unique id for MIME boundary */ + snprintf(boundaryStr, sizeof(boundaryStr)-1, TIME_T_FMT "-eximdsn-%d", + time(NULL), rand()); + DEBUG(D_deliver) debug_printf("DSN: MIME boundary: %s\n", boundaryStr); + + if (errors_reply_to) + fprintf(f, "Reply-To: %s\n", errors_reply_to); + + fprintf(f, "Auto-Submitted: auto-generated\n" + "From: Mail Delivery System \n" + "To: %s\n" + "Subject: Delivery Status Notification\n" + "Content-Type: multipart/report; report-type=delivery-status; boundary=%s\n" + "MIME-Version: 1.0\n\n" + + "--%s\n" + "Content-type: text/plain; charset=us-ascii\n\n" + + "This message was created automatically by mail delivery software.\n" + " ----- The following addresses had successful delivery notifications -----\n", + qualify_domain_sender, sender_address, boundaryStr, boundaryStr); + + addr_dsntmp = addr_senddsn; + while(addr_dsntmp) + { + fprintf(f, "<%s> (relayed %s)\n\n", + addr_dsntmp->address, + (addr_dsntmp->dsn_flags & rf_dsnlasthop) == 1 + ? "via non DSN router" + : addr_dsntmp->dsn_aware == dsn_support_no + ? "to non-DSN-aware mailer" + : "via non \"Remote SMTP\" router" + ); + addr_dsntmp = addr_dsntmp->next; + } + fprintf(f, "--%s\n" + "Content-type: message/delivery-status\n\n" + "Reporting-MTA: dns; %s\n", + boundaryStr, smtp_active_hostname); + + if (dsn_envid != NULL) { + /* must be decoded from xtext: see RFC 3461:6.3a */ + uschar *xdec_envid; + if (auth_xtextdecode(dsn_envid, &xdec_envid) > 0) + fprintf(f, "Original-Envelope-ID: %s\n", dsn_envid); + else + fprintf(f, "X-Original-Envelope-ID: error decoding xtext formated ENVID\n"); + } + fputc('\n', f); + + for (addr_dsntmp = addr_senddsn; + addr_dsntmp; + addr_dsntmp = addr_dsntmp->next) + { + if (addr_dsntmp->dsn_orcpt) + fprintf(f,"Original-Recipient: %s\n", addr_dsntmp->dsn_orcpt); + + fprintf(f, "Action: delivered\n" + "Final-Recipient: rfc822;%s\n" + "Status: 2.0.0\n", + addr_dsntmp->address); + + if (addr_dsntmp->host_used && addr_dsntmp->host_used->name) + fprintf(f, "Remote-MTA: dns; %s\nDiagnostic-Code: smtp; 250 Ok\n", + addr_dsntmp->host_used->name); + else + fprintf(f,"Diagnostic-Code: X-Exim; relayed via non %s router\n", + (addr_dsntmp->dsn_flags & rf_dsnlasthop) == 1 ? "DSN" : "SMTP"); + fputc('\n', f); + } + + fprintf(f, "--%s\nContent-type: text/rfc822-headers\n\n", boundaryStr); + + fflush(f); + transport_filter_argv = NULL; /* Just in case */ + return_path = sender_address; /* In case not previously set */ + + /* Write the original email out */ + transport_write_message(NULL, fileno(f), topt, 0, NULL, NULL, NULL, NULL, NULL, 0); + fflush(f); + + fprintf(f,"\n"); + fprintf(f,"--%s--\n", boundaryStr); + + fflush(f); + fclose(f); + rc = child_close(pid, 0); /* Waits for child to close, no timeout */ + } + } +#endif /*EXPERIMENTAL_DSN*/ + /* If any addresses failed, we must send a message to somebody, unless af_ignore_error is set, in which case no action is taken. It is possible for several messages to get sent if there are addresses with different @@ -6324,8 +6640,13 @@ it from the list, throw away any saved message file, log it, and mark the recipient done. */ - if (testflag(addr_failed, af_ignore_error)) - { + if (testflag(addr_failed, af_ignore_error) +#ifdef EXPERIMENTAL_DSN + || (((addr_failed->dsn_flags & rf_dsnflags) != 0) + && ((addr_failed->dsn_flags & rf_notify_failure) != rf_notify_failure)) +#endif + ) + { addr = addr_failed; addr_failed = addr->next; if (addr->return_filename != NULL) Uunlink(addr->return_filename); @@ -6377,6 +6698,12 @@ BOOL to_sender = strcmpic(sender_address, bounce_recipient) == 0; int max = (bounce_return_size_limit/DELIVER_IN_BUFFER_SIZE + 1) * DELIVER_IN_BUFFER_SIZE; +#ifdef EXPERIMENTAL_DSN + uschar boundaryStr[64]; + uschar *dsnlimitmsg; + uschar *dsnnotifyhdr; + int topt; +#endif DEBUG(D_deliver) debug_printf("sending error message to: %s\n", bounce_recipient); @@ -6430,57 +6757,70 @@ moan_write_from(f); fprintf(f, "To: %s\n", bounce_recipient); +#ifdef EXPERIMENTAL_DSN + /* generate boundary string and output MIME-Headers */ + snprintf(boundaryStr, sizeof(boundaryStr)-1, TIME_T_FMT "-eximdsn-%d", + time(NULL), rand()); + + fprintf(f, "Content-Type: multipart/report;" + " report-type=delivery-status; boundary=%s\n" + "MIME-Version: 1.0\n", + boundaryStr); +#endif + /* Open a template file if one is provided. Log failure to open, but carry on - default texts will be used. */ - if (bounce_message_file != NULL) - { - emf = Ufopen(bounce_message_file, "rb"); - if (emf == NULL) + if (bounce_message_file) + if (!(emf = Ufopen(bounce_message_file, "rb"))) log_write(0, LOG_MAIN|LOG_PANIC, "Failed to open %s for error " "message texts: %s", bounce_message_file, strerror(errno)); - } /* Quietly copy to configured additional addresses if required. */ - bcc = moan_check_errorcopy(bounce_recipient); - if (bcc != NULL) fprintf(f, "Bcc: %s\n", bcc); + if ((bcc = moan_check_errorcopy(bounce_recipient))) + fprintf(f, "Bcc: %s\n", bcc); /* The texts for the message can be read from a template file; if there isn't one, or if it is too short, built-in texts are used. The first emf text is a Subject: and any other headers. */ - emf_text = next_emf(emf, US"header"); - if (emf_text != NULL) fprintf(f, "%s\n", emf_text); else - { + if ((emf_text = next_emf(emf, US"header"))) + fprintf(f, "%s\n", emf_text); + else fprintf(f, "Subject: Mail delivery failed%s\n\n", to_sender? ": returning message to sender" : ""); - } - emf_text = next_emf(emf, US"intro"); - if (emf_text != NULL) fprintf(f, "%s", CS emf_text); else +#ifdef EXPERIMENTAL_DSN + /* output human readable part as text/plain section */ + fprintf(f, "--%s\n" + "Content-type: text/plain; charset=us-ascii\n\n", + boundaryStr); +#endif + + if ((emf_text = next_emf(emf, US"intro"))) + fprintf(f, "%s", CS emf_text); + else { fprintf(f, /* This message has been reworded several times. It seems to be confusing to somebody, however it is worded. I have retreated to the original, simple wording. */ "This message was created automatically by mail delivery software.\n"); - if (bounce_message_text != NULL) fprintf(f, "%s", CS bounce_message_text); + + if (bounce_message_text) + fprintf(f, "%s", CS bounce_message_text); if (to_sender) - { fprintf(f, "\nA message that you sent could not be delivered to one or more of its\n" "recipients. This is a permanent error. The following address(es) failed:\n"); - } else - { fprintf(f, "\nA message sent by\n\n <%s>\n\n" "could not be delivered to one or more of its recipients. The following\n" "address(es) failed:\n", sender_address); - } } - fprintf(f, "\n"); + fputc('\n', f); /* Process the addresses, leaving them on the msgchain if they have a file name for a return message. (There has already been a check in @@ -6517,7 +6857,7 @@ } } - fprintf(f, "\n"); + fputc('\n', f); /* Get the next text, whether we need it or not, so as to be positioned for the one after. */ @@ -6531,11 +6871,13 @@ fd, and the return_filename field in the *last* one will be set (to the name of the file). */ - if (msgchain != NULL) + if (msgchain) { address_item *nextaddr; - if (emf_text != NULL) fprintf(f, "%s", CS emf_text); else + if (emf_text) + fprintf(f, "%s", CS emf_text); + else fprintf(f, "The following text was generated during the delivery " "attempt%s:\n", (filecount > 1)? "s" : ""); @@ -6547,15 +6889,15 @@ /* List all the addresses that relate to this file */ - fprintf(f, "\n"); - while(addr != NULL) /* Insurance */ + fputc('\n', f); + while(addr) /* Insurance */ { print_address_information(addr, f, US"------ ", US"\n ", US" ------\n"); - if (addr->return_filename != NULL) break; + if (addr->return_filename) break; addr = addr->next; } - fprintf(f, "\n"); + fputc('\n', f); /* Now copy the file */ @@ -6578,8 +6920,38 @@ addr->next = handled_addr; handled_addr = topaddr; } - fprintf(f, "\n"); + fputc('\n', f); + } + +#ifdef EXPERIMENTAL_DSN + /* output machine readable part */ + fprintf(f, "--%s\n" + "Content-type: message/delivery-status\n\n" + "Reporting-MTA: dns; %s\n", + boundaryStr, smtp_active_hostname); + + if (dsn_envid) + { + /* must be decoded from xtext: see RFC 3461:6.3a */ + uschar *xdec_envid; + if (auth_xtextdecode(dsn_envid, &xdec_envid) > 0) + fprintf(f, "Original-Envelope-ID: %s\n", dsn_envid); + else + fprintf(f, "X-Original-Envelope-ID: error decoding xtext formated ENVID\n"); + } + fputc('\n', f); + + for (addr = handled_addr; addr; addr = addr->next) + { + fprintf(f, "Action: failed\n" + "Final-Recipient: rfc822;%s\n" + "Status: 5.0.0\n", + addr->address); + if (addr->host_used && addr->host_used->name) + fprintf(f, "Remote-MTA: dns; %s\nDiagnostic-Code: smtp; %d\n", + addr->host_used->name, addr->basic_errno); } +#endif /* Now copy the message, trying to give an intelligible comment if it is too long for it all to be copied. The limit isn't strictly @@ -6588,12 +6960,15 @@ emf_text = next_emf(emf, US"copy"); +#ifndef EXPERIMENTAL_DSN if (bounce_return_message) { int topt = topt_add_return_path; if (!bounce_return_body) topt |= topt_no_body; - if (emf_text != NULL) fprintf(f, "%s", CS emf_text); else + if (emf_text) + fprintf(f, "%s", CS emf_text); + else { if (bounce_return_body) fprintf(f, "------ This is a copy of the message, including all the headers. ------\n"); @@ -6616,18 +6991,17 @@ { struct stat statbuf; if (fstat(deliver_datafile, &statbuf) == 0 && statbuf.st_size > max) - { - if (emf_text != NULL) fprintf(f, "%s", CS emf_text); else - { + if (emf_text) + fprintf(f, "%s", CS emf_text); + else fprintf(f, "------ The body of the message is " OFF_T_FMT " characters long; only the first\n" "------ %d or so are included here.\n", statbuf.st_size, max); - } - } } - fprintf(f, "\n"); + fputc('\n', f); fflush(f); + transport_filter_argv = NULL; /* Just in case */ return_path = sender_address; /* In case not previously set */ transport_write_message(NULL, fileno(f), topt, @@ -6636,12 +7010,71 @@ /* Write final text and close the template file if one is open */ - if (emf != NULL) + if (emf) { - emf_text = next_emf(emf, US"final"); - if (emf_text != NULL) fprintf(f, "%s", CS emf_text); + if ((emf_text = next_emf(emf, US"final"))) + fprintf(f, "%s", CS emf_text); (void)fclose(emf); } +#else + /* add message body + we ignore the intro text from template and add + the text for bounce_return_size_limit at the end. + + bounce_return_message is ignored + in case RET= is defined we honor these values + otherwise bounce_return_body is honored. + + bounce_return_size_limit is always honored. + */ + + fprintf(f, "\n--%s\n", boundaryStr); + + dsnlimitmsg = US"X-Exim-DSN-Information: Due to administrative limits only headers are returned"; + dsnnotifyhdr = NULL; + topt = topt_add_return_path; + + /* RET=HDRS? top priority */ + if (dsn_ret == dsn_ret_hdrs) + topt |= topt_no_body; + else + /* no full body return at all? */ + if (!bounce_return_body) + { + topt |= topt_no_body; + /* add header if we overrule RET=FULL */ + if (dsn_ret == dsn_ret_full) + dsnnotifyhdr = dsnlimitmsg; + } + /* size limited ... return headers only if limit reached */ + else if (bounce_return_size_limit > 0) + { + struct stat statbuf; + if (fstat(deliver_datafile, &statbuf) == 0 && statbuf.st_size > max) + { + topt |= topt_no_body; + dsnnotifyhdr = dsnlimitmsg; + } + } + + if (topt & topt_no_body) + fprintf(f,"Content-type: text/rfc822-headers\n\n"); + else + fprintf(f,"Content-type: message/rfc822\n\n"); + + fflush(f); + transport_filter_argv = NULL; /* Just in case */ + return_path = sender_address; /* In case not previously set */ + transport_write_message(NULL, fileno(f), topt, + 0, dsnnotifyhdr, NULL, NULL, NULL, NULL, 0); + fflush(f); + + /* we never add the final text. close the file */ + if (emf) + (void)fclose(emf); + + fprintf(f, "\n--%s--\n", boundaryStr); +#endif /*EXPERIMENTAL_DSN*/ /* Close the file, which should send an EOF to the child process that is receiving the message. Wait for it to finish. */ @@ -6750,7 +7183,7 @@ if ((log_extra_selector & LX_queue_time_overall) != 0) log_write(0, LOG_MAIN, "Completed QT=%s", - readconf_printtime(time(NULL) - received_time)); + readconf_printtime( (int) ((long)time(NULL) - (long)received_time)) ); else log_write(0, LOG_MAIN, "Completed"); @@ -6873,6 +7306,10 @@ it also defers). */ if (!queue_2stage && delivery_attempted && +#ifdef EXPERIMENTAL_DSN + (((addr_defer->dsn_flags & rf_dsnflags) == 0) || + (addr_defer->dsn_flags & rf_notify_delay) == rf_notify_delay) && +#endif delay_warning[1] > 0 && sender_address[0] != 0 && (delay_warning_condition == NULL || expand_check_condition(delay_warning_condition, @@ -6937,8 +7374,11 @@ uschar *wmf_text; FILE *wmf = NULL; FILE *f = fdopen(fd, "wb"); +#ifdef EXPERIMENTAL_DSN + uschar boundaryStr[64]; +#endif - if (warn_message_file != NULL) + if (warn_message_file) { wmf = Ufopen(warn_message_file, "rb"); if (wmf == NULL) @@ -6951,21 +7391,39 @@ string_sprintf("%d minutes", show_time/60): string_sprintf("%d hours", show_time/3600); - if (errors_reply_to != NULL) + if (errors_reply_to) fprintf(f, "Reply-To: %s\n", errors_reply_to); fprintf(f, "Auto-Submitted: auto-replied\n"); moan_write_from(f); fprintf(f, "To: %s\n", recipients); - wmf_text = next_emf(wmf, US"header"); - if (wmf_text != NULL) +#ifdef EXPERIMENTAL_DSN + /* generated boundary string and output MIME-Headers */ + snprintf(boundaryStr, sizeof(boundaryStr)-1, + TIME_T_FMT "-eximdsn-%d", time(NULL), rand()); + + fprintf(f, "Content-Type: multipart/report;" + " report-type=delivery-status; boundary=%s\n" + "MIME-Version: 1.0\n", + boundaryStr); +#endif + + if ((wmf_text = next_emf(wmf, US"header"))) fprintf(f, "%s\n", wmf_text); else fprintf(f, "Subject: Warning: message %s delayed %s\n\n", message_id, warnmsg_delay); - wmf_text = next_emf(wmf, US"intro"); - if (wmf_text != NULL) fprintf(f, "%s", CS wmf_text); else +#ifdef EXPERIMENTAL_DSN + /* output human readable part as text/plain section */ + fprintf(f, "--%s\n" + "Content-type: text/plain; charset=us-ascii\n\n", + boundaryStr); +#endif + + if ((wmf_text = next_emf(wmf, US"intro"))) + fprintf(f, "%s", CS wmf_text); + else { fprintf(f, "This message was created automatically by mail delivery software.\n"); @@ -6975,49 +7433,52 @@ "A message that you sent has not yet been delivered to one or more of its\n" "recipients after more than "); - else fprintf(f, + else + fprintf(f, "A message sent by\n\n <%s>\n\n" "has not yet been delivered to one or more of its recipients after more than \n", - sender_address); + sender_address); - fprintf(f, "%s on the queue on %s.\n\n", warnmsg_delay, - primary_hostname); - fprintf(f, "The message identifier is: %s\n", message_id); + fprintf(f, "%s on the queue on %s.\n\n" + "The message identifier is: %s\n", + warnmsg_delay, primary_hostname, message_id); for (h = header_list; h != NULL; h = h->next) - { if (strncmpic(h->text, US"Subject:", 8) == 0) fprintf(f, "The subject of the message is: %s", h->text + 9); else if (strncmpic(h->text, US"Date:", 5) == 0) fprintf(f, "The date of the message is: %s", h->text + 6); - } - fprintf(f, "\n"); + fputc('\n', f); fprintf(f, "The address%s to which the message has not yet been " "delivered %s:\n", - (addr_defer->next == NULL)? "" : "es", - (addr_defer->next == NULL)? "is": "are"); + !addr_defer->next ? "" : "es", + !addr_defer->next ? "is": "are"); } /* List the addresses, with error information if allowed */ - fprintf(f, "\n"); - while (addr_defer != NULL) +#ifdef EXPERIMENTAL_DSN + /* store addr_defer for machine readable part */ + address_item *addr_dsndefer = addr_defer; +#endif + fputc('\n', f); + while (addr_defer) { address_item *addr = addr_defer; addr_defer = addr->next; if (print_address_information(addr, f, US" ", US"\n ", US"")) print_address_error(addr, f, US"Delay reason: "); - fprintf(f, "\n"); + fputc('\n', f); } - fprintf(f, "\n"); + fputc('\n', f); /* Final text */ - if (wmf != NULL) + if (wmf) { - wmf_text = next_emf(wmf, US"final"); - if (wmf_text != NULL) fprintf(f, "%s", CS wmf_text); + if ((wmf_text = next_emf(wmf, US"final"))) + fprintf(f, "%s", CS wmf_text); (void)fclose(wmf); } else @@ -7029,6 +7490,58 @@ "and when that happens, the message will be returned to you.\n"); } +#ifdef EXPERIMENTAL_DSN + /* output machine readable part */ + fprintf(f, "\n--%s\n" + "Content-type: message/delivery-status\n\n" + "Reporting-MTA: dns; %s\n", + boundaryStr, + smtp_active_hostname); + + + if (dsn_envid) + { + /* must be decoded from xtext: see RFC 3461:6.3a */ + uschar *xdec_envid; + if (auth_xtextdecode(dsn_envid, &xdec_envid) > 0) + fprintf(f,"Original-Envelope-ID: %s\n", dsn_envid); + else + fprintf(f,"X-Original-Envelope-ID: error decoding xtext formated ENVID\n"); + } + fputc('\n', f); + + while (addr_dsndefer) + { + if (addr_dsndefer->dsn_orcpt) + fprintf(f,"Original-Recipient: %s\n", addr_dsndefer->dsn_orcpt); + + fprintf(f,"Action: delayed\n"); + fprintf(f,"Final-Recipient: rfc822;%s\n", addr_dsndefer->address); + fprintf(f,"Status: 4.0.0\n"); + if (addr_dsndefer->host_used && addr_dsndefer->host_used->name) + fprintf(f,"Remote-MTA: dns; %s\nDiagnostic-Code: smtp; %d\n", + addr_dsndefer->host_used->name, addr_dsndefer->basic_errno); + addr_dsndefer = addr_dsndefer->next; + } + + fprintf(f, "\n--%s\n" + "Content-type: text/rfc822-headers\n\n", + boundaryStr); + + fflush(f); + /* header only as required by RFC. only failure DSN needs to honor RET=FULL */ + int topt = topt_add_return_path | topt_no_body; + transport_filter_argv = NULL; /* Just in case */ + return_path = sender_address; /* In case not previously set */ + /* Write the original email out */ + transport_write_message(NULL, fileno(f), topt, 0, NULL, NULL, NULL, NULL, NULL, 0); + fflush(f); + + fprintf(f,"\n--%s--\n", boundaryStr); + + fflush(f); +#endif /*EXPERIMENTAL_DSN*/ + /* Close and wait for child process to complete, without a timeout. If there's an error, don't update the count. */ @@ -7165,4 +7678,6 @@ return final_yield; } +/* vi: aw ai sw=2 +*/ /* End of deliver.c */ diff -Nru exim4-4.82/src/dkim.c exim4-4.84/src/dkim.c --- exim4-4.82/src/dkim.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/dkim.c 2014-08-09 12:44:29.000000000 +0000 @@ -23,6 +23,7 @@ dns_scan dnss; dns_record *rr; + lookup_dnssec_authenticated = NULL; if (dns_lookup(&dnsa, (uschar *)name, T_TXT, NULL) != DNS_SUCCEED) return PDKIM_FAIL; /* Search for TXT record */ diff -Nru exim4-4.82/src/dmarc.c exim4-4.84/src/dmarc.c --- exim4-4.82/src/dmarc.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/dmarc.c 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ /* Experimental DMARC support. - Copyright (c) Todd Lyons 2012, 2013 + Copyright (c) Todd Lyons 2012 - 2014 License: GPL */ /* Portions Copyright (c) 2012, 2013, The Trusted Domain Project; @@ -12,15 +12,15 @@ #include "exim.h" #ifdef EXPERIMENTAL_DMARC -#if !defined EXPERIMENTAL_SPF -#error SPF must also be enabled for DMARC -#elif defined DISABLE_DKIM -#error DKIM must also be enabled for DMARC -#else - -#include "functions.h" -#include "dmarc.h" -#include "pdkim/pdkim.h" +# if !defined EXPERIMENTAL_SPF +# error SPF must also be enabled for DMARC +# elif defined DISABLE_DKIM +# error DKIM must also be enabled for DMARC +# else + +# include "functions.h" +# include "dmarc.h" +# include "pdkim/pdkim.h" OPENDMARC_LIB_T dmarc_ctx; DMARC_POLICY_T *dmarc_pctx = NULL; @@ -38,6 +38,18 @@ int history_file_status = DMARC_HIST_OK; uschar *dkim_history_buffer= NULL; +typedef struct dmarc_exim_p { + uschar *name; + int value; +} dmarc_exim_p; + +static dmarc_exim_p dmarc_policy_description[] = { + { US"", DMARC_RECORD_P_UNSPECIFIED }, + { US"none", DMARC_RECORD_P_NONE }, + { US"quarantine", DMARC_RECORD_P_QUARANTINE }, + { US"reject", DMARC_RECORD_P_REJECT }, + { NULL, 0 } +}; /* Accept an error_block struct, initialize if empty, parse to the * end, and append the two strings passed to it. Used for adding * variable amounts of value:pair data to the forensic emails. */ @@ -45,21 +57,21 @@ static error_block * add_to_eblock(error_block *eblock, uschar *t1, uschar *t2) { - error_block *eb = malloc(sizeof(error_block)); - if (eblock == NULL) - eblock = eb; - else - { - /* Find the end of the eblock struct and point it at eb */ - error_block *tmp = eblock; - while(tmp->next != NULL) - tmp = tmp->next; - tmp->next = eb; - } - eb->text1 = t1; - eb->text2 = t2; - eb->next = NULL; - return eblock; +error_block *eb = malloc(sizeof(error_block)); +if (eblock == NULL) + eblock = eb; +else + { + /* Find the end of the eblock struct and point it at eb */ + error_block *tmp = eblock; + while(tmp->next != NULL) + tmp = tmp->next; + tmp->next = eb; + } +eb->text1 = t1; +eb->text2 = t2; +eb->next = NULL; +return eblock; } /* dmarc_init sets up a context that can be re-used for several @@ -68,64 +80,62 @@ int dmarc_init() { - int *netmask = NULL; /* Ignored */ - int is_ipv6 = 0; - char *tld_file = (dmarc_tld_file == NULL) ? - "/etc/exim/opendmarc.tlds" : - (char *)dmarc_tld_file; - - /* Set some sane defaults. Also clears previous results when - * multiple messages in one connection. */ - dmarc_pctx = NULL; - dmarc_status = US"none"; - dmarc_abort = FALSE; - dmarc_pass_fail = US"skipped"; - dmarc_used_domain = US""; - dmarc_ar_header = NULL; - dmarc_has_been_checked = FALSE; - header_from_sender = NULL; - spf_sender_domain = NULL; - spf_human_readable = NULL; - - /* ACLs have "control=dmarc_disable_verify" */ - if (dmarc_disable_verify == TRUE) - return OK; - - (void) memset(&dmarc_ctx, '\0', sizeof dmarc_ctx); - dmarc_ctx.nscount = 0; - libdm_status = opendmarc_policy_library_init(&dmarc_ctx); - if (libdm_status != DMARC_PARSE_OKAY) - { - log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to init library: %s", - opendmarc_policy_status_to_str(libdm_status)); - dmarc_abort = TRUE; - } - if (dmarc_tld_file == NULL) - dmarc_abort = TRUE; - else if (opendmarc_tld_read_file(tld_file, NULL, NULL, NULL)) - { - log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to load tld list %s: %d", - tld_file, errno); - dmarc_abort = TRUE; - } - if (sender_host_address == NULL) - dmarc_abort = TRUE; - /* This catches locally originated email and startup errors above. */ - if ( dmarc_abort == FALSE ) - { - is_ipv6 = string_is_ip_address(sender_host_address, netmask); - is_ipv6 = (is_ipv6 == 6) ? TRUE : - (is_ipv6 == 4) ? FALSE : FALSE; - dmarc_pctx = opendmarc_policy_connect_init(sender_host_address, is_ipv6); - if (dmarc_pctx == NULL ) +int *netmask = NULL; /* Ignored */ +int is_ipv6 = 0; +char *tld_file = (dmarc_tld_file == NULL) ? + "/etc/exim/opendmarc.tlds" : + (char *)dmarc_tld_file; + +/* Set some sane defaults. Also clears previous results when + * multiple messages in one connection. */ +dmarc_pctx = NULL; +dmarc_status = US"none"; +dmarc_abort = FALSE; +dmarc_pass_fail = US"skipped"; +dmarc_used_domain = US""; +dmarc_ar_header = NULL; +dmarc_has_been_checked = FALSE; +header_from_sender = NULL; +spf_sender_domain = NULL; +spf_human_readable = NULL; + +/* ACLs have "control=dmarc_disable_verify" */ +if (dmarc_disable_verify == TRUE) + return OK; + +(void) memset(&dmarc_ctx, '\0', sizeof dmarc_ctx); +dmarc_ctx.nscount = 0; +libdm_status = opendmarc_policy_library_init(&dmarc_ctx); +if (libdm_status != DMARC_PARSE_OKAY) + { + log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to init library: %s", + opendmarc_policy_status_to_str(libdm_status)); + dmarc_abort = TRUE; + } +if (dmarc_tld_file == NULL) + dmarc_abort = TRUE; +else if (opendmarc_tld_read_file(tld_file, NULL, NULL, NULL)) + { + log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to load tld list %s: %d", + tld_file, errno); + dmarc_abort = TRUE; + } +if (sender_host_address == NULL) + dmarc_abort = TRUE; +/* This catches locally originated email and startup errors above. */ +if (!dmarc_abort) + { + is_ipv6 = string_is_ip_address(sender_host_address, netmask) == 6; + dmarc_pctx = opendmarc_policy_connect_init(sender_host_address, is_ipv6); + if (dmarc_pctx == NULL) { - log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure creating policy context: ip=%s", - sender_host_address); - dmarc_abort = TRUE; + log_write(0, LOG_MAIN|LOG_PANIC, + "DMARC failure creating policy context: ip=%s", sender_host_address); + dmarc_abort = TRUE; } } - return OK; +return OK; } @@ -144,470 +154,488 @@ context (if any), retrieves the result, sets up expansion strings and evaluates the condition outcome. */ -int dmarc_process() { - int sr, origin; /* used in SPF section */ - int dmarc_spf_result = 0; /* stores spf into dmarc conn ctx */ - pdkim_signature *sig = NULL; - BOOL has_dmarc_record = TRUE; - u_char **ruf; /* forensic report addressees, if called for */ - - /* ACLs have "control=dmarc_disable_verify" */ - if (dmarc_disable_verify == TRUE) - { - dmarc_ar_header = dmarc_auth_results_header(from_header, NULL); - return OK; - } - - /* Store the header From: sender domain for this part of DMARC. - * If there is no from_header struct, then it's likely this message - * is locally generated and relying on fixups to add it. Just skip - * the entire DMARC system if we can't find a From: header....or if - * there was a previous error. - */ - if (from_header == NULL || dmarc_abort == TRUE) - dmarc_abort = TRUE; - else +int +dmarc_process() +{ +int sr, origin; /* used in SPF section */ +int dmarc_spf_result = 0; /* stores spf into dmarc conn ctx */ +int tmp_ans, c; +pdkim_signature *sig = NULL; +BOOL has_dmarc_record = TRUE; +u_char **ruf; /* forensic report addressees, if called for */ + +/* ACLs have "control=dmarc_disable_verify" */ +if (dmarc_disable_verify) { - /* I strongly encourage anybody who can make this better to contact me directly! - * Is this an insane way to extract the email address from the From: header? - * it's sure a horrid layer-crossing.... - * I'm not denying that :-/ - * there may well be no better though - */ - header_from_sender = expand_string( - string_sprintf("${domain:${extract{1}{:}{${addresses:%s}}}}", - from_header->text) ); + dmarc_ar_header = dmarc_auth_results_header(from_header, NULL); + return OK; + } + +/* Store the header From: sender domain for this part of DMARC. + * If there is no from_header struct, then it's likely this message + * is locally generated and relying on fixups to add it. Just skip + * the entire DMARC system if we can't find a From: header....or if + * there was a previous error. + */ +if (!from_header || dmarc_abort) + dmarc_abort = TRUE; +else + { + uschar * errormsg; + int dummy, domain; + uschar * p; + uschar saveend; + + parse_allow_group = TRUE; + p = parse_find_address_end(from_header->text, FALSE); + saveend = *p; *p = '\0'; + if ((header_from_sender = parse_extract_address(from_header->text, &errormsg, + &dummy, &dummy, &domain, FALSE))) + header_from_sender += domain; + *p = saveend; + /* The opendmarc library extracts the domain from the email address, but * only try to store it if it's not empty. Otherwise, skip out of DMARC. */ - if (strcmp( CCS header_from_sender, "") == 0) + if (!header_from_sender || (strcmp( CCS header_from_sender, "") == 0)) dmarc_abort = TRUE; - libdm_status = (dmarc_abort == TRUE) ? - DMARC_PARSE_OKAY : - opendmarc_policy_store_from_domain(dmarc_pctx, header_from_sender); + libdm_status = dmarc_abort ? + DMARC_PARSE_OKAY : + opendmarc_policy_store_from_domain(dmarc_pctx, header_from_sender); if (libdm_status != DMARC_PARSE_OKAY) { - log_write(0, LOG_MAIN|LOG_PANIC, "failure to store header From: in DMARC: %s, header was '%s'", - opendmarc_policy_status_to_str(libdm_status), from_header->text); - dmarc_abort = TRUE; + log_write(0, LOG_MAIN|LOG_PANIC, + "failure to store header From: in DMARC: %s, header was '%s'", + opendmarc_policy_status_to_str(libdm_status), from_header->text); + dmarc_abort = TRUE; } } - /* Skip DMARC if connection is SMTP Auth. Temporarily, admin should - * instead do this in the ACLs. */ - if (dmarc_abort == FALSE && sender_host_authenticated == NULL) - { - /* Use the envelope sender domain for this part of DMARC */ - spf_sender_domain = expand_string(US"$sender_address_domain"); - if ( spf_response == NULL ) - { - /* No spf data means null envelope sender so generate a domain name - * from the sender_helo_name */ - if (spf_sender_domain == NULL) +/* Skip DMARC if connection is SMTP Auth. Temporarily, admin should + * instead do this in the ACLs. */ +if (!dmarc_abort && !sender_host_authenticated) + { + /* Use the envelope sender domain for this part of DMARC */ + spf_sender_domain = expand_string(US"$sender_address_domain"); + if (!spf_response) + { + /* No spf data means null envelope sender so generate a domain name + * from the sender_helo_name */ + if (!spf_sender_domain) { - spf_sender_domain = sender_helo_name; - log_write(0, LOG_MAIN, "DMARC using synthesized SPF sender domain = %s\n", - spf_sender_domain); - DEBUG(D_receive) - debug_printf("DMARC using synthesized SPF sender domain = %s\n", spf_sender_domain); - } - dmarc_spf_result = DMARC_POLICY_SPF_OUTCOME_NONE; - dmarc_spf_ares_result = ARES_RESULT_UNKNOWN; - origin = DMARC_POLICY_SPF_ORIGIN_HELO; - spf_human_readable = US""; - } - else - { - sr = spf_response->result; - dmarc_spf_result = (sr == SPF_RESULT_NEUTRAL) ? DMARC_POLICY_SPF_OUTCOME_NONE : - (sr == SPF_RESULT_PASS) ? DMARC_POLICY_SPF_OUTCOME_PASS : - (sr == SPF_RESULT_FAIL) ? DMARC_POLICY_SPF_OUTCOME_FAIL : - (sr == SPF_RESULT_SOFTFAIL) ? DMARC_POLICY_SPF_OUTCOME_TMPFAIL : - DMARC_POLICY_SPF_OUTCOME_NONE; - dmarc_spf_ares_result = (sr == SPF_RESULT_NEUTRAL) ? ARES_RESULT_NEUTRAL : - (sr == SPF_RESULT_PASS) ? ARES_RESULT_PASS : - (sr == SPF_RESULT_FAIL) ? ARES_RESULT_FAIL : - (sr == SPF_RESULT_SOFTFAIL) ? ARES_RESULT_SOFTFAIL : - (sr == SPF_RESULT_NONE) ? ARES_RESULT_NONE : - (sr == SPF_RESULT_TEMPERROR) ? ARES_RESULT_TEMPERROR : - (sr == SPF_RESULT_PERMERROR) ? ARES_RESULT_PERMERROR : - ARES_RESULT_UNKNOWN; - origin = DMARC_POLICY_SPF_ORIGIN_MAILFROM; - spf_human_readable = (uschar *)spf_response->header_comment; + spf_sender_domain = sender_helo_name; + log_write(0, LOG_MAIN, "DMARC using synthesized SPF sender domain = %s\n", + spf_sender_domain); DEBUG(D_receive) - debug_printf("DMARC using SPF sender domain = %s\n", spf_sender_domain); + debug_printf("DMARC using synthesized SPF sender domain = %s\n", + spf_sender_domain); + } + dmarc_spf_result = DMARC_POLICY_SPF_OUTCOME_NONE; + dmarc_spf_ares_result = ARES_RESULT_UNKNOWN; + origin = DMARC_POLICY_SPF_ORIGIN_HELO; + spf_human_readable = US""; } - if (strcmp( CCS spf_sender_domain, "") == 0) - dmarc_abort = TRUE; - if (dmarc_abort == FALSE) - { - libdm_status = opendmarc_policy_store_spf(dmarc_pctx, spf_sender_domain, - dmarc_spf_result, origin, spf_human_readable); - if (libdm_status != DMARC_PARSE_OKAY) - log_write(0, LOG_MAIN|LOG_PANIC, "failure to store spf for DMARC: %s", - opendmarc_policy_status_to_str(libdm_status)); - } - - /* Now we cycle through the dkim signature results and put into - * the opendmarc context, further building the DMARC reply. */ - sig = dkim_signatures; - dkim_history_buffer = US""; - while (sig != NULL) - { - int dkim_result, dkim_ares_result, vs, ves; - vs = sig->verify_status; - ves = sig->verify_ext_status; - dkim_result = ( vs == PDKIM_VERIFY_PASS ) ? DMARC_POLICY_DKIM_OUTCOME_PASS : - ( vs == PDKIM_VERIFY_FAIL ) ? DMARC_POLICY_DKIM_OUTCOME_FAIL : - ( vs == PDKIM_VERIFY_INVALID ) ? DMARC_POLICY_DKIM_OUTCOME_TMPFAIL : - DMARC_POLICY_DKIM_OUTCOME_NONE; - libdm_status = opendmarc_policy_store_dkim(dmarc_pctx, (uschar *)sig->domain, - dkim_result, US""); - DEBUG(D_receive) - debug_printf("DMARC adding DKIM sender domain = %s\n", sig->domain); - if (libdm_status != DMARC_PARSE_OKAY) - log_write(0, LOG_MAIN|LOG_PANIC, "failure to store dkim (%s) for DMARC: %s", - sig->domain, opendmarc_policy_status_to_str(libdm_status)); - - dkim_ares_result = ( vs == PDKIM_VERIFY_PASS ) ? ARES_RESULT_PASS : - ( vs == PDKIM_VERIFY_FAIL ) ? ARES_RESULT_FAIL : - ( vs == PDKIM_VERIFY_NONE ) ? ARES_RESULT_NONE : - ( vs == PDKIM_VERIFY_INVALID ) ? - ( ves == PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE ? ARES_RESULT_PERMERROR : - ves == PDKIM_VERIFY_INVALID_BUFFER_SIZE ? ARES_RESULT_PERMERROR : - ves == PDKIM_VERIFY_INVALID_PUBKEY_PARSING ? ARES_RESULT_PERMERROR : - ARES_RESULT_UNKNOWN ) : - ARES_RESULT_UNKNOWN; - dkim_history_buffer = string_sprintf("%sdkim %s %d\n", dkim_history_buffer, - sig->domain, dkim_ares_result); - sig = sig->next; - } - libdm_status = opendmarc_policy_query_dmarc(dmarc_pctx, US""); - switch (libdm_status) - { - case DMARC_DNS_ERROR_NXDOMAIN: - case DMARC_DNS_ERROR_NO_RECORD: - DEBUG(D_receive) - debug_printf("DMARC no record found for %s\n", header_from_sender); - has_dmarc_record = FALSE; - break; - case DMARC_PARSE_OKAY: - DEBUG(D_receive) - debug_printf("DMARC record found for %s\n", header_from_sender); - break; - case DMARC_PARSE_ERROR_BAD_VALUE: - DEBUG(D_receive) - debug_printf("DMARC record parse error for %s\n", header_from_sender); - has_dmarc_record = FALSE; - break; - default: - /* everything else, skip dmarc */ - DEBUG(D_receive) - debug_printf("DMARC skipping (%d), unsure what to do with %s", - libdm_status, from_header->text); - has_dmarc_record = FALSE; - break; - } - /* Can't use exim's string manipulation functions so allocate memory - * for libopendmarc using its max hostname length definition. */ - uschar *dmarc_domain = (uschar *)calloc(DMARC_MAXHOSTNAMELEN, sizeof(uschar)); - libdm_status = opendmarc_policy_fetch_utilized_domain(dmarc_pctx, dmarc_domain, - DMARC_MAXHOSTNAMELEN-1); - dmarc_used_domain = string_copy(dmarc_domain); - free(dmarc_domain); - if (libdm_status != DMARC_PARSE_OKAY) + else { - log_write(0, LOG_MAIN|LOG_PANIC, "failure to read domainname used for DMARC lookup: %s", - opendmarc_policy_status_to_str(libdm_status)); + sr = spf_response->result; + dmarc_spf_result = sr == SPF_RESULT_NEUTRAL ? DMARC_POLICY_SPF_OUTCOME_NONE : + sr == SPF_RESULT_PASS ? DMARC_POLICY_SPF_OUTCOME_PASS : + sr == SPF_RESULT_FAIL ? DMARC_POLICY_SPF_OUTCOME_FAIL : + sr == SPF_RESULT_SOFTFAIL ? DMARC_POLICY_SPF_OUTCOME_TMPFAIL : + DMARC_POLICY_SPF_OUTCOME_NONE; + dmarc_spf_ares_result = sr == SPF_RESULT_NEUTRAL ? ARES_RESULT_NEUTRAL : + sr == SPF_RESULT_PASS ? ARES_RESULT_PASS : + sr == SPF_RESULT_FAIL ? ARES_RESULT_FAIL : + sr == SPF_RESULT_SOFTFAIL ? ARES_RESULT_SOFTFAIL : + sr == SPF_RESULT_NONE ? ARES_RESULT_NONE : + sr == SPF_RESULT_TEMPERROR ? ARES_RESULT_TEMPERROR : + sr == SPF_RESULT_PERMERROR ? ARES_RESULT_PERMERROR : + ARES_RESULT_UNKNOWN; + origin = DMARC_POLICY_SPF_ORIGIN_MAILFROM; + spf_human_readable = (uschar *)spf_response->header_comment; + DEBUG(D_receive) + debug_printf("DMARC using SPF sender domain = %s\n", spf_sender_domain); } - libdm_status = opendmarc_get_policy_to_enforce(dmarc_pctx); - dmarc_policy = libdm_status; - switch(libdm_status) - { - case DMARC_POLICY_ABSENT: /* No DMARC record found */ - dmarc_status = US"norecord"; - dmarc_pass_fail = US"none"; - dmarc_status_text = US"No DMARC record"; - action = DMARC_RESULT_ACCEPT; - break; - case DMARC_FROM_DOMAIN_ABSENT: /* No From: domain */ - dmarc_status = US"nofrom"; - dmarc_pass_fail = US"temperror"; - dmarc_status_text = US"No From: domain found"; - action = DMARC_RESULT_ACCEPT; - break; - case DMARC_POLICY_NONE: /* Accept and report */ - dmarc_status = US"none"; - dmarc_pass_fail = US"none"; - dmarc_status_text = US"None, Accept"; - action = DMARC_RESULT_ACCEPT; - break; - case DMARC_POLICY_PASS: /* Explicit accept */ - dmarc_status = US"accept"; - dmarc_pass_fail = US"pass"; - dmarc_status_text = US"Accept"; - action = DMARC_RESULT_ACCEPT; - break; - case DMARC_POLICY_REJECT: /* Explicit reject */ - dmarc_status = US"reject"; - dmarc_pass_fail = US"fail"; - dmarc_status_text = US"Reject"; - action = DMARC_RESULT_REJECT; - break; - case DMARC_POLICY_QUARANTINE: /* Explicit quarantine */ - dmarc_status = US"quarantine"; - dmarc_pass_fail = US"fail"; - dmarc_status_text = US"Quarantine"; - action = DMARC_RESULT_QUARANTINE; - break; - default: - dmarc_status = US"temperror"; - dmarc_pass_fail = US"temperror"; - dmarc_status_text = US"Internal Policy Error"; - action = DMARC_RESULT_TEMPFAIL; - break; + if (strcmp( CCS spf_sender_domain, "") == 0) + dmarc_abort = TRUE; + if (!dmarc_abort) + { + libdm_status = opendmarc_policy_store_spf(dmarc_pctx, spf_sender_domain, + dmarc_spf_result, origin, spf_human_readable); + if (libdm_status != DMARC_PARSE_OKAY) + log_write(0, LOG_MAIN|LOG_PANIC, "failure to store spf for DMARC: %s", + opendmarc_policy_status_to_str(libdm_status)); } - libdm_status = opendmarc_policy_fetch_alignment(dmarc_pctx, &da, &sa); + /* Now we cycle through the dkim signature results and put into + * the opendmarc context, further building the DMARC reply. */ + sig = dkim_signatures; + dkim_history_buffer = US""; + while (sig) + { + int dkim_result, dkim_ares_result, vs, ves; + vs = sig->verify_status; + ves = sig->verify_ext_status; + dkim_result = vs == PDKIM_VERIFY_PASS ? DMARC_POLICY_DKIM_OUTCOME_PASS : + vs == PDKIM_VERIFY_FAIL ? DMARC_POLICY_DKIM_OUTCOME_FAIL : + vs == PDKIM_VERIFY_INVALID ? DMARC_POLICY_DKIM_OUTCOME_TMPFAIL : + DMARC_POLICY_DKIM_OUTCOME_NONE; + libdm_status = opendmarc_policy_store_dkim(dmarc_pctx, (uschar *)sig->domain, + dkim_result, US""); + DEBUG(D_receive) + debug_printf("DMARC adding DKIM sender domain = %s\n", sig->domain); if (libdm_status != DMARC_PARSE_OKAY) - { - log_write(0, LOG_MAIN|LOG_PANIC, "failure to read DMARC alignment: %s", - opendmarc_policy_status_to_str(libdm_status)); + log_write(0, LOG_MAIN|LOG_PANIC, + "failure to store dkim (%s) for DMARC: %s", + sig->domain, opendmarc_policy_status_to_str(libdm_status)); + + dkim_ares_result = + vs == PDKIM_VERIFY_PASS ? ARES_RESULT_PASS : + vs == PDKIM_VERIFY_FAIL ? ARES_RESULT_FAIL : + vs == PDKIM_VERIFY_NONE ? ARES_RESULT_NONE : + vs == PDKIM_VERIFY_INVALID ? + ves == PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE ? ARES_RESULT_PERMERROR : + ves == PDKIM_VERIFY_INVALID_BUFFER_SIZE ? ARES_RESULT_PERMERROR : + ves == PDKIM_VERIFY_INVALID_PUBKEY_PARSING ? ARES_RESULT_PERMERROR : + ARES_RESULT_UNKNOWN : + ARES_RESULT_UNKNOWN; + dkim_history_buffer = string_sprintf("%sdkim %s %d\n", dkim_history_buffer, + sig->domain, dkim_ares_result); + sig = sig->next; } - - if (has_dmarc_record == TRUE) + libdm_status = opendmarc_policy_query_dmarc(dmarc_pctx, US""); + switch (libdm_status) { - log_write(0, LOG_MAIN, "DMARC results: spf_domain=%s dmarc_domain=%s " - "spf_align=%s dkim_align=%s enforcement='%s'", - spf_sender_domain, dmarc_used_domain, - (sa==DMARC_POLICY_SPF_ALIGNMENT_PASS) ?"yes":"no", - (da==DMARC_POLICY_DKIM_ALIGNMENT_PASS)?"yes":"no", - dmarc_status_text); - history_file_status = dmarc_write_history_file(); - /* Now get the forensic reporting addresses, if any */ - ruf = opendmarc_policy_fetch_ruf(dmarc_pctx, NULL, 0, 1); - dmarc_send_forensic_report(ruf); + case DMARC_DNS_ERROR_NXDOMAIN: + case DMARC_DNS_ERROR_NO_RECORD: + DEBUG(D_receive) + debug_printf("DMARC no record found for %s\n", header_from_sender); + has_dmarc_record = FALSE; + break; + case DMARC_PARSE_OKAY: + DEBUG(D_receive) + debug_printf("DMARC record found for %s\n", header_from_sender); + break; + case DMARC_PARSE_ERROR_BAD_VALUE: + DEBUG(D_receive) + debug_printf("DMARC record parse error for %s\n", header_from_sender); + has_dmarc_record = FALSE; + break; + default: + /* everything else, skip dmarc */ + DEBUG(D_receive) + debug_printf("DMARC skipping (%d), unsure what to do with %s", + libdm_status, from_header->text); + has_dmarc_record = FALSE; + break; } - } - /* set some global variables here */ - dmarc_ar_header = dmarc_auth_results_header(from_header, NULL); +/* Store the policy string in an expandable variable. */ - /* shut down libopendmarc */ - if ( dmarc_pctx != NULL ) - (void) opendmarc_policy_connect_shutdown(dmarc_pctx); - if ( dmarc_disable_verify == FALSE ) - (void) opendmarc_policy_library_shutdown(&dmarc_ctx); + libdm_status = opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans); + for (c = 0; dmarc_policy_description[c].name; c++) + if (tmp_ans == dmarc_policy_description[c].value) + { + dmarc_domain_policy = string_sprintf("%s",dmarc_policy_description[c].name); + break; + } - return OK; -} + /* Can't use exim's string manipulation functions so allocate memory + * for libopendmarc using its max hostname length definition. */ -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 == NULL) - 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\n", message_id); - history_buffer = string_sprintf("%sreporter %s\n", history_buffer, primary_hostname); - history_buffer = string_sprintf("%sreceived %ld\n", history_buffer, time(NULL)); - history_buffer = string_sprintf("%sipaddr %s\n", history_buffer, sender_host_address); - history_buffer = string_sprintf("%sfrom %s\n", history_buffer, header_from_sender); - history_buffer = string_sprintf("%smfrom %s\n", history_buffer, - expand_string(US"$sender_address_domain")); - - if (spf_response != NULL) - 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%s", history_buffer, dkim_history_buffer); - history_buffer = string_sprintf("%spdomain %s\n", history_buffer, dmarc_used_domain); - history_buffer = string_sprintf("%spolicy %d\n", history_buffer, dmarc_policy); + uschar *dmarc_domain = (uschar *)calloc(DMARC_MAXHOSTNAMELEN, sizeof(uschar)); + libdm_status = opendmarc_policy_fetch_utilized_domain(dmarc_pctx, + dmarc_domain, DMARC_MAXHOSTNAMELEN-1); + dmarc_used_domain = string_copy(dmarc_domain); + free(dmarc_domain); - rua = opendmarc_policy_fetch_rua(dmarc_pctx, NULL, 0, 1); - if (rua != NULL) - { - for (tmp_ans = 0; rua[tmp_ans] != NULL; tmp_ans++) - { - history_buffer = string_sprintf("%srua %s\n", history_buffer, rua[tmp_ans]); + if (libdm_status != DMARC_PARSE_OKAY) + log_write(0, LOG_MAIN|LOG_PANIC, + "failure to read domainname used for DMARC lookup: %s", + opendmarc_policy_status_to_str(libdm_status)); + + libdm_status = opendmarc_get_policy_to_enforce(dmarc_pctx); + dmarc_policy = libdm_status; + switch(libdm_status) + { + case DMARC_POLICY_ABSENT: /* No DMARC record found */ + dmarc_status = US"norecord"; + dmarc_pass_fail = US"none"; + dmarc_status_text = US"No DMARC record"; + action = DMARC_RESULT_ACCEPT; + break; + case DMARC_FROM_DOMAIN_ABSENT: /* No From: domain */ + dmarc_status = US"nofrom"; + dmarc_pass_fail = US"temperror"; + dmarc_status_text = US"No From: domain found"; + action = DMARC_RESULT_ACCEPT; + break; + case DMARC_POLICY_NONE: /* Accept and report */ + dmarc_status = US"none"; + dmarc_pass_fail = US"none"; + dmarc_status_text = US"None, Accept"; + action = DMARC_RESULT_ACCEPT; + break; + case DMARC_POLICY_PASS: /* Explicit accept */ + dmarc_status = US"accept"; + dmarc_pass_fail = US"pass"; + dmarc_status_text = US"Accept"; + action = DMARC_RESULT_ACCEPT; + break; + case DMARC_POLICY_REJECT: /* Explicit reject */ + dmarc_status = US"reject"; + dmarc_pass_fail = US"fail"; + dmarc_status_text = US"Reject"; + action = DMARC_RESULT_REJECT; + break; + case DMARC_POLICY_QUARANTINE: /* Explicit quarantine */ + dmarc_status = US"quarantine"; + dmarc_pass_fail = US"fail"; + dmarc_status_text = US"Quarantine"; + action = DMARC_RESULT_QUARANTINE; + break; + default: + dmarc_status = US"temperror"; + dmarc_pass_fail = US"temperror"; + dmarc_status_text = US"Internal Policy Error"; + action = DMARC_RESULT_TEMPFAIL; + break; } - } - 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); + libdm_status = opendmarc_policy_fetch_alignment(dmarc_pctx, &da, &sa); + if (libdm_status != DMARC_PARSE_OKAY) + log_write(0, LOG_MAIN|LOG_PANIC, "failure to read DMARC alignment: %s", + opendmarc_policy_status_to_str(libdm_status)); - opendmarc_policy_fetch_adkim(dmarc_pctx, &tmp_ans); - history_buffer = string_sprintf("%sadkim %d\n", history_buffer, tmp_ans); + if (has_dmarc_record == TRUE) + { + log_write(0, LOG_MAIN, "DMARC results: spf_domain=%s dmarc_domain=%s " + "spf_align=%s dkim_align=%s enforcement='%s'", + spf_sender_domain, dmarc_used_domain, + (sa==DMARC_POLICY_SPF_ALIGNMENT_PASS) ?"yes":"no", + (da==DMARC_POLICY_DKIM_ALIGNMENT_PASS)?"yes":"no", + dmarc_status_text); + history_file_status = dmarc_write_history_file(); + /* Now get the forensic reporting addresses, if any */ + ruf = opendmarc_policy_fetch_ruf(dmarc_pctx, NULL, 0, 1); + dmarc_send_forensic_report(ruf); + } + } - opendmarc_policy_fetch_aspf(dmarc_pctx, &tmp_ans); - history_buffer = string_sprintf("%saspf %d\n", history_buffer, tmp_ans); +/* set some global variables here */ +dmarc_ar_header = dmarc_auth_results_header(from_header, NULL); + +/* shut down libopendmarc */ +if ( dmarc_pctx != NULL ) + (void) opendmarc_policy_connect_shutdown(dmarc_pctx); +if ( dmarc_disable_verify == FALSE ) + (void) opendmarc_policy_library_shutdown(&dmarc_ctx); - opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans); - history_buffer = string_sprintf("%sp %d\n", history_buffer, tmp_ans); +return OK; +} - opendmarc_policy_fetch_sp(dmarc_pctx, &tmp_ans); - history_buffer = string_sprintf("%ssp %d\n", history_buffer, tmp_ans); +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) + return DMARC_HIST_DISABLED; +history_file_fd = log_create(dmarc_history_file); - history_buffer = string_sprintf("%salign_dkim %d\n", history_buffer, da); - history_buffer = string_sprintf("%salign_spf %d\n", history_buffer, sa); - history_buffer = string_sprintf("%saction %d\n", history_buffer, action); +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; +} - /* Write the contents to the history file */ - DEBUG(D_receive) - debug_printf("DMARC logging history data for opendmarc reporting%s\n", - (host_checking || running_in_test_harness) ? " (not really)" : ""); - if (host_checking || running_in_test_harness) +/* 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 || running_in_test_harness) ? " (not really)" : ""); +if (host_checking || running_in_test_harness) { - DEBUG(D_receive) - debug_printf("DMARC history data for debugging:\n%s", history_buffer); + DEBUG(D_receive) + debug_printf("DMARC history data for debugging:\n%s", history_buffer); } - else +else { - written_len = write_to_fd_buf(history_file_fd, - history_buffer, - Ustrlen(history_buffer)); - if (written_len == 0) + 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; + 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); + (void)close(history_file_fd); } - return DMARC_HIST_OK; +return DMARC_HIST_OK; } -void dmarc_send_forensic_report(u_char **ruf) +void +dmarc_send_forensic_report(u_char **ruf) { - int c; - uschar *recipient, *save_sender; - BOOL send_status = FALSE; - error_block *eblock = NULL; - FILE *message_file = NULL; - - /* Earlier ACL does not have *required* control=dmarc_enable_forensic */ - if (dmarc_enable_forensic == FALSE) - return; - - if ((dmarc_policy == DMARC_POLICY_REJECT && action == DMARC_RESULT_REJECT) || - (dmarc_policy == DMARC_POLICY_QUARANTINE && action == DMARC_RESULT_QUARANTINE) ) - { - if (ruf != NULL) - { - eblock = add_to_eblock(eblock, US"Sender Domain", dmarc_used_domain); - eblock = add_to_eblock(eblock, US"Sender IP Address", sender_host_address); - eblock = add_to_eblock(eblock, US"Received Date", tod_stamp(tod_full)); - eblock = add_to_eblock(eblock, US"SPF Alignment", - (sa==DMARC_POLICY_SPF_ALIGNMENT_PASS) ?US"yes":US"no"); - eblock = add_to_eblock(eblock, US"DKIM Alignment", - (da==DMARC_POLICY_DKIM_ALIGNMENT_PASS)?US"yes":US"no"); - eblock = add_to_eblock(eblock, US"DMARC Results", dmarc_status_text); - /* Set a sane default envelope sender */ - dsn_from = dmarc_forensic_sender ? dmarc_forensic_sender : - dsn_from ? dsn_from : - string_sprintf("do-not-reply@%s",primary_hostname); - for (c = 0; ruf[c] != NULL; c++) +int c; +uschar *recipient, *save_sender; +BOOL send_status = FALSE; +error_block *eblock = NULL; +FILE *message_file = NULL; + +/* Earlier ACL does not have *required* control=dmarc_enable_forensic */ +if (!dmarc_enable_forensic) + return; + +if ((dmarc_policy == DMARC_POLICY_REJECT && action == DMARC_RESULT_REJECT) || + (dmarc_policy == DMARC_POLICY_QUARANTINE && action == DMARC_RESULT_QUARANTINE) ) + if (ruf) + { + eblock = add_to_eblock(eblock, US"Sender Domain", dmarc_used_domain); + eblock = add_to_eblock(eblock, US"Sender IP Address", sender_host_address); + eblock = add_to_eblock(eblock, US"Received Date", tod_stamp(tod_full)); + eblock = add_to_eblock(eblock, US"SPF Alignment", + (sa==DMARC_POLICY_SPF_ALIGNMENT_PASS) ?US"yes":US"no"); + eblock = add_to_eblock(eblock, US"DKIM Alignment", + (da==DMARC_POLICY_DKIM_ALIGNMENT_PASS)?US"yes":US"no"); + eblock = add_to_eblock(eblock, US"DMARC Results", dmarc_status_text); + /* Set a sane default envelope sender */ + dsn_from = dmarc_forensic_sender ? dmarc_forensic_sender : + dsn_from ? dsn_from : + string_sprintf("do-not-reply@%s",primary_hostname); + for (c = 0; ruf[c]; c++) { - recipient = string_copylc(ruf[c]); - if (Ustrncmp(recipient, "mailto:",7)) - continue; - /* Move to first character past the colon */ - recipient += 7; - DEBUG(D_receive) - debug_printf("DMARC forensic report to %s%s\n", recipient, - (host_checking || running_in_test_harness) ? " (not really)" : ""); - if (host_checking || running_in_test_harness) - continue; - save_sender = sender_address; - sender_address = recipient; - send_status = moan_to_sender(ERRMESS_DMARC_FORENSIC, eblock, - header_list, message_file, FALSE); - sender_address = save_sender; - if (send_status == FALSE) - log_write(0, LOG_MAIN|LOG_PANIC, "failure to send DMARC forensic report to %s", - recipient); + recipient = string_copylc(ruf[c]); + if (Ustrncmp(recipient, "mailto:",7)) + continue; + /* Move to first character past the colon */ + recipient += 7; + DEBUG(D_receive) + debug_printf("DMARC forensic report to %s%s\n", recipient, + (host_checking || running_in_test_harness) ? " (not really)" : ""); + if (host_checking || running_in_test_harness) + continue; + + save_sender = sender_address; + sender_address = recipient; + send_status = moan_to_sender(ERRMESS_DMARC_FORENSIC, eblock, + header_list, message_file, FALSE); + sender_address = save_sender; + if (!send_status) + log_write(0, LOG_MAIN|LOG_PANIC, + "failure to send DMARC forensic report to %s", recipient); } } - } } -uschar *dmarc_exim_expand_query(int what) +uschar * +dmarc_exim_expand_query(int what) { - if (dmarc_disable_verify || !dmarc_pctx) - return dmarc_exim_expand_defaults(what); +if (dmarc_disable_verify || !dmarc_pctx) + return dmarc_exim_expand_defaults(what); - switch(what) { - case DMARC_VERIFY_STATUS: - return(dmarc_status); - default: - return US""; +switch(what) + { + case DMARC_VERIFY_STATUS: + return(dmarc_status); + default: + return US""; } } -uschar *dmarc_exim_expand_defaults(int what) +uschar * +dmarc_exim_expand_defaults(int what) { - switch(what) { - case DMARC_VERIFY_STATUS: - return (dmarc_disable_verify) ? - US"off" : - US"none"; - default: - return US""; +switch(what) + { + case DMARC_VERIFY_STATUS: + return dmarc_disable_verify ? US"off" : US"none"; + default: + return US""; } } -uschar *dmarc_auth_results_header(header_line *from_header, uschar *hostname) +uschar * +dmarc_auth_results_header(header_line *from_header, uschar *hostname) { - uschar *hdr_tmp = US""; +uschar *hdr_tmp = US""; - /* Allow a server hostname to be passed to this function, but is - * currently unused */ - if (hostname == NULL) - hostname = primary_hostname; - hdr_tmp = string_sprintf("%s %s;", DMARC_AR_HEADER, hostname); +/* Allow a server hostname to be passed to this function, but is + * currently unused */ +if (!hostname) + hostname = primary_hostname; +hdr_tmp = string_sprintf("%s %s;", DMARC_AR_HEADER, hostname); #if 0 - /* I don't think this belongs here, but left it here commented out - * because it was a lot of work to get working right. */ - if (spf_response != NULL) { - uschar *dmarc_ar_spf = US""; - int sr = 0; - sr = spf_response->result; - dmarc_ar_spf = (sr == SPF_RESULT_NEUTRAL) ? US"neutral" : - (sr == SPF_RESULT_PASS) ? US"pass" : - (sr == SPF_RESULT_FAIL) ? US"fail" : - (sr == SPF_RESULT_SOFTFAIL) ? US"softfail" : - US"none"; - hdr_tmp = string_sprintf("%s spf=%s (%s) smtp.mail=%s;", - hdr_tmp, dmarc_ar_spf_result, - spf_response->header_comment, - expand_string(US"$sender_address") ); - } +/* I don't think this belongs here, but left it here commented out + * because it was a lot of work to get working right. */ +if (spf_response != NULL) { + uschar *dmarc_ar_spf = US""; + int sr = 0; + sr = spf_response->result; + dmarc_ar_spf = (sr == SPF_RESULT_NEUTRAL) ? US"neutral" : + (sr == SPF_RESULT_PASS) ? US"pass" : + (sr == SPF_RESULT_FAIL) ? US"fail" : + (sr == SPF_RESULT_SOFTFAIL) ? US"softfail" : + US"none"; + hdr_tmp = string_sprintf("%s spf=%s (%s) smtp.mail=%s;", + hdr_tmp, dmarc_ar_spf_result, + spf_response->header_comment, + expand_string(US"$sender_address") ); +} #endif - hdr_tmp = string_sprintf("%s dmarc=%s", - hdr_tmp, dmarc_pass_fail); - if (header_from_sender) - hdr_tmp = string_sprintf("%s header.from=%s", - hdr_tmp, header_from_sender); - return hdr_tmp; + +hdr_tmp = string_sprintf("%s dmarc=%s", hdr_tmp, dmarc_pass_fail); +if (header_from_sender) + hdr_tmp = string_sprintf("%s header.from=%s", + hdr_tmp, header_from_sender); +return hdr_tmp; } -#endif /* EXPERIMENTAL_SPF */ +# endif /* EXPERIMENTAL_SPF */ #endif /* EXPERIMENTAL_DMARC */ - -// vim:sw=2 expandtab +/* vi: aw ai sw=2 + */ diff -Nru exim4-4.82/src/dmarc.h exim4-4.84/src/dmarc.h --- exim4-4.82/src/dmarc.h 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/dmarc.h 2014-08-09 12:44:29.000000000 +0000 @@ -3,7 +3,7 @@ *************************************************/ /* Experimental DMARC support. - Copyright (c) Todd Lyons 2012, 2013 + Copyright (c) Todd Lyons 2012 - 2014 License: GPL */ /* Portions Copyright (c) 2012, 2013, The Trusted Domain Project; @@ -11,10 +11,10 @@ #ifdef EXPERIMENTAL_DMARC -#include "opendmarc/dmarc.h" -#ifdef EXPERIMENTAL_SPF -#include "spf2/spf.h" -#endif /* EXPERIMENTAL_SPF */ +# include "opendmarc/dmarc.h" +# ifdef EXPERIMENTAL_SPF +# include "spf2/spf.h" +# endif /* EXPERIMENTAL_SPF */ /* prototypes */ int dmarc_init(); @@ -60,5 +60,3 @@ #define ARES_RESULT_DISCARD 12 #endif /* EXPERIMENTAL_DMARC */ - -// vim:sw=2 expandtab diff -Nru exim4-4.82/src/dns.c exim4-4.84/src/dns.c --- exim4-4.82/src/dns.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/dns.c 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* Functions for interfacing with the DNS. */ @@ -159,12 +159,13 @@ Arguments: qualify_single TRUE to set the RES_DEFNAMES option search_parents TRUE to set the RES_DNSRCH option + use_dnssec TRUE to set the RES_USE_DNSSEC option Returns: nothing */ void -dns_init(BOOL qualify_single, BOOL search_parents) +dns_init(BOOL qualify_single, BOOL search_parents, BOOL use_dnssec) { res_state resp = os_get_dns_resolver_res(); @@ -206,6 +207,8 @@ # ifndef RES_USE_EDNS0 # error Have RES_USE_DNSSEC but not RES_USE_EDNS0? Something hinky ... # endif +if (use_dnssec) + resp->options |= RES_USE_DNSSEC; if (dns_dnssec_ok >= 0) { if (dns_use_edns0 == 0 && dns_dnssec_ok != 0) @@ -228,6 +231,9 @@ DEBUG(D_resolver) debug_printf("Unable to %sset DNSSEC without resolver support.\n", dns_dnssec_ok ? "" : "un"); +if (use_dnssec) + DEBUG(D_resolver) + debug_printf("Unable to set DNSSEC without resolver support.\n"); # endif #endif /* DISABLE_DNSSEC */ @@ -479,6 +485,7 @@ case T_SRV: return US"SRV"; case T_NS: return US"NS"; case T_CNAME: return US"CNAME"; + case T_TLSA: return US"TLSA"; default: return US"?"; } } @@ -1248,4 +1255,6 @@ return yield; } +/* vi: aw ai sw=2 +*/ /* End of dns.c */ diff -Nru exim4-4.82/src/drtables.c exim4-4.84/src/drtables.c --- exim4-4.82/src/drtables.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/drtables.c 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ diff -Nru exim4-4.82/src/EDITME exim4-4.84/src/EDITME --- exim4-4.82/src/EDITME 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/EDITME 2014-08-09 12:44:29.000000000 +0000 @@ -410,6 +410,17 @@ # DISABLE_DKIM=yes +#------------------------------------------------------------------------------ +# Uncomment the following line to remove Per-Recipient-Data-Response support. + +# DISABLE_PRDR=yes + +#------------------------------------------------------------------------------ +# Uncomment the following line to remove OCSP stapling support in TLS, +# from Exim. Note it can only be supported when built with +# GnuTLS 3.1.3 or later, or OpenSSL + +# DISABLE_OCSP=yes #------------------------------------------------------------------------------ # By default, Exim has support for checking the AD bit in a DNS response, to @@ -455,19 +466,12 @@ # CFLAGS += -I/opt/brightmail/bsdk-6.0/include # LDFLAGS += -lxml2_single -lbmiclient_single -L/opt/brightmail/bsdk-6.0/lib -# Uncomment the following line to add OCSP stapling support in TLS, if Exim -# was built using OpenSSL. - -# EXPERIMENTAL_OCSP=yes - # Uncomment the following line to add DMARC checking capability, implemented # using libopendmarc libraries. # EXPERIMENTAL_DMARC=yes # CFLAGS += -I/usr/local/include # LDFLAGS += -lopendmarc -# Uncomment the following line to add Per-Recipient-Data-Response support. -# EXPERIMENTAL_PRDR=yes # Uncomment the following line to support Transport post-delivery actions, # eg. for logging to a database. @@ -480,6 +484,15 @@ # CFLAGS += -I/usr/local/include # LDFLAGS += -lhiredis +# Uncomment the following line to enable Experimental Proxy Protocol +# EXPERIMENTAL_PROXY=yes + +# Uncomment the following line to enable support for checking certiticate +# ownership +# EXPERIMENTAL_CERTNAMES=yes + +# Uncomment the following line to add DSN support +# EXPERIMENTAL_DSN=yes ############################################################################### # THESE ARE THINGS YOU MIGHT WANT TO SPECIFY # diff -Nru exim4-4.82/src/exigrep.src exim4-4.84/src/exigrep.src --- exim4-4.82/src/exigrep.src 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/exigrep.src 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ use strict; -# Copyright (c) 2007 University of Cambridge. +# Copyright (c) 2007-2014 University of Cambridge. # See the file NOTICE for conditions of use and distribution. # Except when they appear in comments, the following placeholders in this @@ -124,6 +124,54 @@ { print "$_\n"; } } +# Rotated log files are frequently compressed and there are a variety of +# formats it could be compressed with. Rather than use just one that is +# detected and hardcoded at Exim compile time, detect and use what the +# logfile is compressed with on the fly. +# +# List of known compression extensions and their associated commands: +my $compressors = { + gz => { cmd => 'zcat', args => '' }, + bz2 => { cmd => 'bzcat', args => '' }, + xz => { cmd => 'xzcat', args => '' }, + lzma => { cmd => 'lzma', args => '-dc' } +}; +my $csearch = 0; + +sub detect_compressor_bin + { + my $ext = shift(); + my $c = $compressors->{$ext}->{cmd}; + $compressors->{$ext}->{bin} = `which $c 2>/dev/null`; + chomp($compressors->{$ext}->{bin}); + } + +sub detect_compressor_capable + { + my $filename = shift(); + map { &detect_compressor_bin($_) } keys %$compressors + if (!$csearch); + $csearch = 1; + return undef + unless (grep {$filename =~ /\.(?:$_)$/} keys %$compressors); + # Loop through them, figure out which one it detected, + # and build the commandline. + my $cmdline = undef; + foreach my $ext (keys %$compressors) + { + if ($filename =~ /\.(?:$ext)$/) + { + # Just die if compressor not found; if this occurrs in the middle of + # two valid files with a lot of matches, error could easily be missed. + die("Didn't find $ext decompressor for $filename\n") + if ($compressors->{$ext}->{bin} eq ''); + $cmdline = $compressors->{$ext}->{bin} ." ". + $compressors->{$ext}->{args}; + last; + } + } + return $cmdline; + } # The main program. Extract the pattern and make sure any relevant characters # are quoted if the -l flag is given. The -t flag gives a time-on-queue value @@ -154,6 +202,11 @@ open(LOG, "ZCAT_COMMAND $filename |") || die "Unable to zcat $filename: $!\n"; } + elsif (my $cmdline = &detect_compressor_capable($filename)) + { + open(LOG, "$cmdline $filename |") || + die "Unable to decompress $filename: $!\n"; + } else { open(LOG, "<$filename") || die "Unable to open $filename: $!\n"; diff -Nru exim4-4.82/src/exim.c exim4-4.84/src/exim.c --- exim4-4.82/src/exim.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/exim.c 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ @@ -399,9 +399,10 @@ if (!running_in_test_harness) { debug_printf("tick check: %lu.%06lu %lu.%06lu\n", - then_tv->tv_sec, then_tv->tv_usec, now_tv.tv_sec, now_tv.tv_usec); + then_tv->tv_sec, (long) then_tv->tv_usec, + now_tv.tv_sec, (long) now_tv.tv_usec); debug_printf("waiting %lu.%06lu\n", itval.it_value.tv_sec, - itval.it_value.tv_usec); + (long) itval.it_value.tv_usec); } } @@ -526,7 +527,7 @@ if (smtp_input) { #ifdef SUPPORT_TLS - tls_close(FALSE, FALSE); /* Shut down the TLS library */ + tls_close(TRUE, FALSE); /* Shut down the TLS library */ #endif (void)close(fileno(smtp_in)); (void)close(fileno(smtp_out)); @@ -804,6 +805,12 @@ #ifdef WITH_OLD_DEMIME fprintf(f, " Old_Demime"); #endif +#ifndef DISABLE_PRDR + fprintf(f, " PRDR"); +#endif +#ifndef DISABLE_OCSP + fprintf(f, " OCSP"); +#endif #ifdef EXPERIMENTAL_SPF fprintf(f, " Experimental_SPF"); #endif @@ -819,11 +826,8 @@ #ifdef EXPERIMENTAL_DMARC fprintf(f, " Experimental_DMARC"); #endif -#ifdef EXPERIMENTAL_OCSP - fprintf(f, " Experimental_OCSP"); -#endif -#ifdef EXPERIMENTAL_PRDR - fprintf(f, " Experimental_PRDR"); +#ifdef EXPERIMENTAL_PROXY + fprintf(f, " Experimental_Proxy"); #endif #ifdef EXPERIMENTAL_TPDA fprintf(f, " Experimental_TPDA"); @@ -831,6 +835,12 @@ #ifdef EXPERIMENTAL_REDIS fprintf(f, " Experimental_Redis"); #endif +#ifdef EXPERIMENTAL_CERTNAMES + fprintf(f, " Experimental_Certnames"); +#endif +#ifdef EXPERIMENTAL_DSN + fprintf(f, " Experimental_DSN"); +#endif fprintf(f, "\n"); fprintf(f, "Lookups (built-in):"); @@ -2653,6 +2663,16 @@ break; } + #ifdef EXPERIMENTAL_DSN + /* -MCD: set the smtp_use_dsn flag; this indicates that the host + that exim is connected to supports the esmtp extension DSN */ + else if (strcmp(argrest, "CD") == 0) + { + smtp_use_dsn = TRUE; + break; + } + #endif + /* -MCP: set the smtp_use_pipelining flag; this is useful only when it preceded -MC (see above) */ @@ -2986,6 +3006,23 @@ else if (Ustrcmp(argrest, "Mi") == 0) interface_address = argv[++i]; + /* -oMm: Message reference */ + + else if (Ustrcmp(argrest, "Mm") == 0) + { + if (!mac_ismsgid(argv[i+1])) + { + fprintf(stderr,"-oMm must be a valid message ID\n"); + exit(EXIT_FAILURE); + } + if (!trusted_config) + { + fprintf(stderr,"-oMm must be called by a trusted user/config\n"); + exit(EXIT_FAILURE); + } + message_reference = argv[++i]; + } + /* -oMr: Received protocol */ else if (Ustrcmp(argrest, "Mr") == 0) received_protocol = argv[++i]; diff -Nru exim4-4.82/src/exim.h exim4-4.84/src/exim.h --- exim4-4.82/src/exim.h 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/exim.h 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ @@ -35,7 +35,7 @@ /* If it didn't define os_find_running_interfaces, use the common function. */ #ifndef os_find_running_interfaces -#define os_find_running_interfaces os_common_find_running_interfaces +# define os_find_running_interfaces os_common_find_running_interfaces #endif /* If it didn't define the base for "base 62" numbers, we really do use 62. @@ -44,15 +44,15 @@ making unique names. */ #ifndef BASE_62 -#define BASE_62 62 +# define BASE_62 62 #endif /* The maximum value of localhost_number depends on the base being used */ #if BASE_62 == 62 -#define LOCALHOST_MAX 16 +# define LOCALHOST_MAX 16 #else -#define LOCALHOST_MAX 10 +# define LOCALHOST_MAX 10 #endif /* If not overridden by os.h, dynamic libraries have filenames ending .so */ @@ -77,11 +77,11 @@ #include #if defined(__svr4__) && defined(__sparc) && ! defined(__EXTENSIONS__) -#define __EXTENSIONS__ /* so that SunOS 5 gets NGROUPS_MAX */ -#include -#undef __EXTENSIONS__ +# define __EXTENSIONS__ /* so that SunOS 5 gets NGROUPS_MAX */ +# include +# undef __EXTENSIONS__ #else -#include +# include #endif /* C99 integer types, figure out how to undo this if needed for older systems */ @@ -91,19 +91,19 @@ /* Just in case some aged system doesn't define them... */ #ifndef INT_MAX -#define INT_MAX 2147483647 +# define INT_MAX 2147483647 #endif #ifndef INT_MIN -#define INT_MIN (-INT_MAX - 1) +# define INT_MIN (-INT_MAX - 1) #endif #ifndef SHRT_MAX -#define SHRT_MAX 32767 +# define SHRT_MAX 32767 #endif #ifndef UCHAR_MAX -#define UCHAR_MAX 255 +# define UCHAR_MAX 255 #endif @@ -118,11 +118,11 @@ /* Some systems have PATH_MAX and some have MAX_PATH_LEN. */ #ifndef PATH_MAX -#ifdef MAX_PATH_LEN -#define PATH_MAX MAX_PATH_LEN -#else -#define PATH_MAX 1024 -#endif +# ifdef MAX_PATH_LEN +# define PATH_MAX MAX_PATH_LEN +# else +# define PATH_MAX 1024 +# endif #endif #include @@ -130,7 +130,7 @@ #include #include #ifndef NO_POLL_H -#include +# include #endif #include #include @@ -140,11 +140,11 @@ in sys/file.h. */ #ifndef LOCK_SH -#define NO_FLOCK +# define NO_FLOCK #endif #ifndef NO_SYSEXITS /* some OS don't have this */ -#include +# include #endif /* A few OS don't have socklen_t; their os.h files define EXIM_SOCKLEN_T to @@ -152,22 +152,22 @@ that this is used by the AIX include files. */ #ifndef EXIM_SOCKLEN_T -#define EXIM_SOCKLEN_T socklen_t +# define EXIM_SOCKLEN_T socklen_t #endif /* Ensure that the sysexits we reference are defined */ #ifndef EX_UNAVAILABLE -#define EX_UNAVAILABLE 69 /* service unavailable; used for execv fail */ +# define EX_UNAVAILABLE 69 /* service unavailable; used for execv fail */ #endif #ifndef EX_CANTCREAT -#define EX_CANTCREAT 73 /* can't create file: treat as temporary */ +# define EX_CANTCREAT 73 /* can't create file: treat as temporary */ #endif #ifndef EX_TEMPFAIL -#define EX_TEMPFAIL 75 /* temp failure; user is invited to retry */ +# define EX_TEMPFAIL 75 /* temp failure; user is invited to retry */ #endif #ifndef EX_CONFIG -#define EX_CONFIG 78 /* configuration error */ +# define EX_CONFIG 78 /* configuration error */ #endif /* This one is not in any sysexits file that I've come across */ @@ -179,7 +179,7 @@ #include #ifndef NO_SYS_RESOURCE_H /* QNX doesn't have this */ -#include +# include #endif #include @@ -190,7 +190,7 @@ the code is run. It saves some #ifdef occurrences. */ #ifndef AF_INET6 -#define AF_INET6 24 +# define AF_INET6 24 #endif #include @@ -231,24 +231,24 @@ f_free. */ #ifndef F_BAVAIL - #define F_BAVAIL f_bavail + # define F_BAVAIL f_bavail #endif #ifndef F_FAVAIL - #define F_FAVAIL f_ffree + # define F_FAVAIL f_ffree #endif /* All the systems I've been able to look at seem to have F_FILES */ #ifndef F_FILES - #define F_FILES f_files + # define F_FILES f_files #endif #endif #ifndef SIOCGIFCONF /* HACK for SunOS 5 */ -#include +# include #endif #include @@ -265,14 +265,14 @@ disabused of the notion. Luckily, since EX_OK is not used, it didn't matter.] */ #ifdef EX_OK -#undef EX_OK +# undef EX_OK #endif #include #include #ifndef NO_NET_IF_H -#include +# include #endif #include #include @@ -308,45 +308,51 @@ header files. I don't suppose they have T_SRV either. */ #ifndef T_TXT -#define T_TXT 16 +# define T_TXT 16 #endif #ifndef T_SRV -#define T_SRV 33 +# define T_SRV 33 #endif /* Many systems do not have T_SPF. */ #ifndef T_SPF -#define T_SPF 99 +# define T_SPF 99 +#endif + +/* New TLSA record for DANE */ +#ifndef T_TLSA +# define T_TLSA 52 #endif +#define MAX_TLSA_EXPANDED_SIZE 8192 /* It seems that some versions of arpa/nameser.h don't define *any* of the T_xxx macros, which seem to be non-standard nowadays. Just to be on the safe side, put in definitions for all the ones that Exim uses. */ #ifndef T_A -#define T_A 1 +# define T_A 1 #endif #ifndef T_CNAME -#define T_CNAME 5 +# define T_CNAME 5 #endif #ifndef T_SOA -#define T_SOA 6 +# define T_SOA 6 #endif #ifndef T_MX -#define T_MX 15 +# define T_MX 15 #endif #ifndef T_NS -#define T_NS 2 +# define T_NS 2 #endif #ifndef T_PTR -#define T_PTR 12 +# define T_PTR 12 #endif @@ -358,12 +364,17 @@ . T_CSA gets the domain's Client SMTP Authorization SRV record + . T_ADDRESSES looks up both AAAA (or A6) and A records + +If any of these names appear in the RRtype list at: + +then we should rename Exim's private type away from the conflict. */ #define T_ZNS (-1) #define T_MXH (-2) #define T_CSA (-3) -#define T_APL (-4) +#define T_ADDRESSES (-4) /* The resolv.h header defines __P(x) on some Solaris 2.5.1 systems (without checking that it is already defined, in fact). This conflicts with other @@ -371,13 +382,13 @@ to undefine it if resolv.h defines it. */ #if defined(__P) -#define __P_WAS_DEFINED_BEFORE_RESOLV +# define __P_WAS_DEFINED_BEFORE_RESOLV #endif #include #if defined(__P) && ! defined (__P_WAS_DEFINED_BEFORE_RESOLV) -#undef __P +# undef __P #endif /* If not defined by os.h, we do nothing special to push DNS resolver state @@ -399,7 +410,7 @@ #include #ifndef NO_IP_VAR_H -#include +# include #endif /* Linux (and some others) uses a different type for the 2nd argument of @@ -407,20 +418,20 @@ here. */ #ifndef ICONV_ARG2_TYPE -#define ICONV_ARG2_TYPE const char ** +# define ICONV_ARG2_TYPE const char ** #endif /* One OS uses a different type for the 5th argument of getsockopt */ #ifndef GETSOCKOPT_ARG5_TYPE -#define GETSOCKOPT_ARG5_TYPE socklen_t * +# define GETSOCKOPT_ARG5_TYPE socklen_t * #endif /* One operating system uses a different type for the 2nd argument of select(). Its os.h file defines SELECT_ARG2_TYPE. For the rest, define a default here. */ #ifndef SELECT_ARG2_TYPE -#define SELECT_ARG2_TYPE fd_set +# define SELECT_ARG2_TYPE fd_set #endif /* One operating system uses a different type for the 4th argument of @@ -428,7 +439,7 @@ default here. */ #ifndef DN_EXPAND_ARG4_TYPE -#define DN_EXPAND_ARG4_TYPE char * +# define DN_EXPAND_ARG4_TYPE char * #endif /* One operating system defines a different type for the yield of inet_addr(). @@ -439,7 +450,7 @@ use different values for specific OS if ever necessary. */ #ifndef S_ADDR_TYPE -#define S_ADDR_TYPE u_long +# define S_ADDR_TYPE u_long #endif /* (At least) one operating system (Solaris) defines a different type for the @@ -448,7 +459,7 @@ here. */ #ifndef PAM_CONVERSE_ARG2_TYPE -#define PAM_CONVERSE_ARG2_TYPE const struct pam_message +# define PAM_CONVERSE_ARG2_TYPE const struct pam_message #endif /* One operating system (SunOS4) defines getc, ungetc, feof, and ferror as @@ -456,13 +467,13 @@ flag gets set to cause this to be sorted out here. */ #ifdef FUDGE_GETC_AND_FRIENDS -#undef getc +# undef getc extern int getc(FILE *); -#undef ungetc +# undef ungetc extern int ungetc(int, FILE *); -#undef feof +# undef feof extern int feof(FILE *); -#undef ferror +# undef ferror extern int ferror(FILE *); #endif @@ -484,43 +495,43 @@ #include "osfunctions.h" #ifdef EXPERIMENTAL_BRIGHTMAIL -#include "bmi_spam.h" +# include "bmi_spam.h" #endif #ifdef EXPERIMENTAL_SPF -#include "spf.h" +# include "spf.h" #endif #ifdef EXPERIMENTAL_SRS -#include "srs.h" +# include "srs.h" #endif #ifndef DISABLE_DKIM -#include "dkim.h" +# include "dkim.h" #endif #ifdef EXPERIMENTAL_DMARC -#include "dmarc.h" -#include +# include "dmarc.h" +# include #endif /* The following stuff must follow the inclusion of config.h because it requires various things that are set therein. */ #if HAVE_ICONV /* Not all OS have this */ -#include +# include #endif #if defined(USE_READLINE) || defined(EXPAND_DLFUNC) || defined (LOOKUP_MODULE_DIR) -#include +# include #endif #ifdef ENABLE_DISABLE_FSYNC -#define EXIMfsync(f) (disable_fsync? 0 : fsync(f)) +# define EXIMfsync(f) (disable_fsync? 0 : fsync(f)) #else -#define EXIMfsync(f) fsync(f) +# define EXIMfsync(f) fsync(f) #endif /* Backward compatibility; LOOKUP_LSEARCH now includes all three */ #if (!defined LOOKUP_LSEARCH) && (defined LOOKUP_WILDLSEARCH || defined LOOKUP_NWILDLSEARCH) -#define LOOKUP_LSEARCH yes +# define LOOKUP_LSEARCH yes #endif /* Define a union to hold either an IPv4 or an IPv6 sockaddr structure; this @@ -539,7 +550,7 @@ so that if USE_GNUTLS *is* set, we can assume SUPPORT_TLS is also set. */ #ifndef SUPPORT_TLS -#undef USE_GNUTLS +# undef USE_GNUTLS #endif /* If SPOOL_DIRECTORY, LOG_FILE_PATH or PID_FILE_PATH have not been defined, @@ -563,20 +574,20 @@ default to EDQUOT if it exists, otherwise ENOSPC. */ #ifndef ERRNO_QUOTA -#ifdef EDQUOT -#define ERRNO_QUOTA EDQUOT -#else -#define ERRNO_QUOTA ENOSPC -#endif +# ifdef EDQUOT +# define ERRNO_QUOTA EDQUOT +# else +# define ERRNO_QUOTA ENOSPC +# endif #endif /* Ensure PATH_MAX is defined */ #ifndef PATH_MAX #ifdef MAXPATHLEN - #define PATH_MAX MAXPATHLEN + # define PATH_MAX MAXPATHLEN #else - #define PATH_MAX 1024 + # define PATH_MAX 1024 #endif #endif diff -Nru exim4-4.82/src/exim_lock.c exim4-4.84/src/exim_lock.c --- exim4-4.82/src/exim_lock.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/exim_lock.c 2014-08-09 12:44:29.000000000 +0000 @@ -175,7 +175,7 @@ int hd = -1; int md = -1; int yield = 0; -int now = time(NULL); +time_t now = time(NULL); BOOL use_lockfile = FALSE; BOOL use_fcntl = FALSE; BOOL use_flock = FALSE; @@ -300,8 +300,10 @@ lockname = malloc(len + 8); sprintf(lockname, "%s.lock", filename); hitchname = malloc(len + 32 + (int)strlen(primary_hostname)); + + /* Presumably, this must match appendfile.c */ sprintf(hitchname, "%s.%s.%08x.%08x", lockname, primary_hostname, - now, (int)getpid()); + (unsigned int)now, (unsigned int)getpid()); if (verbose) printf("exim_lock: lockname = %s\n hitchname = %s\n", lockname, diff -Nru exim4-4.82/src/eximstats.src exim4-4.84/src/eximstats.src --- exim4-4.82/src/eximstats.src 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/eximstats.src 2014-08-09 12:44:29.000000000 +0000 @@ -1,6 +1,6 @@ #!PERL_COMMAND -w -# Copyright (c) 2001 University of Cambridge. +# Copyright (c) 2001-2014 University of Cambridge. # See the file NOTICE for conditions of use and distribution. # Perl script to generate statistics from one or more Exim log files. diff -Nru exim4-4.82/src/exipick.src exim4-4.84/src/exipick.src --- exim4-4.82/src/exipick.src 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/exipick.src 2014-08-09 12:44:29.000000000 +0000 @@ -1020,6 +1020,12 @@ return($self->_error("incorrect format: $_")) if (length($2) != $3); $self->{_recips}{$1} = { pno => $4, errors_to => $2 }; $addr = $1; + } elsif (/^(\S*)\s(\S*)\s(\d+),(\d+)\s(\S*)\s(\d+),(-?\d+)#3$/) { + #print STDERR "exim4 new type #3 DSN (untested): $_\n"; + return($self->_error("incorrect format: $_")) + if ((length($2) != $3) || (length($5) != $6)); + $self->{_recips}{$1} = { pno => $7, errors_to => $5 }; + $addr = $1; } elsif (/^.*#(\d+)$/) { #print STDERR "exim4 #$1 style (unimplemented): $_\n"; $self->_error("exim4 #$1 style (unimplemented): $_"); diff -Nru exim4-4.82/src/exiqgrep.src exim4-4.84/src/exiqgrep.src --- exim4-4.82/src/exiqgrep.src 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/exiqgrep.src 2014-08-09 12:44:29.000000000 +0000 @@ -43,8 +43,10 @@ $base = 62; }; -getopts('hf:r:y:o:s:zxlibRc',\%opt); +getopts('hf:r:y:o:s:C:zxlibRca',\%opt); if ($opt{h}) { &help; exit;} +if ($opt{a}) { $eargs = '-bp'; } +if ($opt{C} && -e $opt{C} && -f $opt{C} && -R $opt{C}) { $eargs .= ' -C '.$opt{C}; } # Read message queue output into hash &collect(); @@ -60,6 +62,7 @@ Exim message queue display utility. -h This help message. + -C Specify which exim.conf to use. Selection criteria: -f Match sender address sender (field is "< >" wrapped) @@ -78,6 +81,7 @@ -i Message IDs only -b Brief Format -R Reverse order + -a All recipients (including delivered) EOF } diff -Nru exim4-4.82/src/expand.c exim4-4.84/src/expand.c --- exim4-4.82/src/expand.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/expand.c 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ @@ -13,7 +13,8 @@ /* Recursively called function */ -static uschar *expand_string_internal(uschar *, BOOL, uschar **, BOOL, BOOL); +static uschar *expand_string_internal(uschar *, BOOL, uschar **, BOOL, BOOL, BOOL *); +static int_eximarith_t expanded_string_integer(uschar *, BOOL); #ifdef STAND_ALONE #ifndef SUPPORT_CRYPTEQ @@ -93,6 +94,9 @@ +#ifndef nelements +# define nelements(arr) (sizeof(arr) / sizeof(*arr)) +#endif /************************************************* * Local statics and tables * @@ -103,6 +107,7 @@ static uschar *item_table[] = { US"acl", + US"certextract", US"dlfunc", US"extract", US"filter", @@ -110,6 +115,7 @@ US"hmac", US"if", US"length", + US"listextract", US"lookup", US"map", US"nhash", @@ -126,6 +132,7 @@ enum { EITEM_ACL, + EITEM_CERTEXTRACT, EITEM_DLFUNC, EITEM_EXTRACT, EITEM_FILTER, @@ -133,6 +140,7 @@ EITEM_HMAC, EITEM_IF, EITEM_LENGTH, + EITEM_LISTEXTRACT, EITEM_LOOKUP, EITEM_MAP, EITEM_NHASH, @@ -198,11 +206,13 @@ US"rxquote", US"s", US"sha1", + US"sha256", US"stat", US"str2b64", US"strlen", US"substr", - US"uc" }; + US"uc", + US"utf8clean" }; enum { EOP_ADDRESS = sizeof(op_table_underscore)/sizeof(uschar *), @@ -234,11 +244,13 @@ EOP_RXQUOTE, EOP_S, EOP_SHA1, + EOP_SHA256, EOP_STAT, EOP_STR2B64, EOP_STRLEN, EOP_SUBSTR, - EOP_UC }; + EOP_UC, + EOP_UTF8CLEAN }; /* Table of condition names, and corresponding switch numbers. The names must @@ -337,25 +349,9 @@ }; -/* Type for main variable table */ - -typedef struct { - const char *name; - int type; - void *value; -} var_entry; - -/* Type for entries pointing to address/length pairs. Not currently -in use. */ - -typedef struct { - uschar **address; - int *length; -} alblock; - /* Types of table entry */ -enum { +enum vtypes { vtype_int, /* value is address of int */ vtype_filter_int, /* ditto, but recognized only when filtering */ vtype_ino, /* value is address of ino_t (not always an int) */ @@ -383,11 +379,28 @@ vtype_host_lookup, /* value not used; get host name */ vtype_load_avg, /* value not used; result is int from os_getloadavg */ vtype_pspace, /* partition space; value is T/F for spool/log */ - vtype_pinodes /* partition inodes; value is T/F for spool/log */ + vtype_pinodes, /* partition inodes; value is T/F for spool/log */ + vtype_cert /* SSL certificate */ #ifndef DISABLE_DKIM ,vtype_dkim /* Lookup of value in DKIM signature */ #endif - }; +}; + +/* Type for main variable table */ + +typedef struct { + const char *name; + enum vtypes type; + void *value; +} var_entry; + +/* Type for entries pointing to address/length pairs. Not currently +in use. */ + +typedef struct { + uschar **address; + int *length; +} alblock; static uschar * fn_recipients(void); @@ -464,6 +477,7 @@ #endif #ifdef EXPERIMENTAL_DMARC { "dmarc_ar_header", vtype_stringptr, &dmarc_ar_header }, + { "dmarc_domain_policy", vtype_stringptr, &dmarc_domain_policy }, { "dmarc_status", vtype_stringptr, &dmarc_status }, { "dmarc_status_text", vtype_stringptr, &dmarc_status_text }, { "dmarc_used_domain", vtype_stringptr, &dmarc_used_domain }, @@ -505,6 +519,7 @@ { "localhost_number", vtype_int, &host_number }, { "log_inodes", vtype_pinodes, (void *)FALSE }, { "log_space", vtype_pspace, (void *)FALSE }, + { "lookup_dnssec_authenticated",vtype_stringptr,&lookup_dnssec_authenticated}, { "mailstore_basename", vtype_stringptr, &mailstore_basename }, #ifdef WITH_CONTENT_SCAN { "malware_name", vtype_stringptr, &malware_name }, @@ -556,6 +571,13 @@ { "parent_local_part", vtype_stringptr, &deliver_localpart_parent }, { "pid", vtype_pid, NULL }, { "primary_hostname", vtype_stringptr, &primary_hostname }, +#ifdef EXPERIMENTAL_PROXY + { "proxy_host_address", vtype_stringptr, &proxy_host_address }, + { "proxy_host_port", vtype_int, &proxy_host_port }, + { "proxy_session", vtype_bool, &proxy_session }, + { "proxy_target_address",vtype_stringptr, &proxy_target_address }, + { "proxy_target_port", vtype_int, &proxy_target_port }, +#endif { "prvscheck_address", vtype_stringptr, &prvscheck_address }, { "prvscheck_keynum", vtype_stringptr, &prvscheck_keynum }, { "prvscheck_result", vtype_stringptr, &prvscheck_result }, @@ -652,20 +674,26 @@ { "tls_in_bits", vtype_int, &tls_in.bits }, { "tls_in_certificate_verified", vtype_int, &tls_in.certificate_verified }, { "tls_in_cipher", vtype_stringptr, &tls_in.cipher }, + { "tls_in_ocsp", vtype_int, &tls_in.ocsp }, + { "tls_in_ourcert", vtype_cert, &tls_in.ourcert }, + { "tls_in_peercert", vtype_cert, &tls_in.peercert }, { "tls_in_peerdn", vtype_stringptr, &tls_in.peerdn }, -#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS) +#if defined(SUPPORT_TLS) { "tls_in_sni", vtype_stringptr, &tls_in.sni }, #endif { "tls_out_bits", vtype_int, &tls_out.bits }, { "tls_out_certificate_verified", vtype_int,&tls_out.certificate_verified }, { "tls_out_cipher", vtype_stringptr, &tls_out.cipher }, + { "tls_out_ocsp", vtype_int, &tls_out.ocsp }, + { "tls_out_ourcert", vtype_cert, &tls_out.ourcert }, + { "tls_out_peercert", vtype_cert, &tls_out.peercert }, { "tls_out_peerdn", vtype_stringptr, &tls_out.peerdn }, -#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS) +#if defined(SUPPORT_TLS) { "tls_out_sni", vtype_stringptr, &tls_out.sni }, #endif { "tls_peerdn", vtype_stringptr, &tls_in.peerdn }, /* mind the alphabetical order! */ -#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS) +#if defined(SUPPORT_TLS) { "tls_sni", vtype_stringptr, &tls_in.sni }, /* mind the alphabetical order! */ #endif @@ -1052,6 +1080,23 @@ +static var_entry * +find_var_ent(uschar * name) +{ +int first = 0; +int last = var_table_size; + +while (last > first) + { + int middle = (first + last)/2; + int c = Ustrcmp(name, var_table[middle].name); + + if (c > 0) { first = middle + 1; continue; } + if (c < 0) { last = middle; continue; } + return &var_table[middle]; + } +return NULL; +} /************************************************* * Extract numbered subfield from string * @@ -1126,6 +1171,90 @@ } +static uschar * +expand_getlistele(int field, uschar * list) +{ +uschar * tlist= list; +int sep= 0; +uschar dummy; + +if(field<0) +{ + for(field++; string_nextinlist(&tlist, &sep, &dummy, 1); ) field++; + sep= 0; +} +if(field==0) return NULL; +while(--field>0 && (string_nextinlist(&list, &sep, &dummy, 1))) ; +return string_nextinlist(&list, &sep, NULL, 0); +} + + +/* Certificate fields, by name. Worry about by-OID later */ +/* Names are chosen to not have common prefixes */ + +#ifdef SUPPORT_TLS +typedef struct +{ +uschar * name; +int namelen; +uschar * (*getfn)(void * cert, uschar * mod); +} certfield; +static certfield certfields[] = +{ /* linear search; no special order */ + { US"version", 7, &tls_cert_version }, + { US"serial_number", 13, &tls_cert_serial_number }, + { US"subject", 7, &tls_cert_subject }, + { US"notbefore", 9, &tls_cert_not_before }, + { US"notafter", 8, &tls_cert_not_after }, + { US"issuer", 6, &tls_cert_issuer }, + { US"signature", 9, &tls_cert_signature }, + { US"sig_algorithm", 13, &tls_cert_signature_algorithm }, + { US"subj_altname", 12, &tls_cert_subject_altname }, + { US"ocsp_uri", 8, &tls_cert_ocsp_uri }, + { US"crl_uri", 7, &tls_cert_crl_uri }, +}; + +static uschar * +expand_getcertele(uschar * field, uschar * certvar) +{ +var_entry * vp; +certfield * cp; + +if (!(vp = find_var_ent(certvar))) + { + expand_string_message = + string_sprintf("no variable named \"%s\"", certvar); + return NULL; /* Unknown variable name */ + } +/* NB this stops us passing certs around in variable. Might +want to do that in future */ +if (vp->type != vtype_cert) + { + expand_string_message = + string_sprintf("\"%s\" is not a certificate", certvar); + return NULL; /* Unknown variable name */ + } +if (!*(void **)vp->value) + return NULL; + +if (*field >= '0' && *field <= '9') + return tls_cert_ext_by_oid(*(void **)vp->value, field, 0); + +for(cp = certfields; + cp < certfields + nelements(certfields); + cp++) + if (Ustrncmp(cp->name, field, cp->namelen) == 0) + { + uschar * modifier = *(field += cp->namelen) == ',' + ? ++field : NULL; + return (*cp->getfn)( *(void **)vp->value, modifier ); + } + +expand_string_message = + string_sprintf("bad field selector \"%s\" for certextract", field); +return NULL; +} +#endif /*SUPPORT_TLS*/ /************************************************* * Extract a substring from a string * @@ -1522,8 +1651,10 @@ static uschar * find_variable(uschar *name, BOOL exists_only, BOOL skipping, int *newsize) { -int first = 0; -int last = var_table_size; +var_entry * vp; +uschar *s, *domain; +uschar **ss; +void * val; /* Handle ACL variables, whose names are of the form acl_cxxx or acl_mxxx. Originally, xxx had to be a number in the range 0-9 (later 0-19), but from @@ -1556,203 +1687,200 @@ /* For all other variables, search the table */ -while (last > first) - { - uschar *s, *domain; - uschar **ss; - int middle = (first + last)/2; - int c = Ustrcmp(name, var_table[middle].name); +if (!(vp = find_var_ent(name))) + return NULL; /* Unknown variable name */ - if (c > 0) { first = middle + 1; continue; } - if (c < 0) { last = middle; continue; } - - /* Found an existing variable. If in skipping state, the value isn't needed, - and we want to avoid processing (such as looking up the host name). */ +/* Found an existing variable. If in skipping state, the value isn't needed, +and we want to avoid processing (such as looking up the host name). */ - if (skipping) return US""; +if (skipping) + return US""; - switch (var_table[middle].type) - { - case vtype_filter_int: - if (!filter_running) return NULL; - /* Fall through */ - /* VVVVVVVVVVVV */ - case vtype_int: - sprintf(CS var_buffer, "%d", *(int *)(var_table[middle].value)); /* Integer */ - return var_buffer; - - case vtype_ino: - sprintf(CS var_buffer, "%ld", (long int)(*(ino_t *)(var_table[middle].value))); /* Inode */ - return var_buffer; - - case vtype_gid: - sprintf(CS var_buffer, "%ld", (long int)(*(gid_t *)(var_table[middle].value))); /* gid */ - return var_buffer; - - case vtype_uid: - sprintf(CS var_buffer, "%ld", (long int)(*(uid_t *)(var_table[middle].value))); /* uid */ - return var_buffer; - - case vtype_bool: - sprintf(CS var_buffer, "%s", *(BOOL *)(var_table[middle].value) ? "yes" : "no"); /* bool */ - return var_buffer; - - case vtype_stringptr: /* Pointer to string */ - s = *((uschar **)(var_table[middle].value)); - return (s == NULL)? US"" : s; - - case vtype_pid: - sprintf(CS var_buffer, "%d", (int)getpid()); /* pid */ - return var_buffer; - - case vtype_load_avg: - sprintf(CS var_buffer, "%d", OS_GETLOADAVG()); /* load_average */ - return var_buffer; - - case vtype_host_lookup: /* Lookup if not done so */ - if (sender_host_name == NULL && sender_host_address != NULL && - !host_lookup_failed && host_name_lookup() == OK) - host_build_sender_fullhost(); - return (sender_host_name == NULL)? US"" : sender_host_name; - - case vtype_localpart: /* Get local part from address */ - s = *((uschar **)(var_table[middle].value)); - if (s == NULL) return US""; - domain = Ustrrchr(s, '@'); - if (domain == NULL) return s; - if (domain - s > sizeof(var_buffer) - 1) - log_write(0, LOG_MAIN|LOG_PANIC_DIE, "local part longer than " SIZE_T_FMT - " in string expansion", sizeof(var_buffer)); - Ustrncpy(var_buffer, s, domain - s); - var_buffer[domain - s] = 0; - return var_buffer; - - case vtype_domain: /* Get domain from address */ - s = *((uschar **)(var_table[middle].value)); - if (s == NULL) return US""; - domain = Ustrrchr(s, '@'); - return (domain == NULL)? US"" : domain + 1; - - case vtype_msgheaders: - return find_header(NULL, exists_only, newsize, FALSE, NULL); - - case vtype_msgheaders_raw: - return find_header(NULL, exists_only, newsize, TRUE, NULL); - - case vtype_msgbody: /* Pointer to msgbody string */ - case vtype_msgbody_end: /* Ditto, the end of the msg */ - ss = (uschar **)(var_table[middle].value); - if (*ss == NULL && deliver_datafile >= 0) /* Read body when needed */ - { - uschar *body; - off_t start_offset = SPOOL_DATA_START_OFFSET; - int len = message_body_visible; - if (len > message_size) len = message_size; - *ss = body = store_malloc(len+1); - body[0] = 0; - if (var_table[middle].type == vtype_msgbody_end) - { - struct stat statbuf; - if (fstat(deliver_datafile, &statbuf) == 0) - { - start_offset = statbuf.st_size - len; - if (start_offset < SPOOL_DATA_START_OFFSET) - start_offset = SPOOL_DATA_START_OFFSET; - } - } - lseek(deliver_datafile, start_offset, SEEK_SET); - len = read(deliver_datafile, body, len); - if (len > 0) - { - body[len] = 0; - if (message_body_newlines) /* Separate loops for efficiency */ - { - while (len > 0) - { if (body[--len] == 0) body[len] = ' '; } - } - else - { - while (len > 0) - { if (body[--len] == '\n' || body[len] == 0) body[len] = ' '; } - } - } +val = vp->value; +switch (vp->type) + { + case vtype_filter_int: + if (!filter_running) return NULL; + /* Fall through */ + /* VVVVVVVVVVVV */ + case vtype_int: + sprintf(CS var_buffer, "%d", *(int *)(val)); /* Integer */ + return var_buffer; + + case vtype_ino: + sprintf(CS var_buffer, "%ld", (long int)(*(ino_t *)(val))); /* Inode */ + return var_buffer; + + case vtype_gid: + sprintf(CS var_buffer, "%ld", (long int)(*(gid_t *)(val))); /* gid */ + return var_buffer; + + case vtype_uid: + sprintf(CS var_buffer, "%ld", (long int)(*(uid_t *)(val))); /* uid */ + return var_buffer; + + case vtype_bool: + sprintf(CS var_buffer, "%s", *(BOOL *)(val) ? "yes" : "no"); /* bool */ + return var_buffer; + + case vtype_stringptr: /* Pointer to string */ + s = *((uschar **)(val)); + return (s == NULL)? US"" : s; + + case vtype_pid: + sprintf(CS var_buffer, "%d", (int)getpid()); /* pid */ + return var_buffer; + + case vtype_load_avg: + sprintf(CS var_buffer, "%d", OS_GETLOADAVG()); /* load_average */ + return var_buffer; + + case vtype_host_lookup: /* Lookup if not done so */ + if (sender_host_name == NULL && sender_host_address != NULL && + !host_lookup_failed && host_name_lookup() == OK) + host_build_sender_fullhost(); + return (sender_host_name == NULL)? US"" : sender_host_name; + + case vtype_localpart: /* Get local part from address */ + s = *((uschar **)(val)); + if (s == NULL) return US""; + domain = Ustrrchr(s, '@'); + if (domain == NULL) return s; + if (domain - s > sizeof(var_buffer) - 1) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "local part longer than " SIZE_T_FMT + " in string expansion", sizeof(var_buffer)); + Ustrncpy(var_buffer, s, domain - s); + var_buffer[domain - s] = 0; + return var_buffer; + + case vtype_domain: /* Get domain from address */ + s = *((uschar **)(val)); + if (s == NULL) return US""; + domain = Ustrrchr(s, '@'); + return (domain == NULL)? US"" : domain + 1; + + case vtype_msgheaders: + return find_header(NULL, exists_only, newsize, FALSE, NULL); + + case vtype_msgheaders_raw: + return find_header(NULL, exists_only, newsize, TRUE, NULL); + + case vtype_msgbody: /* Pointer to msgbody string */ + case vtype_msgbody_end: /* Ditto, the end of the msg */ + ss = (uschar **)(val); + if (*ss == NULL && deliver_datafile >= 0) /* Read body when needed */ + { + uschar *body; + off_t start_offset = SPOOL_DATA_START_OFFSET; + int len = message_body_visible; + if (len > message_size) len = message_size; + *ss = body = store_malloc(len+1); + body[0] = 0; + if (vp->type == vtype_msgbody_end) + { + struct stat statbuf; + if (fstat(deliver_datafile, &statbuf) == 0) + { + start_offset = statbuf.st_size - len; + if (start_offset < SPOOL_DATA_START_OFFSET) + start_offset = SPOOL_DATA_START_OFFSET; + } + } + lseek(deliver_datafile, start_offset, SEEK_SET); + len = read(deliver_datafile, body, len); + if (len > 0) + { + body[len] = 0; + if (message_body_newlines) /* Separate loops for efficiency */ + { + while (len > 0) + { if (body[--len] == 0) body[len] = ' '; } + } + else + { + while (len > 0) + { if (body[--len] == '\n' || body[len] == 0) body[len] = ' '; } + } } - return (*ss == NULL)? US"" : *ss; + } + return (*ss == NULL)? US"" : *ss; - case vtype_todbsdin: /* BSD inbox time of day */ - return tod_stamp(tod_bsdin); + case vtype_todbsdin: /* BSD inbox time of day */ + return tod_stamp(tod_bsdin); - case vtype_tode: /* Unix epoch time of day */ - return tod_stamp(tod_epoch); + case vtype_tode: /* Unix epoch time of day */ + return tod_stamp(tod_epoch); - case vtype_todel: /* Unix epoch/usec time of day */ - return tod_stamp(tod_epoch_l); + case vtype_todel: /* Unix epoch/usec time of day */ + return tod_stamp(tod_epoch_l); - case vtype_todf: /* Full time of day */ - return tod_stamp(tod_full); + case vtype_todf: /* Full time of day */ + return tod_stamp(tod_full); - case vtype_todl: /* Log format time of day */ - return tod_stamp(tod_log_bare); /* (without timezone) */ + case vtype_todl: /* Log format time of day */ + return tod_stamp(tod_log_bare); /* (without timezone) */ - case vtype_todzone: /* Time zone offset only */ - return tod_stamp(tod_zone); + case vtype_todzone: /* Time zone offset only */ + return tod_stamp(tod_zone); - case vtype_todzulu: /* Zulu time */ - return tod_stamp(tod_zulu); + case vtype_todzulu: /* Zulu time */ + return tod_stamp(tod_zulu); - case vtype_todlf: /* Log file datestamp tod */ - return tod_stamp(tod_log_datestamp_daily); + case vtype_todlf: /* Log file datestamp tod */ + return tod_stamp(tod_log_datestamp_daily); - case vtype_reply: /* Get reply address */ - s = find_header(US"reply-to:", exists_only, newsize, TRUE, - headers_charset); - if (s != NULL) while (isspace(*s)) s++; - if (s == NULL || *s == 0) - { - *newsize = 0; /* For the *s==0 case */ - s = find_header(US"from:", exists_only, newsize, TRUE, headers_charset); - } - if (s != NULL) - { - uschar *t; - while (isspace(*s)) s++; - for (t = s; *t != 0; t++) if (*t == '\n') *t = ' '; - while (t > s && isspace(t[-1])) t--; - *t = 0; - } - return (s == NULL)? US"" : s; + case vtype_reply: /* Get reply address */ + s = find_header(US"reply-to:", exists_only, newsize, TRUE, + headers_charset); + if (s != NULL) while (isspace(*s)) s++; + if (s == NULL || *s == 0) + { + *newsize = 0; /* For the *s==0 case */ + s = find_header(US"from:", exists_only, newsize, TRUE, headers_charset); + } + if (s != NULL) + { + uschar *t; + while (isspace(*s)) s++; + for (t = s; *t != 0; t++) if (*t == '\n') *t = ' '; + while (t > s && isspace(t[-1])) t--; + *t = 0; + } + return (s == NULL)? US"" : s; - case vtype_string_func: - { - uschar * (*fn)() = var_table[middle].value; - return fn(); - } + case vtype_string_func: + { + uschar * (*fn)() = val; + return fn(); + } - case vtype_pspace: - { - int inodes; - sprintf(CS var_buffer, "%d", - receive_statvfs(var_table[middle].value == (void *)TRUE, &inodes)); - } - return var_buffer; + case vtype_pspace: + { + int inodes; + sprintf(CS var_buffer, "%d", + receive_statvfs(val == (void *)TRUE, &inodes)); + } + return var_buffer; - case vtype_pinodes: - { - int inodes; - (void) receive_statvfs(var_table[middle].value == (void *)TRUE, &inodes); - sprintf(CS var_buffer, "%d", inodes); - } - return var_buffer; + case vtype_pinodes: + { + int inodes; + (void) receive_statvfs(val == (void *)TRUE, &inodes); + sprintf(CS var_buffer, "%d", inodes); + } + return var_buffer; - #ifndef DISABLE_DKIM - case vtype_dkim: - return dkim_exim_expand_query((int)(long)var_table[middle].value); - #endif + case vtype_cert: + return *(void **)val ? US"" : US""; + + #ifndef DISABLE_DKIM + case vtype_dkim: + return dkim_exim_expand_query((int)(long)val); + #endif - } } -return NULL; /* Unknown variable name */ +return NULL; /* Unknown variable. Silences static checkers. */ } @@ -1761,21 +1889,8 @@ void modify_variable(uschar *name, void * value) { -int first = 0; -int last = var_table_size; - -while (last > first) - { - int middle = (first + last)/2; - int c = Ustrcmp(name, var_table[middle].name); - - if (c > 0) { first = middle + 1; continue; } - if (c < 0) { last = middle; continue; } - - /* Found an existing variable; change the item it refers to */ - var_table[middle].value = value; - return; - } +var_entry * vp; +if ((vp = find_var_ent(name))) vp->value = value; return; /* Unknown variable name, fail silently */ } @@ -1799,6 +1914,8 @@ skipping the skipping flag check_end if TRUE, check for final '}' name name of item, for error message + resetok if not NULL, pointer to flag - write FALSE if unsafe to reset + the store. Returns: 0 OK; string pointer updated 1 curly bracketing error (too few arguments) @@ -1808,7 +1925,7 @@ static int read_subs(uschar **sub, int n, int m, uschar **sptr, BOOL skipping, - BOOL check_end, uschar *name) + BOOL check_end, uschar *name, BOOL *resetok) { int i; uschar *s = *sptr; @@ -1822,7 +1939,7 @@ sub[i] = NULL; break; } - sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE); + sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, resetok); if (sub[i] == NULL) return 3; if (*s++ != '}') return 1; while (isspace(*s)) s++; @@ -1931,6 +2048,9 @@ /* Arguments: s points to the start of the condition text + resetok points to a BOOL which is written false if it is unsafe to + free memory. Certain condition types (acl) may have side-effect + allocation which must be preserved. yield points to a BOOL to hold the result of the condition test; if NULL, we are just reading through a condition that is part of an "or" combination to check syntax, or in a state @@ -1941,7 +2061,7 @@ */ static uschar * -eval_condition(uschar *s, BOOL *yield) +eval_condition(uschar *s, BOOL *resetok, BOOL *yield) { BOOL testfor = TRUE; BOOL tempcond, combined_cond; @@ -2080,7 +2200,7 @@ while (isspace(*s)) s++; if (*s != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */ - sub[0] = expand_string_internal(s+1, TRUE, &s, yield == NULL, TRUE); + sub[0] = expand_string_internal(s+1, TRUE, &s, yield == NULL, TRUE, resetok); if (sub[0] == NULL) return NULL; /* {-for-text-editors */ if (*s++ != '}') goto COND_FAILED_CURLY_END; @@ -2164,6 +2284,8 @@ like the saslauthd condition does, to permit a variable number of args. See also the expansion-item version EITEM_ACL and the traditional acl modifier ACLC_ACL. + Since the ACL may allocate new global variables, tell our caller to not + reclaim memory. */ case ECOND_ACL: @@ -2178,7 +2300,7 @@ if (*s++ != '{') goto COND_FAILED_CURLY_START; /*}*/ switch(read_subs(sub, sizeof(sub)/sizeof(*sub), 1, - &s, yield == NULL, TRUE, US"acl")) + &s, yield == NULL, TRUE, US"acl", resetok)) { case 1: expand_string_message = US"too few arguments or bracketing " "error for acl"; @@ -2186,6 +2308,7 @@ case 3: return NULL; } + *resetok = FALSE; if (yield != NULL) switch(eval_acl(sub, sizeof(sub)/sizeof(*sub), &user_msg)) { case OK: @@ -2212,7 +2335,7 @@ /* saslauthd: does Cyrus saslauthd authentication. Four parameters are used: - ${if saslauthd {{username}{password}{service}{realm}} {yes}[no}} + ${if saslauthd {{username}{password}{service}{realm}} {yes}{no}} However, the last two are optional. That is why the whole set is enclosed in their own set of braces. */ @@ -2223,7 +2346,7 @@ #else while (isspace(*s)) s++; if (*s++ != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */ - switch(read_subs(sub, 4, 2, &s, yield == NULL, TRUE, US"saslauthd")) + switch(read_subs(sub, 4, 2, &s, yield == NULL, TRUE, US"saslauthd", resetok)) { case 1: expand_string_message = US"too few arguments or bracketing " "error for saslauthd"; @@ -2307,7 +2430,7 @@ return NULL; } sub[i] = expand_string_internal(s+1, TRUE, &s, yield == NULL, - honour_dollar); + honour_dollar, resetok); if (sub[i] == NULL) return NULL; if (*s++ != '}') goto COND_FAILED_CURLY_END; @@ -2325,7 +2448,7 @@ } else { - num[i] = expand_string_integer(sub[i], FALSE); + num[i] = expanded_string_integer(sub[i], FALSE); if (expand_string_message != NULL) return NULL; } } @@ -2665,8 +2788,7 @@ return NULL; } - s = eval_condition(s+1, subcondptr); - if (s == NULL) + if (!(s = eval_condition(s+1, resetok, subcondptr))) { expand_string_message = string_sprintf("%s inside \"%s{...}\" condition", expand_string_message, name); @@ -2712,7 +2834,7 @@ while (isspace(*s)) s++; if (*s++ != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */ - sub[0] = expand_string_internal(s, TRUE, &s, (yield == NULL), TRUE); + sub[0] = expand_string_internal(s, TRUE, &s, (yield == NULL), TRUE, resetok); if (sub[0] == NULL) return NULL; /* {-for-text-editors */ if (*s++ != '}') goto COND_FAILED_CURLY_END; @@ -2726,8 +2848,7 @@ "false" part). This allows us to find the end of the condition, because if the list it empty, we won't actually evaluate the condition for real. */ - s = eval_condition(sub[1], NULL); - if (s == NULL) + if (!(s = eval_condition(sub[1], resetok, NULL))) { expand_string_message = string_sprintf("%s inside \"%s\" condition", expand_string_message, name); @@ -2748,7 +2869,7 @@ while ((iterate_item = string_nextinlist(&sub[0], &sep, NULL, 0)) != NULL) { DEBUG(D_expand) debug_printf("%s: $item = \"%s\"\n", name, iterate_item); - if (eval_condition(sub[1], &tempcond) == NULL) + if (!eval_condition(sub[1], resetok, &tempcond)) { expand_string_message = string_sprintf("%s inside \"%s\" condition", expand_string_message, name); @@ -2788,7 +2909,7 @@ while (isspace(*s)) s++; if (*s != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */ ourname = cond_type == ECOND_BOOL_LAX ? US"bool_lax" : US"bool"; - switch(read_subs(sub_arg, 1, 1, &s, yield == NULL, FALSE, ourname)) + switch(read_subs(sub_arg, 1, 1, &s, yield == NULL, FALSE, ourname, resetok)) { case 1: expand_string_message = string_sprintf( "too few arguments or bracketing error for %s", @@ -2818,7 +2939,9 @@ be no maintenance burden from replicating it. */ if (len == 0) boolvalue = FALSE; - else if (Ustrspn(t, "0123456789") == len) + else if (*t == '-' + ? Ustrspn(t+1, "0123456789") == len-1 + : Ustrspn(t, "0123456789") == len) { boolvalue = (Uatoi(t) == 0) ? FALSE : TRUE; /* expand_check_condition only does a literal string "0" check */ @@ -2952,6 +3075,8 @@ sizeptr points to the output string size ptrptr points to the output string pointer type "lookup" or "if" or "extract" or "run", for error message + resetok if not NULL, pointer to flag - write FALSE if unsafe to reset + the store. Returns: 0 OK; lookup_value has been reset to save_lookup 1 expansion failed @@ -2960,7 +3085,7 @@ static int process_yesno(BOOL skipping, BOOL yes, uschar *save_lookup, uschar **sptr, - uschar **yieldptr, int *sizeptr, int *ptrptr, uschar *type) + uschar **yieldptr, int *sizeptr, int *ptrptr, uschar *type, BOOL *resetok) { int rc = 0; uschar *s = *sptr; /* Local value */ @@ -2997,7 +3122,7 @@ want this string. Set skipping in the call in the fail case (this will always be the case if we were already skipping). */ -sub1 = expand_string_internal(s, TRUE, &s, !yes, TRUE); +sub1 = expand_string_internal(s, TRUE, &s, !yes, TRUE, resetok); if (sub1 == NULL && (yes || !expand_string_forcedfail)) goto FAILED; expand_string_forcedfail = FALSE; if (*s++ != '}') goto FAILED_CURLY; @@ -3022,7 +3147,7 @@ while (isspace(*s)) s++; if (*s == '{') { - sub2 = expand_string_internal(s+1, TRUE, &s, yes || skipping, TRUE); + sub2 = expand_string_internal(s+1, TRUE, &s, yes || skipping, TRUE, resetok); if (sub2 == NULL && (!yes || !expand_string_forcedfail)) goto FAILED; expand_string_forcedfail = FALSE; if (*s++ != '}') goto FAILED_CURLY; @@ -3587,8 +3712,9 @@ There's a problem if a ${dlfunc item has side-effects that cause allocation, since resetting the store at the end of the expansion will free store that was allocated by the plugin code as well as the slop after the expanded string. So -we skip any resets if ${dlfunc has been used. The same applies for ${acl. This -is an unfortunate consequence of string expansion becoming too powerful. +we skip any resets if ${dlfunc } has been used. The same applies for ${acl } +and, given the acl condition, ${if }. This is an unfortunate consequence of +string expansion becoming too powerful. Arguments: string the string to be expanded @@ -3599,6 +3725,8 @@ to be used (to allow for optimisation) honour_dollar TRUE if $ is to be expanded, FALSE if it's just another character + resetok_p if not NULL, pointer to flag - write FALSE if unsafe to reset + the store. Returns: NULL if expansion fails: expand_string_forcedfail is set TRUE if failure was forced @@ -3608,7 +3736,7 @@ static uschar * expand_string_internal(uschar *string, BOOL ket_ends, uschar **left, - BOOL skipping, BOOL honour_dollar) + BOOL skipping, BOOL honour_dollar, BOOL *resetok_p) { int ptr = 0; int size = Ustrlen(string)+ 64; @@ -3659,9 +3787,11 @@ continue; } + /*{*/ /* Anything other than $ is just copied verbatim, unless we are looking for a terminating } character. */ + /*{*/ if (ket_ends && *s == '}') break; if (*s != '$' || !honour_dollar) @@ -3676,7 +3806,7 @@ names can contain any printing characters except space and colon. For those that don't like typing this much, "$h_" is a synonym for "$header_". A non-existent header yields a NULL value; nothing is - inserted. */ + inserted. */ /*}*/ if (isalpha((*(++s)))) { @@ -3763,11 +3893,11 @@ continue; } - /* Otherwise, if there's no '{' after $ it's an error. */ + /* Otherwise, if there's no '{' after $ it's an error. */ /*}*/ - if (*s != '{') + if (*s != '{') /*}*/ { - expand_string_message = US"$ not followed by letter, digit, or {"; + expand_string_message = US"$ not followed by letter, digit, or {"; /*}*/ goto EXPAND_FAILED; } @@ -3777,9 +3907,9 @@ if (isdigit((*(++s)))) { int n; - s = read_number(&n, s); + s = read_number(&n, s); /*{*/ if (*s++ != '}') - { + { /*{*/ expand_string_message = US"} expected after number"; goto EXPAND_FAILED; } @@ -3791,7 +3921,7 @@ if (!isalpha(*s)) { - expand_string_message = US"letter or digit expected after ${"; + expand_string_message = US"letter or digit expected after ${"; /*}*/ goto EXPAND_FAILED; } @@ -3819,7 +3949,7 @@ uschar *sub[10]; /* name + arg1-arg9 (which must match number of acl_arg[]) */ uschar *user_msg; - switch(read_subs(sub, 10, 1, &s, skipping, TRUE, US"acl")) + switch(read_subs(sub, 10, 1, &s, skipping, TRUE, US"acl", &resetok)) { case 1: goto EXPAND_FAILED_CURLY; case 2: @@ -3832,6 +3962,8 @@ { case OK: case FAIL: + DEBUG(D_expand) + debug_printf("acl expansion yield: %s\n", user_msg); if (user_msg) yield = string_cat(yield, &size, &ptr, user_msg, Ustrlen(user_msg)); continue; @@ -3857,7 +3989,7 @@ save_expand_strings(save_expand_nstring, save_expand_nlength); while (isspace(*s)) s++; - next_s = eval_condition(s, skipping? NULL : &cond); + next_s = eval_condition(s, &resetok, skipping? NULL : &cond); if (next_s == NULL) goto EXPAND_FAILED; /* message already set */ DEBUG(D_expand) @@ -3877,7 +4009,8 @@ &yield, /* output pointer */ &size, /* output size */ &ptr, /* output current point */ - US"if")) /* condition type */ + US"if", /* condition type */ + &resetok)) { case 1: goto EXPAND_FAILED; /* when all is well, the */ case 2: goto EXPAND_FAILED_CURLY; /* returned value is 0 */ @@ -3918,10 +4051,10 @@ Otherwise set the key NULL pro-tem. */ while (isspace(*s)) s++; - if (*s == '{') + if (*s == '{') /*}*/ { - key = expand_string_internal(s+1, TRUE, &s, skipping, TRUE); - if (key == NULL) goto EXPAND_FAILED; + key = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok); + if (key == NULL) goto EXPAND_FAILED; /*{*/ if (*s++ != '}') goto EXPAND_FAILED_CURLY; while (isspace(*s)) s++; } @@ -3937,9 +4070,9 @@ /* The type is a string that may contain special characters of various kinds. Allow everything except space or { to appear; the actual content - is checked by search_findtype_partial. */ + is checked by search_findtype_partial. */ /*}*/ - while (*s != 0 && *s != '{' && !isspace(*s)) + while (*s != 0 && *s != '{' && !isspace(*s)) /*}*/ { if (nameptr < sizeof(name) - 1) name[nameptr++] = *s; s++; @@ -3986,7 +4119,7 @@ first. */ if (*s != '{') goto EXPAND_FAILED_CURLY; - filename = expand_string_internal(s+1, TRUE, &s, skipping, TRUE); + filename = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok); if (filename == NULL) goto EXPAND_FAILED; if (*s++ != '}') goto EXPAND_FAILED_CURLY; while (isspace(*s)) s++; @@ -4064,7 +4197,8 @@ &yield, /* output pointer */ &size, /* output size */ &ptr, /* output current point */ - US"lookup")) /* condition type */ + US"lookup", /* condition type */ + &resetok)) { case 1: goto EXPAND_FAILED; /* when all is well, the */ case 2: goto EXPAND_FAILED_CURLY; /* returned value is 0 */ @@ -4087,7 +4221,7 @@ case EITEM_PERL: #ifndef EXIM_PERL - expand_string_message = US"\"${perl\" encountered, but this facility " + expand_string_message = US"\"${perl\" encountered, but this facility " /*}*/ "is not included in this binary"; goto EXPAND_FAILED; @@ -4103,7 +4237,7 @@ } switch(read_subs(sub_arg, EXIM_PERL_MAX_ARGS + 1, 1, &s, skipping, TRUE, - US"perl")) + US"perl", &resetok)) { case 1: goto EXPAND_FAILED_CURLY; case 2: @@ -4175,7 +4309,7 @@ uschar *sub_arg[3]; uschar *p,*domain; - switch(read_subs(sub_arg, 3, 2, &s, skipping, TRUE, US"prvs")) + switch(read_subs(sub_arg, 3, 2, &s, skipping, TRUE, US"prvs", &resetok)) { case 1: goto EXPAND_FAILED_CURLY; case 2: @@ -4249,7 +4383,7 @@ prvscheck_address = NULL; prvscheck_keynum = NULL; - switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, US"prvs")) + switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, US"prvs", &resetok)) { case 1: goto EXPAND_FAILED_CURLY; case 2: @@ -4281,7 +4415,7 @@ prvscheck_keynum = string_copy(key_num); /* Now expand the second argument */ - switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, US"prvs")) + switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, US"prvs", &resetok)) { case 1: goto EXPAND_FAILED_CURLY; case 2: @@ -4335,7 +4469,7 @@ /* Now expand the final argument. We leave this till now so that it can include $prvscheck_result. */ - switch(read_subs(sub_arg, 1, 0, &s, skipping, TRUE, US"prvs")) + switch(read_subs(sub_arg, 1, 0, &s, skipping, TRUE, US"prvs", &resetok)) { case 1: goto EXPAND_FAILED_CURLY; case 2: @@ -4359,7 +4493,7 @@ We need to make sure all subs are expanded first, so as to skip over the entire item. */ - switch(read_subs(sub_arg, 2, 1, &s, skipping, TRUE, US"prvs")) + switch(read_subs(sub_arg, 2, 1, &s, skipping, TRUE, US"prvs", &resetok)) { case 1: goto EXPAND_FAILED_CURLY; case 2: @@ -4383,7 +4517,7 @@ goto EXPAND_FAILED; } - switch(read_subs(sub_arg, 2, 1, &s, skipping, TRUE, US"readfile")) + switch(read_subs(sub_arg, 2, 1, &s, skipping, TRUE, US"readfile", &resetok)) { case 1: goto EXPAND_FAILED_CURLY; case 2: @@ -4429,7 +4563,7 @@ /* Read up to 4 arguments, but don't do the end of item check afterwards, because there may be a string for expansion on failure. */ - switch(read_subs(sub_arg, 4, 2, &s, skipping, FALSE, US"readsocket")) + switch(read_subs(sub_arg, 4, 2, &s, skipping, FALSE, US"readsocket", &resetok)) { case 1: goto EXPAND_FAILED_CURLY; case 2: /* Won't occur: no end check */ @@ -4459,10 +4593,7 @@ if (Ustrncmp(sub_arg[0], "inet:", 5) == 0) { - BOOL connected = FALSE; - int namelen, port; - host_item shost; - host_item *h; + int port; uschar *server_name = sub_arg[0] + 5; uschar *port_name = Ustrrchr(server_name, ':'); @@ -4499,76 +4630,9 @@ port = ntohs(service_info->s_port); } - /* Sort out the server. */ - - shost.next = NULL; - shost.address = NULL; - shost.port = port; - shost.mx = -1; - - namelen = Ustrlen(server_name); - - /* Anything enclosed in [] must be an IP address. */ - - if (server_name[0] == '[' && - server_name[namelen - 1] == ']') - { - server_name[namelen - 1] = 0; - server_name++; - if (string_is_ip_address(server_name, NULL) == 0) - { - expand_string_message = - string_sprintf("malformed IP address \"%s\"", server_name); - goto EXPAND_FAILED; - } - shost.name = shost.address = server_name; - } - - /* Otherwise check for an unadorned IP address */ - - else if (string_is_ip_address(server_name, NULL) != 0) - shost.name = shost.address = server_name; - - /* Otherwise lookup IP address(es) from the name */ - - else - { - shost.name = server_name; - if (host_find_byname(&shost, NULL, HOST_FIND_QUALIFY_SINGLE, NULL, - FALSE) != HOST_FOUND) - { - expand_string_message = - string_sprintf("no IP address found for host %s", shost.name); - goto EXPAND_FAILED; - } - } - - /* Try to connect to the server - test each IP till one works */ - - for (h = &shost; h != NULL; h = h->next) - { - int af = (Ustrchr(h->address, ':') != 0)? AF_INET6 : AF_INET; - if ((fd = ip_socket(SOCK_STREAM, af)) == -1) - { - expand_string_message = string_sprintf("failed to create socket: " - "%s", strerror(errno)); + if ((fd = ip_connectedsocket(SOCK_STREAM, server_name, port, port, + timeout, NULL, &expand_string_message)) < 0) goto SOCK_FAIL; - } - - if (ip_connect(fd, af, h->address, port, timeout) == 0) - { - connected = TRUE; - break; - } - } - - if (!connected) - { - expand_string_message = string_sprintf("failed to connect to " - "socket %s: couldn't connect to any host", sub_arg[0], - strerror(errno)); - goto SOCK_FAIL; - } } /* Handle a Unix domain socket */ @@ -4606,6 +4670,9 @@ DEBUG(D_expand) debug_printf("connected to socket %s\n", sub_arg[0]); + /* Allow sequencing of test actions */ + if (running_in_test_harness) millisleep(100); + /* Write the request string, if not empty */ if (sub_arg[1][0] != 0) @@ -4629,6 +4696,8 @@ shutdown(fd, SHUT_WR); #endif + if (running_in_test_harness) millisleep(100); + /* Now we need to read from the socket, under a timeout. The function that reads a file can be used. */ @@ -4655,7 +4724,7 @@ if (*s == '{') { - if (expand_string_internal(s+1, TRUE, &s, TRUE, TRUE) == NULL) + if (expand_string_internal(s+1, TRUE, &s, TRUE, TRUE, &resetok) == NULL) goto EXPAND_FAILED; if (*s++ != '}') goto EXPAND_FAILED_CURLY; while (isspace(*s)) s++; @@ -4670,7 +4739,7 @@ SOCK_FAIL: if (*s != '{') goto EXPAND_FAILED; DEBUG(D_any) debug_printf("%s\n", expand_string_message); - arg = expand_string_internal(s+1, TRUE, &s, FALSE, TRUE); + arg = expand_string_internal(s+1, TRUE, &s, FALSE, TRUE, &resetok); if (arg == NULL) goto EXPAND_FAILED; yield = string_cat(yield, &size, &ptr, arg, Ustrlen(arg)); if (*s++ != '}') goto EXPAND_FAILED_CURLY; @@ -4699,7 +4768,7 @@ while (isspace(*s)) s++; if (*s != '{') goto EXPAND_FAILED_CURLY; - arg = expand_string_internal(s+1, TRUE, &s, skipping, TRUE); + arg = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok); if (arg == NULL) goto EXPAND_FAILED; while (isspace(*s)) s++; if (*s++ != '}') goto EXPAND_FAILED_CURLY; @@ -4781,7 +4850,8 @@ &yield, /* output pointer */ &size, /* output size */ &ptr, /* output current point */ - US"run")) /* condition type */ + US"run", /* condition type */ + &resetok)) { case 1: goto EXPAND_FAILED; /* when all is well, the */ case 2: goto EXPAND_FAILED_CURLY; /* returned value is 0 */ @@ -4798,7 +4868,7 @@ int o2m; uschar *sub[3]; - switch(read_subs(sub, 3, 3, &s, skipping, TRUE, US"tr")) + switch(read_subs(sub, 3, 3, &s, skipping, TRUE, US"tr", &resetok)) { case 1: goto EXPAND_FAILED_CURLY; case 2: @@ -4836,11 +4906,11 @@ uschar *sub[3]; /* "length" takes only 2 arguments whereas the others take 2 or 3. - Ensure that sub[2] is set in the ${length case. */ + Ensure that sub[2] is set in the ${length } case. */ sub[2] = NULL; switch(read_subs(sub, (item_type == EITEM_LENGTH)? 2:3, 2, &s, skipping, - TRUE, name)) + TRUE, name, &resetok)) { case 1: goto EXPAND_FAILED_CURLY; case 2: @@ -4915,7 +4985,7 @@ uschar innerkey[MAX_HASHBLOCKLEN]; uschar outerkey[MAX_HASHBLOCKLEN]; - switch (read_subs(sub, 3, 3, &s, skipping, TRUE, name)) + switch (read_subs(sub, 3, 3, &s, skipping, TRUE, name, &resetok)) { case 1: goto EXPAND_FAILED_CURLY; case 2: @@ -5010,7 +5080,7 @@ int save_expand_nmax = save_expand_strings(save_expand_nstring, save_expand_nlength); - switch(read_subs(sub, 3, 3, &s, skipping, TRUE, US"sg")) + switch(read_subs(sub, 3, 3, &s, skipping, TRUE, US"sg", &resetok)) { case 1: goto EXPAND_FAILED_CURLY; case 2: @@ -5128,10 +5198,10 @@ for (i = 0; i < j; i++) { while (isspace(*s)) s++; - if (*s == '{') + if (*s == '{') /*}*/ { - sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE); - if (sub[i] == NULL) goto EXPAND_FAILED; + sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok); + if (sub[i] == NULL) goto EXPAND_FAILED; /*{*/ if (*s++ != '}') goto EXPAND_FAILED_CURLY; /* After removal of leading and trailing white space, the first @@ -5194,7 +5264,101 @@ &yield, /* output pointer */ &size, /* output size */ &ptr, /* output current point */ - US"extract")) /* condition type */ + US"extract", /* condition type */ + &resetok)) + { + case 1: goto EXPAND_FAILED; /* when all is well, the */ + case 2: goto EXPAND_FAILED_CURLY; /* returned value is 0 */ + } + + /* All done - restore numerical variables. */ + + restore_expand_strings(save_expand_nmax, save_expand_nstring, + save_expand_nlength); + + continue; + } + + /* return the Nth item from a list */ + + case EITEM_LISTEXTRACT: + { + int i; + int field_number = 1; + uschar *save_lookup_value = lookup_value; + uschar *sub[2]; + int save_expand_nmax = + save_expand_strings(save_expand_nstring, save_expand_nlength); + + /* Read the field & list arguments */ + + for (i = 0; i < 2; i++) + { + while (isspace(*s)) s++; + if (*s != '{') /*}*/ + goto EXPAND_FAILED_CURLY; + + sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok); + if (!sub[i]) goto EXPAND_FAILED; /*{*/ + if (*s++ != '}') goto EXPAND_FAILED_CURLY; + + /* After removal of leading and trailing white space, the first + argument must be numeric and nonempty. */ + + if (i == 0) + { + int len; + int x = 0; + uschar *p = sub[0]; + + while (isspace(*p)) p++; + sub[0] = p; + + len = Ustrlen(p); + while (len > 0 && isspace(p[len-1])) len--; + p[len] = 0; + + if (!*p && !skipping) + { + expand_string_message = US"first argument of \"listextract\" must " + "not be empty"; + goto EXPAND_FAILED; + } + + if (*p == '-') + { + field_number = -1; + p++; + } + while (*p && isdigit(*p)) x = x * 10 + *p++ - '0'; + if (*p) + { + expand_string_message = US"first argument of \"listextract\" must " + "be numeric"; + goto EXPAND_FAILED; + } + field_number *= x; + } + } + + /* Extract the numbered element into $value. If + skipping, just pretend the extraction failed. */ + + lookup_value = skipping? NULL : expand_getlistele(field_number, sub[1]); + + /* If no string follows, $value gets substituted; otherwise there can + be yes/no strings, as for lookup or if. */ + + switch(process_yesno( + skipping, /* were previously skipping */ + lookup_value != NULL, /* success/failure indicator */ + save_lookup_value, /* value to reset for string2 */ + &s, /* input pointer */ + &yield, /* output pointer */ + &size, /* output size */ + &ptr, /* output current point */ + US"extract", /* condition type */ + &resetok)) { case 1: goto EXPAND_FAILED; /* when all is well, the */ case 2: goto EXPAND_FAILED_CURLY; /* returned value is 0 */ @@ -5208,6 +5372,75 @@ continue; } +#ifdef SUPPORT_TLS + case EITEM_CERTEXTRACT: + { + uschar *save_lookup_value = lookup_value; + uschar *sub[2]; + int save_expand_nmax = + save_expand_strings(save_expand_nstring, save_expand_nlength); + + /* Read the field argument */ + while (isspace(*s)) s++; + if (*s != '{') /*}*/ + goto EXPAND_FAILED_CURLY; + sub[0] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok); + if (!sub[0]) goto EXPAND_FAILED; /*{*/ + if (*s++ != '}') goto EXPAND_FAILED_CURLY; + /* strip spaces fore & aft */ + { + int len; + uschar *p = sub[0]; + + while (isspace(*p)) p++; + sub[0] = p; + + len = Ustrlen(p); + while (len > 0 && isspace(p[len-1])) len--; + p[len] = 0; + } + + /* inspect the cert argument */ + while (isspace(*s)) s++; + if (*s != '{') /*}*/ + goto EXPAND_FAILED_CURLY; + if (*++s != '$') + { + expand_string_message = US"second argument of \"certextract\" must " + "be a certificate variable"; + goto EXPAND_FAILED; + } + sub[1] = expand_string_internal(s+1, TRUE, &s, skipping, FALSE, &resetok); + if (!sub[1]) goto EXPAND_FAILED; /*{*/ + if (*s++ != '}') goto EXPAND_FAILED_CURLY; + + if (skipping) + lookup_value = NULL; + else + { + lookup_value = expand_getcertele(sub[0], sub[1]); + if (*expand_string_message) goto EXPAND_FAILED; + } + switch(process_yesno( + skipping, /* were previously skipping */ + lookup_value != NULL, /* success/failure indicator */ + save_lookup_value, /* value to reset for string2 */ + &s, /* input pointer */ + &yield, /* output pointer */ + &size, /* output size */ + &ptr, /* output current point */ + US"extract", /* condition type */ + &resetok)) + { + case 1: goto EXPAND_FAILED; /* when all is well, the */ + case 2: goto EXPAND_FAILED_CURLY; /* returned value is 0 */ + } + + restore_expand_strings(save_expand_nmax, save_expand_nstring, + save_expand_nlength); + continue; + } +#endif /*SUPPORT_TLS*/ /* Handle list operations */ @@ -5225,7 +5458,7 @@ while (isspace(*s)) s++; if (*s++ != '{') goto EXPAND_FAILED_CURLY; - list = expand_string_internal(s, TRUE, &s, skipping, TRUE); + list = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok); if (list == NULL) goto EXPAND_FAILED; if (*s++ != '}') goto EXPAND_FAILED_CURLY; @@ -5233,7 +5466,7 @@ { while (isspace(*s)) s++; if (*s++ != '{') goto EXPAND_FAILED_CURLY; - temp = expand_string_internal(s, TRUE, &s, skipping, TRUE); + temp = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok); if (temp == NULL) goto EXPAND_FAILED; lookup_value = temp; if (*s++ != '}') goto EXPAND_FAILED_CURLY; @@ -5252,12 +5485,12 @@ if (item_type == EITEM_FILTER) { - temp = eval_condition(expr, NULL); + temp = eval_condition(expr, &resetok, NULL); if (temp != NULL) s = temp; } else { - temp = expand_string_internal(s, TRUE, &s, TRUE, TRUE); + temp = expand_string_internal(s, TRUE, &s, TRUE, TRUE, &resetok); } if (temp == NULL) @@ -5269,15 +5502,15 @@ while (isspace(*s)) s++; if (*s++ != '}') - { + { /*{*/ expand_string_message = string_sprintf("missing } at end of condition " "or expression inside \"%s\"", name); goto EXPAND_FAILED; } - while (isspace(*s)) s++; + while (isspace(*s)) s++; /*{*/ if (*s++ != '}') - { + { /*{*/ expand_string_message = string_sprintf("missing } at end of \"%s\"", name); goto EXPAND_FAILED; @@ -5296,7 +5529,7 @@ if (item_type == EITEM_FILTER) { BOOL condresult; - if (eval_condition(expr, &condresult) == NULL) + if (eval_condition(expr, &resetok, &condresult) == NULL) { iterate_item = save_iterate_item; lookup_value = save_lookup_value; @@ -5316,7 +5549,7 @@ else { - temp = expand_string_internal(expr, TRUE, NULL, skipping, TRUE); + temp = expand_string_internal(expr, TRUE, NULL, skipping, TRUE, &resetok); if (temp == NULL) { iterate_item = save_iterate_item; @@ -5387,7 +5620,7 @@ } - /* If ${dlfunc support is configured, handle calling dynamically-loaded + /* If ${dlfunc } support is configured, handle calling dynamically-loaded functions, unless locked out at this time. Syntax is ${dlfunc{file}{func}} or ${dlfunc{file}{func}{arg}} or ${dlfunc{file}{func}{arg1}{arg2}} or up to a maximum of EXPAND_DLFUNC_MAX_ARGS arguments (defined below). */ @@ -5396,7 +5629,7 @@ case EITEM_DLFUNC: #ifndef EXPAND_DLFUNC - expand_string_message = US"\"${dlfunc\" encountered, but this facility " + expand_string_message = US"\"${dlfunc\" encountered, but this facility " /*}*/ "is not included in this binary"; goto EXPAND_FAILED; @@ -5416,7 +5649,7 @@ } switch(read_subs(argv, EXPAND_DLFUNC_MAX_ARGS + 2, 2, &s, skipping, - TRUE, US"dlfunc")) + TRUE, US"dlfunc", &resetok)) { case 1: goto EXPAND_FAILED_CURLY; case 2: @@ -5487,7 +5720,7 @@ } } #endif /* EXPAND_DLFUNC */ - } + } /* EITEM_* switch */ /* Control reaches here if the name is not recognized as one of the more complicated expansion items. Check for the "operator" syntax (name terminated @@ -5498,19 +5731,16 @@ { int c; uschar *arg = NULL; - uschar *sub = expand_string_internal(s+1, TRUE, &s, skipping, TRUE); - if (sub == NULL) goto EXPAND_FAILED; - s++; + uschar *sub; + var_entry *vp = NULL; /* Owing to an historical mis-design, an underscore may be part of the operator name, or it may introduce arguments. We therefore first scan the table of names that contain underscores. If there is no match, we cut off the arguments and then scan the main table. */ - c = chop_match(name, op_table_underscore, - sizeof(op_table_underscore)/sizeof(uschar *)); - - if (c < 0) + if ((c = chop_match(name, op_table_underscore, + sizeof(op_table_underscore)/sizeof(uschar *))) < 0) { arg = Ustrchr(name, '_'); if (arg != NULL) *arg = 0; @@ -5520,6 +5750,37 @@ if (arg != NULL) *arg++ = '_'; /* Put back for error messages */ } + /* Deal specially with operators that might take a certificate variable + as we do not want to do the usual expansion. For most, expand the string.*/ + switch(c) + { +#ifdef SUPPORT_TLS + case EOP_MD5: + case EOP_SHA1: + case EOP_SHA256: + if (s[1] == '$') + { + uschar * s1 = s; + sub = expand_string_internal(s+2, TRUE, &s1, skipping, + FALSE, &resetok); + if (!sub) goto EXPAND_FAILED; /*{*/ + if (*s1 != '}') goto EXPAND_FAILED_CURLY; + if ((vp = find_var_ent(sub)) && vp->type == vtype_cert) + { + s = s1+1; + break; + } + vp = NULL; + } + /*FALLTHROUGH*/ +#endif + default: + sub = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok); + if (!sub) goto EXPAND_FAILED; + s++; + break; + } + /* If we are skipping, we don't need to perform the operation at all. This matters for operations like "mask", because the data may not be in the correct format when skipping. For example, the expression may test @@ -5573,7 +5834,7 @@ case EOP_EXPAND: { - uschar *expanded = expand_string_internal(sub, FALSE, NULL, skipping, TRUE); + uschar *expanded = expand_string_internal(sub, FALSE, NULL, skipping, TRUE, &resetok); if (expanded == NULL) { expand_string_message = @@ -5604,30 +5865,58 @@ } case EOP_MD5: - { - md5 base; - uschar digest[16]; - int j; - char st[33]; - md5_start(&base); - md5_end(&base, sub, Ustrlen(sub), digest); - for(j = 0; j < 16; j++) sprintf(st+2*j, "%02x", digest[j]); - yield = string_cat(yield, &size, &ptr, US st, (int)strlen(st)); +#ifdef SUPPORT_TLS + if (vp && *(void **)vp->value) + { + uschar * cp = tls_cert_fprt_md5(*(void **)vp->value); + yield = string_cat(yield, &size, &ptr, cp, Ustrlen(cp)); + } + else +#endif + { + md5 base; + uschar digest[16]; + int j; + char st[33]; + md5_start(&base); + md5_end(&base, sub, Ustrlen(sub), digest); + for(j = 0; j < 16; j++) sprintf(st+2*j, "%02x", digest[j]); + yield = string_cat(yield, &size, &ptr, US st, (int)strlen(st)); + } continue; - } case EOP_SHA1: - { - sha1 base; - uschar digest[20]; - int j; - char st[41]; - sha1_start(&base); - sha1_end(&base, sub, Ustrlen(sub), digest); - for(j = 0; j < 20; j++) sprintf(st+2*j, "%02X", digest[j]); - yield = string_cat(yield, &size, &ptr, US st, (int)strlen(st)); +#ifdef SUPPORT_TLS + if (vp && *(void **)vp->value) + { + uschar * cp = tls_cert_fprt_sha1(*(void **)vp->value); + yield = string_cat(yield, &size, &ptr, cp, Ustrlen(cp)); + } + else +#endif + { + sha1 base; + uschar digest[20]; + int j; + char st[41]; + sha1_start(&base); + sha1_end(&base, sub, Ustrlen(sub), digest); + for(j = 0; j < 20; j++) sprintf(st+2*j, "%02X", digest[j]); + yield = string_cat(yield, &size, &ptr, US st, (int)strlen(st)); + } + continue; + + case EOP_SHA256: +#ifdef SUPPORT_TLS + if (vp && *(void **)vp->value) + { + uschar * cp = tls_cert_fprt_sha256(*(void **)vp->value); + yield = string_cat(yield, &size, &ptr, cp, (int)Ustrlen(cp)); + } + else +#endif + expand_string_message = US"sha256 only supported for certificates"; continue; - } /* Convert hex encoding to base64 encoding */ @@ -5765,7 +6054,7 @@ if (*item == '+') /* list item is itself a named list */ { uschar * sub = string_sprintf("${listnamed%s:%s}", suffix, item); - item = expand_string_internal(sub, FALSE, NULL, FALSE, TRUE); + item = expand_string_internal(sub, FALSE, NULL, FALSE, TRUE, &resetok); } else if (sep != ':') /* item from non-colon-sep list, re-quote for colon list-separator */ { @@ -6073,6 +6362,94 @@ continue; } + /* replace illegal UTF-8 sequences by replacement character */ + + #define UTF8_REPLACEMENT_CHAR US"?" + + case EOP_UTF8CLEAN: + { + int seq_len, index = 0; + int bytes_left = 0; + uschar seq_buff[4]; /* accumulate utf-8 here */ + + while (*sub != 0) + { + int complete; + long codepoint; + uschar c; + + complete = 0; + c = *sub++; + if (bytes_left) + { + if ((c & 0xc0) != 0x80) + { + /* wrong continuation byte; invalidate all bytes */ + complete = 1; /* error */ + } + else + { + codepoint = (codepoint << 6) | (c & 0x3f); + seq_buff[index++] = c; + if (--bytes_left == 0) /* codepoint complete */ + { + if(codepoint > 0x10FFFF) /* is it too large? */ + complete = -1; /* error */ + else + { /* finished; output utf-8 sequence */ + yield = string_cat(yield, &size, &ptr, seq_buff, seq_len); + index = 0; + } + } + } + } + else /* no bytes left: new sequence */ + { + if((c & 0x80) == 0) /* 1-byte sequence, US-ASCII, keep it */ + { + yield = string_cat(yield, &size, &ptr, &c, 1); + continue; + } + if((c & 0xe0) == 0xc0) /* 2-byte sequence */ + { + if(c == 0xc0 || c == 0xc1) /* 0xc0 and 0xc1 are illegal */ + complete = -1; + else + { + bytes_left = 1; + codepoint = c & 0x1f; + } + } + else if((c & 0xf0) == 0xe0) /* 3-byte sequence */ + { + bytes_left = 2; + codepoint = c & 0x0f; + } + else if((c & 0xf8) == 0xf0) /* 4-byte sequence */ + { + bytes_left = 3; + codepoint = c & 0x07; + } + else /* invalid or too long (RFC3629 allows only 4 bytes) */ + complete = -1; + + seq_buff[index++] = c; + seq_len = bytes_left + 1; + } /* if(bytes_left) */ + + if (complete != 0) + { + bytes_left = index = 0; + yield = string_cat(yield, &size, &ptr, UTF8_REPLACEMENT_CHAR, 1); + } + if ((complete == 1) && ((c & 0x80) == 0)) + { /* ASCII character follows incomplete sequence */ + yield = string_cat(yield, &size, &ptr, &c, 1); + } + } + continue; + } + /* escape turns all non-printing characters into escape sequences. */ case EOP_ESCAPE: @@ -6305,7 +6682,7 @@ int_eximarith_t max; uschar *s; - max = expand_string_integer(sub, TRUE); + max = expanded_string_integer(sub, TRUE); if (expand_string_message != NULL) goto EXPAND_FAILED; s = string_sprintf("%d", vaguely_random_number((int)max)); @@ -6347,7 +6724,7 @@ store instead of copying. Many expansion strings contain just one reference, so this is a useful optimization, especially for humungous headers ($message_headers). */ - + /*{*/ if (*s++ == '}') { int len; @@ -6410,6 +6787,8 @@ will be optimal store usage. */ if (resetok) store_reset(yield + ptr + 1); +else if (resetok_p) *resetok_p = FALSE; + DEBUG(D_expand) { debug_printf("expanding: %.*s\n result: %s\n", (int)(s - string), string, @@ -6439,6 +6818,7 @@ debug_printf(" error message: %s\n", expand_string_message); if (expand_string_forcedfail) debug_printf("failure was forced\n"); } +if (resetok_p) *resetok_p = resetok; return NULL; } @@ -6457,7 +6837,7 @@ search_find_defer = FALSE; malformed_header = FALSE; return (Ustrpbrk(string, "$\\") == NULL)? string : - expand_string_internal(string, FALSE, NULL, FALSE, TRUE); + expand_string_internal(string, FALSE, NULL, FALSE, TRUE, NULL); } @@ -6502,8 +6882,32 @@ int_eximarith_t expand_string_integer(uschar *string, BOOL isplus) { +return expanded_string_integer(expand_string(string), isplus); +} + + +/************************************************* + * Interpret string as an integer * + *************************************************/ + +/* Convert a string (that has already been expanded) into an integer. + +This function is used inside the expansion code. + +Arguments: + s the string to be expanded + isplus TRUE if a non-negative number is expected + +Returns: the integer value, or + -1 if string is NULL (which implies an expansion error) + -2 for an integer interpretation error + expand_string_message is set NULL for an OK integer +*/ + +static int_eximarith_t +expanded_string_integer(uschar *s, BOOL isplus) +{ int_eximarith_t value; -uschar *s = expand_string(string); uschar *msg = US"invalid integer \"%s\""; uschar *endptr; @@ -6698,4 +7102,6 @@ #endif +/* vi: aw ai sw=2 +*/ /* End of expand.c */ diff -Nru exim4-4.82/src/functions.h exim4-4.84/src/functions.h --- exim4-4.82/src/functions.h 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/functions.h 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ @@ -25,16 +25,33 @@ std_dh_prime_default(void); extern const char * std_dh_prime_named(const uschar *); + +extern uschar * tls_cert_crl_uri(void *, uschar * mod); +extern uschar * tls_cert_ext_by_oid(void *, uschar *, int); +extern uschar * tls_cert_issuer(void *, uschar * mod); +extern uschar * tls_cert_not_before(void *, uschar * mod); +extern uschar * tls_cert_not_after(void *, uschar * mod); +extern uschar * tls_cert_ocsp_uri(void *, uschar * mod); +extern uschar * tls_cert_serial_number(void *, uschar * mod); +extern uschar * tls_cert_signature(void *, uschar * mod); +extern uschar * tls_cert_signature_algorithm(void *, uschar * mod); +extern uschar * tls_cert_subject(void *, uschar * mod); +extern uschar * tls_cert_subject_altname(void *, uschar * mod); +extern uschar * tls_cert_version(void *, uschar * mod); + +extern uschar * tls_cert_fprt_md5(void *); +extern uschar * tls_cert_fprt_sha1(void *); +extern uschar * tls_cert_fprt_sha256(void *); + extern int tls_client_start(int, host_item *, address_item *, - uschar *, uschar *, uschar *, uschar *, uschar *, uschar *, -# ifdef EXPERIMENTAL_OCSP - uschar *, -# endif - int, int); + void *); extern void tls_close(BOOL, BOOL); +extern int tls_export_cert(uschar *, size_t, void *); extern int tls_feof(void); extern int tls_ferror(void); +extern void tls_free_cert(void *); extern int tls_getc(void); +extern int tls_import_cert(const uschar *, void **); extern int tls_read(BOOL, uschar *, size_t); extern int tls_server_start(const uschar *); extern BOOL tls_smtp_buffered(void); @@ -42,10 +59,14 @@ extern int tls_write(BOOL, const uschar *, size_t); extern uschar *tls_validate_require_cipher(void); extern void tls_version_report(FILE *); -#ifndef USE_GNUTLS +# ifndef USE_GNUTLS extern BOOL tls_openssl_options_parse(uschar *, long *); -#endif -#endif +# endif +extern uschar * tls_field_from_dn(uschar *, uschar *); +# ifdef EXPERIMENTAL_CERTNAMES +extern BOOL tls_is_name_for_cert(uschar *, void *); +# endif +#endif /*SUPPORT_TLS*/ /* Everything else... */ @@ -115,7 +136,7 @@ #endif extern dns_address *dns_address_from_rr(dns_answer *, dns_record *); extern void dns_build_reverse(uschar *, uschar *); -extern void dns_init(BOOL, BOOL); +extern void dns_init(BOOL, BOOL, BOOL); extern int dns_basic_lookup(dns_answer *, uschar *, int); extern BOOL dns_is_secure(dns_answer *); extern int dns_lookup(dns_answer *, uschar *, int, uschar **); @@ -157,7 +178,7 @@ extern void host_build_sender_fullhost(void); extern BOOL host_find_byname(host_item *, uschar *, int, uschar **, BOOL); extern int host_find_bydns(host_item *, uschar *, int, uschar *, uschar *, - uschar *,uschar **, BOOL *); + uschar *, uschar *, uschar *, uschar **, BOOL *); extern ip_address_item *host_find_interfaces(void); extern BOOL host_is_in_net(uschar *, uschar *, int); extern BOOL host_is_tls_on_connect_port(int); @@ -171,6 +192,8 @@ extern void invert_address(uschar *, uschar *); extern int ip_bind(int, int, uschar *, int); extern int ip_connect(int, int, uschar *, int, int); +extern int ip_connectedsocket(int, const uschar *, int, int, + int, host_item *, uschar **); extern int ip_get_address_family(int); extern void ip_keepalive(int, uschar *, BOOL); extern int ip_recv(int, uschar *, int, int); @@ -348,6 +371,7 @@ extern int stdin_ferror(void); extern int stdin_ungetc(int); extern uschar *string_append(uschar *, int *, int *, int, ...); +extern uschar *string_append_listele(uschar *, uschar, const uschar *); extern uschar *string_base62(unsigned long int); extern uschar *string_cat(uschar *, int *, int *, const uschar *, int); extern uschar *string_copy_dnsdomain(uschar *); @@ -358,7 +382,7 @@ extern BOOL string_format(uschar *, int, const char *, ...) ALMOST_PRINTF(3,4); extern uschar *string_format_size(int, uschar *); extern int string_interpret_escape(uschar **); -extern int string_is_ip_address(uschar *, int *); +extern int string_is_ip_address(const uschar *, int *); extern uschar *string_log_address(address_item *, BOOL, BOOL); extern uschar *string_nextinlist(uschar **, int *, uschar *, int); extern uschar *string_open_failed(int, const char *, ...) PRINTF_FUNCTION(2,3); @@ -371,6 +395,7 @@ extern uschar *strstric(uschar *, uschar *, BOOL); extern uschar *tod_stamp(int); +extern void tls_modify_variables(tls_support *); extern BOOL transport_check_waiting(uschar *, uschar *, int, uschar *, BOOL *); extern void transport_init(void); @@ -382,6 +407,8 @@ extern void transport_update_waiting(host_item *, uschar *); extern BOOL transport_write_block(int, uschar *, int); extern BOOL transport_write_string(int, const char *, ...); +extern BOOL transport_headers_send(address_item *, int, uschar *, uschar *, + BOOL (*)(int, uschar *, int, BOOL), BOOL, rewrite_rule *, int); extern BOOL transport_write_message(address_item *, int, int, int, uschar *, uschar *, uschar *, uschar *, rewrite_rule *, int); extern void tree_add_duplicate(uschar *, address_item *); @@ -402,6 +429,7 @@ extern int verify_check_header_address(uschar **, uschar **, int, int, int, uschar *, uschar *, int, int *); extern int verify_check_headers(uschar **); +extern int verify_check_header_names_ascii(uschar **); extern int verify_check_host(uschar **); extern int verify_check_notblind(void); extern int verify_check_this_host(uschar **, unsigned int *, uschar*, @@ -414,4 +442,6 @@ extern ssize_t write_to_fd_buf(int, const uschar *, size_t); +/* vi: aw +*/ /* End of functions.h */ diff -Nru exim4-4.82/src/globals.c exim4-4.84/src/globals.c --- exim4-4.82/src/globals.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/globals.c 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* All the global variables are defined together in this one module, so @@ -106,8 +106,11 @@ NULL, /* tls_cipher */ FALSE,/* tls_on_connect */ NULL, /* tls_on_connect_ports */ + NULL, /* tls_ourcert */ + NULL, /* tls_peercert */ NULL, /* tls_peerdn */ - NULL /* tls_sni */ + NULL, /* tls_sni */ + 0 /* tls_ocsp */ }; tls_support tls_out = { -1, /* tls_active */ @@ -116,10 +119,20 @@ NULL, /* tls_cipher */ FALSE,/* tls_on_connect */ NULL, /* tls_on_connect_ports */ + NULL, /* tls_ourcert */ + NULL, /* tls_peercert */ NULL, /* tls_peerdn */ - NULL /* tls_sni */ + NULL, /* tls_sni */ + 0 /* tls_ocsp */ }; +#ifdef EXPERIMENTAL_DSN +uschar *dsn_envid = NULL; +int dsn_ret = 0; +const pcre *regex_DSN = NULL; +BOOL smtp_use_dsn = FALSE; +uschar *dsn_advertise_hosts = NULL; +#endif #ifdef SUPPORT_TLS BOOL gnutls_compat_mode = FALSE; @@ -137,7 +150,7 @@ bit-count as "NORMAL" (2432) and Thunderbird dropping connection. */ int tls_dh_max_bits = 2236; uschar *tls_dhparam = NULL; -#if defined(EXPERIMENTAL_OCSP) && !defined(USE_GNUTLS) +#ifndef DISABLE_OCSP uschar *tls_ocsp_file = NULL; #endif BOOL tls_offered = FALSE; @@ -149,7 +162,7 @@ uschar *tls_verify_hosts = NULL; #endif -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR /* Per Recipient Data Response variables */ BOOL prdr_enable = FALSE; BOOL prdr_requested = FALSE; @@ -212,7 +225,7 @@ uschar *acl_smtp_auth = NULL; uschar *acl_smtp_connect = NULL; uschar *acl_smtp_data = NULL; -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR uschar *acl_smtp_data_prdr = NULL; #endif #ifndef DISABLE_DKIM @@ -248,7 +261,7 @@ US"MIME", US"DKIM", US"DATA", -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR US"PRDR", #endif US"non-SMTP", @@ -273,7 +286,7 @@ US"550", /* MIME */ US"550", /* DKIM */ US"550", /* DATA */ -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR US"550", /* RCPT PRDR */ #endif US"0", /* not SMTP; not relevant */ @@ -332,11 +345,19 @@ NULL, /* shadow_message */ #ifdef SUPPORT_TLS NULL, /* cipher */ + NULL, /* ourcert */ + NULL, /* peercert */ NULL, /* peerdn */ + OCSP_NOT_REQ, /* ocsp */ #endif NULL, /* authenticator */ NULL, /* auth_id */ NULL, /* auth_sndr */ + #ifdef EXPERIMENTAL_DSN + NULL, /* dsn_orcpt */ + 0, /* dsn_flags */ + 0, /* dsn_aware */ + #endif (uid_t)(-1), /* uid */ (gid_t)(-1), /* gid */ 0, /* flags */ @@ -552,7 +573,7 @@ BOOL deliver_firsttime = FALSE; BOOL deliver_force = FALSE; BOOL deliver_freeze = FALSE; -int deliver_frozen_at = 0; +time_t deliver_frozen_at = 0; uschar *deliver_home = NULL; uschar *deliver_host = NULL; uschar *deliver_host_address = NULL; @@ -598,6 +619,7 @@ #ifdef EXPERIMENTAL_DMARC BOOL dmarc_has_been_checked = FALSE; uschar *dmarc_ar_header = NULL; +uschar *dmarc_domain_policy = NULL; uschar *dmarc_forensic_sender = NULL; uschar *dmarc_history_file = NULL; uschar *dmarc_status = NULL; @@ -797,6 +819,9 @@ { US"lost_incoming_connection", L_lost_incoming_connection }, { US"outgoing_port", LX_outgoing_port }, { US"pid", LX_pid }, +#ifdef EXPERIMENTAL_PROXY + { US"proxy", LX_proxy }, +#endif { US"queue_run", L_queue_run }, { US"queue_time", LX_queue_time }, { US"queue_time_overall", LX_queue_time_overall }, @@ -833,6 +858,7 @@ BOOL log_timezone = FALSE; unsigned int log_write_selector= L_default; uschar *login_sender_address = NULL; +uschar *lookup_dnssec_authenticated = NULL; int lookup_open_max = 25; uschar *lookup_value = NULL; @@ -914,6 +940,17 @@ int process_info_len = 0; uschar *process_log_path = NULL; BOOL prod_requires_admin = TRUE; + +#ifdef EXPERIMENTAL_PROXY +uschar *proxy_host_address = US""; +int proxy_host_port = 0; +uschar *proxy_required_hosts = US""; +BOOL proxy_session = FALSE; +BOOL proxy_session_failed = FALSE; +uschar *proxy_target_address = US""; +int proxy_target_port = 0; +#endif + uschar *prvscheck_address = NULL; uschar *prvscheck_keynum = NULL; uschar *prvscheck_result = NULL; @@ -1092,6 +1129,9 @@ TRUE, /* verify_sender */ FALSE, /* uid_set */ FALSE, /* unseen */ +#ifdef EXPERIMENTAL_DSN + FALSE, /* dsn_lasthop */ +#endif self_freeze, /* self_code */ (uid_t)(-1), /* uid */ @@ -1347,6 +1387,9 @@ FALSE, /* log_defer_output */ TRUE_UNSET /* retry_use_local_part: BOOL, but set neither 1 nor 0 so can detect unset */ +#ifdef EXPERIMENTAL_TPDA + ,NULL /* tpda_delivery_action */ +#endif }; int transport_count; @@ -1403,8 +1446,8 @@ BOOL write_rejectlog = TRUE; uschar *version_copyright = - US"Copyright (c) University of Cambridge, 1995 - 2013\n" - "(c) The Exim Maintainers and contributors in ACKNOWLEDGMENTS file, 2007 - 2013"; + US"Copyright (c) University of Cambridge, 1995 - 2014\n" + "(c) The Exim Maintainers and contributors in ACKNOWLEDGMENTS file, 2007 - 2014"; uschar *version_date = US"?"; uschar *version_cnumber = US"????"; uschar *version_string = US"?"; diff -Nru exim4-4.82/src/globals.h exim4-4.84/src/globals.h --- exim4-4.82/src/globals.h 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/globals.h 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* Almost all the global variables are defined together in this one header, so @@ -85,8 +85,17 @@ uschar *cipher; /* Cipher used */ BOOL on_connect; /* For older MTAs that don't STARTTLS */ uschar *on_connect_ports; /* Ports always tls-on-connect */ + void *ourcert; /* Certificate we presented, binary */ + void *peercert; /* Certificate of peer, binary */ uschar *peerdn; /* DN from peer */ uschar *sni; /* Server Name Indication */ + enum { + OCSP_NOT_REQ=0, /* not requested */ + OCSP_NOT_RESP, /* no response to request */ + OCSP_VFY_NOT_TRIED, /* response not verified */ + OCSP_FAILED, /* verify failed */ + OCSP_VFIED /* verified */ + } ocsp; /* Stapled OCSP status */ } tls_support; extern tls_support tls_in; extern tls_support tls_out; @@ -105,7 +114,7 @@ extern uschar *tls_crl; /* CRL File */ extern int tls_dh_max_bits; /* don't accept higher lib suggestions */ extern uschar *tls_dhparam; /* DH param file */ -#if defined(EXPERIMENTAL_OCSP) && !defined(USE_GNUTLS) +#ifndef DISABLE_OCSP extern uschar *tls_ocsp_file; /* OCSP stapling proof file */ #endif extern BOOL tls_offered; /* Server offered TLS */ @@ -117,6 +126,13 @@ extern uschar *tls_verify_hosts; /* Mandatory client verification */ #endif +#ifdef EXPERIMENTAL_DSN +extern uschar *dsn_envid; /* DSN envid string */ +extern int dsn_ret; /* DSN ret type*/ +extern const pcre *regex_DSN; /* For recognizing DSN settings */ +extern BOOL smtp_use_dsn; /* Global for passed connections */ +extern uschar *dsn_advertise_hosts; /* host for which TLS is advertised */ +#endif /* Input-reading functions for messages, so we can use special ones for incoming TCP/IP. */ @@ -151,7 +167,7 @@ extern uschar *acl_smtp_auth; /* ACL run for AUTH */ extern uschar *acl_smtp_connect; /* ACL run on SMTP connection */ extern uschar *acl_smtp_data; /* ACL run after DATA received */ -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR extern uschar *acl_smtp_data_prdr; /* ACL run after DATA received if in PRDR mode*/ const extern pcre *regex_PRDR; /* For recognizing PRDR settings */ #endif @@ -307,7 +323,7 @@ extern BOOL deliver_firsttime; /* True for first delivery attempt */ extern BOOL deliver_force; /* TRUE if delivery was forced */ extern BOOL deliver_freeze; /* TRUE if delivery is frozen */ -extern int deliver_frozen_at; /* Time of freezing */ +extern time_t deliver_frozen_at; /* Time of freezing */ extern uschar *deliver_home; /* Home directory for pipes */ extern uschar *deliver_host; /* (First) host for routed local deliveries */ /* Remote host for filter */ @@ -354,6 +370,7 @@ #ifdef EXPERIMENTAL_DMARC extern BOOL dmarc_has_been_checked; /* Global variable to check if test has been called yet */ extern uschar *dmarc_ar_header; /* Expansion variable, suggested header for dmarc auth results */ +extern uschar *dmarc_domain_policy; /* Expansion for declared policy of used domain */ extern uschar *dmarc_forensic_sender; /* Set sender address for forensic reports */ extern uschar *dmarc_history_file; /* Expansion variable, file to store dmarc results */ extern uschar *dmarc_status; /* Expansion variable, one word value */ @@ -502,6 +519,7 @@ extern uschar *login_sender_address; /* The actual sender address */ extern lookup_info **lookup_list; /* Array of pointers to available lookups */ extern int lookup_list_count; /* Number of entries in the list */ +extern uschar *lookup_dnssec_authenticated; /* AD status of dns lookup */ extern int lookup_open_max; /* Max lookup files to cache */ extern uschar *lookup_value; /* Value looked up from file */ @@ -581,7 +599,7 @@ extern uschar *pid_file_path; /* For writing daemon pids */ extern uschar *pipelining_advertise_hosts; /* As it says */ extern BOOL pipelining_enable; /* As it says */ -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR extern BOOL prdr_enable; /* As it says */ extern BOOL prdr_requested; /* Connecting mail server wants PRDR */ #endif @@ -592,6 +610,17 @@ extern int process_info_len; extern uschar *process_log_path; /* Alternate path */ extern BOOL prod_requires_admin; /* TRUE if prodding requires admin */ + +#ifdef EXPERIMENTAL_PROXY +extern uschar *proxy_host_address; /* IP of host being proxied */ +extern int proxy_host_port; /* Port of host being proxied */ +extern uschar *proxy_required_hosts; /* Hostlist which (require) use proxy protocol */ +extern BOOL proxy_session; /* TRUE if receiving mail from valid proxy */ +extern BOOL proxy_session_failed; /* TRUE if required proxy negotiation failed */ +extern uschar *proxy_target_address; /* IP of proxy server inbound */ +extern int proxy_target_port; /* Port of proxy server inbound */ +#endif + extern uschar *prvscheck_address; /* Set during prvscheck expansion item */ extern uschar *prvscheck_keynum; /* Set during prvscheck expansion item */ extern uschar *prvscheck_result; /* Set during prvscheck expansion item */ diff -Nru exim4-4.82/src/host.c exim4-4.84/src/host.c --- exim4-4.82/src/host.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/host.c 2014-08-09 12:44:29.000000000 +0000 @@ -220,6 +220,8 @@ int rc = dns_lookup(&dnsa, lname, type, NULL); int count = 0; + lookup_dnssec_authenticated = NULL; + switch(rc) { case DNS_SUCCEED: break; @@ -1179,17 +1181,13 @@ uschar buffer[32]; uschar *list = tls_in.on_connect_ports; uschar *s; +uschar *end; if (tls_in.on_connect) return TRUE; -while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL) - { - uschar *end; - int lport = Ustrtol(s, &end, 10); - if (*end != 0) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "tls_on_connect_ports " - "contains \"%s\", which is not a port number: exim abandoned", s); - if (lport == port) return TRUE; - } +while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))) + if (Ustrtol(s, &end, 10) == port) + return TRUE; return FALSE; } @@ -1622,7 +1620,7 @@ { if (strcmpic(ordername, US"bydns") == 0) { - dns_init(FALSE, FALSE); + dns_init(FALSE, FALSE, FALSE); /* dnssec ctrl by dns_dnssec_ok glbl */ dns_build_reverse(sender_host_address, buffer); rc = dns_lookup(&dnsa, buffer, T_PTR, NULL); @@ -1919,7 +1917,8 @@ 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); + (flags & HOST_FIND_SEARCH_PARENTS) != 0, + FALSE); /*XXX dnssec? */ /* In an IPv6 world, unless IPv6 has been disabled, we need to scan for both kinds of address, so go round the loop twice. Note that we have ensured that @@ -2062,6 +2061,7 @@ host->port = PORT_NONE; host->status = hstatus_unknown; host->why = hwhy_unknown; + host->dnssec = DS_UNK; last = host; } @@ -2077,6 +2077,7 @@ next->port = PORT_NONE; next->status = hstatus_unknown; next->why = hwhy_unknown; + next->dnssec = DS_UNK; next->last_try = 0; next->next = last->next; last->next = next; @@ -2195,6 +2196,7 @@ fully_qualified_name if not NULL, return fully qualified name here if the contents are different (i.e. it must be preset to something) + dnnssec_require if TRUE check the DNS result AD bit Returns: HOST_FIND_FAILED couldn't find A record HOST_FIND_AGAIN try again later @@ -2204,7 +2206,8 @@ static int set_address_from_dns(host_item *host, host_item **lastptr, - uschar *ignore_target_hosts, BOOL allow_ip, uschar **fully_qualified_name) + uschar *ignore_target_hosts, BOOL allow_ip, uschar **fully_qualified_name, + BOOL dnssec_requested, BOOL dnssec_require) { dns_record *rr; host_item *thishostlast = NULL; /* Indicates not yet filled in anything */ @@ -2265,6 +2268,8 @@ dns_scan dnss; int rc = dns_lookup(&dnsa, host->name, type, fully_qualified_name); + lookup_dnssec_authenticated = !dnssec_requested ? NULL + : dns_is_secure(&dnsa) ? US"yes" : US"no"; /* We want to return HOST_FIND_AGAIN if one of the A, A6, or AAAA lookups fails or times out, but not if another one succeeds. (In the early @@ -2287,6 +2292,12 @@ if (rc != DNS_NOMATCH && rc != DNS_NODATA) v6_find_again = TRUE; continue; } + if (dnssec_require && !dns_is_secure(&dnsa)) + { + log_write(L_host_lookup_failed, LOG_MAIN, "dnssec fail on %s for %.256s", + i>1 ? "A6" : i>0 ? "AAAA" : "A", host->name); + continue; + } /* Lookup succeeded: fill in the given host item with the first non-ignored address found; create additional items for any others. A single A6 record @@ -2433,6 +2444,8 @@ srv_service when SRV used, the service name srv_fail_domains DNS errors for these domains => assume nonexist mx_fail_domains DNS errors for these domains => assume nonexist + dnssec_request_domains => make dnssec request + dnssec_require_domains => ditto and nonexist failures fully_qualified_name if not NULL, return fully-qualified name removed set TRUE if local host was removed from the list @@ -2450,6 +2463,7 @@ int host_find_bydns(host_item *host, uschar *ignore_target_hosts, int whichrrs, uschar *srv_service, uschar *srv_fail_domains, uschar *mx_fail_domains, + uschar *dnssec_request_domains, uschar *dnssec_require_domains, uschar **fully_qualified_name, BOOL *removed) { host_item *h, *last; @@ -2459,6 +2473,12 @@ int yield; dns_answer dnsa; dns_scan dnss; +BOOL dnssec_require = match_isinlist(host->name, &dnssec_require_domains, + 0, NULL, NULL, MCL_DOMAIN, TRUE, NULL) == OK; +BOOL dnssec_request = dnssec_require + || match_isinlist(host->name, &dnssec_request_domains, + 0, NULL, NULL, MCL_DOMAIN, TRUE, NULL) == OK; +dnssec_status_t dnssec; /* Set the default fully qualified name to the incoming name, initialize the resolver if necessary, set up the relevant options, and initialize the flag @@ -2466,7 +2486,9 @@ if (fully_qualified_name != NULL) *fully_qualified_name = host->name; dns_init((whichrrs & HOST_FIND_QUALIFY_SINGLE) != 0, - (whichrrs & HOST_FIND_SEARCH_PARENTS) != 0); + (whichrrs & HOST_FIND_SEARCH_PARENTS) != 0, + dnssec_request + ); host_find_failed_syntax = FALSE; /* First, if requested, look for SRV records. The service name is given; we @@ -2487,20 +2509,37 @@ the input name, pass back the new original domain, without the prepended magic. */ + dnssec = DS_UNK; + lookup_dnssec_authenticated = NULL; rc = dns_lookup(&dnsa, buffer, ind_type, &temp_fully_qualified_name); + + if (dnssec_request) + { + if (dns_is_secure(&dnsa)) + { dnssec = DS_YES; lookup_dnssec_authenticated = US"yes"; } + else + { dnssec = DS_NO; lookup_dnssec_authenticated = US"no"; } + } + if (temp_fully_qualified_name != buffer && fully_qualified_name != NULL) *fully_qualified_name = temp_fully_qualified_name + prefix_length; /* On DNS failures, we give the "try again" error unless the domain is listed as one for which we continue. */ + if (rc == DNS_SUCCEED && dnssec_require && !dns_is_secure(&dnsa)) + { + log_write(L_host_lookup_failed, LOG_MAIN, + "dnssec fail on SRV for %.256s", host->name); + rc = DNS_FAIL; + } if (rc == DNS_FAIL || rc == DNS_AGAIN) { #ifndef STAND_ALONE if (match_isinlist(host->name, &srv_fail_domains, 0, NULL, NULL, MCL_DOMAIN, TRUE, NULL) != OK) #endif - return HOST_FIND_AGAIN; + { yield = HOST_FIND_AGAIN; goto out; } DEBUG(D_host_lookup) debug_printf("DNS_%s treated as DNS_NODATA " "(domain in srv_fail_domains)\n", (rc == DNS_FAIL)? "FAIL":"AGAIN"); } @@ -2516,17 +2555,41 @@ if (rc != DNS_SUCCEED && (whichrrs & HOST_FIND_BY_MX) != 0) { ind_type = T_MX; + dnssec = DS_UNK; + lookup_dnssec_authenticated = NULL; rc = dns_lookup(&dnsa, host->name, ind_type, fully_qualified_name); - if (rc == DNS_NOMATCH) return HOST_FIND_FAILED; - if (rc == DNS_FAIL || rc == DNS_AGAIN) + + if (dnssec_request) { - #ifndef STAND_ALONE - if (match_isinlist(host->name, &mx_fail_domains, 0, NULL, NULL, MCL_DOMAIN, - TRUE, NULL) != OK) - #endif - return HOST_FIND_AGAIN; - DEBUG(D_host_lookup) debug_printf("DNS_%s treated as DNS_NODATA " - "(domain in mx_fail_domains)\n", (rc == DNS_FAIL)? "FAIL":"AGAIN"); + if (dns_is_secure(&dnsa)) + { dnssec = DS_YES; lookup_dnssec_authenticated = US"yes"; } + else + { dnssec = DS_NO; lookup_dnssec_authenticated = US"no"; } + } + + switch (rc) + { + case DNS_NOMATCH: + yield = HOST_FIND_FAILED; goto out; + + case DNS_SUCCEED: + if (!dnssec_require || dns_is_secure(&dnsa)) + break; + log_write(L_host_lookup_failed, LOG_MAIN, + "dnssec fail on MX for %.256s", host->name); + rc = DNS_FAIL; + /*FALLTRHOUGH*/ + + case DNS_FAIL: + case DNS_AGAIN: + #ifndef STAND_ALONE + if (match_isinlist(host->name, &mx_fail_domains, 0, NULL, NULL, MCL_DOMAIN, + TRUE, NULL) != OK) + #endif + { yield = HOST_FIND_AGAIN; goto out; } + DEBUG(D_host_lookup) debug_printf("DNS_%s treated as DNS_NODATA " + "(domain in mx_fail_domains)\n", (rc == DNS_FAIL)? "FAIL":"AGAIN"); + break; } } @@ -2539,14 +2602,25 @@ if ((whichrrs & HOST_FIND_BY_A) == 0) { DEBUG(D_host_lookup) debug_printf("Address records are not being sought\n"); - return HOST_FIND_FAILED; + yield = HOST_FIND_FAILED; + goto out; } last = host; /* End of local chainlet */ host->mx = MX_NONE; host->port = PORT_NONE; + dnssec = DS_UNK; + lookup_dnssec_authenticated = NULL; rc = set_address_from_dns(host, &last, ignore_target_hosts, FALSE, - fully_qualified_name); + fully_qualified_name, dnssec_request, dnssec_require); + + if (dnssec_request) + { + if (dns_is_secure(&dnsa)) + { dnssec = DS_YES; lookup_dnssec_authenticated = US"yes"; } + else + { dnssec = DS_NO; lookup_dnssec_authenticated = US"no"; } + } /* If one or more address records have been found, check that none of them are local. Since we know the host items all have their IP addresses @@ -2573,7 +2647,8 @@ } } - return rc; + yield = rc; + goto out; } /* We have found one or more MX or SRV records. Sort them according to @@ -2616,9 +2691,7 @@ the same precedence to sort randomly. */ if (ind_type == T_MX) - { weight = random_number(500); - } /* SRV records are specified with a port and a weight. The weight is used in a special algorithm. However, to start with, we just use it to order the @@ -2682,6 +2755,7 @@ host->sort_key = precedence * 1000 + weight; host->status = hstatus_unknown; host->why = hwhy_unknown; + host->dnssec = dnssec; last = host; } @@ -2698,6 +2772,7 @@ next->sort_key = sort_key; next->status = hstatus_unknown; next->why = hwhy_unknown; + next->dnssec = dnssec; next->last_try = 0; /* Handle the case when we have to insert before the first item. */ @@ -2757,7 +2832,8 @@ if (host == last && host->name[0] == 0) { DEBUG(D_host_lookup) debug_printf("the single SRV record is \".\"\n"); - return HOST_FIND_FAILED; + yield = HOST_FIND_FAILED; + goto out; } DEBUG(D_host_lookup) @@ -2867,12 +2943,14 @@ if they happen to match something local. */ yield = HOST_FIND_FAILED; /* Default yield */ -dns_init(FALSE, FALSE); /* Disable qualify_single and search_parents */ +dns_init(FALSE, FALSE, /* Disable qualify_single and search_parents */ + dnssec_request || dnssec_require); for (h = host; h != last->next; h = h->next) { if (h->address != NULL) continue; /* Inserted by a multihomed host */ - rc = set_address_from_dns(h, &last, ignore_target_hosts, allow_mx_to_ip, NULL); + rc = set_address_from_dns(h, &last, ignore_target_hosts, allow_mx_to_ip, + NULL, dnssec_request, dnssec_require); if (rc != HOST_FOUND) { h->status = hstatus_unusable; @@ -2981,6 +3059,9 @@ } } +out: + +dns_init(FALSE, FALSE, FALSE); /* clear the dnssec bit for getaddrbyname */ return yield; } @@ -3002,6 +3083,8 @@ BOOL byname = FALSE; BOOL qualify_single = TRUE; BOOL search_parents = FALSE; +BOOL request_dnssec = FALSE; +BOOL require_dnssec = FALSE; uschar **argv = USS cargv; uschar buffer[256]; @@ -3021,7 +3104,7 @@ /* So that debug level changes can be done first */ -dns_init(qualify_single, search_parents); +dns_init(qualify_single, search_parents, FALSE); printf("Testing host lookup\n"); printf("> "); @@ -3047,10 +3130,14 @@ whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_MX; else if (Ustrcmp(buffer, "srv+mx+a") == 0) whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_MX | HOST_FIND_BY_A; - else if (Ustrcmp(buffer, "qualify_single") == 0) qualify_single = TRUE; + else if (Ustrcmp(buffer, "qualify_single") == 0) qualify_single = TRUE; else if (Ustrcmp(buffer, "no_qualify_single") == 0) qualify_single = FALSE; - else if (Ustrcmp(buffer, "search_parents") == 0) search_parents = TRUE; + else if (Ustrcmp(buffer, "search_parents") == 0) search_parents = TRUE; else if (Ustrcmp(buffer, "no_search_parents") == 0) search_parents = FALSE; + else if (Ustrcmp(buffer, "request_dnssec") == 0) request_dnssec = TRUE; + else if (Ustrcmp(buffer, "no_request_dnssec") == 0) request_dnssec = FALSE; + else if (Ustrcmp(buffer, "require_dnssec") == 0) require_dnssec = TRUE; + else if (Ustrcmp(buffer, "no_reqiret_dnssec") == 0) require_dnssec = FALSE; else if (Ustrcmp(buffer, "test_harness") == 0) running_in_test_harness = !running_in_test_harness; else if (Ustrcmp(buffer, "ipv6") == 0) disable_ipv6 = !disable_ipv6; @@ -3083,11 +3170,12 @@ if (qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE; if (search_parents) flags |= HOST_FIND_SEARCH_PARENTS; - rc = byname? - host_find_byname(&h, NULL, flags, &fully_qualified_name, TRUE) - : - host_find_bydns(&h, NULL, flags, US"smtp", NULL, NULL, - &fully_qualified_name, NULL); + rc = byname + ? host_find_byname(&h, NULL, flags, &fully_qualified_name, TRUE) + : host_find_bydns(&h, NULL, flags, US"smtp", NULL, NULL, + request_dnssec ? &h.name : NULL, + require_dnssec ? &h.name : NULL, + &fully_qualified_name, NULL); if (rc == HOST_FIND_FAILED) printf("Failed\n"); else if (rc == HOST_FIND_AGAIN) printf("Again\n"); @@ -3146,4 +3234,6 @@ } #endif /* STAND_ALONE */ +/* vi: aw ai sw=2 +*/ /* End of host.c */ diff -Nru exim4-4.82/src/ip.c exim4-4.84/src/ip.c --- exim4-4.82/src/ip.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/ip.c 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2009 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* Functions for doing things with sockets. With the advent of IPv6 this has @@ -172,7 +172,7 @@ af AF_INET6 or AF_INET for the socket type address the remote address, in text form port the remote port - timeout a timeout + timeout a timeout (zero for indefinite timeout) Returns: 0 on success; -1 on failure, with errno set */ @@ -250,6 +250,111 @@ /************************************************* +* Create connected socket to remote host * +*************************************************/ + +/* Create a socket and connect to host (name or number, ipv6 ok) + at one of port-range. + +Arguments: + type SOCK_DGRAM or SOCK_STREAM + af AF_INET6 or AF_INET for the socket type + address the remote address, in text form + portlo,porthi the remote port range + timeout a timeout + connhost if not NULL, host_item filled in with connection details + errstr pointer for allocated string on error + +Return: + socket fd, or -1 on failure (having allocated an error string) +*/ +int +ip_connectedsocket(int type, const uschar * hostname, int portlo, int porthi, + int timeout, host_item * connhost, uschar ** errstr) +{ +int namelen, port; +host_item shost; +host_item *h; +int af = 0, fd, fd4 = -1, fd6 = -1; + +shost.next = NULL; +shost.address = NULL; +shost.port = portlo; +shost.mx = -1; + +namelen = Ustrlen(hostname); + +/* Anything enclosed in [] must be an IP address. */ + +if (hostname[0] == '[' && + hostname[namelen - 1] == ']') + { + uschar * host = string_copy(hostname); + host[namelen - 1] = 0; + host++; + if (string_is_ip_address(host, NULL) == 0) + { + *errstr = string_sprintf("malformed IP address \"%s\"", hostname); + return -1; + } + shost.name = shost.address = host; + } + +/* Otherwise check for an unadorned IP address */ + +else if (string_is_ip_address(hostname, NULL) != 0) + shost.name = shost.address = string_copy(hostname); + +/* Otherwise lookup IP address(es) from the name */ + +else + { + shost.name = string_copy(hostname); + if (host_find_byname(&shost, NULL, HOST_FIND_QUALIFY_SINGLE, NULL, + FALSE) != HOST_FOUND) + { + *errstr = string_sprintf("no IP address found for host %s", shost.name); + return -1; + } + } + +/* Try to connect to the server - test each IP till one works */ + +for (h = &shost; h != NULL; h = h->next) + { + fd = (Ustrchr(h->address, ':') != 0) + ? (fd6 < 0) ? (fd6 = ip_socket(type, af = AF_INET6)) : fd6 + : (fd4 < 0) ? (fd4 = ip_socket(type, af = AF_INET )) : fd4; + + if (fd < 0) + { + *errstr = string_sprintf("failed to create socket: %s", strerror(errno)); + goto bad; + } + + for(port = portlo; port <= porthi; port++) + if (ip_connect(fd, af, h->address, port, timeout) == 0) + { + if (fd != fd6) close(fd6); + if (fd != fd4) close(fd4); + if (connhost) { + h->port = port; + *connhost = *h; + connhost->next = NULL; + } + return fd; + } + } + +*errstr = string_sprintf("failed to connect to %s: " + "couldn't connect to any host: %s", hostname, strerror(errno)); + +bad: + close(fd4); close(fd6); return -1; +} + + +/************************************************* * Set keepalive on a socket * *************************************************/ @@ -298,7 +403,7 @@ { fd_set select_inset; struct timeval tv; -int start_recv = time(NULL); +time_t start_recv = time(NULL); int rc; /* Wait until the socket is ready */ @@ -464,7 +569,7 @@ *level = IPPROTO_IP; *optname = IP_TOS; } -#if HAVE_IPV6 +#if HAVE_IPV6 && defined(IPV6_TCLASS) else if (af == AF_INET6) { *level = IPPROTO_IPV6; diff -Nru exim4-4.82/src/local_scan.h exim4-4.84/src/local_scan.h --- exim4-4.82/src/local_scan.h 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/local_scan.h 2014-08-09 12:44:29.000000000 +0000 @@ -128,6 +128,10 @@ uschar *address; /* the recipient address */ int pno; /* parent number for "one_time" alias, or -1 */ uschar *errors_to; /* the errors_to address or NULL */ +#ifdef EXPERIMENTAL_DSN + uschar *orcpt; /* DSN orcpt */ + int dsn_flags; /* DSN flags */ +#endif #ifdef EXPERIMENTAL_BRIGHTMAIL uschar *bmi_optin; #endif diff -Nru exim4-4.82/src/log.c exim4-4.84/src/log.c --- exim4-4.82/src/log.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/log.c 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* Functions for writing log files. The code for maintaining datestamped diff -Nru exim4-4.82/src/lookups/dnsdb.c exim4-4.84/src/lookups/dnsdb.c --- exim4-4.82/src/lookups/dnsdb.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/lookups/dnsdb.c 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ #include "../exim.h" @@ -22,6 +22,11 @@ #define T_SPF 99 #endif +/* New TLSA record for DANE */ +#ifndef T_TLSA +#define T_TLSA 52 +#endif + /* Table of recognized DNS record types and their integer values. */ static const char *type_names[] = { @@ -41,6 +46,7 @@ "ptr", "spf", "srv", + "tlsa", "txt", "zns" }; @@ -48,7 +54,7 @@ static int type_values[] = { T_A, #if HAVE_IPV6 - T_APL, /* Private type for AAAA + A */ + T_ADDRESSES, /* Private type for AAAA + A */ T_AAAA, #ifdef SUPPORT_A6 T_A6, @@ -62,6 +68,7 @@ T_PTR, T_SPF, T_SRV, + T_TLSA, T_TXT, T_ZNS /* Private type for "zone nameservers" */ }; @@ -107,11 +114,15 @@ whole lookup to defer only if none of the DNS queries succeeds; and 'never', where all defers are as if the lookup failed. The default is 'lax'. -(d) If the next sequence of characters is a sequence of letters and digits +(d) Another optional comma-sep field: 'dnssec_FOO', with 'strict', 'lax' +and 'never' (default); can appear before or after (c). The meanings are +require, try and don't-try dnssec respectively. + +(e) If the next sequence of characters is a sequence of letters and digits followed by '=', it is interpreted as the name of the DNS record type. The default is "TXT". -(e) Then there follows list of domain names. This is a generalized Exim list, +(f) Then there follows list of domain names. This is a generalized Exim list, which may start with '<' in order to set a specific separator. The default separator, as always, is colon. */ @@ -124,6 +135,7 @@ int ptr = 0; int sep = 0; int defer_mode = PASS; +int dnssec_mode = OK; int type; int failrc = FAIL; uschar *outsep = US"\n"; @@ -166,35 +178,64 @@ while (isspace(*keystring)) keystring++; } -/* Check for a defer behaviour keyword. */ +/* Check for a modifier keyword. */ -if (strncmpic(keystring, US"defer_", 6) == 0) +while ( strncmpic(keystring, US"defer_", 6) == 0 + || strncmpic(keystring, US"dnssec_", 7) == 0 + ) { - keystring += 6; - if (strncmpic(keystring, US"strict", 6) == 0) + if (strncmpic(keystring, US"defer_", 6) == 0) { - defer_mode = DEFER; keystring += 6; - } - else if (strncmpic(keystring, US"lax", 3) == 0) - { - defer_mode = PASS; - keystring += 3; - } - else if (strncmpic(keystring, US"never", 5) == 0) - { - defer_mode = OK; - keystring += 5; + if (strncmpic(keystring, US"strict", 6) == 0) + { + defer_mode = DEFER; + keystring += 6; + } + else if (strncmpic(keystring, US"lax", 3) == 0) + { + defer_mode = PASS; + keystring += 3; + } + else if (strncmpic(keystring, US"never", 5) == 0) + { + defer_mode = OK; + keystring += 5; + } + else + { + *errmsg = US"unsupported dnsdb defer behaviour"; + return DEFER; + } } else { - *errmsg = US"unsupported dnsdb defer behaviour"; - return DEFER; + keystring += 7; + if (strncmpic(keystring, US"strict", 6) == 0) + { + dnssec_mode = DEFER; + keystring += 6; + } + else if (strncmpic(keystring, US"lax", 3) == 0) + { + dnssec_mode = PASS; + keystring += 3; + } + else if (strncmpic(keystring, US"never", 5) == 0) + { + dnssec_mode = OK; + keystring += 5; + } + else + { + *errmsg = US"unsupported dnsdb dnssec behaviour"; + return DEFER; + } } while (isspace(*keystring)) keystring++; if (*keystring++ != ',') { - *errmsg = US"dnsdb defer behaviour syntax error"; + *errmsg = US"dnsdb modifier syntax error"; return DEFER; } while (isspace(*keystring)) keystring++; @@ -234,7 +275,7 @@ /* Initialize the resolver in case this is the first time it has been used. */ -dns_init(FALSE, FALSE); +dns_init(FALSE, FALSE, dnssec_mode != OK); /* The remainder of the string must be a list of domains. As long as the lookup for at least one of them succeeds, we return success. Failure means that none @@ -287,9 +328,9 @@ DEBUG(D_lookup) debug_printf("dnsdb key: %s\n", domain); /* Do the lookup and sort out the result. There are four special types that - are handled specially: T_CSA, T_ZNS, T_APL and T_MXH. + are handled specially: T_CSA, T_ZNS, T_ADDRESSES and T_MXH. The first two are handled in a special lookup function so that the facility - could be used from other parts of the Exim code. T_APL is handled by looping + could be used from other parts of the Exim code. T_ADDRESSES is handled by looping over the types of A lookup. T_MXH affects only what happens later on in this function, but for tidiness it is handled by the "special". If the lookup fails, continue with the next domain. In the case of DEFER, adjust @@ -297,9 +338,9 @@ found = domain; #if HAVE_IPV6 - if (type == T_APL) /* NB cannot happen unless HAVE_IPV6 */ + if (type == T_ADDRESSES) /* NB cannot happen unless HAVE_IPV6 */ { - if (searchtype == T_APL) + if (searchtype == T_ADDRESSES) # if defined(SUPPORT_A6) searchtype = T_A6; # else @@ -313,14 +354,24 @@ #endif rc = dns_special_lookup(&dnsa, domain, type, &found); + lookup_dnssec_authenticated = dnssec_mode==OK ? NULL + : dns_is_secure(&dnsa) ? US"yes" : US"no"; + if (rc == DNS_NOMATCH || rc == DNS_NODATA) continue; - if (rc != DNS_SUCCEED) + if ( rc != DNS_SUCCEED + || (dnssec_mode == DEFER && !dns_is_secure(&dnsa)) + ) { - if (defer_mode == DEFER) return DEFER; /* always defer */ + if (defer_mode == DEFER) + { + dns_init(FALSE, FALSE, FALSE); /* clr dnssec bit */ + return DEFER; /* always defer */ + } if (defer_mode == PASS) failrc = DEFER; /* defer only if all do */ continue; /* treat defer as fail */ } + /* Search the returned records */ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS); @@ -338,7 +389,7 @@ type == T_A6 || #endif type == T_AAAA || - type == T_APL) + type == T_ADDRESSES) { dns_address *da; for (da = dns_address_from_rr(&dnsa, rr); da != NULL; da = da->next) @@ -378,6 +429,29 @@ } } } + else if (type == T_TLSA) + { + uint8_t usage, selector, matching_type; + uint16_t i, payload_length; + uschar s[MAX_TLSA_EXPANDED_SIZE]; + uschar * sp = s; + uschar *p = (uschar *)(rr->data); + + usage = *p++; + selector = *p++; + matching_type = *p++; + /* What's left after removing the first 3 bytes above */ + payload_length = rr->size - 3; + sp += sprintf(CS s, "%d %d %d ", usage, selector, matching_type); + /* Now append the cert/identifier, one hex char at a time */ + for (i=0; + i < payload_length && sp-s < (MAX_TLSA_EXPANDED_SIZE - 4); + i++) + { + sp += sprintf(CS sp, "%02x", (unsigned char)p[i]); + } + yield = string_cat(yield, &size, &ptr, s, Ustrlen(s)); + } else /* T_CNAME, T_CSA, T_MX, T_MXH, T_NS, T_PTR, T_SRV */ { int priority, weight, port; @@ -453,7 +527,7 @@ } /* Loop for list of returned records */ /* Loop for set of A-lookupu types */ - } while (type == T_APL && searchtype != T_A); + } while (type == T_ADDRESSES && searchtype != T_A); } /* Loop for list of domains */ @@ -464,6 +538,8 @@ /* If ptr == 0 we have not found anything. Otherwise, insert the terminating zero and return the result. */ +dns_init(FALSE, FALSE, FALSE); /* clear the dnssec bit for getaddrbyname */ + if (ptr == 0) return failrc; yield[ptr] = 0; *result = yield; @@ -508,4 +584,6 @@ static lookup_info *_lookup_list[] = { &_lookup_info }; lookup_module_info dnsdb_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 }; +/* vi: aw ai sw=2 +*/ /* End of lookups/dnsdb.c */ diff -Nru exim4-4.82/src/lookups/ldap.c exim4-4.84/src/lookups/ldap.c --- exim4-4.82/src/lookups/ldap.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/lookups/ldap.c 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* Many thanks to Stuart Lynne for contributing the original code for this @@ -280,6 +280,13 @@ { LDAP *ld; + #ifdef LDAP_OPT_X_TLS_NEWCTX + int am_server = 0; + LDAP *ldsetctx; + #else + LDAP *ldsetctx = NULL; + #endif + /* --------------------------- OpenLDAP ------------------------ */ @@ -365,6 +372,10 @@ goto RETURN_ERROR; } + #ifdef LDAP_OPT_X_TLS_NEWCTX + ldsetctx = ld; + #endif + /* Set the TCP connect time limit if available. This is something that is in Netscape SDK v4.1; I don't know about other libraries. */ @@ -461,31 +472,31 @@ #ifdef LDAP_OPT_X_TLS_CACERTFILE if (eldap_ca_cert_file != NULL) { - ldap_set_option(ld, LDAP_OPT_X_TLS_CACERTFILE, eldap_ca_cert_file); + ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_CACERTFILE, eldap_ca_cert_file); } #endif #ifdef LDAP_OPT_X_TLS_CACERTDIR if (eldap_ca_cert_dir != NULL) { - ldap_set_option(ld, LDAP_OPT_X_TLS_CACERTDIR, eldap_ca_cert_dir); + ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_CACERTDIR, eldap_ca_cert_dir); } #endif #ifdef LDAP_OPT_X_TLS_CERTFILE if (eldap_cert_file != NULL) { - ldap_set_option(ld, LDAP_OPT_X_TLS_CERTFILE, eldap_cert_file); + ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_CERTFILE, eldap_cert_file); } #endif #ifdef LDAP_OPT_X_TLS_KEYFILE if (eldap_cert_key != NULL) { - ldap_set_option(ld, LDAP_OPT_X_TLS_KEYFILE, eldap_cert_key); + ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_KEYFILE, eldap_cert_key); } #endif #ifdef LDAP_OPT_X_TLS_CIPHER_SUITE if (eldap_cipher_suite != NULL) { - ldap_set_option(ld, LDAP_OPT_X_TLS_CIPHER_SUITE, eldap_cipher_suite); + ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_CIPHER_SUITE, eldap_cipher_suite); } #endif #ifdef LDAP_OPT_X_TLS_REQUIRE_CERT @@ -508,8 +519,26 @@ { cert_option = LDAP_OPT_X_TLS_TRY; } - /* Use NULL ldap handle because is a global option */ - ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &cert_option); + /* This ldap handle is set at compile time based on client libs. Older + * versions want it to be global and newer versions can force a reload + * of the TLS context (to reload these settings we are changing from the + * default that loaded at instantiation). */ + rc = ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_REQUIRE_CERT, &cert_option); + if (rc) + { + DEBUG(D_lookup) + debug_printf("Unable to set TLS require cert_option(%d) globally: %s\n", + cert_option, ldap_err2string(rc)); + } + } + #endif + #ifdef LDAP_OPT_X_TLS_NEWCTX + rc = ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_NEWCTX, &am_server); + if (rc) + { + DEBUG(D_lookup) + debug_printf("Unable to reload TLS context %d: %s\n", + rc, ldap_err2string(rc)); } #endif @@ -1104,6 +1133,7 @@ uschar *p; uschar *user = NULL; uschar *password = NULL; +uschar *local_servers = NULL; uschar *server, *list; uschar buffer[512]; @@ -1132,6 +1162,7 @@ else if (strncmpic(name, US"TIME=", namelen) == 0) timelimit = Uatoi(value); else if (strncmpic(name, US"CONNECT=", namelen) == 0) tcplimit = Uatoi(value); else if (strncmpic(name, US"NETTIME=", namelen) == 0) tcplimit = Uatoi(value); + else if (strncmpic(name, US"SERVERS=", namelen) == 0) local_servers = value; /* Don't know if all LDAP libraries have LDAP_OPT_DEREF */ @@ -1259,16 +1290,16 @@ /* No default servers, or URL contains a server name: just one attempt */ -if (eldap_default_servers == NULL || p[3] != '/') +if ((eldap_default_servers == NULL && local_servers == NULL) || p[3] != '/') { return perform_ldap_search(url, NULL, 0, search_type, res, errmsg, &defer_break, user, password, sizelimit, timelimit, tcplimit, dereference, referrals); } -/* Loop through the default servers until OK or FAIL */ - -list = eldap_default_servers; +/* Loop through the default servers until OK or FAIL. Use local_servers list + * if defined in the lookup, otherwise use the global default list */ +list = (local_servers == NULL) ? eldap_default_servers : local_servers; while ((server = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL) { int rc; @@ -1367,7 +1398,8 @@ { DEBUG(D_lookup) debug_printf("unbind LDAP connection to %s:%d\n", lcp->host, lcp->port); - ldap_unbind(lcp->ld); + if(lcp->bound == TRUE) + ldap_unbind(lcp->ld); ldap_connections = lcp->next; } } diff -Nru exim4-4.82/src/macros.h exim4-4.84/src/macros.h --- exim4-4.82/src/macros.h 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/macros.h 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ @@ -177,6 +177,14 @@ #define WAIT_NAME_MAX 50 +/* Wait this long before determining that a Proxy Protocol configured +host isn't speaking the protocol, and so is disallowed. Can be moved to +runtime configuration if per site settings become needed. */ +#ifdef EXPERIMENTAL_PROXY +#define PROXY_NEGOTIATION_TIMEOUT_SEC 3 +#define PROXY_NEGOTIATION_TIMEOUT_USEC 0 +#endif + /* Fixed option values for all PCRE functions */ #define PCRE_COPT 0 /* compile */ @@ -414,6 +422,7 @@ #define LX_unknown_in_list 0x81000000 #define LX_8bitmime 0x82000000 #define LX_smtp_mailauth 0x84000000 +#define LX_proxy 0x88000000 #define L_default (L_connection_reject | \ L_delay_delivery | \ @@ -481,6 +490,7 @@ #define ERRNO_RCPT4XX (-44) /* RCPT gave 4xx error */ #define ERRNO_MAIL4XX (-45) /* MAIL gave 4xx error */ #define ERRNO_DATA4XX (-46) /* DATA gave 4xx error */ +#define ERRNO_PROXYFAIL (-47) /* Negotiation failed for proxy configured host */ /* These must be last, so all retry deferments can easily be identified */ @@ -778,6 +788,29 @@ #define topt_no_body 0x040 /* Omit body */ #define topt_escape_headers 0x080 /* Apply escape check to headers */ +#ifdef EXPERIMENTAL_DSN +/* Flags for recipient_block, used in DSN support */ + +#define rf_dsnlasthop 0x01 /* Do not propagate DSN any further */ +#define rf_notify_never 0x02 /* NOTIFY= settings */ +#define rf_notify_success 0x04 +#define rf_notify_failure 0x08 +#define rf_notify_delay 0x10 + +#define rf_dsnflags (rf_notify_never | rf_notify_success | \ + rf_notify_failure | rf_notify_delay) + +/* DSN RET types */ + +#define dsn_ret_full 1 +#define dsn_ret_hdrs 2 + +#define dsn_support_unknown 0 +#define dsn_support_yes 1 +#define dsn_support_no 2 + +#endif + /* Codes for the host_find_failed and host_all_ignored options. */ #define hff_freeze 0 @@ -816,7 +849,7 @@ ACL_WHERE_MIME, /* ) implemented by <= WHERE_NOTSMTP */ ACL_WHERE_DKIM, /* ) */ ACL_WHERE_DATA, /* ) */ -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR ACL_WHERE_PRDR, /* ) */ #endif ACL_WHERE_NOTSMTP, /* ) */ diff -Nru exim4-4.82/src/malware.c exim4-4.84/src/malware.c --- exim4-4.82/src/malware.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/malware.c 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) Tom Kistner 2003-???? */ +/* Copyright (c) Tom Kistner 2003-2014 */ /* License: GPL */ /* Code for calling virus (malware) scanners. Called from acl.c. */ @@ -10,6 +10,30 @@ #include "exim.h" #ifdef WITH_CONTENT_SCAN +typedef enum {M_FPROTD, M_DRWEB, M_AVES, M_FSEC, M_KAVD, M_CMDL, + M_SOPHIE, M_CLAMD, M_SOCK, M_MKSD} scanner_t; +typedef enum {MC_NONE, MC_TCP, MC_UNIX, MC_STRM} contype_t; +static struct scan +{ + scanner_t scancode; + const uschar * name; + const uschar * options_default; + contype_t conn; +} m_scans[] = +{ + { M_FPROTD, US"f-protd", US"localhost 10200-10204", MC_TCP }, + { M_DRWEB, US"drweb", US"/usr/local/drweb/run/drwebd.sock", MC_STRM }, + { M_AVES, US"aveserver", US"/var/run/aveserver", MC_UNIX }, + { M_FSEC, US"fsecure", US"/var/run/.fsav", MC_UNIX }, + { M_KAVD, US"kavdaemon", US"/var/run/AvpCtl", MC_UNIX }, + { M_CMDL, US"cmdline", NULL, MC_NONE }, + { M_SOPHIE, US"sophie", US"/var/run/sophie", MC_UNIX }, + { M_CLAMD, US"clamd", US"/tmp/clamd", MC_NONE }, + { M_SOCK, US"sock", US"/tmp/malware.sock", MC_STRM }, + { M_MKSD, US"mksd", NULL, MC_NONE }, + { -1, NULL, NULL, MC_NONE } /* end-marker */ +}; + /* The maximum number of clamd servers that are supported in the configuration */ #define MAX_CLAMD_SERVERS 32 #define MAX_CLAMD_SERVERS_S "32" @@ -18,17 +42,16 @@ #define MAX_CLAMD_ADDRESS_LENGTH_S "64" typedef struct clamd_address_container { - uschar tcp_addr[MAX_CLAMD_ADDRESS_LENGTH]; + uschar tcp_addr[MAX_CLAMD_ADDRESS_LENGTH+1]; unsigned int tcp_port; } clamd_address_container; /* declaration of private routines */ -static int mksd_scan_packed(int sock, uschar *scan_filename); +static int mksd_scan_packed(struct scan * scanent, int sock, uschar *scan_filename); static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking); -/* SHUT_WR seems to be undefined on Unixware? */ -#ifndef SHUT_WR -#define SHUT_WR 1 +#ifndef nelements +# define nelements(arr) (sizeof(arr) / sizeof(arr[0])) #endif @@ -44,20 +67,21 @@ #define DERR_TIMEOUT (1<<9) /* scan timeout has run out */ #define DERR_BAD_CALL (1<<15) /* wrong command */ -/* Routine to check whether a system is big- or litte-endian. +/* Routine to check whether a system is big- or little-endian. Ripped from http://www.faqs.org/faqs/graphics/fileformats-faq/part4/section-7.html Needed for proper kavdaemon implementation. Sigh. */ #define BIG_MY_ENDIAN 0 #define LITTLE_MY_ENDIAN 1 -int test_byte_order(void); -int test_byte_order() { - short int word = 0x0001; - char *byte = (char *) &word; - return(byte[0] ? LITTLE_MY_ENDIAN : BIG_MY_ENDIAN); +static int test_byte_order(void); +static inline int +test_byte_order() +{ + short int word = 0x0001; + char *byte = (char *) &word; + return(byte[0] ? LITTLE_MY_ENDIAN : BIG_MY_ENDIAN); } -uschar malware_name_buffer[256]; -int malware_ok = 0; +BOOL malware_ok = FALSE; /* Gross hacks for the -bmalware option; perhaps we should just create the scan directory normally for that case, but look into rigging up the @@ -78,21 +102,14 @@ Returns: Exim message processing code (OK, FAIL, DEFER, ...) where true means malware was found (condition applies) */ -int malware(uschar **listptr) { - uschar scan_filename[1024]; - BOOL fits; +int +malware(uschar **listptr) +{ + uschar * scan_filename; int ret; - fits = string_format(scan_filename, sizeof(scan_filename), - CS"%s/scan/%s/%s.eml", spool_directory, message_id, message_id); - if (!fits) - { - av_failed = TRUE; - log_write(0, LOG_MAIN|LOG_PANIC, - "malware filename does not fit in buffer [malware()]"); - return DEFER; - } - + scan_filename = string_sprintf("%s/scan/%s/%s.eml", + spool_directory, message_id, message_id); ret = malware_internal(listptr, scan_filename, FALSE); if (ret == DEFER) av_failed = TRUE; @@ -116,7 +133,8 @@ where true means malware was found (condition applies) */ int -malware_in_file(uschar *eml_filename) { +malware_in_file(uschar *eml_filename) +{ uschar *scan_options[2]; uschar message_id_buf[64]; int ret; @@ -150,6 +168,142 @@ } +static inline int +malware_errlog_defer(const uschar * str) +{ + log_write(0, LOG_MAIN|LOG_PANIC, "malware acl condition: %s", str); + return DEFER; +} + +static int +m_errlog_defer(struct scan * scanent, const uschar * str) +{ + return malware_errlog_defer(string_sprintf("%s: %s", scanent->name, str)); +} +static int +m_errlog_defer_3(struct scan * scanent, const uschar * str, + int fd_to_close) +{ + (void) close(fd_to_close); + return m_errlog_defer(scanent, str); +} + +/*************************************************/ + +/* Only used by the Clamav code, which is working from a list of servers and +uses the returned in_addr to get a second connection to the same system. +*/ +static inline int +m_tcpsocket(const uschar * hostname, unsigned int port, + host_item * host, uschar ** errstr) +{ + return ip_connectedsocket(SOCK_STREAM, hostname, port, port, 5, host, errstr); +} + +static int +m_tcpsocket_fromdef(const uschar * hostport, uschar ** errstr) +{ + int scan; + uschar hostname[256]; + unsigned int portlow, porthigh; + + /* extract host and port part */ + scan = sscanf(CS hostport, "%255s %u-%u", hostname, &portlow, &porthigh); + if ( scan != 3 ) { + if ( scan != 2 ) { + *errstr = string_sprintf("invalid socket '%s'", hostport); + return -1; + } + porthigh = portlow; + } + + return ip_connectedsocket(SOCK_STREAM, hostname, portlow, porthigh, + 5, NULL, errstr); +} + +static int +m_unixsocket(const uschar * path, uschar ** errstr) +{ + int sock; + struct sockaddr_un server; + + if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { + *errstr = US"can't open UNIX socket."; + return -1; + } + + server.sun_family = AF_UNIX; + Ustrncpy(server.sun_path, path, sizeof(server.sun_path)-1); + server.sun_path[sizeof(server.sun_path)-1] = '\0'; + if (connect(sock, (struct sockaddr *) &server, sizeof(server)) < 0) { + int err = errno; + (void)close(sock); + *errstr = string_sprintf("unable to connect to UNIX socket (%s): %s", + path, strerror(err)); + return -1; + } + return sock; +} + +static inline int +m_streamsocket(const uschar * spec, uschar ** errstr) +{ + return *spec == '/' + ? m_unixsocket(spec, errstr) : m_tcpsocket_fromdef(spec, errstr); +} + +static int +m_sock_send(int sock, uschar * buf, int cnt, uschar ** errstr) +{ + if (send(sock, buf, cnt, 0) < 0) { + int err = errno; + (void)close(sock); + *errstr = string_sprintf("unable to send to socket (%s): %s", + buf, strerror(err)); + return -1; + } + return sock; +} + +static const pcre * +m_pcre_compile(const uschar * re, uschar ** errstr) +{ + const uschar * rerror; + int roffset; + const pcre * cre; + + cre = pcre_compile(CS re, PCRE_COPT, (const char **)&rerror, &roffset, NULL); + if (!cre) + *errstr= string_sprintf("regular expression error in '%s': %s at offset %d", + re, rerror, roffset); + return cre; +} + +uschar * +m_pcre_exec(const pcre * cre, uschar * text) +{ + int ovector[10*3]; + int i = pcre_exec(cre, NULL, CS text, Ustrlen(text), 0, 0, + ovector, nelements(ovector)); + uschar * substr = NULL; + if (i >= 2) /* Got it */ + pcre_get_substring(CS text, ovector, i, 1, (const char **) &substr); + return substr; +} + +static const pcre * +m_pcre_nextinlist(uschar ** list, int * sep, char * listerr, uschar ** errstr) +{ + const uschar * list_ele; + const pcre * cre = NULL; + + if (!(list_ele = string_nextinlist(list, sep, NULL, 0))) + *errstr = US listerr; + else + cre = m_pcre_compile(CUS list_ele, errstr); + return cre; +} + /************************************************* * Scan content for malware * *************************************************/ @@ -165,1728 +319,1235 @@ Returns: Exim message processing code (OK, FAIL, DEFER, ...) where true means malware was found (condition applies) */ -static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) { +static int +malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking) +{ int sep = 0; uschar *list = *listptr; uschar *av_scanner_work = av_scanner; uschar *scanner_name; - uschar scanner_name_buffer[16]; uschar *malware_regex; - uschar malware_regex_buffer[64]; uschar malware_regex_default[] = ".+"; unsigned long mbox_size; FILE *mbox_file; - int roffset; const pcre *re; - const uschar *rerror; + uschar * errstr; + struct scan * scanent; + const uschar * scanner_options; + int sock = -1; /* make sure the eml mbox file is spooled up */ - mbox_file = spool_mbox(&mbox_size, faking ? eml_filename : NULL); - if (mbox_file == NULL) { - /* error while spooling */ - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: error while creating mbox spool file"); - return DEFER; - }; + if (!(mbox_file = spool_mbox(&mbox_size, faking ? eml_filename : NULL))) + return malware_errlog_defer(US"error while creating mbox spool file"); + /* none of our current scanners need the mbox file as a stream, so we can close it right away */ (void)fclose(mbox_file); /* extract the malware regex to match against from the option list */ - if ((malware_regex = string_nextinlist(&list, &sep, - malware_regex_buffer, - sizeof(malware_regex_buffer))) != NULL) { + if (!(malware_regex = string_nextinlist(&list, &sep, NULL, 0))) + return FAIL; /* empty means "don't match anything" */ - /* parse 1st option */ + /* parse 1st option */ if ( (strcmpic(malware_regex,US"false") == 0) || - (Ustrcmp(malware_regex,"0") == 0) ) { - /* explicitly no matching */ - return FAIL; - }; - - /* special cases (match anything except empty) */ - if ( (strcmpic(malware_regex,US"true") == 0) || - (Ustrcmp(malware_regex,"*") == 0) || - (Ustrcmp(malware_regex,"1") == 0) ) { - malware_regex = malware_regex_default; - }; - } - else { - /* empty means "don't match anything" */ - return FAIL; - }; + (Ustrcmp(malware_regex,"0") == 0) ) + return FAIL; /* explicitly no matching */ + + /* special cases (match anything except empty) */ + if ( (strcmpic(malware_regex,US"true") == 0) || + (Ustrcmp(malware_regex,"*") == 0) || + (Ustrcmp(malware_regex,"1") == 0) ) + malware_regex = malware_regex_default; /* Reset sep that is set by previous string_nextinlist() call */ sep = 0; /* compile the regex, see if it works */ - re = pcre_compile(CS malware_regex, PCRE_COPT, (const char **)&rerror, &roffset, NULL); - if (re == NULL) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: regular expression error in '%s': %s at offset %d", malware_regex, rerror, roffset); - return DEFER; - }; + if (!(re = m_pcre_compile(malware_regex, &errstr))) + return malware_errlog_defer(errstr); /* if av_scanner starts with a dollar, expand it first */ if (*av_scanner == '$') { - av_scanner_work = expand_string(av_scanner); - if (av_scanner_work == NULL) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: av_scanner starts with $, but expansion failed: %s", expand_string_message); - return DEFER; - } - else { - debug_printf("Expanded av_scanner global: %s\n", av_scanner_work); - /* disable result caching in this case */ - malware_name = NULL; - malware_ok = 0; - }; + if (!(av_scanner_work = expand_string(av_scanner))) + return malware_errlog_defer( + string_sprintf("av_scanner starts with $, but expansion failed: %s", + expand_string_message)); + + debug_printf("Expanded av_scanner global: %s\n", av_scanner_work); + /* disable result caching in this case */ + malware_name = NULL; + malware_ok = FALSE; } - /* Do not scan twice. */ - if (malware_ok == 0) { + /* Do not scan twice (unless av_scanner is dynamic). */ + if (!malware_ok) { /* find the scanner type from the av_scanner option */ - if ((scanner_name = string_nextinlist(&av_scanner_work, &sep, - scanner_name_buffer, - sizeof(scanner_name_buffer))) == NULL) { - /* no scanner given */ - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: av_scanner configuration variable is empty"); - return DEFER; - }; - - /* "f-protd" scanner type ----------------------------------------------- */ - if (strcmpic(scanner_name, US"f-protd") == 0) { - uschar *fp_options, *fp_scan_option; - uschar fp_scan_option_buffer[1024]; - uschar fp_options_buffer[1024]; - uschar fp_options_default[] = "localhost 10200-10204"; - uschar hostname[256]; - unsigned int port, portlow, porthigh, connect_ok=0, detected=0, par_count = 0; - struct hostent *he; - struct in_addr in; - int sock; - uschar scanrequest[2048], buf[32768], *strhelper, *strhelper2; - - if ((fp_options = string_nextinlist(&av_scanner_work, &sep, - fp_options_buffer, sizeof(fp_options_buffer))) == NULL) { - /* no options supplied, use default options */ - fp_options = fp_options_default; - }; - - /* extract host and port part */ - if ( sscanf(CS fp_options, "%s %u-%u", hostname, &portlow, &porthigh) != 3 ) { - if ( sscanf(CS fp_options, "%s %u", hostname, &portlow) != 2 ) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: f-protd: invalid socket '%s'", fp_options); - return DEFER; - } - porthigh = portlow; - } - - /* Lookup the host */ - if((he = gethostbyname(CS hostname)) == 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: f-protd: failed to lookup host '%s'", hostname); - return DEFER; - } - - in = *(struct in_addr *) he->h_addr_list[0]; - port = portlow; - - - /* Open the f-protd TCP socket */ - if ( (sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: f-protd: unable to acquire socket (%s)", - strerror(errno)); - return DEFER; - } + if (!(scanner_name = string_nextinlist(&av_scanner_work, &sep, NULL, 0))) + return malware_errlog_defer(US"av_scanner configuration variable is empty"); - /* Try to connect to all portslow-high until connection is established */ - for (port = portlow; !connect_ok && port < porthigh; port++) { - if (ip_connect(sock, AF_INET, (uschar*)inet_ntoa(in), port, 5) >= 0) { - connect_ok = 1; - } - } - - if ( !connect_ok ) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: f-protd: connection to %s, port %u-%u failed (%s)", - inet_ntoa(in), portlow, porthigh, strerror(errno)); - (void)close(sock); - return DEFER; - } - - DEBUG(D_acl) debug_printf("Malware scan: issuing %s GET\n", scanner_name); - (void)string_format(scanrequest, 1024, CS"GET %s", eml_filename); - - while ((fp_scan_option = string_nextinlist(&av_scanner_work, &sep, - fp_scan_option_buffer, sizeof(fp_scan_option_buffer))) != NULL) { - if ( par_count ) { - Ustrcat(scanrequest, "%20"); - } else { - Ustrcat(scanrequest, "?"); - } - Ustrcat(scanrequest, fp_scan_option); - par_count++; - } - Ustrcat(scanrequest, " HTTP/1.0\r\n\r\n"); - - /* send scan request */ - if (send(sock, &scanrequest, Ustrlen(scanrequest)+1, 0) < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: f-protd: unable to send command to socket (%s)", scanrequest); - return DEFER; - } - - /* We get a lot of empty lines, so we need this hack to check for any data at all */ - while( recv(sock, buf, 1, MSG_PEEK) > 0 ) { - if ( recv_line(sock, buf, 32768) > 0) { - if ( Ustrstr(buf, US"")) ) { - if ((strhelper2 = Ustrstr(buf, US"")) != NULL) { - *strhelper2 = '\0'; - Ustrcpy(malware_name_buffer, strhelper + 6); - } - } else if ( Ustrstr(buf, US"") ) { - malware_name = malware_name_buffer; - } else { - malware_name = NULL; - } - } - } - } - (void)close(sock); - } - /* "drweb" scanner type ----------------------------------------------- */ - /* v0.1 - added support for tcp sockets */ - /* v0.0 - initial release -- support for unix sockets */ - else if (strcmpic(scanner_name,US"drweb") == 0) { - uschar *drweb_options; - uschar drweb_options_buffer[1024]; - uschar drweb_options_default[] = "/usr/local/drweb/run/drwebd.sock"; - struct sockaddr_un server; - int sock, result, ovector[30]; - unsigned int port, fsize; - uschar tmpbuf[1024], *drweb_fbuf; - uschar drweb_match_string[128]; - int drweb_rc, drweb_cmd, drweb_flags = 0x0000, drweb_fd, - drweb_vnum, drweb_slen, drweb_fin = 0x0000; - unsigned long bread; - uschar hostname[256]; - struct hostent *he; - struct in_addr in; - pcre *drweb_re; - - if ((drweb_options = string_nextinlist(&av_scanner_work, &sep, - drweb_options_buffer, sizeof(drweb_options_buffer))) == NULL) { - /* no options supplied, use default options */ - drweb_options = drweb_options_default; - }; - - if (*drweb_options != '/') { - - /* extract host and port part */ - if( sscanf(CS drweb_options, "%s %u", hostname, &port) != 2 ) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: invalid socket '%s'", drweb_options); - return DEFER; - } - - /* Lookup the host */ - if((he = gethostbyname(CS hostname)) == 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: failed to lookup host '%s'", hostname); - return DEFER; - } - - in = *(struct in_addr *) he->h_addr_list[0]; - - /* Open the drwebd TCP socket */ - if ( (sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: unable to acquire socket (%s)", - strerror(errno)); - return DEFER; - } - - if (ip_connect(sock, AF_INET, (uschar*)inet_ntoa(in), port, 5) < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: connection to %s, port %u failed (%s)", - inet_ntoa(in), port, strerror(errno)); - return DEFER; - } - - /* prepare variables */ - drweb_cmd = htonl(DRWEBD_SCAN_CMD); - drweb_flags = htonl(DRWEBD_RETURN_VIRUSES | DRWEBD_IS_MAIL); - - /* calc file size */ - drweb_fd = open(CS eml_filename, O_RDONLY); - if (drweb_fd == -1) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: can't open spool file %s: %s", - eml_filename, strerror(errno)); - return DEFER; - } - fsize = lseek(drweb_fd, 0, SEEK_END); - if (fsize == -1) { - (void)close(sock); - (void)close(drweb_fd); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: can't seek spool file %s: %s", - eml_filename, strerror(errno)); - return DEFER; - } - drweb_slen = htonl(fsize); - lseek(drweb_fd, 0, SEEK_SET); - - DEBUG(D_acl) debug_printf("Malware scan: issuing %s remote scan [%s %u]\n", - scanner_name, hostname, port); - - /* send scan request */ - if ((send(sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) || - (send(sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) || - (send(sock, &drweb_fin, sizeof(drweb_fin), 0) < 0) || - (send(sock, &drweb_slen, sizeof(drweb_slen), 0) < 0)) { - (void)close(sock); - (void)close(drweb_fd); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: unable to send commands to socket (%s)", drweb_options); - return DEFER; - } - - drweb_fbuf = (uschar *) malloc (fsize); - if (!drweb_fbuf) { - (void)close(sock); - (void)close(drweb_fd); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: unable to allocate memory %u for file (%s)", - fsize, eml_filename); - return DEFER; - } - - result = read (drweb_fd, drweb_fbuf, fsize); - if (result == -1) { - (void)close(sock); - (void)close(drweb_fd); - free(drweb_fbuf); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: can't read spool file %s: %s", - eml_filename, strerror(errno)); - return DEFER; - } - (void)close(drweb_fd); - - /* send file body to socket */ - if (send(sock, drweb_fbuf, fsize, 0) < 0) { - (void)close(sock); - free(drweb_fbuf); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: unable to send file body to socket (%s)", drweb_options); - return DEFER; - } - (void)close(drweb_fd); - } - else { - /* open the drwebd UNIX socket */ - sock = socket(AF_UNIX, SOCK_STREAM, 0); - if (sock < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: can't open UNIX socket"); - return DEFER; - } - server.sun_family = AF_UNIX; - Ustrcpy(server.sun_path, drweb_options); - if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: unable to connect to socket (%s). errno=%d", drweb_options, errno); - return DEFER; - } - - /* prepare variables */ - drweb_cmd = htonl(DRWEBD_SCAN_CMD); - drweb_flags = htonl(DRWEBD_RETURN_VIRUSES | DRWEBD_IS_MAIL); - drweb_slen = htonl(Ustrlen(eml_filename)); - - DEBUG(D_acl) debug_printf("Malware scan: issuing %s local scan [%s]\n", - scanner_name, drweb_options); - - /* send scan request */ - if ((send(sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) || - (send(sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) || - (send(sock, &drweb_slen, sizeof(drweb_slen), 0) < 0) || - (send(sock, eml_filename, Ustrlen(eml_filename), 0) < 0) || - (send(sock, &drweb_fin, sizeof(drweb_fin), 0) < 0)) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: unable to send commands to socket (%s)", drweb_options); - return DEFER; - } - } - - /* wait for result */ - if ((bread = recv(sock, &drweb_rc, sizeof(drweb_rc), 0) != sizeof(drweb_rc))) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: unable to read return code"); - return DEFER; - } - drweb_rc = ntohl(drweb_rc); - - if ((bread = recv(sock, &drweb_vnum, sizeof(drweb_vnum), 0) != sizeof(drweb_vnum))) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: unable to read the number of viruses"); - return DEFER; - } - drweb_vnum = ntohl(drweb_vnum); - - /* "virus(es) found" if virus number is > 0 */ - if (drweb_vnum) - { - int i; - uschar pre_malware_nb[256]; - - malware_name = malware_name_buffer; - - /* setup default virus name */ - Ustrcpy(malware_name_buffer,"unknown"); - - /* read and concatenate virus names into one string */ - for (i=0;iname) + return malware_errlog_defer(string_sprintf("unknown scanner type '%s'", + scanner_name)); + if (strcmpic(scanner_name, US scanent->name) != 0) + continue; + if (!(scanner_options = string_nextinlist(&av_scanner_work, &sep, NULL, 0))) + scanner_options = scanent->options_default; + if (scanent->conn == MC_NONE) + break; + switch(scanent->conn) { - /* read the size of report */ - if ((bread = recv(sock, &drweb_slen, sizeof(drweb_slen), 0) != sizeof(drweb_slen))) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: cannot read report size"); - return DEFER; - }; - drweb_slen = ntohl(drweb_slen); - - /* read report body */ - if ((bread = recv(sock, tmpbuf, drweb_slen, 0)) != drweb_slen) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: cannot read report string"); - return DEFER; - }; - tmpbuf[drweb_slen] = '\0'; - - /* set up match regex, depends on retcode */ - Ustrcpy(drweb_match_string, "infected\\swith\\s*(.+?)$"); - - drweb_re = pcre_compile( CS drweb_match_string, - PCRE_COPT, - (const char **)&rerror, - &roffset, - NULL ); - - /* try matcher on the line, grab substring */ - result = pcre_exec(drweb_re, NULL, CS tmpbuf, Ustrlen(tmpbuf), 0, 0, ovector, 30); - if (result >= 2) { - pcre_copy_substring(CS tmpbuf, ovector, result, 1, CS pre_malware_nb, 255); - } - /* the first name we just copy to malware_name */ - if (i==0) - Ustrcpy(CS malware_name_buffer, CS pre_malware_nb); - else { - /* concatenate each new virus name to previous */ - int slen = Ustrlen(malware_name_buffer); - if (slen < (slen+Ustrlen(pre_malware_nb))) { - Ustrcat(malware_name_buffer, "/"); - Ustrcat(malware_name_buffer, pre_malware_nb); - } - } - } + case MC_TCP: sock = m_tcpsocket_fromdef(scanner_options, &errstr); break; + case MC_UNIX: sock = m_unixsocket(scanner_options, &errstr); break; + case MC_STRM: sock = m_streamsocket(scanner_options, &errstr); break; + default: /* compiler quietening */ break; + } + if (sock < 0) + return m_errlog_defer(scanent, errstr); + break; } - else { - const char *drweb_s = NULL; + DEBUG(D_lookup) debug_printf("Malware scan: %s\n", scanner_name); - if (drweb_rc & DERR_READ_ERR) drweb_s = "read error"; - if (drweb_rc & DERR_NOMEMORY) drweb_s = "no memory"; - if (drweb_rc & DERR_TIMEOUT) drweb_s = "timeout"; - if (drweb_rc & DERR_BAD_CALL) drweb_s = "wrong command"; - /* retcodes DERR_SYMLINK, DERR_NO_REGFILE, DERR_SKIPPED. - * DERR_TOO_BIG, DERR_TOO_COMPRESSED, DERR_SPAM, - * DERR_CRC_ERROR, DERR_READSOCKET, DERR_WRITE_ERR - * and others are ignored */ - if (drweb_s) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: drweb: drweb daemon retcode 0x%x (%s)", drweb_rc, drweb_s); - (void)close(sock); - return DEFER; - } - /* no virus found */ - malware_name = NULL; - }; - (void)close(sock); - } - /* ----------------------------------------------------------------------- */ - else if (strcmpic(scanner_name,US"aveserver") == 0) { - uschar *kav_options; - uschar kav_options_buffer[1024]; - uschar kav_options_default[] = "/var/run/aveserver"; - uschar buf[32768]; - struct sockaddr_un server; - int sock; - int result; - - if ((kav_options = string_nextinlist(&av_scanner_work, &sep, - kav_options_buffer, - sizeof(kav_options_buffer))) == NULL) { - /* no options supplied, use default options */ - kav_options = kav_options_default; - }; - - /* open the aveserver socket */ - sock = socket(AF_UNIX, SOCK_STREAM, 0); - if (sock < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: can't open UNIX socket."); - return DEFER; - } - server.sun_family = AF_UNIX; - Ustrcpy(server.sun_path, kav_options); - if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to connect to aveserver UNIX socket (%s). errno=%d", kav_options, errno); - return DEFER; - } - - /* read aveserver's greeting and see if it is ready (2xx greeting) */ - recv_line(sock, buf, 32768); - - if (buf[0] != '2') { - /* aveserver is having problems */ - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: aveserver is unavailable (Responded: %s).", ((buf[0] != 0) ? buf : (uschar *)"nothing") ); - return DEFER; - }; - - /* prepare our command */ - (void)string_format(buf, 32768, "SCAN bPQRSTUW %s\r\n", eml_filename); - - DEBUG(D_acl) debug_printf("Malware scan: issuing %s SCAN\n", scanner_name); - - /* and send it */ - if (send(sock, buf, Ustrlen(buf), 0) < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to write to aveserver UNIX socket (%s)", kav_options); - return DEFER; - } - - malware_name = NULL; - result = 0; - /* read response lines, find malware name and final response */ - while (recv_line(sock, buf, 32768) > 0) { - debug_printf("aveserver: %s\n", buf); - if (buf[0] == '2') { - break; - } else if (buf[0] == '5') { - /* aveserver is having problems */ - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to scan file %s (Responded: %s).", - eml_filename, buf); - result = DEFER; - break; - } else if (Ustrncmp(buf,"322",3) == 0) { - uschar *p = Ustrchr(&buf[4],' '); - *p = '\0'; - Ustrcpy(malware_name_buffer,&buf[4]); - malware_name = malware_name_buffer; - }; - } - - /* prepare our command */ - (void)string_format(buf, 32768, "quit\r\n"); - - /* and send it */ - if (send(sock, buf, Ustrlen(buf), 0) < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to write to aveserver UNIX socket (%s)", kav_options); - return DEFER; - } - - /* read aveserver's greeting and see if it is ready (2xx greeting) */ - recv_line(sock, buf, 32768); - - if (buf[0] != '2') { - /* aveserver is having problems */ - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to quit aveserver dialogue (Responded: %s).", ((buf[0] != 0) ? buf : (uschar *)"nothing") ); - return DEFER; - }; - - (void)close(sock); - - if (result == DEFER) return DEFER; - } - /* "fsecure" scanner type ------------------------------------------------- */ - else if (strcmpic(scanner_name,US"fsecure") == 0) { - uschar *fsecure_options; - uschar fsecure_options_buffer[1024]; - uschar fsecure_options_default[] = "/var/run/.fsav"; - struct sockaddr_un server; - int sock, i, j, bread = 0; - uschar file_name[1024]; - uschar av_buffer[1024]; - pcre *fs_inf; - static uschar *cmdoptions[] = { US"CONFIGURE\tARCHIVE\t1\n", - US"CONFIGURE\tTIMEOUT\t0\n", - US"CONFIGURE\tMAXARCH\t5\n", - US"CONFIGURE\tMIME\t1\n" }; - - malware_name = NULL; - if ((fsecure_options = string_nextinlist(&av_scanner_work, &sep, - fsecure_options_buffer, - sizeof(fsecure_options_buffer))) == NULL) { - /* no options supplied, use default options */ - fsecure_options = fsecure_options_default; - }; - - /* open the fsecure socket */ - sock = socket(AF_UNIX, SOCK_STREAM, 0); - if (sock < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to open fsecure socket %s (%s)", - fsecure_options, strerror(errno)); - return DEFER; - } - server.sun_family = AF_UNIX; - Ustrcpy(server.sun_path, fsecure_options); - if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to connect to fsecure socket %s (%s)", - fsecure_options, strerror(errno)); - return DEFER; - } - - DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", - scanner_name, fsecure_options); - - /* pass options */ - memset(av_buffer, 0, sizeof(av_buffer)); - for (i=0; i != 4; i++) { - /* debug_printf("send option \"%s\"",cmdoptions[i]); */ - if (write(sock, cmdoptions[i], Ustrlen(cmdoptions[i])) < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to write fsecure option %d to %s (%s)", - i, fsecure_options, strerror(errno)); - return DEFER; - }; - - bread = ip_recv(sock, av_buffer, sizeof(av_buffer), MALWARE_TIMEOUT); - if (bread >0) av_buffer[bread]='\0'; - if (bread < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to read fsecure answer %d (%s)", i, strerror(errno)); - return DEFER; - }; - for (j=0;j= 2) { - /* Got it */ - pcre_copy_substring(CS av_buffer, ovector, i, 1, CS malware_name_buffer, 255); - malware_name = malware_name_buffer; - }; - }; - } - while (Ustrstr(av_buffer, "OK\tScan ok.") == NULL); - (void)close(sock); - } - /* ----------------------------------------------------------------------- */ - - /* "kavdaemon" scanner type ------------------------------------------------ */ - else if (strcmpic(scanner_name,US"kavdaemon") == 0) { - uschar *kav_options; - uschar kav_options_buffer[1024]; - uschar kav_options_default[] = "/var/run/AvpCtl"; - struct sockaddr_un server; - int sock; - time_t t; - uschar tmpbuf[1024]; - uschar scanrequest[1024]; - uschar kav_match_string[128]; - int kav_rc; - unsigned long kav_reportlen, bread; - pcre *kav_re; - uschar *p; - int fits; - - if ((kav_options = string_nextinlist(&av_scanner_work, &sep, - kav_options_buffer, - sizeof(kav_options_buffer))) == NULL) { - /* no options supplied, use default options */ - kav_options = kav_options_default; - }; - - /* open the kavdaemon socket */ - sock = socket(AF_UNIX, SOCK_STREAM, 0); - if (sock < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: can't open UNIX socket."); - return DEFER; - } - server.sun_family = AF_UNIX; - Ustrcpy(server.sun_path, kav_options); - if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to connect to kavdaemon UNIX socket (%s). errno=%d", kav_options, errno); - return DEFER; - } - - /* get current date and time, build scan request */ - time(&t); - /* pdp note: before the eml_filename parameter, this scanned the - directory; not finding documentation, so we'll strip off the directory. - The side-effect is that the test framework scanning may end up in - scanning more than was requested, but for the normal interface, this is - fine. */ - strftime(CS tmpbuf, sizeof(tmpbuf), "<0>%d %b %H:%M:%S:%%s", localtime(&t)); - fits = string_format(scanrequest, 1024,CS tmpbuf, eml_filename); - if (!fits) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware filename does not fit in buffer [malware_internal() kavdaemon]"); - } - p = Ustrrchr(scanrequest, '/'); - if (p) - *p = '\0'; - - DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", - scanner_name, kav_options); - - /* send scan request */ - if (send(sock, scanrequest, Ustrlen(scanrequest)+1, 0) < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to write to kavdaemon UNIX socket (%s)", kav_options); - return DEFER; - } - - /* wait for result */ - if ((bread = recv(sock, tmpbuf, 2, 0) != 2)) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to read 2 bytes from kavdaemon socket."); - return DEFER; - } - - /* get errorcode from one nibble */ - if (test_byte_order() == LITTLE_MY_ENDIAN) { - kav_rc = tmpbuf[0] & 0x0F; - } - else { - kav_rc = tmpbuf[1] & 0x0F; - }; - - /* improper kavdaemon configuration */ - if ( (kav_rc == 5) || (kav_rc == 6) ) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: please reconfigure kavdaemon to NOT disinfect or remove infected files."); - return DEFER; - }; - - if (kav_rc == 1) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: kavdaemon reported 'scanning not completed' (code 1)."); - return DEFER; - }; - - if (kav_rc == 7) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: kavdaemon reported 'kavdaemon damaged' (code 7)."); - return DEFER; - }; - - /* code 8 is not handled, since it is ambigous. It appears mostly on - bounces where part of a file has been cut off */ - - /* "virus found" return codes (2-4) */ - if ((kav_rc > 1) && (kav_rc < 5)) { - int report_flag = 0; - - /* setup default virus name */ - Ustrcpy(malware_name_buffer,"unknown"); - malware_name = malware_name_buffer; - - if (test_byte_order() == LITTLE_MY_ENDIAN) { - report_flag = tmpbuf[1]; - } - else { - report_flag = tmpbuf[0]; - }; - - /* read the report, if available */ - if( report_flag == 1 ) { - /* read report size */ - if ((bread = recv(sock, &kav_reportlen, 4, 0)) != 4) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: cannot read report size from kavdaemon"); - return DEFER; - }; - - /* it's possible that avp returns av_buffer[1] == 1 but the - reportsize is 0 (!?) */ - if (kav_reportlen > 0) { - /* set up match regex, depends on retcode */ - if( kav_rc == 3 ) - Ustrcpy(kav_match_string, "suspicion:\\s*(.+?)\\s*$"); - else - Ustrcpy(kav_match_string, "infected:\\s*(.+?)\\s*$"); - - kav_re = pcre_compile( CS kav_match_string, - PCRE_COPT, - (const char **)&rerror, - &roffset, - NULL ); - - /* read report, linewise */ - while (kav_reportlen > 0) { - int result = 0; - int ovector[30]; - - bread = 0; - while ( recv(sock, &tmpbuf[bread], 1, 0) == 1 ) { - kav_reportlen--; - if ( (tmpbuf[bread] == '\n') || (bread > 1021) ) break; - bread++; - }; - bread++; - tmpbuf[bread] = '\0'; - - /* try matcher on the line, grab substring */ - result = pcre_exec(kav_re, NULL, CS tmpbuf, Ustrlen(tmpbuf), 0, 0, ovector, 30); - if (result >= 2) { - pcre_copy_substring(CS tmpbuf, ovector, result, 1, CS malware_name_buffer, 255); - break; - }; - }; - }; - }; - } - else { - /* no virus found */ - malware_name = NULL; - }; - - (void)close(sock); - } - /* ----------------------------------------------------------------------- */ - - - /* "cmdline" scanner type ------------------------------------------------ */ - else if (strcmpic(scanner_name,US"cmdline") == 0) { - uschar *cmdline_scanner; - uschar cmdline_scanner_buffer[1024]; - uschar *cmdline_trigger; - uschar cmdline_trigger_buffer[1024]; - const pcre *cmdline_trigger_re; - uschar *cmdline_regex; - uschar cmdline_regex_buffer[1024]; - const pcre *cmdline_regex_re; - uschar file_name[1024]; - uschar commandline[1024]; - void (*eximsigchld)(int); - void (*eximsigpipe)(int); - FILE *scanner_out = NULL; - FILE *scanner_record = NULL; - uschar linebuffer[32767]; - int trigger = 0; - int result; - int ovector[30]; - uschar *p; - BOOL fits; - - /* find scanner command line */ - if ((cmdline_scanner = string_nextinlist(&av_scanner_work, &sep, - cmdline_scanner_buffer, - sizeof(cmdline_scanner_buffer))) == NULL) { - /* no command line supplied */ - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: missing commandline specification for cmdline scanner type."); - return DEFER; - }; - - /* find scanner output trigger */ - if ((cmdline_trigger = string_nextinlist(&av_scanner_work, &sep, - cmdline_trigger_buffer, - sizeof(cmdline_trigger_buffer))) == NULL) { - /* no trigger regex supplied */ - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: missing trigger specification for cmdline scanner type."); - return DEFER; - }; - - /* precompile trigger regex */ - cmdline_trigger_re = pcre_compile(CS cmdline_trigger, PCRE_COPT, (const char **)&rerror, &roffset, NULL); - if (cmdline_trigger_re == NULL) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: regular expression error in '%s': %s at offset %d", cmdline_trigger, rerror, roffset); - return DEFER; - }; - - /* find scanner name regex */ - if ((cmdline_regex = string_nextinlist(&av_scanner_work, &sep, - cmdline_regex_buffer, - sizeof(cmdline_regex_buffer))) == NULL) { - /* no name regex supplied */ - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: missing virus name regex specification for cmdline scanner type."); - return DEFER; - }; - - /* precompile name regex */ - cmdline_regex_re = pcre_compile(CS cmdline_regex, PCRE_COPT, (const char **)&rerror, &roffset, NULL); - if (cmdline_regex_re == NULL) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: regular expression error in '%s': %s at offset %d", cmdline_regex, rerror, roffset); - return DEFER; - }; - - /* prepare scanner call; despite the naming, file_name holds a directory - name which is documented as the value given to %s. */ - if (Ustrlen(eml_filename) > sizeof(file_name) - 1) - { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware filename does not fit in buffer [malware_internal() cmdline]"); - return DEFER; - } - Ustrcpy(file_name, eml_filename); - p = Ustrrchr(file_name, '/'); - if (p) - *p = '\0'; - fits = string_format(commandline, sizeof(commandline), CS cmdline_scanner, file_name); - if (!fits) - { - log_write(0, LOG_MAIN|LOG_PANIC, - "cmdline scanner command-line does not fit in buffer"); - return DEFER; - } - - /* redirect STDERR too */ - if (Ustrlen(commandline) + 5 > sizeof(commandline)) - { - log_write(0, LOG_MAIN|LOG_PANIC, - "cmdline scanner command-line does not fit in buffer (STDERR redirect)"); - return DEFER; - } - Ustrcat(commandline," 2>&1"); - - DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", scanner_name, commandline); - - /* store exims signal handlers */ - eximsigchld = signal(SIGCHLD,SIG_DFL); - eximsigpipe = signal(SIGPIPE,SIG_DFL); - - scanner_out = popen(CS commandline,"r"); - if (scanner_out == NULL) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: calling cmdline scanner (%s) failed: %s.", commandline, strerror(errno)); - signal(SIGCHLD,eximsigchld); - signal(SIGPIPE,eximsigpipe); - return DEFER; - }; - - (void)string_format(file_name,1024,"%s/scan/%s/%s_scanner_output", spool_directory, message_id, message_id); - scanner_record = modefopen(file_name,"wb",SPOOL_MODE); - - if (scanner_record == NULL) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: opening scanner output file (%s) failed: %s.", file_name, strerror(errno)); - pclose(scanner_out); - signal(SIGCHLD,eximsigchld); - signal(SIGPIPE,eximsigpipe); - return DEFER; - }; - - /* look for trigger while recording output */ - while(fgets(CS linebuffer,32767,scanner_out) != NULL) { - if ( Ustrlen(linebuffer) > fwrite(linebuffer, 1, Ustrlen(linebuffer), scanner_record) ) { - /* short write */ - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: short write on scanner output file (%s).", file_name); - pclose(scanner_out); - signal(SIGCHLD,eximsigchld); - signal(SIGPIPE,eximsigpipe); - return DEFER; - }; - /* try trigger match */ - if (!trigger && regex_match_and_setup(cmdline_trigger_re, linebuffer, 0, -1)) - trigger = 1; - }; - - (void)fclose(scanner_record); - pclose(scanner_out); - signal(SIGCHLD,eximsigchld); - signal(SIGPIPE,eximsigpipe); - - if (trigger) { - /* setup default virus name */ - Ustrcpy(malware_name_buffer,"unknown"); - malware_name = malware_name_buffer; - - /* re-open the scanner output file, look for name match */ - scanner_record = fopen(CS file_name,"rb"); - while(fgets(CS linebuffer,32767,scanner_record) != NULL) { - /* try match */ - result = pcre_exec(cmdline_regex_re, NULL, CS linebuffer, Ustrlen(linebuffer), 0, 0, ovector, 30); - if (result >= 2) { - pcre_copy_substring(CS linebuffer, ovector, result, 1, CS malware_name_buffer, 255); - }; - }; - (void)fclose(scanner_record); - } - else { - /* no virus found */ - malware_name = NULL; - }; - } - /* ----------------------------------------------------------------------- */ - - - /* "sophie" scanner type ------------------------------------------------- */ - else if (strcmpic(scanner_name,US"sophie") == 0) { - uschar *sophie_options; - uschar sophie_options_buffer[1024]; - uschar sophie_options_default[] = "/var/run/sophie"; - int bread = 0; - struct sockaddr_un server; - int sock, len; - uschar *p; - uschar file_name[1024]; - uschar av_buffer[1024]; - - if ((sophie_options = string_nextinlist(&av_scanner_work, &sep, - sophie_options_buffer, - sizeof(sophie_options_buffer))) == NULL) { - /* no options supplied, use default options */ - sophie_options = sophie_options_default; - } + switch (scanent->scancode) { + case M_FPROTD: /* "f-protd" scanner type -------------------------------- */ + { + uschar *fp_scan_option; + unsigned int detected=0, par_count=0; + uschar * scanrequest; + uschar buf[32768], *strhelper, *strhelper2; + uschar * malware_name_internal = NULL; + + DEBUG(D_acl) debug_printf("Malware scan: issuing %s GET\n", scanner_name); + scanrequest = string_sprintf("GET %s", eml_filename); + + while ((fp_scan_option = string_nextinlist(&av_scanner_work, &sep, + NULL, 0))) { + scanrequest = string_sprintf("%s%s%s", scanrequest, + par_count ? "%20" : "?", fp_scan_option); + par_count++; + } + scanrequest = string_sprintf("%s HTTP/1.0\r\n\r\n", scanrequest); - /* open the sophie socket */ - sock = socket(AF_UNIX, SOCK_STREAM, 0); - if (sock < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: can't open UNIX socket."); - return DEFER; - } - server.sun_family = AF_UNIX; - Ustrcpy(server.sun_path, sophie_options); - if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to connect to sophie UNIX socket (%s). errno=%d", sophie_options, errno); - return DEFER; - } + /* send scan request */ + if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0) + return m_errlog_defer(scanent, errstr); + + /* We get a lot of empty lines, so we need this hack to check for any data at all */ + while( recv(sock, buf, 1, MSG_PEEK) > 0 ) { + if ( recv_line(sock, buf, sizeof(buf)) > 0) { + if ( Ustrstr(buf, US"")) ) { + if ((strhelper2 = Ustrstr(buf, US"")) != NULL) { + *strhelper2 = '\0'; + malware_name_internal = string_copy(strhelper+6); + } + } else if ( Ustrstr(buf, US"") + ? malware_name_internal : NULL; + } + } + break; + } /* f-protd */ - /* pass the scan directory to sophie */ - len = Ustrlen(eml_filename) + 1; - if (len > sizeof(file_name)) - { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware filename does not fit in buffer [malware_internal() sophie]"); - return DEFER; - } - memcpy(file_name, eml_filename, len); - p = Ustrrchr(file_name, '/'); - if (p) - *p = '\0'; + case M_DRWEB: /* "drweb" scanner type ----------------------------------- */ + /* v0.1 - added support for tcp sockets */ + /* v0.0 - initial release -- support for unix sockets */ + { + int result; + off_t fsize; + unsigned int fsize_uint; + uschar * tmpbuf, *drweb_fbuf; + int drweb_rc, drweb_cmd, drweb_flags = 0x0000, drweb_fd, + drweb_vnum, drweb_slen, drweb_fin = 0x0000; + unsigned long bread; + const pcre *drweb_re; + + /* prepare variables */ + drweb_cmd = htonl(DRWEBD_SCAN_CMD); + drweb_flags = htonl(DRWEBD_RETURN_VIRUSES | DRWEBD_IS_MAIL); + + if (*scanner_options != '/') { + + /* calc file size */ + if ((drweb_fd = open(CS eml_filename, O_RDONLY)) == -1) + return m_errlog_defer_3(scanent, + string_sprintf("can't open spool file %s: %s", + eml_filename, strerror(errno)), + sock); + + if ((fsize = lseek(drweb_fd, 0, SEEK_END)) == -1) { + int err = errno; + (void)close(drweb_fd); + return m_errlog_defer_3(scanent, + string_sprintf("can't seek spool file %s: %s", + eml_filename, strerror(err)), + sock); + } + fsize_uint = (unsigned int) fsize; + if ((off_t)fsize_uint != fsize) { + (void)close(drweb_fd); + return m_errlog_defer_3(scanent, + string_sprintf("seeking spool file %s, size overflow", + eml_filename), + sock); + } + drweb_slen = htonl(fsize); + lseek(drweb_fd, 0, SEEK_SET); + + DEBUG(D_acl) debug_printf("Malware scan: issuing %s remote scan [%s]\n", + scanner_name, scanner_options); + + /* send scan request */ + if ((send(sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) || + (send(sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) || + (send(sock, &drweb_fin, sizeof(drweb_fin), 0) < 0) || + (send(sock, &drweb_slen, sizeof(drweb_slen), 0) < 0)) { + (void)close(drweb_fd); + return m_errlog_defer_3(scanent, + string_sprintf("unable to send commands to socket (%s)", scanner_options), + sock); + } + + if (!(drweb_fbuf = (uschar *) malloc (fsize_uint))) { + (void)close(drweb_fd); + return m_errlog_defer_3(scanent, + string_sprintf("unable to allocate memory %u for file (%s)", + fsize_uint, eml_filename), + sock); + } + + if ((result = read (drweb_fd, drweb_fbuf, fsize)) == -1) { + int err = errno; + (void)close(drweb_fd); + free(drweb_fbuf); + return m_errlog_defer_3(scanent, + string_sprintf("can't read spool file %s: %s", + eml_filename, strerror(err)), + sock); + } + (void)close(drweb_fd); + + /* send file body to socket */ + if (send(sock, drweb_fbuf, fsize, 0) < 0) { + free(drweb_fbuf); + return m_errlog_defer_3(scanent, + string_sprintf("unable to send file body to socket (%s)", scanner_options), + sock); + } + (void)close(drweb_fd); + + } else { + + drweb_slen = htonl(Ustrlen(eml_filename)); + + DEBUG(D_acl) debug_printf("Malware scan: issuing %s local scan [%s]\n", + scanner_name, scanner_options); + + /* send scan request */ + if ((send(sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) || + (send(sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) || + (send(sock, &drweb_slen, sizeof(drweb_slen), 0) < 0) || + (send(sock, eml_filename, Ustrlen(eml_filename), 0) < 0) || + (send(sock, &drweb_fin, sizeof(drweb_fin), 0) < 0)) + return m_errlog_defer_3(scanent, + string_sprintf("unable to send commands to socket (%s)", scanner_options), + sock); + } - DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", - scanner_name, sophie_options); + /* wait for result */ + if ((bread = recv(sock, &drweb_rc, sizeof(drweb_rc), 0) != sizeof(drweb_rc))) + return m_errlog_defer_3(scanent, + US"unable to read return code", sock); + drweb_rc = ntohl(drweb_rc); + + if ((bread = recv(sock, &drweb_vnum, sizeof(drweb_vnum), 0) != sizeof(drweb_vnum))) + return m_errlog_defer_3(scanent, + US"unable to read the number of viruses", sock); + drweb_vnum = ntohl(drweb_vnum); + + /* "virus(es) found" if virus number is > 0 */ + if (drweb_vnum) { + int i; + + /* setup default virus name */ + malware_name = US"unknown"; + + /* set up match regex */ + drweb_re = m_pcre_compile(US"infected\\swith\\s*(.+?)$", &errstr); + + /* read and concatenate virus names into one string */ + for (i=0;i= 2) { + const char * pre_malware_nb; + + pcre_get_substring(CS tmpbuf, ovector, result, 1, &pre_malware_nb); + + if (i==0) /* the first name we just copy to malware_name */ + malware_name = string_append(NULL, &size, &off, + 1, pre_malware_nb); + + else /* concatenate each new virus name to previous */ + malware_name = string_append(malware_name, &size, &off, + 2, "/", pre_malware_nb); + + pcre_free_substring(pre_malware_nb); + } + } + } + else { + const char *drweb_s = NULL; - if ( write(sock, file_name, Ustrlen(file_name)) < 0 - || write(sock, "\n", 1) != 1 - ) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to write to sophie UNIX socket (%s)", sophie_options); - return DEFER; - } + if (drweb_rc & DERR_READ_ERR) drweb_s = "read error"; + if (drweb_rc & DERR_NOMEMORY) drweb_s = "no memory"; + if (drweb_rc & DERR_TIMEOUT) drweb_s = "timeout"; + if (drweb_rc & DERR_BAD_CALL) drweb_s = "wrong command"; + /* retcodes DERR_SYMLINK, DERR_NO_REGFILE, DERR_SKIPPED. + * DERR_TOO_BIG, DERR_TOO_COMPRESSED, DERR_SPAM, + * DERR_CRC_ERROR, DERR_READSOCKET, DERR_WRITE_ERR + * and others are ignored */ + if (drweb_s) + return m_errlog_defer_3(scanent, + string_sprintf("drweb daemon retcode 0x%x (%s)", drweb_rc, drweb_s), + sock); - /* wait for result */ - memset(av_buffer, 0, sizeof(av_buffer)); - if ((!(bread = ip_recv(sock, av_buffer, sizeof(av_buffer), MALWARE_TIMEOUT)) > 0)) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to read from sophie UNIX socket (%s)", sophie_options); - return DEFER; - } + /* no virus found */ + malware_name = NULL; + } + break; + } /* drweb */ - (void)close(sock); + case M_AVES: /* "aveserver" scanner type -------------------------------- */ + { + uschar buf[32768]; + int result; - /* infected ? */ - if (av_buffer[0] == '1') { - if (Ustrchr(av_buffer, '\n')) *Ustrchr(av_buffer, '\n') = '\0'; - Ustrcpy(malware_name_buffer,&av_buffer[2]); - malware_name = malware_name_buffer; - } - else if (!strncmp(CS av_buffer, "-1", 2)) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: malware acl condition: sophie reported error"); - return DEFER; - } - else { - /* all ok, no virus */ - malware_name = NULL; - } - } - /* ----------------------------------------------------------------------- */ + /* read aveserver's greeting and see if it is ready (2xx greeting) */ + recv_line(sock, buf, sizeof(buf)); + if (buf[0] != '2') /* aveserver is having problems */ + return m_errlog_defer_3(scanent, + string_sprintf("unavailable (Responded: %s).", + ((buf[0] != 0) ? buf : (uschar *)"nothing") ), + sock); + + /* prepare our command */ + (void)string_format(buf, sizeof(buf), "SCAN bPQRSTUW %s\r\n", + eml_filename); + + DEBUG(D_acl) debug_printf("Malware scan: issuing %s SCAN\n", scanner_name); + + /* and send it */ + if (m_sock_send(sock, buf, Ustrlen(buf), &errstr) < 0) + return m_errlog_defer(scanent, errstr); - /* "clamd" scanner type ------------------------------------------------- */ - /* This code was originally contributed by David Saez */ - /* There are three scanning methods available to us: - * (1) Use the SCAN command, pointing to a file in the filesystem - * (2) Use the STREAM command, send the data on a separate port - * (3) Use the zINSTREAM command, send the data inline - * The zINSTREAM command was introduced with ClamAV 0.95, which marked - * STREAM deprecated; see: http://wiki.clamav.net/bin/view/Main/UpgradeNotes095 - * In Exim, we use SCAN if using a Unix-domain socket or explicitly told that - * the TCP-connected daemon is actually local; otherwise we use zINSTREAM unless - * WITH_OLD_CLAMAV_STREAM is defined. - * See Exim bug 926 for details. */ - else if (strcmpic(scanner_name,US"clamd") == 0) { - uschar *clamd_options = NULL; - uschar clamd_options_buffer[1024]; - uschar clamd_options_default[] = "/tmp/clamd"; - uschar *p, *vname, *result_tag, *response_end; - struct sockaddr_un server; - int sock,bread=0; - unsigned int port; - uschar file_name[1024]; - uschar av_buffer[1024]; - uschar *hostname = ""; - struct hostent *he; - struct in_addr in; - uschar *clamav_fbuf; - int clam_fd, result; - unsigned int fsize; - BOOL use_scan_command = FALSE, fits; - clamd_address_container * clamd_address_vector[MAX_CLAMD_SERVERS]; - int current_server; - int num_servers = 0; -#ifdef WITH_OLD_CLAMAV_STREAM - uschar av_buffer2[1024]; - int sockData; -#else - uint32_t send_size, send_final_zeroblock; -#endif + malware_name = NULL; + result = 0; + /* read response lines, find malware name and final response */ + while (recv_line(sock, buf, sizeof(buf)) > 0) { + debug_printf("aveserver: %s\n", buf); + if (buf[0] == '2') + break; + if (buf[0] == '5') { /* aveserver is having problems */ + result = m_errlog_defer(scanent, + string_sprintf("unable to scan file %s (Responded: %s).", + eml_filename, buf)); + break; + } else if (Ustrncmp(buf,"322",3) == 0) { + uschar *p = Ustrchr(&buf[4],' '); + *p = '\0'; + malware_name = string_copy(&buf[4]); + } + } - if ((clamd_options = string_nextinlist(&av_scanner_work, &sep, - clamd_options_buffer, - sizeof(clamd_options_buffer))) == NULL) { - /* no options supplied, use default options */ - clamd_options = clamd_options_default; - } + /* and send it */ + if (m_sock_send(sock, US"quit\r\n", 6, &errstr) < 0) + return m_errlog_defer(scanent, errstr); + + /* read aveserver's greeting and see if it is ready (2xx greeting) */ + recv_line(sock, buf, sizeof(buf)); + + if (buf[0] != '2') /* aveserver is having problems */ + return m_errlog_defer_3(scanent, + string_sprintf("unable to quit dialogue (Responded: %s).", + ((buf[0] != 0) ? buf : (uschar *)"nothing") ), + sock); + + if (result == DEFER) { + (void)close(sock); + return DEFER; + } + break; + } /* aveserver */ - if (*clamd_options == '/') - /* Local file; so we def want to use_scan_command and don't want to try - * passing IP/port combinations */ - use_scan_command = TRUE; - else { - uschar *address = clamd_options; - uschar address_buffer[MAX_CLAMD_ADDRESS_LENGTH + 20]; - - /* Go through the rest of the list of host/port and construct an array - * of servers to try. The first one is the bit we just passed from - * clamd_options so process that first and then scan the remainder of - * the address buffer */ - do { - clamd_address_container *this_clamd; - - /* The 'local' option means use the SCAN command over the network - * socket (ie common file storage in use) */ - if (strcmpic(address,US"local") == 0) { - use_scan_command = TRUE; - continue; - } + case M_FSEC: /* "fsecure" scanner type ---------------------------------- */ + { + int i, j, bread = 0; + uschar * file_name; + uschar av_buffer[1024]; + const pcre * fs_inf; + static uschar *cmdopt[] = { US"CONFIGURE\tARCHIVE\t1\n", + US"CONFIGURE\tTIMEOUT\t0\n", + US"CONFIGURE\tMAXARCH\t5\n", + US"CONFIGURE\tMIME\t1\n" }; - /* XXX: If unsuccessful we should free this memory */ - this_clamd = - (clamd_address_container *)store_get(sizeof(clamd_address_container)); - - /* extract host and port part */ - if( sscanf(CS address, "%" MAX_CLAMD_ADDRESS_LENGTH_S "s %u", this_clamd->tcp_addr, - &(this_clamd->tcp_port)) != 2 ) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: invalid address '%s'", address); - continue; - } + malware_name = NULL; - clamd_address_vector[num_servers] = this_clamd; - num_servers++; - if (num_servers >= MAX_CLAMD_SERVERS) { - log_write(0, LOG_MAIN|LOG_PANIC, - "More than " MAX_CLAMD_SERVERS_S " clamd servers specified; " - "only using the first " MAX_CLAMD_SERVERS_S ); - break; - } - } while ((address = string_nextinlist(&av_scanner_work, &sep, - address_buffer, - sizeof(address_buffer))) != NULL); - - /* check if we have at least one server */ - if (!num_servers) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: no useable clamd server addresses in malware configuration option."); - return DEFER; - } - } + DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", + scanner_name, scanner_options); - /* See the discussion of response formats below to see why we really don't - like colons in filenames when passing filenames to ClamAV. */ - if (use_scan_command && Ustrchr(eml_filename, ':')) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: local/SCAN mode incompatible with" \ - " : in path to email filename [%s]", eml_filename); - return DEFER; - } + /* pass options */ + memset(av_buffer, 0, sizeof(av_buffer)); + for (i=0; i != nelements(cmdopt); i++) { + + if (m_sock_send(sock, cmdopt[i], Ustrlen(cmdopt[i]), &errstr) < 0) + return m_errlog_defer(scanent, errstr); + + bread = ip_recv(sock, av_buffer, sizeof(av_buffer), MALWARE_TIMEOUT); + if (bread >0) av_buffer[bread]='\0'; + if (bread < 0) + return m_errlog_defer_3(scanent, + string_sprintf("unable to read answer %d (%s)", i, strerror(errno)), + sock); + for (j=0;j 0 ) { - /* Randomly pick a server to start with */ - current_server = random_number( num_servers ); - - debug_printf("trying server name %s, port %u\n", - clamd_address_vector[current_server]->tcp_addr, - clamd_address_vector[current_server]->tcp_port); - - /* Lookup the host. This is to ensure that we connect to the same IP - * on both connections (as one host could resolve to multiple ips) */ - if((he = gethostbyname(CS clamd_address_vector[current_server]->tcp_addr)) - == 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: failed to lookup host '%s'", - clamd_address_vector[current_server]->tcp_addr - ); - goto try_next_server; - } + if (m_sock_send(sock, file_name, Ustrlen(file_name), &errstr) < 0) + return m_errlog_defer(scanent, errstr); - in = *(struct in_addr *) he->h_addr_list[0]; + /* set up match */ + /* todo also SUSPICION\t */ + fs_inf = m_pcre_compile(US"\\S{0,5}INFECTED\\t[^\\t]*\\t([^\\t]+)\\t\\S*$", &errstr); + + /* read report, linewise */ + do { + i = 0; + memset(av_buffer, 0, sizeof(av_buffer)); + do { + if ((bread= ip_recv(sock, &av_buffer[i], 1, MALWARE_TIMEOUT)) < 0) + return m_errlog_defer_3(scanent, + string_sprintf("unable to read result (%s)", strerror(errno)), + sock); + } while (++i < sizeof(av_buffer)-1 && av_buffer[i-1] != '\n'); + av_buffer[i-1] = '\0'; + + /* Really search for virus again? */ + if (malware_name == NULL) + /* try matcher on the line, grab substring */ + malware_name = m_pcre_exec(fs_inf, av_buffer); + } + while (Ustrstr(av_buffer, "OK\tScan ok.") == NULL); + break; + } /* fsecure */ - /* Open the ClamAV Socket */ - if ( (sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: unable to acquire socket (%s)", - strerror(errno)); - goto try_next_server; - } + case M_KAVD: /* "kavdaemon" scanner type -------------------------------- */ + { + time_t t; + uschar tmpbuf[1024]; + uschar * scanrequest; + int kav_rc; + unsigned long kav_reportlen, bread; + const pcre *kav_re; + uschar *p; + + /* get current date and time, build scan request */ + time(&t); + /* pdp note: before the eml_filename parameter, this scanned the + directory; not finding documentation, so we'll strip off the directory. + The side-effect is that the test framework scanning may end up in + scanning more than was requested, but for the normal interface, this is + fine. */ + + strftime(CS tmpbuf, sizeof(tmpbuf), "%d %b %H:%M:%S", localtime(&t)); + scanrequest = string_sprintf("<0>%s:%s", CS tmpbuf, eml_filename); + p = Ustrrchr(scanrequest, '/'); + if (p) + *p = '\0'; + + DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", + scanner_name, scanner_options); + + /* send scan request */ + if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0) + return m_errlog_defer(scanent, errstr); + + /* wait for result */ + if ((bread = recv(sock, tmpbuf, 2, 0) != 2)) + return m_errlog_defer_3(scanent, + US"unable to read 2 bytes from socket.", sock); + + /* get errorcode from one nibble */ + kav_rc = tmpbuf[ test_byte_order()==LITTLE_MY_ENDIAN ? 0 : 1 ] & 0x0F; + switch(kav_rc) + { + case 5: case 6: /* improper kavdaemon configuration */ + return m_errlog_defer_3(scanent, + US"please reconfigure kavdaemon to NOT disinfect or remove infected files.", + sock); + case 1: + return m_errlog_defer_3(scanent, + US"reported 'scanning not completed' (code 1).", sock); + case 7: + return m_errlog_defer_3(scanent, + US"reported 'kavdaemon damaged' (code 7).", sock); + } - if (ip_connect( sock, - AF_INET, - (uschar*)inet_ntoa(in), - clamd_address_vector[current_server]->tcp_port, - 5 ) > -1) { - /* Connection successfully established with a server */ - hostname = clamd_address_vector[current_server]->tcp_addr; - break; - } else { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: connection to %s, port %u failed (%s)", - clamd_address_vector[current_server]->tcp_addr, - clamd_address_vector[current_server]->tcp_port, - strerror(errno)); + /* code 8 is not handled, since it is ambigous. It appears mostly on + bounces where part of a file has been cut off */ - (void)close(sock); - } + /* "virus found" return codes (2-4) */ + if ((kav_rc > 1) && (kav_rc < 5)) { + int report_flag = 0; + + /* setup default virus name */ + malware_name = US"unknown"; + + report_flag = tmpbuf[ test_byte_order() == LITTLE_MY_ENDIAN ? 1 : 0 ]; + + /* read the report, if available */ + if( report_flag == 1 ) { + /* read report size */ + if ((bread = recv(sock, &kav_reportlen, 4, 0)) != 4) + return m_errlog_defer_3(scanent, + US"cannot read report size", sock); + + /* it's possible that avp returns av_buffer[1] == 1 but the + reportsize is 0 (!?) */ + if (kav_reportlen > 0) { + /* set up match regex, depends on retcode */ + kav_re = m_pcre_compile( kav_rc == 3 + ? US"suspicion:\\s*(.+?)\\s*$" + : US"infected:\\s*(.+?)\\s*$", + &errstr ); + + /* read report, linewise */ + while (kav_reportlen > 0) { + bread = 0; + while ( recv(sock, &tmpbuf[bread], 1, 0) == 1 ) { + kav_reportlen--; + if ( (tmpbuf[bread] == '\n') || (bread > 1021) ) break; + bread++; + } + bread++; + tmpbuf[bread] = '\0'; + + /* try matcher on the line, grab substring */ + if ((malware_name = m_pcre_exec(kav_re, tmpbuf))) + break; + } + } + } + } + else /* no virus found */ + malware_name = NULL; -try_next_server: - /* Remove the server from the list. XXX We should free the memory */ - num_servers--; - int i; - for( i = current_server; i < num_servers; i++ ) - clamd_address_vector[i] = clamd_address_vector[i+1]; - } - - if ( num_servers == 0 ) { - log_write(0, LOG_MAIN|LOG_PANIC, "malware acl condition: all clamd servers failed"); - return DEFER; - } - } else { - /* open the local socket */ - if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: unable to acquire socket (%s)", - strerror(errno)); - return DEFER; - } - - server.sun_family = AF_UNIX; - Ustrcpy(server.sun_path, clamd_options); - - if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: unable to connect to UNIX socket %s (%s)", - clamd_options, strerror(errno) ); - return DEFER; - } + break; } - /* have socket in variable "sock"; command to use is semi-independent of - * the socket protocol. We use SCAN if is local (either Unix/local - * domain socket, or explicitly told local) else we stream the data. - * How we stream the data depends upon how we were built. */ - - if (!use_scan_command) { - -#ifdef WITH_OLD_CLAMAV_STREAM - /* "STREAM\n" command, get back a "PORT \n" response, send data to - * that port on a second connection; then in the scan-method-neutral - * part, read the response back on the original connection. */ - - DEBUG(D_acl) debug_printf("Malware scan: issuing %s old-style remote scan (PORT)\n", - scanner_name); - - /* Pass the string to ClamAV (7 = "STREAM\n") */ - if (send(sock, "STREAM\n", 7, 0) < 0) { - log_write(0, LOG_MAIN|LOG_PANIC,"malware acl condition: clamd: unable to write to socket (%s)", - strerror(errno)); - (void)close(sock); - return DEFER; - } - memset(av_buffer2, 0, sizeof(av_buffer2)); - bread = ip_recv(sock, av_buffer2, sizeof(av_buffer2), MALWARE_TIMEOUT); - - if (bread < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: unable to read PORT from socket (%s)", - strerror(errno)); - (void)close(sock); - return DEFER; - } - - if (bread == sizeof(av_buffer)) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: buffer too small"); - (void)close(sock); - return DEFER; - } - - if (!(*av_buffer2)) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: ClamAV returned null"); - (void)close(sock); - return DEFER; - } - - av_buffer2[bread] = '\0'; - if( sscanf(CS av_buffer2, "PORT %u\n", &port) != 1 ) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: Expected port information from clamd, got '%s'", av_buffer2); - (void)close(sock); - return DEFER; - }; - - if ( (sockData = ip_socket(SOCK_STREAM, AF_INET)) < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: unable to acquire socket (%s)", - strerror(errno)); - (void)close(sock); - return DEFER; - } - - if (ip_connect(sockData, AF_INET, (uschar*)inet_ntoa(in), port, 5) < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: connection to %s, port %u failed (%s)", - inet_ntoa(in), port, strerror(errno)); - (void)close(sockData); (void)close(sock); - return DEFER; - } - -#define CLOSE_SOCKDATA (void)close(sockData) -#else /* WITH_OLD_CLAMAV_STREAM not defined */ - /* New protocol: "zINSTREAM\n" followed by a sequence of - chunks, a 4-byte number (network order), terminated by a zero-length - chunk. */ - - DEBUG(D_acl) debug_printf("Malware scan: issuing %s new-style remote scan (zINSTREAM)\n", - scanner_name); - - /* Pass the string to ClamAV (10 = "zINSTREAM\0") */ - if (send(sock, "zINSTREAM", 10, 0) < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: unable to send zINSTREAM to socket (%s)", - strerror(errno)); - (void)close(sock); - return DEFER; - } - -#define CLOSE_SOCKDATA /**/ -#endif - - /* calc file size */ - clam_fd = open(CS eml_filename, O_RDONLY); - if (clam_fd == -1) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: can't open spool file %s: %s", - eml_filename, strerror(errno)); - CLOSE_SOCKDATA; (void)close(sock); - return DEFER; - } - fsize = lseek(clam_fd, 0, SEEK_END); - if (fsize == -1) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: can't seek spool file %s: %s", - eml_filename, strerror(errno)); - CLOSE_SOCKDATA; (void)close(sock); - return DEFER; - } - lseek(clam_fd, 0, SEEK_SET); - - clamav_fbuf = (uschar *) malloc (fsize); - if (!clamav_fbuf) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: unable to allocate memory %u for file (%s)", - fsize, eml_filename); - CLOSE_SOCKDATA; (void)close(sock); (void)close(clam_fd); - return DEFER; - } - - result = read (clam_fd, clamav_fbuf, fsize); - if (result == -1) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: can't read spool file %s: %s", - eml_filename, strerror(errno)); - CLOSE_SOCKDATA; (void)close(sock); (void)close(clam_fd); - free(clamav_fbuf); - return DEFER; - } - (void)close(clam_fd); - - /* send file body to socket */ -#ifdef WITH_OLD_CLAMAV_STREAM - if (send(sockData, clamav_fbuf, fsize, 0) < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: unable to send file body to socket (%s:%u)", hostname, port); - CLOSE_SOCKDATA; (void)close(sock); - free(clamav_fbuf); - return DEFER; - } -#else - send_size = htonl(fsize); - send_final_zeroblock = 0; - if ((send(sock, &send_size, sizeof(send_size), 0) < 0) || - (send(sock, clamav_fbuf, fsize, 0) < 0) || - (send(sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0)) - { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: unable to send file body to socket (%s:%u)", hostname, port); - (void)close(sock); - free(clamav_fbuf); - return DEFER; - } -#endif + case M_CMDL: /* "cmdline" scanner type ---------------------------------- */ + { + const uschar *cmdline_scanner = scanner_options; + const pcre *cmdline_trigger_re; + const pcre *cmdline_regex_re; + uschar * file_name; + uschar * commandline; + void (*eximsigchld)(int); + void (*eximsigpipe)(int); + FILE *scanner_out = NULL; + FILE *scanner_record = NULL; + uschar linebuffer[32767]; + int trigger = 0; + uschar *p; + + if (!cmdline_scanner) + return m_errlog_defer(scanent, errstr); + + /* find scanner output trigger */ + cmdline_trigger_re = m_pcre_nextinlist(&av_scanner_work, &sep, + "missing trigger specification", &errstr); + if (!cmdline_trigger_re) + return m_errlog_defer(scanent, errstr); + + /* find scanner name regex */ + cmdline_regex_re = m_pcre_nextinlist(&av_scanner_work, &sep, + "missing virus name regex specification", &errstr); + if (!cmdline_regex_re) + return m_errlog_defer(scanent, errstr); + + /* prepare scanner call; despite the naming, file_name holds a directory + name which is documented as the value given to %s. */ + + file_name = string_copy(eml_filename); + p = Ustrrchr(file_name, '/'); + if (p) + *p = '\0'; + commandline = string_sprintf(CS cmdline_scanner, file_name); + + /* redirect STDERR too */ + commandline = string_sprintf("%s 2>&1", commandline); + + DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", scanner_name, commandline); + + /* store exims signal handlers */ + eximsigchld = signal(SIGCHLD,SIG_DFL); + eximsigpipe = signal(SIGPIPE,SIG_DFL); + + if (!(scanner_out = popen(CS commandline,"r"))) { + int err = errno; + signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe); + return m_errlog_defer(scanent, + string_sprintf("call (%s) failed: %s.", commandline, strerror(err))); + } - free(clamav_fbuf); + file_name = string_sprintf("%s/scan/%s/%s_scanner_output", + spool_directory, message_id, message_id); + scanner_record = modefopen(file_name, "wb", SPOOL_MODE); + + if (scanner_record == NULL) { + int err = errno; + (void) pclose(scanner_out); + signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe); + return m_errlog_defer(scanent, + string_sprintf("opening scanner output file (%s) failed: %s.", + file_name, strerror(err))); + } - CLOSE_SOCKDATA; -#undef CLOSE_SOCKDATA + /* look for trigger while recording output */ + while(fgets(CS linebuffer, sizeof(linebuffer), scanner_out)) { + if ( Ustrlen(linebuffer) > fwrite(linebuffer, 1, Ustrlen(linebuffer), scanner_record) ) { + /* short write */ + (void) pclose(scanner_out); + signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe); + return m_errlog_defer(scanent, + string_sprintf("short write on scanner output file (%s).", file_name)); + } + /* try trigger match */ + if (!trigger && regex_match_and_setup(cmdline_trigger_re, linebuffer, 0, -1)) + trigger = 1; + } - } else { /* use scan command */ - /* Send a SCAN command pointing to a filename; then in the then in the - * scan-method-neutral part, read the response back */ - -/* ================================================================= */ - - /* Prior to the reworking post-Exim-4.72, this scanned a directory, - which dates to when ClamAV needed us to break apart the email into the - MIME parts (eg, with the now deprecated demime condition coming first). - Some time back, ClamAV gained the ability to deconstruct the emails, so - doing this would actually have resulted in the mail attachments being - scanned twice, in the broken out files and from the original .eml. - Since ClamAV now handles emails (and has for quite some time) we can - just use the email file itself. */ - /* Pass the string to ClamAV (7 = "SCAN \n" + \0) */ - fits = string_format(file_name, sizeof(file_name), "SCAN %s\n", - eml_filename); - if (!fits) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware filename does not fit in buffer [malware_internal() clamd]"); - } - - DEBUG(D_acl) debug_printf("Malware scan: issuing %s local-path scan [%s]\n", - scanner_name, clamd_options); - - if (send(sock, file_name, Ustrlen(file_name), 0) < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC,"malware acl condition: clamd: unable to write to socket (%s)", - strerror(errno)); - return DEFER; - } + (void)fclose(scanner_record); + sep = pclose(scanner_out); + signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe); + if (sep != 0) + return m_errlog_defer(scanent, + sep == -1 + ? string_sprintf("running scanner failed: %s", strerror(sep)) + : string_sprintf("scanner returned error code: %d", sep)); + + if (trigger) { + uschar * s; + /* setup default virus name */ + malware_name = US"unknown"; + + /* re-open the scanner output file, look for name match */ + scanner_record = fopen(CS file_name, "rb"); + while(fgets(CS linebuffer, sizeof(linebuffer), scanner_record)) { + /* try match */ + if ((s = m_pcre_exec(cmdline_regex_re, linebuffer))) + malware_name = s; + } + (void)fclose(scanner_record); + } + else /* no virus found */ + malware_name = NULL; + break; + } /* cmdline */ - /* Do not shut down the socket for writing; a user report noted that - * clamd 0.70 does not react well to this. */ - } - /* Commands have been sent, no matter which scan method or connection - * type we're using; now just read the result, independent of method. */ + case M_SOPHIE: /* "sophie" scanner type --------------------------------- */ + { + int bread = 0; + uschar *p; + uschar * file_name; + uschar av_buffer[1024]; + + /* pass the scan directory to sophie */ + file_name = string_copy(eml_filename); + if ((p = Ustrrchr(file_name, '/'))) + *p = '\0'; + + DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n", + scanner_name, scanner_options); + + if ( write(sock, file_name, Ustrlen(file_name)) < 0 + || write(sock, "\n", 1) != 1 + ) + return m_errlog_defer_3(scanent, + string_sprintf("unable to write to UNIX socket (%s)", scanner_options), + sock); + + /* wait for result */ + memset(av_buffer, 0, sizeof(av_buffer)); + if ((!(bread = ip_recv(sock, av_buffer, sizeof(av_buffer), MALWARE_TIMEOUT)) > 0)) + return m_errlog_defer_3(scanent, + string_sprintf("unable to read from UNIX socket (%s)", scanner_options), + sock); + + /* infected ? */ + if (av_buffer[0] == '1') { + uschar * s = Ustrchr(av_buffer, '\n'); + if (s) + *s = '\0'; + malware_name = string_copy(&av_buffer[2]); + } + else if (!strncmp(CS av_buffer, "-1", 2)) + return m_errlog_defer_3(scanent, US"scanner reported error", sock); + else /* all ok, no virus */ + malware_name = NULL; - /* Read the result */ - memset(av_buffer, 0, sizeof(av_buffer)); - bread = ip_recv(sock, av_buffer, sizeof(av_buffer), MALWARE_TIMEOUT); - (void)close(sock); - - if (!(bread > 0)) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: unable to read from socket (%s)", - strerror(errno)); - return DEFER; + break; } - if (bread == sizeof(av_buffer)) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: buffer too small"); - return DEFER; - } + case M_CLAMD: /* "clamd" scanner type ----------------------------------- */ + { + /* This code was originally contributed by David Saez */ + /* There are three scanning methods available to us: + * (1) Use the SCAN command, pointing to a file in the filesystem + * (2) Use the STREAM command, send the data on a separate port + * (3) Use the zINSTREAM command, send the data inline + * The zINSTREAM command was introduced with ClamAV 0.95, which marked + * STREAM deprecated; see: http://wiki.clamav.net/bin/view/Main/UpgradeNotes095 + * In Exim, we use SCAN if using a Unix-domain socket or explicitly told that + * the TCP-connected daemon is actually local; otherwise we use zINSTREAM unless + * WITH_OLD_CLAMAV_STREAM is defined. + * See Exim bug 926 for details. */ + + uschar *p, *vname, *result_tag, *response_end; + int bread=0; + uschar * file_name; + uschar av_buffer[1024]; + uschar *hostname = US""; + host_item connhost; + uschar *clamav_fbuf; + int clam_fd, result; + off_t fsize; + unsigned int fsize_uint; + BOOL use_scan_command = FALSE; + clamd_address_container * clamd_address_vector[MAX_CLAMD_SERVERS]; + int current_server; + int num_servers = 0; + #ifdef WITH_OLD_CLAMAV_STREAM + unsigned int port; + uschar av_buffer2[1024]; + int sockData; + #else + uint32_t send_size, send_final_zeroblock; + #endif + + if (*scanner_options == '/') + /* Local file; so we def want to use_scan_command and don't want to try + * passing IP/port combinations */ + use_scan_command = TRUE; + else { + const uschar *address = scanner_options; + uschar address_buffer[MAX_CLAMD_ADDRESS_LENGTH + 20]; + + /* Go through the rest of the list of host/port and construct an array + * of servers to try. The first one is the bit we just passed from + * scanner_options so process that first and then scan the remainder of + * the address buffer */ + do { + clamd_address_container *this_clamd; + + /* The 'local' option means use the SCAN command over the network + * socket (ie common file storage in use) */ + if (strcmpic(address,US"local") == 0) { + use_scan_command = TRUE; + continue; + } + + /* XXX: If unsuccessful we should free this memory */ + this_clamd = + (clamd_address_container *)store_get(sizeof(clamd_address_container)); + + /* extract host and port part */ + if( sscanf(CS address, "%" MAX_CLAMD_ADDRESS_LENGTH_S "s %u", + this_clamd->tcp_addr, &(this_clamd->tcp_port)) != 2 ) { + (void) m_errlog_defer(scanent, + string_sprintf("invalid address '%s'", address)); + continue; + } + + clamd_address_vector[num_servers] = this_clamd; + num_servers++; + if (num_servers >= MAX_CLAMD_SERVERS) { + (void) m_errlog_defer(scanent, + US"More than " MAX_CLAMD_SERVERS_S " clamd servers " + "specified; only using the first " MAX_CLAMD_SERVERS_S ); + break; + } + } while ((address = string_nextinlist(&av_scanner_work, &sep, + address_buffer, + sizeof(address_buffer))) != NULL); + + /* check if we have at least one server */ + if (!num_servers) + return m_errlog_defer(scanent, + US"no useable server addresses in malware configuration option."); + } - /* Check the result. ClamAV returns one of two result formats. - In the basic mode, the response is of the form: - infected: -> ": FOUND" - not-infected: -> ": OK" - error: -> ": ERROR - If the ExtendedDetectionInfo option has been turned on, then we get: - ": (:) FOUND" - for the infected case. Compare: -/tmp/eicar.com: Eicar-Test-Signature FOUND -/tmp/eicar.com: Eicar-Test-Signature(44d88612fea8a8f36de82e1278abb02f:68) FOUND - - In the streaming case, clamd uses the filename "stream" which you should - be able to verify with { ktrace clamdscan --stream /tmp/eicar.com }. (The - client app will replace "stream" with the original filename before returning - results to stdout, but the trace shows the data). - - We will assume that the pathname passed to clamd from Exim does not contain - a colon. We will have whined loudly above if the eml_filename does (and we're - passing a filename to clamd). */ - - if (!(*av_buffer)) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: ClamAV returned null"); - return DEFER; - } + /* See the discussion of response formats below to see why we really don't + like colons in filenames when passing filenames to ClamAV. */ + if (use_scan_command && Ustrchr(eml_filename, ':')) + return m_errlog_defer(scanent, + string_sprintf("local/SCAN mode incompatible with" \ + " : in path to email filename [%s]", eml_filename)); + + /* We have some network servers specified */ + if (num_servers) { + + /* Confirmed in ClamAV source (0.95.3) that the TCPAddr option of clamd + * only supports AF_INET, but we should probably be looking to the + * future and rewriting this to be protocol-independent anyway. */ + + while ( num_servers > 0 ) { + /* Randomly pick a server to start with */ + current_server = random_number( num_servers ); + + debug_printf("trying server name %s, port %u\n", + clamd_address_vector[current_server]->tcp_addr, + clamd_address_vector[current_server]->tcp_port); + + /* Lookup the host. This is to ensure that we connect to the same IP + * on both connections (as one host could resolve to multiple ips) */ + sock= m_tcpsocket(clamd_address_vector[current_server]->tcp_addr, + clamd_address_vector[current_server]->tcp_port, + &connhost, &errstr); + if (sock >= 0) { + /* Connection successfully established with a server */ + hostname = clamd_address_vector[current_server]->tcp_addr; + break; + } + + (void) m_errlog_defer(scanent, errstr); + + /* Remove the server from the list. XXX We should free the memory */ + num_servers--; + int i; + for( i = current_server; i < num_servers; i++ ) + clamd_address_vector[i] = clamd_address_vector[i+1]; + } + + if ( num_servers == 0 ) + return m_errlog_defer(scanent, US"all servers failed"); + + } else { + if ((sock = m_unixsocket(scanner_options, &errstr)) < 0) + return m_errlog_defer(scanent, errstr); + } - /* strip newline at the end (won't be present for zINSTREAM) - (also any trailing whitespace, which shouldn't exist, but we depend upon - this below, so double-check) */ - p = av_buffer + Ustrlen(av_buffer) - 1; - if (*p == '\n') *p = '\0'; - - DEBUG(D_acl) debug_printf("Malware response: %s\n", av_buffer); - - while (isspace(*--p) && (p > av_buffer)) - *p = '\0'; - if (*p) ++p; - response_end = p; - - /* colon in returned output? */ - if((p = Ustrchr(av_buffer,':')) == NULL) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: ClamAV returned malformed result (missing colon): %s", - av_buffer); - return DEFER; - } + /* have socket in variable "sock"; command to use is semi-independent of + * the socket protocol. We use SCAN if is local (either Unix/local + * domain socket, or explicitly told local) else we stream the data. + * How we stream the data depends upon how we were built. */ + + if (!use_scan_command) { + + #ifdef WITH_OLD_CLAMAV_STREAM + /* "STREAM\n" command, get back a "PORT \n" response, send data to + * that port on a second connection; then in the scan-method-neutral + * part, read the response back on the original connection. */ + + DEBUG(D_acl) debug_printf("Malware scan: issuing %s old-style remote scan (PORT)\n", + scanner_name); + + /* Pass the string to ClamAV (7 = "STREAM\n") */ + if (m_sock_send(sock, US"STREAM\n", 7, &errstr) < 0) + return m_errlog_defer(scanent, errstr); + + memset(av_buffer2, 0, sizeof(av_buffer2)); + bread = ip_recv(sock, av_buffer2, sizeof(av_buffer2), MALWARE_TIMEOUT); + + if (bread < 0) + return m_errlog_defer_3(scanent, + string_sprintf("unable to read PORT from socket (%s)", + strerror(errno)), + sock); + + if (bread == sizeof(av_buffer2)) + return m_errlog_defer_3(scanent, "buffer too small", sock); + + if (!(*av_buffer2)) + return m_errlog_defer_3(scanent, "ClamAV returned null", sock); + + av_buffer2[bread] = '\0'; + if( sscanf(CS av_buffer2, "PORT %u\n", &port) != 1 ) + return m_errlog_defer_3(scanent, + string_sprintf("Expected port information from clamd, got '%s'", + av_buffer2), + sock); + + sockData = m_tcpsocket(connhost.address, port, NULL, &errstr); + if (sockData < 0) + return m_errlog_defer_3(scanent, errstr, sock); + + #define CLOSE_SOCKDATA (void)close(sockData) + #else /* WITH_OLD_CLAMAV_STREAM not defined */ + /* New protocol: "zINSTREAM\n" followed by a sequence of + chunks, a 4-byte number (network order), terminated by a zero-length + chunk. */ + + DEBUG(D_acl) debug_printf("Malware scan: issuing %s new-style remote scan (zINSTREAM)\n", + scanner_name); + + /* Pass the string to ClamAV (10 = "zINSTREAM\0") */ + if (send(sock, "zINSTREAM", 10, 0) < 0) + return m_errlog_defer_3(scanent, + string_sprintf("unable to send zINSTREAM to socket (%s)", + strerror(errno)), + sock); + + #define CLOSE_SOCKDATA /**/ + #endif + + /* calc file size */ + if ((clam_fd = open(CS eml_filename, O_RDONLY)) < 0) { + int err = errno; + CLOSE_SOCKDATA; + return m_errlog_defer_3(scanent, + string_sprintf("can't open spool file %s: %s", + eml_filename, strerror(err)), + sock); + } + if ((fsize = lseek(clam_fd, 0, SEEK_END)) < 0) { + int err = errno; + CLOSE_SOCKDATA; (void)close(clam_fd); + return m_errlog_defer_3(scanent, + string_sprintf("can't seek spool file %s: %s", + eml_filename, strerror(err)), + sock); + } + fsize_uint = (unsigned int) fsize; + if ((off_t)fsize_uint != fsize) { + CLOSE_SOCKDATA; (void)close(clam_fd); + return m_errlog_defer_3(scanent, + string_sprintf("seeking spool file %s, size overflow", + eml_filename), + sock); + } + lseek(clam_fd, 0, SEEK_SET); + + if (!(clamav_fbuf = (uschar *) malloc (fsize_uint))) { + CLOSE_SOCKDATA; (void)close(clam_fd); + return m_errlog_defer_3(scanent, + string_sprintf("unable to allocate memory %u for file (%s)", + fsize_uint, eml_filename), + sock); + } + + if ((result = read(clam_fd, clamav_fbuf, fsize_uint)) < 0) { + int err = errno; + free(clamav_fbuf); CLOSE_SOCKDATA; (void)close(clam_fd); + return m_errlog_defer_3(scanent, + string_sprintf("can't read spool file %s: %s", + eml_filename, strerror(err)), + sock); + } + (void)close(clam_fd); + + /* send file body to socket */ + #ifdef WITH_OLD_CLAMAV_STREAM + if (send(sockData, clamav_fbuf, fsize_uint, 0) < 0) { + free(clamav_fbuf); CLOSE_SOCKDATA; + return m_errlog_defer_3(scanent, + string_sprintf("unable to send file body to socket (%s:%u)", + hostname, port), + sock); + } + #else + send_size = htonl(fsize_uint); + send_final_zeroblock = 0; + if ((send(sock, &send_size, sizeof(send_size), 0) < 0) || + (send(sock, clamav_fbuf, fsize_uint, 0) < 0) || + (send(sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0)) + { + free(clamav_fbuf); + return m_errlog_defer_3(scanent, + string_sprintf("unable to send file body to socket (%s)", hostname), + sock); + } + #endif + + free(clamav_fbuf); + + CLOSE_SOCKDATA; + #undef CLOSE_SOCKDATA + + } else { /* use scan command */ + /* Send a SCAN command pointing to a filename; then in the then in the + * scan-method-neutral part, read the response back */ + + /* ================================================================= */ + + /* Prior to the reworking post-Exim-4.72, this scanned a directory, + which dates to when ClamAV needed us to break apart the email into the + MIME parts (eg, with the now deprecated demime condition coming first). + Some time back, ClamAV gained the ability to deconstruct the emails, so + doing this would actually have resulted in the mail attachments being + scanned twice, in the broken out files and from the original .eml. + Since ClamAV now handles emails (and has for quite some time) we can + just use the email file itself. */ + /* Pass the string to ClamAV (7 = "SCAN \n" + \0) */ + file_name = string_sprintf("SCAN %s\n", eml_filename); + + DEBUG(D_acl) debug_printf("Malware scan: issuing %s local-path scan [%s]\n", + scanner_name, scanner_options); + + if (send(sock, file_name, Ustrlen(file_name), 0) < 0) + return m_errlog_defer_3(scanent, + string_sprintf("unable to write to socket (%s)", strerror(errno)), + sock); - /* strip filename */ - while (*p && isspace(*++p)) /**/; - vname = p; - - /* It would be bad to encounter a virus with "FOUND" in part of the name, - but we should at least be resistant to it. */ - p = Ustrrchr(vname, ' '); - if (p) - result_tag = p + 1; - else - result_tag = vname; - - if (Ustrcmp(result_tag, "FOUND") == 0) { - /* p should still be the whitespace before the result_tag */ - while (isspace(*p)) --p; - *++p = '\0'; - /* Strip off the extended information too, which will be in parens - after the virus name, with no intervening whitespace. */ - if (*--p == ')') { - /* "(hash:size)", so previous '(' will do; if not found, we have - a curious virus name, but not an error. */ - p = Ustrrchr(vname, '('); - if (p) - *p = '\0'; + /* Do not shut down the socket for writing; a user report noted that + * clamd 0.70 does not react well to this. */ } - Ustrncpy(malware_name_buffer, vname, sizeof(malware_name_buffer)-1); - malware_name = malware_name_buffer; - DEBUG(D_acl) debug_printf("Malware found, name \"%s\"\n", malware_name); - - } else if (Ustrcmp(result_tag, "ERROR") == 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: ClamAV returned: %s", - av_buffer); - return DEFER; + /* Commands have been sent, no matter which scan method or connection + * type we're using; now just read the result, independent of method. */ - } else if (Ustrcmp(result_tag, "OK") == 0) { - /* Everything should be OK */ - malware_name = NULL; - DEBUG(D_acl) debug_printf("Malware not found\n"); + /* Read the result */ + memset(av_buffer, 0, sizeof(av_buffer)); + bread = ip_recv(sock, av_buffer, sizeof(av_buffer), MALWARE_TIMEOUT); + (void)close(sock); + sock = -1; + + if (!(bread > 0)) + return m_errlog_defer(scanent, + string_sprintf("unable to read from socket (%s)", strerror(errno))); + + if (bread == sizeof(av_buffer)) + return m_errlog_defer(scanent, US"buffer too small"); + /* We're now assured of a NULL at the end of av_buffer */ + + /* Check the result. ClamAV returns one of two result formats. + In the basic mode, the response is of the form: + infected: -> ": FOUND" + not-infected: -> ": OK" + error: -> ": ERROR + If the ExtendedDetectionInfo option has been turned on, then we get: + ": (:) FOUND" + for the infected case. Compare: + /tmp/eicar.com: Eicar-Test-Signature FOUND + /tmp/eicar.com: Eicar-Test-Signature(44d88612fea8a8f36de82e1278abb02f:68) FOUND + + In the streaming case, clamd uses the filename "stream" which you should + be able to verify with { ktrace clamdscan --stream /tmp/eicar.com }. (The + client app will replace "stream" with the original filename before returning + results to stdout, but the trace shows the data). + + We will assume that the pathname passed to clamd from Exim does not contain + a colon. We will have whined loudly above if the eml_filename does (and we're + passing a filename to clamd). */ + + if (!(*av_buffer)) + return m_errlog_defer(scanent, US"ClamAV returned null"); + + /* strip newline at the end (won't be present for zINSTREAM) + (also any trailing whitespace, which shouldn't exist, but we depend upon + this below, so double-check) */ + p = av_buffer + Ustrlen(av_buffer) - 1; + if (*p == '\n') *p = '\0'; + + DEBUG(D_acl) debug_printf("Malware response: %s\n", av_buffer); + + while (isspace(*--p) && (p > av_buffer)) + *p = '\0'; + if (*p) ++p; + response_end = p; + + /* colon in returned output? */ + if((p = Ustrchr(av_buffer,':')) == NULL) + return m_errlog_defer(scanent, + string_sprintf("ClamAV returned malformed result (missing colon): %s", + av_buffer)); + + /* strip filename */ + while (*p && isspace(*++p)) /**/; + vname = p; + + /* It would be bad to encounter a virus with "FOUND" in part of the name, + but we should at least be resistant to it. */ + p = Ustrrchr(vname, ' '); + result_tag = p ? p+1 : vname; + + if (Ustrcmp(result_tag, "FOUND") == 0) { + /* p should still be the whitespace before the result_tag */ + while (isspace(*p)) --p; + *++p = '\0'; + /* Strip off the extended information too, which will be in parens + after the virus name, with no intervening whitespace. */ + if (*--p == ')') { + /* "(hash:size)", so previous '(' will do; if not found, we have + a curious virus name, but not an error. */ + p = Ustrrchr(vname, '('); + if (p) + *p = '\0'; + } + malware_name = string_copy(vname); + DEBUG(D_acl) debug_printf("Malware found, name \"%s\"\n", malware_name); + + } else if (Ustrcmp(result_tag, "ERROR") == 0) + return m_errlog_defer(scanent, + string_sprintf("ClamAV returned: %s", av_buffer)); + + else if (Ustrcmp(result_tag, "OK") == 0) { + /* Everything should be OK */ + malware_name = NULL; + DEBUG(D_acl) debug_printf("Malware not found\n"); + + } else + return m_errlog_defer(scanent, + string_sprintf("unparseable response from ClamAV: {%s}", av_buffer)); + + break; + } /* clamd */ + + case M_SOCK: /* "sock" scanner type ------------------------------------- */ + /* This code was derived by Martin Poole from the clamd code contributed + by David Saez and the cmdline code + */ + { + int bread; + uschar * commandline; + uschar av_buffer[1024]; + uschar * linebuffer; + uschar * sockline_scanner; + uschar sockline_scanner_default[] = "%s\n"; + const pcre *sockline_trig_re; + const pcre *sockline_name_re; + + /* find scanner command line */ + if ((sockline_scanner = string_nextinlist(&av_scanner_work, &sep, + NULL, 0))) + { /* check for no expansions apart from one %s */ + char * s = index(CS sockline_scanner, '%'); + if (s++) + if ((*s != 's' && *s != '%') || index(s+1, '%')) + return m_errlog_defer_3(scanent, + US"unsafe sock scanner call spec", sock); + } + else + sockline_scanner = sockline_scanner_default; - } else { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: clamd: unparseable response from ClamAV: {%s}", - av_buffer); - return DEFER; + /* find scanner output trigger */ + sockline_trig_re = m_pcre_nextinlist(&av_scanner_work, &sep, + "missing trigger specification", &errstr); + if (!sockline_trig_re) + return m_errlog_defer_3(scanent, errstr, sock); + + /* find virus name regex */ + sockline_name_re = m_pcre_nextinlist(&av_scanner_work, &sep, + "missing virus name regex specification", &errstr); + if (!sockline_name_re) + return m_errlog_defer_3(scanent, errstr, sock); + + /* prepare scanner call - security depends on expansions check above */ + commandline = string_sprintf("%s/scan/%s/%s.eml", spool_directory, message_id, message_id); + commandline = string_sprintf( CS sockline_scanner, CS commandline); + + + /* Pass the command string to the socket */ + if (m_sock_send(sock, commandline, Ustrlen(commandline), &errstr) < 0) + return m_errlog_defer(scanent, errstr); + + /* Read the result */ + memset(av_buffer, 0, sizeof(av_buffer)); + bread = read(sock, av_buffer, sizeof(av_buffer)); + + if (!(bread > 0)) + return m_errlog_defer_3(scanent, + string_sprintf("unable to read from socket (%s)", strerror(errno)), + sock); + + if (bread == sizeof(av_buffer)) + return m_errlog_defer_3(scanent, US"buffer too small", sock); + linebuffer = string_copy(av_buffer); + + /* try trigger match */ + if (regex_match_and_setup(sockline_trig_re, linebuffer, 0, -1)) { + if (!(malware_name = m_pcre_exec(sockline_name_re, av_buffer))) + malware_name = US "unknown"; + } + else /* no virus found */ + malware_name = NULL; + break; } - } /* clamd */ + case M_MKSD: /* "mksd" scanner type ------------------------------------- */ + { + char *mksd_options_end; + int mksd_maxproc = 1; /* default, if no option supplied */ + int sock; + int retval; + + if (scanner_options) { + mksd_maxproc = (int)strtol(CS scanner_options, &mksd_options_end, 10); + if ( *scanner_options == '\0' + || *mksd_options_end != '\0' + || mksd_maxproc < 1 + || mksd_maxproc > 32 + ) + return m_errlog_defer(scanent, + string_sprintf("invalid option '%s'", scanner_options)); + } - /* ----------------------------------------------------------------------- */ + if((sock = m_unixsocket(US "/var/run/mksd/socket", &errstr)) < 0) + return m_errlog_defer(scanent, errstr); + malware_name = NULL; - /* "mksd" scanner type --------------------------------------------------- */ - else if (strcmpic(scanner_name,US"mksd") == 0) { - uschar *mksd_options; - char *mksd_options_end; - uschar mksd_options_buffer[32]; - int mksd_maxproc = 1; /* default, if no option supplied */ - struct sockaddr_un server; - int sock; - int retval; - - if ((mksd_options = string_nextinlist(&av_scanner_work, &sep, - mksd_options_buffer, - sizeof(mksd_options_buffer))) != NULL) { - mksd_maxproc = (int) strtol(CS mksd_options, &mksd_options_end, 10); - if ((*mksd_options == '\0') || (*mksd_options_end != '\0') || - (mksd_maxproc < 1) || (mksd_maxproc > 32)) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: mksd: invalid option '%s'", mksd_options); - return DEFER; - } - } + DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan\n", scanner_name); - /* open the mksd socket */ - sock = socket(AF_UNIX, SOCK_STREAM, 0); - if (sock < 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: can't open UNIX socket."); - return DEFER; - } - server.sun_family = AF_UNIX; - Ustrcpy(server.sun_path, "/var/run/mksd/socket"); - if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { - (void)close(sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to connect to mksd UNIX socket (/var/run/mksd/socket). errno=%d", errno); - return DEFER; + if ((retval = mksd_scan_packed(scanent, sock, eml_filename)) != OK) { + close (sock); + return retval; + } + break; } - - malware_name = NULL; - - DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan\n", scanner_name); - - retval = mksd_scan_packed(sock, eml_filename); - - if (retval != OK) - return retval; } - /* ----------------------------------------------------------------------- */ - /* "unknown" scanner type ------------------------------------------------- */ - else { - log_write(0, LOG_MAIN|LOG_PANIC, - "malware condition: unknown scanner type '%s'", scanner_name); - return DEFER; - }; - /* ----------------------------------------------------------------------- */ - - /* set "been here, done that" marker */ - malware_ok = 1; - }; + if (sock >= 0) + (void) close (sock); + malware_ok = TRUE; /* set "been here, done that" marker */ + } /* match virus name against pattern (caseless ------->----------v) */ - if ( (malware_name != NULL) && - (regex_match_and_setup(re, malware_name, 0, -1)) ) { + if ( malware_name && (regex_match_and_setup(re, malware_name, 0, -1)) ) { DEBUG(D_acl) debug_printf("Matched regex to malware [%s] [%s]\n", malware_regex, malware_name); return OK; } - else { + else return FAIL; - }; } /* simple wrapper for reading lines from sockets */ -int recv_line(int sock, uschar *buffer, int size) { +int +recv_line(int sock, uschar *buffer, int size) +{ uschar *p = buffer; memset(buffer,0,size); @@ -1895,7 +1556,7 @@ if ((p-buffer) > (size-2)) break; if (*p == '\n') break; if (*p != '\r') p++; - }; + } *p = '\0'; return (p-buffer); @@ -1906,7 +1567,8 @@ #include -static int mksd_writev (int sock, struct iovec *iov, int iovcnt) +static inline int +mksd_writev (int sock, struct iovec *iov, int iovcnt) { int i; @@ -1915,9 +1577,7 @@ i = writev (sock, iov, iovcnt); while ((i < 0) && (errno == EINTR)); if (i <= 0) { - close (sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to write to mksd UNIX socket (/var/run/mksd/socket)"); + (void) malware_errlog_defer(US"unable to write to mksd UNIX socket (/var/run/mksd/socket)"); return -1; } @@ -1935,25 +1595,22 @@ } } -static int mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size) +static inline int +mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size) { int offset = 0; int i; do { if ((i = recv (sock, av_buffer+offset, av_buffer_size-offset, 0)) <= 0) { - close (sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: unable to read from mksd UNIX socket (/var/run/mksd/socket)"); + (void) malware_errlog_defer(US"unable to read from mksd UNIX socket (/var/run/mksd/socket)"); return -1; } offset += i; /* offset == av_buffer_size -> buffer full */ if (offset == av_buffer_size) { - close (sock); - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: malformed reply received from mksd"); + (void) malware_errlog_defer(US"malformed reply received from mksd"); return -1; } } while (av_buffer[offset-1] != '\n'); @@ -1962,41 +1619,39 @@ return offset; } -static int mksd_parse_line (char *line) +static inline int +mksd_parse_line(struct scan * scanent, char *line) { char *p; switch (*line) { - case 'O': - /* OK */ + case 'O': /* OK */ return OK; + case 'E': - case 'A': - /* ERR */ + case 'A': /* ERR */ if ((p = strchr (line, '\n')) != NULL) - (*p) = '\0'; - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: mksd scanner failed: %s", line); - return DEFER; - default: - /* VIR */ + *p = '\0'; + return m_errlog_defer(scanent, + string_sprintf("scanner failed: %s", line)); + + default: /* VIR */ if ((p = strchr (line, '\n')) != NULL) { - (*p) = '\0'; - if (((p-line) > 5) && ((p-line) < sizeof (malware_name_buffer)) && (line[3] == ' ')) + *p = '\0'; + if (((p-line) > 5) && (line[3] == ' ')) if (((p = strchr (line+4, ' ')) != NULL) && ((p-line) > 4)) { - (*p) = '\0'; - Ustrcpy (malware_name_buffer, line+4); - malware_name = malware_name_buffer; + *p = '\0'; + malware_name = string_copy(US line+4); return OK; } } - log_write(0, LOG_MAIN|LOG_PANIC, - "malware acl condition: malformed reply received from mksd: %s", line); - return DEFER; + return m_errlog_defer(scanent, + string_sprintf("malformed reply received: %s", line)); } } -static int mksd_scan_packed(int sock, uschar *scan_filename) +static int +mksd_scan_packed(struct scan * scanent, int sock, uschar *scan_filename) { struct iovec iov[3]; const char *cmd = "MSQ\n"; @@ -2015,9 +1670,10 @@ if (mksd_read_lines (sock, av_buffer, sizeof (av_buffer)) < 0) return DEFER; - close (sock); - - return mksd_parse_line (CS av_buffer); + return mksd_parse_line (scanent, CS av_buffer); } -#endif +#endif /*WITH_CONTENT_SCAN*/ +/* + * vi: aw ai sw=2 + */ diff -Nru exim4-4.82/src/match.c exim4-4.84/src/match.c --- exim4-4.82/src/match.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/match.c 2014-08-09 12:44:29.000000000 +0000 @@ -221,6 +221,8 @@ NULL, /* service name not relevant */ NULL, /* srv_fail_domains not relevant */ NULL, /* mx_fail_domains not relevant */ + NULL, /* no dnssec request XXX ? */ + NULL, /* no dnssec require XXX ? */ NULL, /* no feedback FQDN */ &removed); /* feedback if local removed */ diff -Nru exim4-4.82/src/mime.c exim4-4.84/src/mime.c --- exim4-4.82/src/mime.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/mime.c 2014-08-09 12:44:29.000000000 +0000 @@ -6,7 +6,7 @@ /* License: GPL */ #include "exim.h" -#ifdef WITH_CONTENT_SCAN +#ifdef WITH_CONTENT_SCAN /* entire file */ #include "mime.h" #include @@ -21,7 +21,9 @@ give info on detected "problems" in MIME encodings. Those are defined in mime.h. */ -void mime_set_anomaly(int level, const char *text) { +void +mime_set_anomaly(int level, const char *text) +{ mime_anomaly_level = level; mime_anomaly_text = CUS text; } @@ -39,36 +41,37 @@ 0-255 - char to write */ -uschar *mime_decode_qp_char(uschar *qp_p, int *c) { - uschar *initial_pos = qp_p; +uschar * +mime_decode_qp_char(uschar *qp_p, int *c) +{ +uschar *initial_pos = qp_p; + +/* advance one char */ +qp_p++; + +/* Check for two hex digits and decode them */ +if (isxdigit(*qp_p) && isxdigit(qp_p[1])) + { + /* Do hex conversion */ + *c = (isdigit(*qp_p) ? *qp_p - '0' : toupper(*qp_p) - 'A' + 10) <<4; + qp_p++; + *c |= isdigit(*qp_p) ? *qp_p - '0' : toupper(*qp_p) - 'A' + 10; + return qp_p + 1; + } - /* advance one char */ +/* tab or whitespace may follow just ignore it if it precedes \n */ +while (*qp_p == '\t' || *qp_p == ' ' || *qp_p == '\r') qp_p++; - /* Check for two hex digits and decode them */ - if (isxdigit(*qp_p) && isxdigit(qp_p[1])) { - /* Do hex conversion */ - if (isdigit(*qp_p)) {*c = *qp_p - '0';} - else {*c = toupper(*qp_p) - 'A' + 10;}; - *c <<= 4; - if (isdigit(qp_p[1])) {*c |= qp_p[1] - '0';} - else {*c |= toupper(qp_p[1]) - 'A' + 10;}; - return qp_p + 2; - }; - - /* tab or whitespace may follow just ignore it if it precedes \n */ - while (*qp_p == '\t' || *qp_p == ' ' || *qp_p == '\r') - qp_p++; - - if (*qp_p == '\n') { - /* hit soft line break */ - *c = -1; - return qp_p; - }; - - /* illegal char here */ - *c = -2; - return initial_pos; +if (*qp_p == '\n') /* hit soft line break */ + { + *c = -1; + return qp_p; + } + +/* illegal char here */ +*c = -2; +return initial_pos; } @@ -79,18 +82,19 @@ ssize_t len, size = 0; uschar buffer[MIME_MAX_LINE_LENGTH]; - while(fgets(CS buffer, MIME_MAX_LINE_LENGTH, mime_stream) != NULL) { + while(fgets(CS buffer, MIME_MAX_LINE_LENGTH, mime_stream) != NULL) + { if (boundary != NULL - && Ustrncmp(buffer, "--", 2) == 0 - && Ustrncmp((buffer+2), boundary, Ustrlen(boundary)) == 0 - ) + && Ustrncmp(buffer, "--", 2) == 0 + && Ustrncmp((buffer+2), boundary, Ustrlen(boundary)) == 0 + ) break; len = Ustrlen(buffer); if (fwrite(buffer, 1, (size_t)len, out) < len) return -1; size += len; - } /* while */ + } /* while */ return size; } @@ -107,26 +111,29 @@ opos = obuf; while (Ufgets(ibuf, MIME_MAX_LINE_LENGTH, in) != NULL) - { + { if (boundary != NULL - && Ustrncmp(ibuf, "--", 2) == 0 - && Ustrncmp((ibuf+2), boundary, Ustrlen(boundary)) == 0 - ) + && Ustrncmp(ibuf, "--", 2) == 0 + && Ustrncmp((ibuf+2), boundary, Ustrlen(boundary)) == 0 + ) break; - for (ipos = ibuf ; *ipos != '\r' && *ipos != '\n' && *ipos != 0; ++ipos) { - /* skip padding */ - if (*ipos == '=') { + for (ipos = ibuf ; *ipos != '\r' && *ipos != '\n' && *ipos != 0; ++ipos) + { + if (*ipos == '=') /* skip padding */ + { ++bytestate; continue; - } - /* skip bad characters */ - if (mime_b64[*ipos] == 128) { + } + if (mime_b64[*ipos] == 128) /* skip bad characters */ + { mime_set_anomaly(MIME_ANOMALY_BROKEN_BASE64); continue; - } + } + /* simple state-machine */ - switch((bytestate++) & 3) { + switch((bytestate++) & 3) + { case 0: *opos = mime_b64[*ipos] << 2; break; @@ -144,27 +151,28 @@ *opos |= mime_b64[*ipos]; ++opos; break; - } /* switch */ - } /* for */ + } /* switch */ + } /* for */ + /* something to write? */ len = opos - obuf; - if (len > 0) { - if (fwrite(obuf, 1, len, out) != len) - return -1; /* error */ + if (len > 0) + { + if (fwrite(obuf, 1, len, out) != len) return -1; /* error */ size += len; /* copy incomplete last byte to start of obuf, where we continue */ if ((bytestate & 3) != 0) *obuf = *opos; opos = obuf; - } - } /* while */ + } + } /* while */ /* write out last byte if it was incomplete */ - if (bytestate & 3) { - if (fwrite(obuf, 1, 1, out) != 1) - return -1; - ++size; - } + if (bytestate & 3) + { + if (fwrite(obuf, 1, 1, out) != 1) return -1; + ++size; + } return size; } @@ -174,516 +182,559 @@ static ssize_t mime_decode_qp(FILE* in, FILE* out, uschar* boundary) { - uschar ibuf[MIME_MAX_LINE_LENGTH], obuf[MIME_MAX_LINE_LENGTH]; - uschar *ipos, *opos; - ssize_t len, size = 0; +uschar ibuf[MIME_MAX_LINE_LENGTH], obuf[MIME_MAX_LINE_LENGTH]; +uschar *ipos, *opos; +ssize_t len, size = 0; - while (fgets(CS ibuf, MIME_MAX_LINE_LENGTH, in) != NULL) +while (fgets(CS ibuf, MIME_MAX_LINE_LENGTH, in) != NULL) { - if (boundary != NULL - && Ustrncmp(ibuf, "--", 2) == 0 - && Ustrncmp((ibuf+2), boundary, Ustrlen(boundary)) == 0 - ) - break; /* todo: check for missing boundary */ - - ipos = ibuf; - opos = obuf; - - while (*ipos != 0) { - if (*ipos == '=') { - int decode_qp_result; - - ipos = mime_decode_qp_char(ipos, &decode_qp_result); - - if (decode_qp_result == -2) { - /* Error from decoder. ipos is unchanged. */ - mime_set_anomaly(MIME_ANOMALY_BROKEN_QP); - *opos = '='; - ++opos; - ++ipos; - } - else if (decode_qp_result == -1) { - break; - } - else if (decode_qp_result >= 0) { - *opos = decode_qp_result; - ++opos; - } + if (boundary != NULL + && Ustrncmp(ibuf, "--", 2) == 0 + && Ustrncmp((ibuf+2), boundary, Ustrlen(boundary)) == 0 + ) + break; /* todo: check for missing boundary */ + + ipos = ibuf; + opos = obuf; + + while (*ipos != 0) + { + if (*ipos == '=') + { + int decode_qp_result; + + ipos = mime_decode_qp_char(ipos, &decode_qp_result); + + if (decode_qp_result == -2) + { + /* Error from decoder. ipos is unchanged. */ + mime_set_anomaly(MIME_ANOMALY_BROKEN_QP); + *opos = '='; + ++opos; + ++ipos; + } + else if (decode_qp_result == -1) + break; + else if (decode_qp_result >= 0) + { + *opos = decode_qp_result; + ++opos; + } } - else { - *opos = *ipos; - ++opos; - ++ipos; + else + { + *opos = *ipos; + ++opos; + ++ipos; } } - /* something to write? */ - len = opos - obuf; - if (len > 0) { - if (fwrite(obuf, 1, len, out) != len) - return -1; /* error */ - size += len; + /* something to write? */ + len = opos - obuf; + if (len > 0) + { + if (fwrite(obuf, 1, len, out) != len) return -1; /* error */ + size += len; } } - return size; +return size; } -FILE *mime_get_decode_file(uschar *pname, uschar *fname) { - FILE *f = NULL; - uschar *filename; +FILE * +mime_get_decode_file(uschar *pname, uschar *fname) +{ +FILE *f = NULL; +uschar *filename; - filename = (uschar *)malloc(2048); +filename = (uschar *)malloc(2048); - if ((pname != NULL) && (fname != NULL)) { - (void)string_format(filename, 2048, "%s/%s", pname, fname); - f = modefopen(filename,"wb+",SPOOL_MODE); - } - else if (pname == NULL) { - f = modefopen(fname,"wb+",SPOOL_MODE); +if (pname && fname) + { + (void)string_format(filename, 2048, "%s/%s", pname, fname); + f = modefopen(filename,"wb+",SPOOL_MODE); } - else if (fname == NULL) { - int file_nr = 0; - int result = 0; +else if (!pname) + f = modefopen(fname,"wb+",SPOOL_MODE); +else if (!fname) + { + int file_nr = 0; + int result = 0; - /* must find first free sequential filename */ - do { - struct stat mystat; - (void)string_format(filename,2048,"%s/%s-%05u", pname, message_id, file_nr); - file_nr++; - /* security break */ - if (file_nr >= 1024) - break; - result = stat(CS filename,&mystat); - } - while(result != -1); - f = modefopen(filename,"wb+",SPOOL_MODE); - }; + /* must find first free sequential filename */ + do + { + struct stat mystat; + (void)string_format(filename, 2048, + "%s/%s-%05u", pname, message_id, file_nr++); + /* security break */ + if (file_nr >= 1024) + break; + result = stat(CS filename, &mystat); + } while(result != -1); - /* set expansion variable */ - mime_decoded_filename = filename; + f = modefopen(filename, "wb+", SPOOL_MODE); + } - return f; -} +/* set expansion variable */ +mime_decoded_filename = filename; +return f; +} -int mime_decode(uschar **listptr) { - int sep = 0; - uschar *list = *listptr; - uschar *option; - uschar option_buffer[1024]; - uschar decode_path[1024]; - FILE *decode_file = NULL; - long f_pos = 0; - ssize_t size_counter = 0; - ssize_t (*decode_function)(FILE*, FILE*, uschar*); - if (mime_stream == NULL) +int +mime_decode(uschar **listptr) +{ +int sep = 0; +uschar *list = *listptr; +uschar *option; +uschar option_buffer[1024]; +uschar decode_path[1024]; +FILE *decode_file = NULL; +long f_pos = 0; +ssize_t size_counter = 0; +ssize_t (*decode_function)(FILE*, FILE*, uschar*); + +if (mime_stream == NULL) + return FAIL; + +f_pos = ftell(mime_stream); + +/* build default decode path (will exist since MBOX must be spooled up) */ +(void)string_format(decode_path,1024,"%s/scan/%s",spool_directory,message_id); + +/* try to find 1st option */ +if ((option = string_nextinlist(&list, &sep, + option_buffer, + sizeof(option_buffer))) != NULL) + { + /* parse 1st option */ + if ( (Ustrcmp(option,"false") == 0) || (Ustrcmp(option,"0") == 0) ) + /* explicitly no decoding */ return FAIL; - f_pos = ftell(mime_stream); - - /* build default decode path (will exist since MBOX must be spooled up) */ - (void)string_format(decode_path,1024,"%s/scan/%s",spool_directory,message_id); - - /* try to find 1st option */ - if ((option = string_nextinlist(&list, &sep, - option_buffer, - sizeof(option_buffer))) != NULL) { - - /* parse 1st option */ - if ( (Ustrcmp(option,"false") == 0) || (Ustrcmp(option,"0") == 0) ) { - /* explicitly no decoding */ - return FAIL; - }; - - if (Ustrcmp(option,"default") == 0) { - /* explicit default path + file names */ - goto DEFAULT_PATH; - }; - - if (option[0] == '/') { - struct stat statbuf; - - memset(&statbuf,0,sizeof(statbuf)); - - /* assume either path or path+file name */ - if ( (stat(CS option, &statbuf) == 0) && S_ISDIR(statbuf.st_mode) ) - /* is directory, use it as decode_path */ - decode_file = mime_get_decode_file(option, NULL); - else - /* does not exist or is a file, use as full file name */ - decode_file = mime_get_decode_file(NULL, option); - } + if (Ustrcmp(option,"default") == 0) + /* explicit default path + file names */ + goto DEFAULT_PATH; + + if (option[0] == '/') + { + struct stat statbuf; + + memset(&statbuf,0,sizeof(statbuf)); + + /* assume either path or path+file name */ + if ( (stat(CS option, &statbuf) == 0) && S_ISDIR(statbuf.st_mode) ) + /* is directory, use it as decode_path */ + decode_file = mime_get_decode_file(option, NULL); else - /* assume file name only, use default path */ - decode_file = mime_get_decode_file(decode_path, option); - } + /* does not exist or is a file, use as full file name */ + decode_file = mime_get_decode_file(NULL, option); + } else - /* no option? patch default path */ - DEFAULT_PATH: decode_file = mime_get_decode_file(decode_path, NULL); + /* assume file name only, use default path */ + decode_file = mime_get_decode_file(decode_path, option); + } +else + { + /* no option? patch default path */ +DEFAULT_PATH: + decode_file = mime_get_decode_file(decode_path, NULL); + } - if (decode_file == NULL) - return DEFER; +if (!decode_file) + return DEFER; - /* decode according to mime type */ - if (mime_content_transfer_encoding == NULL) - /* no encoding, dump as-is */ - decode_function = mime_decode_asis; - else if (Ustrcmp(mime_content_transfer_encoding, "base64") == 0) - decode_function = mime_decode_base64; - else if (Ustrcmp(mime_content_transfer_encoding, "quoted-printable") == 0) - decode_function = mime_decode_qp; - else - /* unknown encoding type, just dump as-is */ - decode_function = mime_decode_asis; +/* decode according to mime type */ +decode_function = + !mime_content_transfer_encoding + ? mime_decode_asis /* no encoding, dump as-is */ + : Ustrcmp(mime_content_transfer_encoding, "base64") == 0 + ? mime_decode_base64 + : Ustrcmp(mime_content_transfer_encoding, "quoted-printable") == 0 + ? mime_decode_qp + : mime_decode_asis; /* unknown encoding type, just dump as-is */ + +size_counter = decode_function(mime_stream, decode_file, mime_current_boundary); + +clearerr(mime_stream); +fseek(mime_stream, f_pos, SEEK_SET); - size_counter = decode_function(mime_stream, decode_file, mime_current_boundary); +if (fclose(decode_file) != 0 || size_counter < 0) + return DEFER; - clearerr(mime_stream); - fseek(mime_stream, f_pos, SEEK_SET); +/* round up to the next KiB */ +mime_content_size = (size_counter + 1023) / 1024; - if (fclose(decode_file) != 0 || size_counter < 0) - return DEFER; +return OK; +} - /* round up to the next KiB */ - mime_content_size = (size_counter + 1023) / 1024; +int +mime_get_header(FILE *f, uschar *header) +{ +int c = EOF; +int done = 0; +int header_value_mode = 0; +int header_open_brackets = 0; +int num_copied = 0; - return OK; -} +while(!done) + { + if ((c = fgetc(f)) == EOF) break; -int mime_get_header(FILE *f, uschar *header) { - int c = EOF; - int done = 0; - int header_value_mode = 0; - int header_open_brackets = 0; - int num_copied = 0; - - while(!done) { - - c = fgetc(f); - if (c == EOF) break; - - /* always skip CRs */ - if (c == '\r') continue; - - if (c == '\n') { - if (num_copied > 0) { - /* look if next char is '\t' or ' ' */ - c = fgetc(f); - if (c == EOF) break; - if ( (c == '\t') || (c == ' ') ) continue; - (void)ungetc(c,f); - }; - /* end of the header, terminate with ';' */ - c = ';'; - done = 1; - }; - - /* skip control characters */ - if (c < 32) continue; - - if (header_value_mode) { - /* --------- value mode ----------- */ - /* skip leading whitespace */ - if ( ((c == '\t') || (c == ' ')) && (header_value_mode == 1) ) - continue; + /* always skip CRs */ + if (c == '\r') continue; + + if (c == '\n') + { + if (num_copied > 0) + { + /* look if next char is '\t' or ' ' */ + if ((c = fgetc(f)) == EOF) break; + if ( (c == '\t') || (c == ' ') ) continue; + (void)ungetc(c,f); + } + /* end of the header, terminate with ';' */ + c = ';'; + done = 1; + } + + /* skip control characters */ + if (c < 32) continue; + + if (header_value_mode) + { + /* --------- value mode ----------- */ + /* skip leading whitespace */ + if ( ((c == '\t') || (c == ' ')) && (header_value_mode == 1) ) + continue; /* we have hit a non-whitespace char, start copying value data */ header_value_mode = 2; - /* skip quotes */ - if (c == '"') continue; + if (c == '"') /* flip "quoted" mode */ + header_value_mode = header_value_mode==2 ? 3 : 2; - /* leave value mode on ';' */ - if (c == ';') { + /* leave value mode on unquoted ';' */ + if (header_value_mode == 2 && c == ';') { header_value_mode = 0; }; /* -------------------------------- */ } - else { - /* -------- non-value mode -------- */ - /* skip whitespace + tabs */ - if ( (c == ' ') || (c == '\t') ) - continue; - if (c == '\\') { - /* quote next char. can be used - to escape brackets. */ - c = fgetc(f); - if (c == EOF) break; + else + { + /* -------- non-value mode -------- */ + /* skip whitespace + tabs */ + if ( (c == ' ') || (c == '\t') ) + continue; + if (c == '\\') + { + /* quote next char. can be used + to escape brackets. */ + if ((c = fgetc(f)) == EOF) break; } - else if (c == '(') { - header_open_brackets++; - continue; + else if (c == '(') + { + header_open_brackets++; + continue; } - else if ((c == ')') && header_open_brackets) { - header_open_brackets--; - continue; + else if ((c == ')') && header_open_brackets) + { + header_open_brackets--; + continue; } - else if ( (c == '=') && !header_open_brackets ) { - /* enter value mode */ - header_value_mode = 1; - }; + else if ( (c == '=') && !header_open_brackets ) /* enter value mode */ + header_value_mode = 1; - /* skip chars while we are in a comment */ - if (header_open_brackets > 0) - continue; - /* -------------------------------- */ - }; + /* skip chars while we are in a comment */ + if (header_open_brackets > 0) + continue; + /* -------------------------------- */ + } - /* copy the char to the buffer */ - header[num_copied] = (uschar)c; - /* raise counter */ - num_copied++; - - /* break if header buffer is full */ - if (num_copied > MIME_MAX_HEADER_SIZE-1) { - done = 1; - }; - }; - - if ((num_copied > 0) && (header[num_copied-1] != ';')) { - header[num_copied-1] = ';'; - }; - - /* 0-terminate */ - header[num_copied] = '\0'; - - /* return 0 for EOF or empty line */ - if ((c == EOF) || (num_copied == 1)) - return 0; - else - return 1; -} + /* copy the char to the buffer */ + header[num_copied++] = (uschar)c; + /* break if header buffer is full */ + if (num_copied > MIME_MAX_HEADER_SIZE-1) + done = 1; + } -int mime_acl_check(uschar *acl, FILE *f, struct mime_boundary_context *context, - uschar **user_msgptr, uschar **log_msgptr) { - int rc = OK; - uschar *header = NULL; - struct mime_boundary_context nested_context; - - /* reserve a line buffer to work in */ - header = (uschar *)malloc(MIME_MAX_HEADER_SIZE+1); - if (header == NULL) { - log_write(0, LOG_PANIC, - "MIME ACL: can't allocate %d bytes of memory.", MIME_MAX_HEADER_SIZE+1); - return DEFER; - }; - - /* Not actually used at the moment, but will be vital to fixing - * some RFC 2046 nonconformance later... */ - nested_context.parent = context; - - /* loop through parts */ - while(1) { - - /* reset all per-part mime variables */ - mime_anomaly_level = 0; - mime_anomaly_text = NULL; - mime_boundary = NULL; - mime_charset = NULL; - mime_decoded_filename = NULL; - mime_filename = NULL; - mime_content_description = NULL; - mime_content_disposition = NULL; - mime_content_id = NULL; - mime_content_transfer_encoding = NULL; - mime_content_type = NULL; - mime_is_multipart = 0; - mime_content_size = 0; - - /* - If boundary is null, we assume that *f is positioned on the start of headers (for example, - at the very beginning of a message. - If a boundary is given, we must first advance to it to reach the start of the next header - block. - */ - - /* NOTE -- there's an error here -- RFC2046 specifically says to - * check for outer boundaries. This code doesn't do that, and - * I haven't fixed this. - * - * (I have moved partway towards adding support, however, by adding - * a "parent" field to my new boundary-context structure.) - */ - if (context != NULL) { - while(fgets(CS header, MIME_MAX_HEADER_SIZE, f) != NULL) { - /* boundary line must start with 2 dashes */ - if (Ustrncmp(header,"--",2) == 0) { - if (Ustrncmp((header+2),context->boundary,Ustrlen(context->boundary)) == 0) { - /* found boundary */ - if (Ustrncmp((header+2+Ustrlen(context->boundary)),"--",2) == 0) { - /* END boundary found */ - debug_printf("End boundary found %s\n", context->boundary); - return rc; - } - else { - debug_printf("Next part with boundary %s\n", context->boundary); - }; - /* can't use break here */ - goto DECODE_HEADERS; - } - }; - } - /* Hit EOF or read error. Ugh. */ - debug_printf("Hit EOF ...\n"); - return rc; - }; - - DECODE_HEADERS: - /* parse headers, set up expansion variables */ - while(mime_get_header(f,header)) { - int i; - /* loop through header list */ - for (i = 0; i < mime_header_list_size; i++) { - uschar *header_value = NULL; - int header_value_len = 0; - - /* found an interesting header? */ - if (strncmpic(mime_header_list[i].name,header,mime_header_list[i].namelen) == 0) { - uschar *p = header + mime_header_list[i].namelen; - /* yes, grab the value (normalize to lower case) - and copy to its corresponding expansion variable */ - while(*p != ';') { - *p = tolower(*p); - p++; - }; - header_value_len = (p - (header + mime_header_list[i].namelen)); - header_value = (uschar *)malloc(header_value_len+1); - memset(header_value,0,header_value_len+1); - p = header + mime_header_list[i].namelen; - Ustrncpy(header_value, p, header_value_len); - debug_printf("Found %s MIME header, value is '%s'\n", mime_header_list[i].name, header_value); - *((uschar **)(mime_header_list[i].value)) = header_value; - - /* make p point to the next character after the closing ';' */ - p += (header_value_len+1); - - /* grab all param=value tags on the remaining line, check if they are interesting */ - NEXT_PARAM_SEARCH: while (*p != 0) { - int j; - for (j = 0; j < mime_parameter_list_size; j++) { - uschar *param_value = NULL; - int param_value_len = 0; - - /* found an interesting parameter? */ - if (strncmpic(mime_parameter_list[j].name,p,mime_parameter_list[j].namelen) == 0) { - uschar *q = p + mime_parameter_list[j].namelen; - /* yes, grab the value and copy to its corresponding expansion variable */ - while(*q != ';') q++; - param_value_len = (q - (p + mime_parameter_list[j].namelen)); - param_value = (uschar *)malloc(param_value_len+1); - memset(param_value,0,param_value_len+1); - q = p + mime_parameter_list[j].namelen; - Ustrncpy(param_value, q, param_value_len); - param_value = rfc2047_decode(param_value, check_rfc2047_length, NULL, 32, ¶m_value_len, &q); - debug_printf("Found %s MIME parameter in %s header, value is '%s'\n", mime_parameter_list[j].name, mime_header_list[i].name, param_value); - *((uschar **)(mime_parameter_list[j].value)) = param_value; - p += (mime_parameter_list[j].namelen + param_value_len + 1); - goto NEXT_PARAM_SEARCH; - }; - } - /* There is something, but not one of our interesting parameters. - Advance to the next semicolon */ - while(*p != ';') p++; - p++; - }; - }; - }; - }; +if ((num_copied > 0) && (header[num_copied-1] != ';')) + header[num_copied-1] = ';'; - /* set additional flag variables (easier access) */ - if ( (mime_content_type != NULL) && - (Ustrncmp(mime_content_type,"multipart",9) == 0) ) - mime_is_multipart = 1; - - /* Make a copy of the boundary pointer. - Required since mime_boundary is global - and can be overwritten further down in recursion */ - nested_context.boundary = mime_boundary; +/* 0-terminate */ +header[num_copied] = '\0'; - /* raise global counter */ - mime_part_count++; +/* return 0 for EOF or empty line */ +if ((c == EOF) || (num_copied == 1)) + return 0; +else + return 1; +} - /* copy current file handle to global variable */ - mime_stream = f; - mime_current_boundary = context ? context->boundary : 0; - /* Note the context */ - mime_is_coverletter = !(context && context->context == MBC_ATTACHMENT); +int +mime_acl_check(uschar *acl, FILE *f, struct mime_boundary_context *context, + uschar **user_msgptr, uschar **log_msgptr) +{ +int rc = OK; +uschar *header = NULL; +struct mime_boundary_context nested_context; - /* call ACL handling function */ - rc = acl_check(ACL_WHERE_MIME, NULL, acl, user_msgptr, log_msgptr); +/* reserve a line buffer to work in */ +if (!(header = (uschar *)malloc(MIME_MAX_HEADER_SIZE+1))) + { + log_write(0, LOG_PANIC, + "MIME ACL: can't allocate %d bytes of memory.", MIME_MAX_HEADER_SIZE+1); + return DEFER; + } - mime_stream = NULL; - mime_current_boundary = NULL; +/* Not actually used at the moment, but will be vital to fixing + * some RFC 2046 nonconformance later... */ +nested_context.parent = context; - if (rc != OK) break; +/* loop through parts */ +while(1) + { + /* reset all per-part mime variables */ + mime_anomaly_level = 0; + mime_anomaly_text = NULL; + mime_boundary = NULL; + mime_charset = NULL; + mime_decoded_filename = NULL; + mime_filename = NULL; + mime_content_description = NULL; + mime_content_disposition = NULL; + mime_content_id = NULL; + mime_content_transfer_encoding = NULL; + mime_content_type = NULL; + mime_is_multipart = 0; + mime_content_size = 0; + + /* + If boundary is null, we assume that *f is positioned on the start of headers (for example, + at the very beginning of a message. + If a boundary is given, we must first advance to it to reach the start of the next header + block. + */ + + /* NOTE -- there's an error here -- RFC2046 specifically says to + * check for outer boundaries. This code doesn't do that, and + * I haven't fixed this. + * + * (I have moved partway towards adding support, however, by adding + * a "parent" field to my new boundary-context structure.) + */ + if (context != NULL) + { + while(fgets(CS header, MIME_MAX_HEADER_SIZE, f) != NULL) + { + /* boundary line must start with 2 dashes */ + if (Ustrncmp(header,"--",2) == 0) + { + if (Ustrncmp((header+2),context->boundary,Ustrlen(context->boundary)) == 0) + { + /* found boundary */ + if (Ustrncmp((header+2+Ustrlen(context->boundary)),"--",2) == 0) + { + /* END boundary found */ + debug_printf("End boundary found %s\n", context->boundary); + return rc; + } + else + debug_printf("Next part with boundary %s\n", context->boundary); + + /* can't use break here */ + goto DECODE_HEADERS; + } + } + } + /* Hit EOF or read error. Ugh. */ + debug_printf("Hit EOF ...\n"); + return rc; + } - /* If we have a multipart entity and a boundary, go recursive */ - if ( (mime_content_type != NULL) && - (nested_context.boundary != NULL) && - (Ustrncmp(mime_content_type,"multipart",9) == 0) ) { - debug_printf("Entering multipart recursion, boundary '%s'\n", nested_context.boundary); - - if (context && context->context == MBC_ATTACHMENT) - nested_context.context = MBC_ATTACHMENT; - else if (!Ustrcmp(mime_content_type,"multipart/alternative") - || !Ustrcmp(mime_content_type,"multipart/related")) - nested_context.context = MBC_COVERLETTER_ALL; - else - nested_context.context = MBC_COVERLETTER_ONESHOT; - - rc = mime_acl_check(acl, f, &nested_context, user_msgptr, log_msgptr); - if (rc != OK) break; - } - else if ( (mime_content_type != NULL) && - (Ustrncmp(mime_content_type,"message/rfc822",14) == 0) ) { - uschar *rfc822name = NULL; - uschar filename[2048]; - int file_nr = 0; - int result = 0; - - /* must find first free sequential filename */ - do { - struct stat mystat; - (void)string_format(filename,2048,"%s/scan/%s/__rfc822_%05u", spool_directory, message_id, file_nr); - file_nr++; - /* security break */ - if (file_nr >= 128) - goto NO_RFC822; - result = stat(CS filename,&mystat); +DECODE_HEADERS: + /* parse headers, set up expansion variables */ + while (mime_get_header(f,header)) + { + int i; + /* loop through header list */ + for (i = 0; i < mime_header_list_size; i++) + { + uschar *header_value = NULL; + int header_value_len = 0; + + /* found an interesting header? */ + if (strncmpic(mime_header_list[i].name,header,mime_header_list[i].namelen) == 0) + { + uschar *p = header + mime_header_list[i].namelen; + /* yes, grab the value (normalize to lower case) + and copy to its corresponding expansion variable */ + while(*p != ';') + { + *p = tolower(*p); + p++; + } + header_value_len = (p - (header + mime_header_list[i].namelen)); + header_value = (uschar *)malloc(header_value_len+1); + memset(header_value,0,header_value_len+1); + p = header + mime_header_list[i].namelen; + Ustrncpy(header_value, p, header_value_len); + debug_printf("Found %s MIME header, value is '%s'\n", mime_header_list[i].name, header_value); + *((uschar **)(mime_header_list[i].value)) = header_value; + + /* make p point to the next character after the closing ';' */ + p += (header_value_len+1); + + /* grab all param=value tags on the remaining line, check if they are interesting */ +NEXT_PARAM_SEARCH: + while (*p != 0) + { + mime_parameter * mp; + for (mp = mime_parameter_list; + mp < &mime_parameter_list[mime_parameter_list_size]; + mp++) + { + uschar *param_value = NULL; + int param_value_len = 0; + + /* found an interesting parameter? */ + if (strncmpic(mp->name, p, mp->namelen) == 0) + { + uschar *q = p + mp->namelen; + int size = 0; + int ptr = 0; + + /* yes, grab the value and copy to its corresponding expansion variable */ + while(*q && *q != ';') /* ; terminates */ + { + if (*q == '"') + { + q++; /* skip leading " */ + while(*q && *q != '"') /* which protects ; */ + param_value = string_cat(param_value, &size, &ptr, q++, 1); + if (*q) q++; /* skip trailing " */ + } + else + param_value = string_cat(param_value, &size, &ptr, q++, 1); + } + param_value[ptr++] = '\0'; + param_value_len = ptr; + + param_value = rfc2047_decode(param_value, check_rfc2047_length, NULL, 32, ¶m_value_len, &q); + debug_printf("Found %s MIME parameter in %s header, value is '%s'\n", mp->name, mime_header_list[i].name, param_value); + *((uschar **)(mp->value)) = param_value; + p += (mp->namelen + param_value_len + 1); + goto NEXT_PARAM_SEARCH; + } + } + /* There is something, but not one of our interesting parameters. + Advance to the next semicolon */ + while(*p != ';') p++; + p++; + } } - while(result != -1); + } + } - rfc822name = filename; + /* set additional flag variables (easier access) */ + if ( (mime_content_type != NULL) && + (Ustrncmp(mime_content_type,"multipart",9) == 0) ) + mime_is_multipart = 1; + + /* Make a copy of the boundary pointer. + Required since mime_boundary is global + and can be overwritten further down in recursion */ + nested_context.boundary = mime_boundary; + + /* raise global counter */ + mime_part_count++; + + /* copy current file handle to global variable */ + mime_stream = f; + mime_current_boundary = context ? context->boundary : 0; + + /* Note the context */ + mime_is_coverletter = !(context && context->context == MBC_ATTACHMENT); + + /* call ACL handling function */ + rc = acl_check(ACL_WHERE_MIME, NULL, acl, user_msgptr, log_msgptr); + + mime_stream = NULL; + mime_current_boundary = NULL; + + if (rc != OK) break; + + /* If we have a multipart entity and a boundary, go recursive */ + if ( (mime_content_type != NULL) && + (nested_context.boundary != NULL) && + (Ustrncmp(mime_content_type,"multipart",9) == 0) ) + { + debug_printf("Entering multipart recursion, boundary '%s'\n", nested_context.boundary); + + nested_context.context = + context && context->context == MBC_ATTACHMENT + ? MBC_ATTACHMENT + : Ustrcmp(mime_content_type,"multipart/alternative") == 0 + || Ustrcmp(mime_content_type,"multipart/related") == 0 + ? MBC_COVERLETTER_ALL + : MBC_COVERLETTER_ONESHOT; - /* decode RFC822 attachment */ - mime_decoded_filename = NULL; - mime_stream = f; - mime_current_boundary = context ? context->boundary : NULL; - mime_decode(&rfc822name); - mime_stream = NULL; - mime_current_boundary = NULL; - if (mime_decoded_filename == NULL) { - /* decoding failed */ - log_write(0, LOG_MAIN, - "mime_regex acl condition warning - could not decode RFC822 MIME part to file."); - return DEFER; - }; - mime_decoded_filename = NULL; - }; + rc = mime_acl_check(acl, f, &nested_context, user_msgptr, log_msgptr); + if (rc != OK) break; + } + else if ( (mime_content_type != NULL) && + (Ustrncmp(mime_content_type,"message/rfc822",14) == 0) ) + { + uschar *rfc822name = NULL; + uschar filename[2048]; + int file_nr = 0; + int result = 0; - NO_RFC822: - /* If the boundary of this instance is NULL, we are finished here */ - if (context == NULL) break; + /* must find first free sequential filename */ + do + { + struct stat mystat; + (void)string_format(filename, 2048, + "%s/scan/%s/__rfc822_%05u", spool_directory, message_id, file_nr++); + /* security break */ + if (file_nr >= 128) + goto NO_RFC822; + result = stat(CS filename,&mystat); + } while (result != -1); + + rfc822name = filename; + + /* decode RFC822 attachment */ + mime_decoded_filename = NULL; + mime_stream = f; + mime_current_boundary = context ? context->boundary : NULL; + mime_decode(&rfc822name); + mime_stream = NULL; + mime_current_boundary = NULL; + if (!mime_decoded_filename) /* decoding failed */ + { + log_write(0, LOG_MAIN, + "mime_regex acl condition warning - could not decode RFC822 MIME part to file."); + return DEFER; + } + mime_decoded_filename = NULL; + } - if (context->context == MBC_COVERLETTER_ONESHOT) - context->context = MBC_ATTACHMENT; +NO_RFC822: + /* If the boundary of this instance is NULL, we are finished here */ + if (context == NULL) break; - }; + if (context->context == MBC_COVERLETTER_ONESHOT) + context->context = MBC_ATTACHMENT; + } - return rc; +return rc; } -#endif +#endif /*WITH_CONTENT_SCAN*/ + +/* vi: sw ai sw=2 +*/ diff -Nru exim4-4.82/src/moan.c exim4-4.84/src/moan.c --- exim4-4.82/src/moan.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/moan.c 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2009 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* Functions for sending messages to sender or to mailmaster. */ diff -Nru exim4-4.82/src/readconf.c exim4-4.84/src/readconf.c --- exim4-4.82/src/readconf.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/readconf.c 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* Functions for reading the configuration file, and for displaying @@ -140,7 +140,7 @@ { "acl_smtp_auth", opt_stringptr, &acl_smtp_auth }, { "acl_smtp_connect", opt_stringptr, &acl_smtp_connect }, { "acl_smtp_data", opt_stringptr, &acl_smtp_data }, -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR { "acl_smtp_data_prdr", opt_stringptr, &acl_smtp_data_prdr }, #endif #ifndef DISABLE_DKIM @@ -229,6 +229,9 @@ /* This option is now a no-op, retained for compability */ { "drop_cr", opt_bool, &drop_cr }, /*********************************************************/ +#ifdef EXPERIMENTAL_DSN + { "dsn_advertise_hosts", opt_stringptr, &dsn_advertise_hosts }, +#endif { "dsn_from", opt_stringptr, &dsn_from }, { "envelope_to_remove", opt_bool, &envelope_to_remove }, { "errors_copy", opt_stringptr, &errors_copy }, @@ -246,6 +249,7 @@ { "gnutls_allow_auto_pkcs11", opt_bool, &gnutls_allow_auto_pkcs11 }, { "gnutls_compat_mode", opt_bool, &gnutls_compat_mode }, /* These three gnutls_require_* options stopped working in Exim 4.80 */ + /* From 4.83 we log a warning; a future relase will remove them */ { "gnutls_require_kx", opt_stringptr, &gnutls_require_kx }, { "gnutls_require_mac", opt_stringptr, &gnutls_require_mac }, { "gnutls_require_protocols", opt_stringptr, &gnutls_require_proto }, @@ -324,7 +328,7 @@ #endif { "pid_file_path", opt_stringptr, &pid_file_path }, { "pipelining_advertise_hosts", opt_stringptr, &pipelining_advertise_hosts }, -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR { "prdr_enable", opt_bool, &prdr_enable }, #endif { "preserve_message_logs", opt_bool, &preserve_message_logs }, @@ -332,6 +336,9 @@ { "print_topbitchars", opt_bool, &print_topbitchars }, { "process_log_path", opt_stringptr, &process_log_path }, { "prod_requires_admin", opt_bool, &prod_requires_admin }, +#ifdef EXPERIMENTAL_PROXY + { "proxy_required_hosts", opt_stringptr, &proxy_required_hosts }, +#endif { "qualify_domain", opt_stringptr, &qualify_domain_sender }, { "qualify_recipient", opt_stringptr, &qualify_domain_recipient }, { "queue_domains", opt_stringptr, &queue_domains }, @@ -433,7 +440,7 @@ { "tls_crl", opt_stringptr, &tls_crl }, { "tls_dh_max_bits", opt_int, &tls_dh_max_bits }, { "tls_dhparam", opt_stringptr, &tls_dhparam }, -# if defined(EXPERIMENTAL_OCSP) && !defined(USE_GNUTLS) +# ifndef DISABLE_OCSP { "tls_ocsp_file", opt_stringptr, &tls_ocsp_file }, # endif { "tls_on_connect_ports", opt_stringptr, &tls_in.on_connect_ports }, @@ -1559,15 +1566,21 @@ Because we only do this once, near process start-up, I'm prepared to let this slide for the time being, even though it rankles. */ } - else if (*str_target && (ol->type & opt_rep_str)) - { + else if (ol->type & opt_rep_str) + { uschar sep = Ustrncmp(name, "headers_add", 11)==0 ? '\n' : ':'; - saved_condition = *str_target; - strtemp = saved_condition + Ustrlen(saved_condition)-1; - if (*strtemp == sep) *strtemp = 0; /* eliminate trailing list-sep */ - strtemp = string_sprintf("%s%c%s", saved_condition, sep, sptr); - *str_target = string_copy_malloc(strtemp); - } + uschar * cp; + + /* Strip trailing whitespace and seperators */ + for (cp = sptr + Ustrlen(sptr) - 1; + cp >= sptr && (*cp == '\n' || *cp == '\t' || *cp == ' ' || *cp == sep); + cp--) *cp = '\0'; + + if (cp >= sptr) + *str_target = string_copy_malloc( + *str_target ? string_sprintf("%s%c%s", *str_target, sep, sptr) + : sptr); + } else { *str_target = sptr; @@ -3361,7 +3374,12 @@ "openssl_options parse error: %s", openssl_options); # endif } -#endif + +if (gnutls_require_kx || gnutls_require_mac || gnutls_require_proto) + log_write(0, LOG_MAIN, "WARNING: main options" + " gnutls_require_kx, gnutls_require_mac and gnutls_require_protocols" + " are obsolete\n"); +#endif /*SUPPORT_TLS*/ } @@ -4128,4 +4146,6 @@ (void)fclose(config_file); } +/* vi: aw ai sw=2 +*/ /* End of readconf.c */ diff -Nru exim4-4.82/src/receive.c exim4-4.84/src/receive.c --- exim4-4.82/src/receive.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/receive.c 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* Code for receiving a message and setting up spool files. */ @@ -14,7 +14,7 @@ #endif #ifdef EXPERIMENTAL_DMARC -#include "dmarc.h" +# include "dmarc.h" #endif /* EXPERIMENTAL_DMARC */ /************************************************* @@ -497,6 +497,10 @@ /* reset optin string pointer for next recipient */ bmi_current_optin = NULL; #endif +#ifdef EXPERIMENTAL_DSN +recipients_list[recipients_count].orcpt = NULL; +recipients_list[recipients_count].dsn_flags = 0; +#endif recipients_list[recipients_count++].errors_to = NULL; } @@ -519,7 +523,7 @@ Returns: nothing */ -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR static void smtp_user_msg(uschar *code, uschar *user_msg) { @@ -680,6 +684,7 @@ case 1: /* After written "\n" */ if (ch == '.') { ch_state = 3; continue; } + if (ch == '\r') { ch_state = 2; continue; } if (ch != '\n') ch_state = 0; else linelength = -1; break; @@ -984,11 +989,24 @@ */ static void -add_acl_headers(uschar *acl_name) +add_acl_headers(int where, uschar *acl_name) { header_line *h, *next; header_line *last_received = NULL; +switch(where) + { + case ACL_WHERE_DKIM: + case ACL_WHERE_MIME: + case ACL_WHERE_DATA: + if (cutthrough_fd >= 0 && (acl_removed_headers || acl_added_headers)) + { + log_write(0, LOG_MAIN|LOG_PANIC, "Header modification in data ACLs" + " will not take effect on cutthrough deliveries"); + return; + } + } + if (acl_removed_headers != NULL) { DEBUG(D_receive|D_acl) debug_printf(">>Headers removed by %s ACL:\n", acl_name); @@ -1264,7 +1282,7 @@ } END_MIME_ACL: -add_acl_headers(US"MIME"); +add_acl_headers(ACL_WHERE_MIME, US"MIME"); if (rc == DISCARD) { recipients_count = 0; @@ -1454,7 +1472,7 @@ uschar *resent_prefix = US""; uschar *blackholed_by = NULL; uschar *blackhole_log_msg = US""; -enum {NOT_TRIED, TMP_REJ, PERM_REJ, ACCEPTED} cutthrough_done; +enum {NOT_TRIED, TMP_REJ, PERM_REJ, ACCEPTED} cutthrough_done = NOT_TRIED; flock_t lock_data; error_block *bad_addresses = NULL; @@ -2826,7 +2844,7 @@ goto TIDYUP; /* Skip to end of function */ } received_header_gen(); - add_acl_headers(US"MAIL or RCPT"); + add_acl_headers(ACL_WHERE_RCPT, US"MAIL or RCPT"); (void) cutthrough_headers_send(); } @@ -3118,7 +3136,7 @@ /* If an ACL from any RCPT commands set up any warning headers to add, do so now, before running the DATA ACL. */ - add_acl_headers(US"MAIL or RCPT"); + add_acl_headers(ACL_WHERE_RCPT, US"MAIL or RCPT"); } else message_body_size = (fstat(data_fd, &statbuf) == 0)? @@ -3231,7 +3249,7 @@ break; } } - add_acl_headers(US"DKIM"); + add_acl_headers(ACL_WHERE_DKIM, US"DKIM"); if (rc == DISCARD) { recipients_count = 0; @@ -3264,8 +3282,8 @@ dmarc_up = dmarc_store_data(from_header); #endif /* EXPERIMENTAL_DMARC */ -#ifdef EXPERIMENTAL_PRDR - if (prdr_requested && recipients_count > 1 && acl_smtp_data_prdr != NULL ) +#ifndef DISABLE_PRDR + if (prdr_requested && recipients_count > 1 && acl_smtp_data_prdr) { unsigned int c; int all_pass = OK; @@ -3333,7 +3351,7 @@ } else prdr_requested = FALSE; -#endif /* EXPERIMENTAL_PRDR */ +#endif /* !DISABLE_PRDR */ /* Check the recipients count again, as the MIME ACL might have changed them. */ @@ -3341,7 +3359,7 @@ if (acl_smtp_data != NULL && recipients_count > 0) { rc = acl_check(ACL_WHERE_DATA, NULL, acl_smtp_data, &user_msg, &log_msg); - add_acl_headers(US"DATA"); + add_acl_headers(ACL_WHERE_DATA, US"DATA"); if (rc == DISCARD) { recipients_count = 0; @@ -3424,7 +3442,7 @@ /* Does not return */ } } - add_acl_headers(US"non-SMTP"); + add_acl_headers(ACL_WHERE_NOTSMTP, US"non-SMTP"); } } @@ -3726,21 +3744,20 @@ s = add_host_info_for_log(s, &size, &sptr); #ifdef SUPPORT_TLS -if ((log_extra_selector & LX_tls_cipher) != 0 && tls_in.cipher != NULL) +if (log_extra_selector & LX_tls_cipher && tls_in.cipher) s = string_append(s, &size, &sptr, 2, US" X=", tls_in.cipher); -if ((log_extra_selector & LX_tls_certificate_verified) != 0 && - tls_in.cipher != NULL) +if (log_extra_selector & LX_tls_certificate_verified && tls_in.cipher) s = string_append(s, &size, &sptr, 2, US" CV=", tls_in.certificate_verified? "yes":"no"); -if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_in.peerdn != NULL) +if (log_extra_selector & LX_tls_peerdn && tls_in.peerdn) s = string_append(s, &size, &sptr, 3, US" DN=\"", string_printing(tls_in.peerdn), US"\""); -if ((log_extra_selector & LX_tls_sni) != 0 && tls_in.sni != NULL) +if (log_extra_selector & LX_tls_sni && tls_in.sni) s = string_append(s, &size, &sptr, 3, US" SNI=\"", string_printing(tls_in.sni), US"\""); #endif -if (sender_host_authenticated != NULL) +if (sender_host_authenticated) { s = string_append(s, &size, &sptr, 2, US" A=", sender_host_authenticated); if (authenticated_id != NULL) @@ -3751,11 +3768,16 @@ } } -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR if (prdr_requested) s = string_append(s, &size, &sptr, 1, US" PRDR"); #endif +#ifdef EXPERIMENTAL_PROXY +if (proxy_session && log_extra_selector & LX_proxy) + s = string_append(s, &size, &sptr, 2, US" PRX=", proxy_host_address); +#endif + sprintf(CS big_buffer, "%d", msg_size); s = string_append(s, &size, &sptr, 2, US" S=", big_buffer); @@ -3949,7 +3971,6 @@ XXX We do not handle queue-only, freezing, or blackholes. */ -cutthrough_done = NOT_TRIED; if(cutthrough_fd >= 0) { uschar * msg= cutthrough_finaldot(); /* Ask the target system to accept the messsage */ @@ -3972,11 +3993,11 @@ } } -if(smtp_reply == NULL -#ifdef EXPERIMENTAL_PRDR - || prdr_requested +#ifndef DISABLE_PRDR +if(!smtp_reply || prdr_requested) +#else +if(!smtp_reply) #endif - ) { log_write(0, LOG_MAIN | (((log_extra_selector & LX_received_recipients) != 0)? LOG_RECIPIENTS : 0) | diff -Nru exim4-4.82/src/route.c exim4-4.84/src/route.c --- exim4-4.82/src/route.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/route.c 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2009 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* Functions concerned with routing, and the list of generic router options. */ @@ -58,6 +58,10 @@ (void *)offsetof(router_instance, domains) }, { "driver", opt_stringptr|opt_public, (void *)offsetof(router_instance, driver_name) }, + #ifdef EXPERIMENTAL_DSN + { "dsn_lasthop", opt_bool|opt_public, + (void *)offsetof(router_instance, dsn_lasthop) }, + #endif { "errors_to", opt_stringptr|opt_public, (void *)(offsetof(router_instance, errors_to)) }, { "expn", opt_bool|opt_public, @@ -270,6 +274,15 @@ if (r->pass_router_name != NULL) set_router(r, r->pass_router_name, &(r->pass_router), TRUE); + + #ifdef EXPERIMENTAL_DSN + DEBUG(D_route) { + if (r->dsn_lasthop == FALSE) + debug_printf("DSN: %s propagating DSN\n", r->name); + else + debug_printf("DSN: %s lasthop set\n", r->name); + } + #endif } } @@ -1412,6 +1425,10 @@ copyflag(new, addr, af_propagate); new->p.address_data = addr->p.address_data; +#ifdef EXPERIMENTAL_DSN +new->dsn_flags = addr->dsn_flags; +new->dsn_orcpt = addr->dsn_orcpt; +#endif /* As it has turned out, we haven't set headers_add or headers_remove for the @@ -1719,6 +1736,17 @@ /* Run the router, and handle the consequences. */ +#ifdef EXPERIMENTAL_DSN +/* ... but let us check on DSN before. If this should be the last hop for DSN + set flag +*/ + if ((r->dsn_lasthop == TRUE) && ((addr->dsn_flags & rf_dsnlasthop) == 0)) + { + addr->dsn_flags |= rf_dsnlasthop; + HDEBUG(D_route) debug_printf("DSN: last hop for %s\n", addr->address); + } +#endif + HDEBUG(D_route) debug_printf("calling %s router\n", r->name); yield = (r->info->code)(r, addr, pw, verify, paddr_local, paddr_remote, @@ -1941,6 +1969,7 @@ if (h->mx >= 0) debug_printf(" MX=%d", h->mx); else if (h->mx != MX_NONE) debug_printf(" rgroup=%d", h->mx); if (h->port != PORT_NONE) debug_printf(" port=%d", h->port); + /* if (h->dnssec != DS_UNK) debug_printf(" dnssec=%s", h->dnssec==DS_YES ? "yes" : "no"); */ debug_printf("\n"); } } diff -Nru exim4-4.82/src/routers/dnslookup.c exim4-4.84/src/routers/dnslookup.c --- exim4-4.82/src/routers/dnslookup.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/routers/dnslookup.c 2014-08-09 12:44:29.000000000 +0000 @@ -18,6 +18,10 @@ (void *)(offsetof(dnslookup_router_options_block, check_secondary_mx)) }, { "check_srv", opt_stringptr, (void *)(offsetof(dnslookup_router_options_block, check_srv)) }, + { "dnssec_request_domains", opt_stringptr, + (void *)(offsetof(dnslookup_router_options_block, dnssec_request_domains)) }, + { "dnssec_require_domains", opt_stringptr, + (void *)(offsetof(dnslookup_router_options_block, dnssec_require_domains)) }, { "mx_domains", opt_stringptr, (void *)(offsetof(dnslookup_router_options_block, mx_domains)) }, { "mx_fail_domains", opt_stringptr, @@ -53,7 +57,9 @@ NULL, /* mx_domains */ NULL, /* mx_fail_domains */ NULL, /* srv_fail_domains */ - NULL /* check_srv */ + NULL, /* check_srv */ + NULL, /* dnssec_request_domains */ + NULL /* dnssec_require_domains */ }; @@ -261,7 +267,9 @@ } rc = host_find_bydns(&h, rblock->ignore_target_hosts, flags, srv_service, - ob->srv_fail_domains, ob->mx_fail_domains, &fully_qualified_name, &removed); + ob->srv_fail_domains, ob->mx_fail_domains, + ob->dnssec_request_domains, ob->dnssec_require_domains, + &fully_qualified_name, &removed); if (removed) setflag(addr, af_local_host_removed); /* If host found with only address records, test for the domain's being in diff -Nru exim4-4.82/src/routers/dnslookup.h exim4-4.84/src/routers/dnslookup.h --- exim4-4.82/src/routers/dnslookup.h 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/routers/dnslookup.h 2014-08-09 12:44:29.000000000 +0000 @@ -17,6 +17,8 @@ uschar *mx_fail_domains; uschar *srv_fail_domains; uschar *check_srv; + uschar *dnssec_request_domains; + uschar *dnssec_require_domains; } dnslookup_router_options_block; /* Data for reading the private options. */ diff -Nru exim4-4.82/src/routers/rf_get_munge_headers.c exim4-4.84/src/routers/rf_get_munge_headers.c --- exim4-4.82/src/routers/rf_get_munge_headers.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/routers/rf_get_munge_headers.c 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2009 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ #include "../exim.h" @@ -13,7 +13,7 @@ * Get additional headers for a router * *************************************************/ -/* This function is called by both routers to sort out the additional headers +/* This function is called by routers to sort out the additional headers and header remove list for a particular address. Arguments: @@ -32,83 +32,78 @@ header_line **extra_headers, uschar **remove_headers) { /* Default is to retain existing headers */ - *extra_headers = addr->p.extra_headers; -if (rblock->extra_headers != NULL) +if (rblock->extra_headers) { - header_line *h; - uschar *s = expand_string(rblock->extra_headers); + uschar * list = rblock->extra_headers; + int sep = '\n'; + uschar * s; + int slen; - if (s == NULL) - { - if (!expand_string_forcedfail) + while ((s = string_nextinlist(&list, &sep, NULL, 0))) + if (!(s = expand_string(s))) { - addr->message = string_sprintf("%s router failed to expand \"%s\": %s", - rblock->name, rblock->extra_headers, expand_string_message); - return DEFER; + if (!expand_string_forcedfail) + { + addr->message = string_sprintf("%s router failed to expand \"%s\": %s", + rblock->name, rblock->extra_headers, expand_string_message); + return DEFER; + } } - } - - /* Expand succeeded. Put extra header at the start of the chain because - further down it may point to headers from other routers, which may be - shared with other addresses. The output function outputs them in reverse - order. */ - - else - { - int slen = Ustrlen(s); - if (slen > 0) + else if ((slen = Ustrlen(s)) > 0) { - h = store_get(sizeof(header_line)); + /* Expand succeeded. Put extra headers at the start of the chain because + further down it may point to headers from other routers, which may be + shared with other addresses. The output function outputs them in reverse + order. */ + + header_line * h = store_get(sizeof(header_line)); /* We used to use string_sprintf() to add the newline if needed, but that causes problems if the header line is exceedingly long (e.g. adding something to a pathologically long line). So avoid it. */ if (s[slen-1] == '\n') - { - h->text = s; - } + h->text = s; else - { - h->text = store_get(slen+2); - memcpy(h->text, s, slen); - h->text[slen++] = '\n'; - h->text[slen] = 0; - } + { + h->text = store_get(slen+2); + memcpy(h->text, s, slen); + h->text[slen++] = '\n'; + h->text[slen] = 0; + } - h->next = addr->p.extra_headers; + h->next = *extra_headers; h->type = htype_other; h->slen = slen; *extra_headers = h; } - } } /* Default is to retain existing removes */ - *remove_headers = addr->p.remove_headers; -if (rblock->remove_headers != NULL) +/* Expand items from colon-sep list separately, then build new list */ +if (rblock->remove_headers) { - uschar *s = expand_string(rblock->remove_headers); - if (s == NULL) - { - if (!expand_string_forcedfail) + uschar * list = rblock->remove_headers; + int sep = ':'; + uschar * s; + uschar buffer[128]; + + while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))) + if (!(s = expand_string(s))) { - addr->message = string_sprintf("%s router failed to expand \"%s\": %s", - rblock->name, rblock->remove_headers, expand_string_message); - return DEFER; + if (!expand_string_forcedfail) + { + addr->message = string_sprintf("%s router failed to expand \"%s\": %s", + rblock->name, rblock->remove_headers, expand_string_message); + return DEFER; + } } - } - else if (*s != 0) - { - if (addr->p.remove_headers == NULL) - *remove_headers = s; - else - *remove_headers = string_sprintf("%s : %s", addr->p.remove_headers, s); - } + else if (*s) + *remove_headers = string_append_listele(*remove_headers, ':', s); } return OK; diff -Nru exim4-4.82/src/routers/rf_lookup_hostlist.c exim4-4.84/src/routers/rf_lookup_hostlist.c --- exim4-4.82/src/routers/rf_lookup_hostlist.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/routers/rf_lookup_hostlist.c 2014-08-09 12:44:29.000000000 +0000 @@ -94,6 +94,8 @@ NULL, /* SRV service not relevant */ NULL, /* failing srv domains not relevant */ NULL, /* no special mx failing domains */ + NULL, /* no dnssec request XXX ? */ + NULL, /* no dnssec require XXX ? */ NULL, /* fully_qualified_name */ NULL); /* indicate local host removed */ } @@ -117,7 +119,9 @@ BOOL removed; DEBUG(D_route|D_host_lookup) debug_printf("doing DNS lookup\n"); rc = host_find_bydns(h, ignore_target_hosts, HOST_FIND_BY_A, NULL, NULL, - NULL, &canonical_name, &removed); + NULL, + NULL, NULL, /*XXX dnssec? */ + &canonical_name, &removed); if (rc == HOST_FOUND) { if (removed) setflag(addr, af_local_host_removed); diff -Nru exim4-4.82/src/sieve.c exim4-4.84/src/sieve.c --- exim4-4.82/src/sieve.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/sieve.c 2014-08-09 12:44:29.000000000 +0000 @@ -3431,8 +3431,8 @@ -1 syntax or execution error */ -static int parse_start(struct Sieve *filter, int exec, - address_item **generated) +static int +parse_start(struct Sieve *filter, int exec, address_item **generated) { filter->pc=filter->filter; filter->line=1; diff -Nru exim4-4.82/src/smtp_in.c exim4-4.84/src/smtp_in.c --- exim4-4.82/src/smtp_in.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/smtp_in.c 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* Functions for handling an incoming SMTP call. */ @@ -94,6 +94,10 @@ QUIT_CMD, HELP_CMD, +#ifdef EXPERIMENTAL_PROXY + PROXY_FAIL_IGNORE_CMD, +#endif + /* These are specials that don't correspond to actual commands */ EOF_CMD, OTHER_CMD, BADARG_CMD, BADCHAR_CMD, BADSYN_CMD, @@ -117,6 +121,9 @@ #ifdef SUPPORT_TLS static BOOL tls_advertised; #endif +#ifdef EXPERIMENTAL_DSN +static BOOL dsn_advertised; +#endif static BOOL esmtp; static BOOL helo_required = FALSE; static BOOL helo_verify = FALSE; @@ -210,9 +217,12 @@ /* Sanity check and validate optional args to MAIL FROM: envelope */ enum { ENV_MAIL_OPT_SIZE, ENV_MAIL_OPT_BODY, ENV_MAIL_OPT_AUTH, -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR ENV_MAIL_OPT_PRDR, #endif +#ifdef EXPERIMENTAL_DSN + ENV_MAIL_OPT_RET, ENV_MAIL_OPT_ENVID, +#endif ENV_MAIL_OPT_NULL }; typedef struct { @@ -225,9 +235,13 @@ { US"SIZE", ENV_MAIL_OPT_SIZE, TRUE }, { US"BODY", ENV_MAIL_OPT_BODY, TRUE }, { US"AUTH", ENV_MAIL_OPT_AUTH, TRUE }, -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR { US"PRDR", ENV_MAIL_OPT_PRDR, FALSE }, #endif +#ifdef EXPERIMENTAL_DSN + { US"RET", ENV_MAIL_OPT_RET, TRUE }, + { US"ENVID", ENV_MAIL_OPT_ENVID, TRUE }, +#endif { US"NULL", ENV_MAIL_OPT_NULL, FALSE } }; @@ -549,6 +563,375 @@ +#ifdef EXPERIMENTAL_PROXY +/************************************************* +* Restore socket timeout to previous value * +*************************************************/ +/* If the previous value was successfully retrieved, restore +it before returning control to the non-proxy routines + +Arguments: fd - File descriptor for input + get_ok - Successfully retrieved previous values + tvtmp - Time struct with previous values + vslen - Length of time struct +Returns: none +*/ +static void +restore_socket_timeout(int fd, int get_ok, struct timeval tvtmp, socklen_t vslen) +{ +if (get_ok == 0) + setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tvtmp, vslen); +} + +/************************************************* +* Check if host is required proxy host * +*************************************************/ +/* The function determines if inbound host will be a regular smtp host +or if it is configured that it must use Proxy Protocol. + +Arguments: none +Returns: bool +*/ + +static BOOL +check_proxy_protocol_host() +{ +int rc; +/* Cannot configure local connection as a proxy inbound */ +if (sender_host_address == NULL) return proxy_session; + +rc = verify_check_this_host(&proxy_required_hosts, NULL, NULL, + sender_host_address, NULL); +if (rc == OK) + { + DEBUG(D_receive) + debug_printf("Detected proxy protocol configured host\n"); + proxy_session = TRUE; + } +return proxy_session; +} + + +/************************************************* +* Setup host for proxy protocol * +*************************************************/ +/* The function configures the connection based on a header from the +inbound host to use Proxy Protocol. The specification is very exact +so exit with an error if do not find the exact required pieces. This +includes an incorrect number of spaces separating args. + +Arguments: none +Returns: int +*/ + +static BOOL +setup_proxy_protocol_host() +{ +union { + struct { + uschar line[108]; + } v1; + struct { + uschar sig[12]; + uint8_t ver_cmd; + uint8_t fam; + uint16_t len; + union { + struct { /* TCP/UDP over IPv4, len = 12 */ + uint32_t src_addr; + uint32_t dst_addr; + uint16_t src_port; + uint16_t dst_port; + } ip4; + struct { /* TCP/UDP over IPv6, len = 36 */ + uint8_t src_addr[16]; + uint8_t dst_addr[16]; + uint16_t src_port; + uint16_t dst_port; + } ip6; + struct { /* AF_UNIX sockets, len = 216 */ + uschar src_addr[108]; + uschar dst_addr[108]; + } unx; + } addr; + } v2; +} hdr; + +/* Temp variables used in PPv2 address:port parsing */ +uint16_t tmpport; +char tmpip[INET_ADDRSTRLEN]; +struct sockaddr_in tmpaddr; +char tmpip6[INET6_ADDRSTRLEN]; +struct sockaddr_in6 tmpaddr6; + +int get_ok = 0; +int size, ret, fd; +const char v2sig[12] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"; +uschar *iptype; /* To display debug info */ +struct timeval tv; +socklen_t vslen = 0; +struct timeval tvtmp; + +vslen = sizeof(struct timeval); + +fd = fileno(smtp_in); + +/* Save current socket timeout values */ +get_ok = getsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tvtmp, + &vslen); + +/* Proxy Protocol host must send header within a short time +(default 3 seconds) or it's considered invalid */ +tv.tv_sec = PROXY_NEGOTIATION_TIMEOUT_SEC; +tv.tv_usec = PROXY_NEGOTIATION_TIMEOUT_USEC; +setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, + sizeof(struct timeval)); + +do + { + /* The inbound host was declared to be a Proxy Protocol host, so + don't do a PEEK into the data, actually slurp it up. */ + ret = recv(fd, &hdr, sizeof(hdr), 0); + } + while (ret == -1 && errno == EINTR); + +if (ret == -1) + { + restore_socket_timeout(fd, get_ok, tvtmp, vslen); + return (errno == EAGAIN) ? 0 : ERRNO_PROXYFAIL; + } + +if (ret >= 16 && + memcmp(&hdr.v2, v2sig, 12) == 0) + { + uint8_t ver, cmd; + + /* May 2014: haproxy combined the version and command into one byte to + allow two full bytes for the length field in order to proxy SSL + connections. SSL Proxy is not supported in this version of Exim, but + must still seperate values here. */ + ver = (hdr.v2.ver_cmd & 0xf0) >> 4; + cmd = (hdr.v2.ver_cmd & 0x0f); + + if (ver != 0x02) + { + DEBUG(D_receive) debug_printf("Invalid Proxy Protocol version: %d\n", ver); + goto proxyfail; + } + DEBUG(D_receive) debug_printf("Detected PROXYv2 header\n"); + /* The v2 header will always be 16 bytes per the spec. */ + size = 16 + hdr.v2.len; + if (ret < size) + { + DEBUG(D_receive) debug_printf("Truncated or too large PROXYv2 header (%d/%d)\n", + ret, size); + goto proxyfail; + } + switch (cmd) + { + case 0x01: /* PROXY command */ + switch (hdr.v2.fam) + { + case 0x11: /* TCPv4 address type */ + iptype = US"IPv4"; + tmpaddr.sin_addr.s_addr = hdr.v2.addr.ip4.src_addr; + inet_ntop(AF_INET, &(tmpaddr.sin_addr), (char *)&tmpip, sizeof(tmpip)); + if (!string_is_ip_address(US tmpip,NULL)) + { + DEBUG(D_receive) debug_printf("Invalid %s source IP\n", iptype); + return ERRNO_PROXYFAIL; + } + proxy_host_address = sender_host_address; + sender_host_address = string_copy(US tmpip); + tmpport = ntohs(hdr.v2.addr.ip4.src_port); + proxy_host_port = sender_host_port; + sender_host_port = tmpport; + /* Save dest ip/port */ + tmpaddr.sin_addr.s_addr = hdr.v2.addr.ip4.dst_addr; + inet_ntop(AF_INET, &(tmpaddr.sin_addr), (char *)&tmpip, sizeof(tmpip)); + if (!string_is_ip_address(US tmpip,NULL)) + { + DEBUG(D_receive) debug_printf("Invalid %s dest port\n", iptype); + return ERRNO_PROXYFAIL; + } + proxy_target_address = string_copy(US tmpip); + tmpport = ntohs(hdr.v2.addr.ip4.dst_port); + proxy_target_port = tmpport; + goto done; + case 0x21: /* TCPv6 address type */ + iptype = US"IPv6"; + memmove(tmpaddr6.sin6_addr.s6_addr, hdr.v2.addr.ip6.src_addr, 16); + inet_ntop(AF_INET6, &(tmpaddr6.sin6_addr), (char *)&tmpip6, sizeof(tmpip6)); + if (!string_is_ip_address(US tmpip6,NULL)) + { + DEBUG(D_receive) debug_printf("Invalid %s source IP\n", iptype); + return ERRNO_PROXYFAIL; + } + proxy_host_address = sender_host_address; + sender_host_address = string_copy(US tmpip6); + tmpport = ntohs(hdr.v2.addr.ip6.src_port); + proxy_host_port = sender_host_port; + sender_host_port = tmpport; + /* Save dest ip/port */ + memmove(tmpaddr6.sin6_addr.s6_addr, hdr.v2.addr.ip6.dst_addr, 16); + inet_ntop(AF_INET6, &(tmpaddr6.sin6_addr), (char *)&tmpip6, sizeof(tmpip6)); + if (!string_is_ip_address(US tmpip6,NULL)) + { + DEBUG(D_receive) debug_printf("Invalid %s dest port\n", iptype); + return ERRNO_PROXYFAIL; + } + proxy_target_address = string_copy(US tmpip6); + tmpport = ntohs(hdr.v2.addr.ip6.dst_port); + proxy_target_port = tmpport; + goto done; + default: + DEBUG(D_receive) + debug_printf("Unsupported PROXYv2 connection type: 0x%02x\n", + hdr.v2.fam); + goto proxyfail; + } + /* Unsupported protocol, keep local connection address */ + break; + case 0x00: /* LOCAL command */ + /* Keep local connection address for LOCAL */ + break; + default: + DEBUG(D_receive) + debug_printf("Unsupported PROXYv2 command: 0x%x\n", cmd); + goto proxyfail; + } + } +else if (ret >= 8 && + memcmp(hdr.v1.line, "PROXY", 5) == 0) + { + uschar *p = string_copy(hdr.v1.line); + uschar *end = memchr(p, '\r', ret - 1); + uschar *sp; /* Utility variables follow */ + int tmp_port; + char *endc; + + if (!end || end[1] != '\n') + { + DEBUG(D_receive) debug_printf("Partial or invalid PROXY header\n"); + goto proxyfail; + } + *end = '\0'; /* Terminate the string */ + size = end + 2 - hdr.v1.line; /* Skip header + CRLF */ + DEBUG(D_receive) debug_printf("Detected PROXYv1 header\n"); + /* Step through the string looking for the required fields. Ensure + strict adherance to required formatting, exit for any error. */ + p += 5; + if (!isspace(*(p++))) + { + DEBUG(D_receive) debug_printf("Missing space after PROXY command\n"); + goto proxyfail; + } + if (!Ustrncmp(p, CCS"TCP4", 4)) + iptype = US"IPv4"; + else if (!Ustrncmp(p,CCS"TCP6", 4)) + iptype = US"IPv6"; + else if (!Ustrncmp(p,CCS"UNKNOWN", 7)) + { + iptype = US"Unknown"; + goto done; + } + else + { + DEBUG(D_receive) debug_printf("Invalid TCP type\n"); + goto proxyfail; + } + + p += Ustrlen(iptype); + if (!isspace(*(p++))) + { + DEBUG(D_receive) debug_printf("Missing space after TCP4/6 command\n"); + goto proxyfail; + } + /* Find the end of the arg */ + if ((sp = Ustrchr(p, ' ')) == NULL) + { + DEBUG(D_receive) + debug_printf("Did not find proxied src %s\n", iptype); + goto proxyfail; + } + *sp = '\0'; + if(!string_is_ip_address(p,NULL)) + { + DEBUG(D_receive) + debug_printf("Proxied src arg is not an %s address\n", iptype); + goto proxyfail; + } + proxy_host_address = sender_host_address; + sender_host_address = p; + p = sp + 1; + if ((sp = Ustrchr(p, ' ')) == NULL) + { + DEBUG(D_receive) + debug_printf("Did not find proxy dest %s\n", iptype); + goto proxyfail; + } + *sp = '\0'; + if(!string_is_ip_address(p,NULL)) + { + DEBUG(D_receive) + debug_printf("Proxy dest arg is not an %s address\n", iptype); + goto proxyfail; + } + proxy_target_address = p; + p = sp + 1; + if ((sp = Ustrchr(p, ' ')) == NULL) + { + DEBUG(D_receive) debug_printf("Did not find proxied src port\n"); + goto proxyfail; + } + *sp = '\0'; + tmp_port = strtol(CCS p,&endc,10); + if (*endc || tmp_port == 0) + { + DEBUG(D_receive) + debug_printf("Proxied src port '%s' not an integer\n", p); + goto proxyfail; + } + proxy_host_port = sender_host_port; + sender_host_port = tmp_port; + p = sp + 1; + if ((sp = Ustrchr(p, '\0')) == NULL) + { + DEBUG(D_receive) debug_printf("Did not find proxy dest port\n"); + goto proxyfail; + } + tmp_port = strtol(CCS p,&endc,10); + if (*endc || tmp_port == 0) + { + DEBUG(D_receive) + debug_printf("Proxy dest port '%s' not an integer\n", p); + goto proxyfail; + } + proxy_target_port = tmp_port; + /* Already checked for /r /n above. Good V1 header received. */ + goto done; + } +else + { + /* Wrong protocol */ + DEBUG(D_receive) debug_printf("Invalid proxy protocol version negotiation\n"); + goto proxyfail; + } + +proxyfail: +restore_socket_timeout(fd, get_ok, tvtmp, vslen); +/* Don't flush any potential buffer contents. Any input should cause a + synchronization failure */ +return FALSE; + +done: +restore_socket_timeout(fd, get_ok, tvtmp, vslen); +DEBUG(D_receive) + debug_printf("Valid %s sender from Proxy Protocol header\n", iptype); +return proxy_session; +} +#endif + /************************************************* * Read one command line * *************************************************/ @@ -622,6 +1005,14 @@ for (p = cmd_list; p < cmd_list_end; p++) { + #ifdef EXPERIMENTAL_PROXY + /* Only allow QUIT command if Proxy Protocol parsing failed */ + if (proxy_session && proxy_session_failed) + { + if (p->cmd != QUIT_CMD) + continue; + } + #endif if (strncmpic(smtp_cmd_buffer, US p->name, p->len) == 0 && (smtp_cmd_buffer[p->len-1] == ':' || /* "mail from:" or "rcpt to:" */ smtp_cmd_buffer[p->len] == 0 || @@ -669,6 +1060,12 @@ } } +#ifdef EXPERIMENTAL_PROXY +/* Only allow QUIT command if Proxy Protocol parsing failed */ +if (proxy_session && proxy_session_failed) + return PROXY_FAIL_IGNORE_CMD; +#endif + /* Enforce synchronization for unknown commands */ if (smtp_inptr < smtp_inend && /* Outstanding input */ @@ -825,6 +1222,45 @@ +#ifdef SUPPORT_TLS +/* Append TLS-related information to a log line + +Arguments: + s String under construction: allocated string to extend, or NULL + sizep Pointer to current allocation size (update on return), or NULL + ptrp Pointer to index for new entries in string (update on return), or NULL + +Returns: Allocated string or NULL +*/ +static uschar * +s_tlslog(uschar * s, int * sizep, int * ptrp) +{ + int size = sizep ? *sizep : 0; + int ptr = ptrp ? *ptrp : 0; + + if ((log_extra_selector & LX_tls_cipher) != 0 && tls_in.cipher != NULL) + s = string_append(s, &size, &ptr, 2, US" X=", tls_in.cipher); + if ((log_extra_selector & LX_tls_certificate_verified) != 0 && + tls_in.cipher != NULL) + s = string_append(s, &size, &ptr, 2, US" CV=", + tls_in.certificate_verified? "yes":"no"); + if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_in.peerdn != NULL) + s = string_append(s, &size, &ptr, 3, US" DN=\"", + string_printing(tls_in.peerdn), US"\""); + if ((log_extra_selector & LX_tls_sni) != 0 && tls_in.sni != NULL) + s = string_append(s, &size, &ptr, 3, US" SNI=\"", + string_printing(tls_in.sni), US"\""); + + if (s) + { + s[ptr] = '\0'; + if (sizep) *sizep = size; + if (ptrp) *ptrp = ptr; + } + return s; +} +#endif + /************************************************* * Log lack of MAIL if so configured * *************************************************/ @@ -857,18 +1293,7 @@ } #ifdef SUPPORT_TLS -if ((log_extra_selector & LX_tls_cipher) != 0 && tls_in.cipher != NULL) - s = string_append(s, &size, &ptr, 2, US" X=", tls_in.cipher); -if ((log_extra_selector & LX_tls_certificate_verified) != 0 && - tls_in.cipher != NULL) - s = string_append(s, &size, &ptr, 2, US" CV=", - tls_in.certificate_verified? "yes":"no"); -if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_in.peerdn != NULL) - s = string_append(s, &size, &ptr, 3, US" DN=\"", - string_printing(tls_in.peerdn), US"\""); -if ((log_extra_selector & LX_tls_sni) != 0 && tls_in.sni != NULL) - s = string_append(s, &size, &ptr, 3, US" SNI=\"", - string_printing(tls_in.sni), US"\""); +s = s_tlslog(s, &size, &ptr); #endif sep = (smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE)? @@ -892,7 +1317,8 @@ if (s != NULL) s[ptr] = 0; else s = US""; log_write(0, LOG_MAIN, "no MAIL in SMTP connection from %s D=%s%s", host_and_ident(FALSE), - readconf_printtime(time(NULL) - smtp_connection_start), s); + readconf_printtime( (int) ((long)time(NULL) - (long)smtp_connection_start)), + s); } @@ -1073,6 +1499,13 @@ sender_verified_list = NULL; /* No senders verified */ memset(sender_address_cache, 0, sizeof(sender_address_cache)); memset(sender_domain_cache, 0, sizeof(sender_domain_cache)); + +#ifdef EXPERIMENTAL_DSN +/* Reset the DSN flags */ +dsn_ret = 0; +dsn_envid = NULL; +#endif + authenticated_sender = NULL; #ifdef EXPERIMENTAL_BRIGHTMAIL bmi_run = 0; @@ -1417,8 +1850,14 @@ #ifdef SUPPORT_TLS tls_in.cipher = tls_in.peerdn = NULL; +tls_in.ourcert = tls_in.peercert = NULL; +tls_in.sni = NULL; +tls_in.ocsp = OCSP_NOT_REQ; tls_advertised = FALSE; #endif +#ifdef EXPERIMENTAL_DSN +dsn_advertised = FALSE; +#endif /* Reset ACL connection variables */ @@ -1832,6 +2271,28 @@ if (smtp_batched_input) return TRUE; +#ifdef EXPERIMENTAL_PROXY +/* If valid Proxy Protocol source is connecting, set up session. + * Failure will not allow any SMTP function other than QUIT. */ +proxy_session = FALSE; +proxy_session_failed = FALSE; +if (check_proxy_protocol_host()) + { + if (setup_proxy_protocol_host() == FALSE) + { + proxy_session_failed = TRUE; + DEBUG(D_receive) + debug_printf("Failure to extract proxied host, only QUIT allowed\n"); + } + else + { + sender_host_name = NULL; + (void)host_name_lookup(); + host_build_sender_fullhost(); + } + } +#endif + /* Run the ACL if it exists */ user_msg = NULL; @@ -2211,7 +2672,7 @@ #endif (where == ACL_WHERE_PREDATA)? US"DATA" : (where == ACL_WHERE_DATA)? US"after DATA" : -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR (where == ACL_WHERE_PRDR)? US"after DATA PRDR" : #endif (smtp_cmd_data == NULL)? @@ -2334,9 +2795,17 @@ is closing if required and return 2. */ if (log_reject_target != 0) - log_write(0, log_reject_target, "%s %s%srejected %s%s", - host_and_ident(TRUE), + { +#ifdef SUPPORT_TLS + uschar * s = s_tlslog(NULL, NULL, NULL); + if (!s) s = US""; +#else + uschar * s = US""; +#endif + log_write(0, log_reject_target, "%s%s %s%srejected %s%s", + host_and_ident(TRUE), s, sender_info, (rc == FAIL)? US"" : US"temporarily ", what, log_msg); + } if (!drop) return 0; @@ -2594,7 +3063,6 @@ - /************************************************* * Initialize for SMTP incoming message * *************************************************/ @@ -2679,6 +3147,10 @@ int ptr, size, rc; int c, i; auth_instance *au; +#ifdef EXPERIMENTAL_DSN + uschar *orcpt = NULL; + int flags; +#endif switch(smtp_read_command(TRUE)) { @@ -3023,6 +3495,9 @@ #ifdef SUPPORT_TLS tls_advertised = FALSE; #endif + #ifdef EXPERIMENTAL_DSN + dsn_advertised = FALSE; + #endif smtp_code = US"250 "; /* Default response code plus space*/ if (user_msg == NULL) @@ -3106,6 +3581,16 @@ s = string_cat(s, &size, &ptr, US"-8BITMIME\r\n", 11); } + #ifdef EXPERIMENTAL_DSN + /* Advertise DSN support if configured to do so. */ + if (verify_check_host(&dsn_advertise_hosts) != FAIL) + { + s = string_cat(s, &size, &ptr, smtp_code, 3); + s = string_cat(s, &size, &ptr, US"-DSN\r\n", 6); + dsn_advertised = TRUE; + } + #endif + /* Advertise ETRN if there's an ACL checking whether a host is permitted to issue it; a check is made when any host actually tries. */ @@ -3195,12 +3680,13 @@ } #endif - #ifdef EXPERIMENTAL_PRDR + #ifndef DISABLE_PRDR /* Per Recipient Data Response, draft by Eric A. Hall extending RFC */ - if (prdr_enable) { + if (prdr_enable) + { s = string_cat(s, &size, &ptr, smtp_code, 3); s = string_cat(s, &size, &ptr, US"-PRDR\r\n", 7); - } + } #endif /* Finish off the multiline reply with one that is always available. */ @@ -3360,6 +3846,45 @@ arg_error = TRUE; break; + #ifdef EXPERIMENTAL_DSN + + /* Handle the two DSN options, but only if configured to do so (which + will have caused "DSN" to be given in the EHLO response). The code itself + is included only if configured in at build time. */ + + case ENV_MAIL_OPT_RET: + if (dsn_advertised) { + /* Check if RET has already been set */ + if (dsn_ret > 0) { + synprot_error(L_smtp_syntax_error, 501, NULL, + US"RET can be specified once only"); + goto COMMAND_LOOP; + } + dsn_ret = (strcmpic(value, US"HDRS") == 0)? dsn_ret_hdrs : + (strcmpic(value, US"FULL") == 0)? dsn_ret_full : 0; + DEBUG(D_receive) debug_printf("DSN_RET: %d\n", dsn_ret); + /* Check for invalid invalid value, and exit with error */ + if (dsn_ret == 0) { + synprot_error(L_smtp_syntax_error, 501, NULL, + US"Value for RET is invalid"); + goto COMMAND_LOOP; + } + } + break; + case ENV_MAIL_OPT_ENVID: + if (dsn_advertised) { + /* Check if the dsn envid has been already set */ + if (dsn_envid != NULL) { + synprot_error(L_smtp_syntax_error, 501, NULL, + US"ENVID can be specified once only"); + goto COMMAND_LOOP; + } + dsn_envid = string_copy(value); + DEBUG(D_receive) debug_printf("DSN_ENVID: %s\n", dsn_envid); + } + break; + #endif + /* Handle the AUTH extension. If the value given is not "<>" and either the ACL says "yes" or there is no ACL but the sending host is authenticated, we set it up as the authenticated sender. However, if the @@ -3429,9 +3954,9 @@ } break; -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR case ENV_MAIL_OPT_PRDR: - if ( prdr_enable ) + if (prdr_enable) prdr_requested = TRUE; break; #endif @@ -3556,29 +4081,32 @@ when pipelining is not advertised, do another sync check in case the ACL delayed and the client started sending in the meantime. */ - if (acl_smtp_mail == NULL) rc = OK; else + if (acl_smtp_mail) { rc = acl_check(ACL_WHERE_MAIL, NULL, acl_smtp_mail, &user_msg, &log_msg); if (rc == OK && !pipelining_advertised && !check_sync()) goto SYNC_FAILURE; } + else + rc = OK; if (rc == OK || rc == DISCARD) { - if (user_msg == NULL) + if (!user_msg) smtp_printf("%s%s%s", US"250 OK", - #ifdef EXPERIMENTAL_PRDR - prdr_requested == TRUE ? US", PRDR Requested" : - #endif + #ifndef DISABLE_PRDR + prdr_requested ? US", PRDR Requested" : US"", + #else US"", + #endif US"\r\n"); else { - #ifdef EXPERIMENTAL_PRDR - if ( prdr_requested == TRUE ) + #ifndef DISABLE_PRDR + if (prdr_requested) user_msg = string_sprintf("%s%s", user_msg, US", PRDR Requested"); #endif - smtp_user_msg(US"250",user_msg); + smtp_user_msg(US"250", user_msg); } smtp_delay_rcpt = smtp_rlr_base; recipients_discarded = (rc == DISCARD); @@ -3633,6 +4161,86 @@ rcpt_fail_count++; break; } + + #ifdef EXPERIMENTAL_DSN + /* Set the DSN flags orcpt and dsn_flags from the session*/ + orcpt = NULL; + flags = 0; + + if (esmtp) for(;;) + { + uschar *name, *value, *end; + int size; + + if (!extract_option(&name, &value)) + { + break; + } + + if (dsn_advertised && strcmpic(name, US"ORCPT") == 0) + { + /* Check whether orcpt has been already set */ + if (orcpt != NULL) { + synprot_error(L_smtp_syntax_error, 501, NULL, + US"ORCPT can be specified once only"); + goto COMMAND_LOOP; + } + orcpt = string_copy(value); + DEBUG(D_receive) debug_printf("DSN orcpt: %s\n", orcpt); + } + + else if (dsn_advertised && strcmpic(name, US"NOTIFY") == 0) + { + /* Check if the notify flags have been already set */ + if (flags > 0) { + synprot_error(L_smtp_syntax_error, 501, NULL, + US"NOTIFY can be specified once only"); + goto COMMAND_LOOP; + } + if (strcmpic(value, US"NEVER") == 0) flags |= rf_notify_never; else + { + uschar *p = value; + while (*p != 0) + { + uschar *pp = p; + while (*pp != 0 && *pp != ',') pp++; + if (*pp == ',') *pp++ = 0; + if (strcmpic(p, US"SUCCESS") == 0) { + DEBUG(D_receive) debug_printf("DSN: Setting notify success\n"); + flags |= rf_notify_success; + } + else if (strcmpic(p, US"FAILURE") == 0) { + DEBUG(D_receive) debug_printf("DSN: Setting notify failure\n"); + flags |= rf_notify_failure; + } + else if (strcmpic(p, US"DELAY") == 0) { + DEBUG(D_receive) debug_printf("DSN: Setting notify delay\n"); + flags |= rf_notify_delay; + } + else { + /* Catch any strange values */ + synprot_error(L_smtp_syntax_error, 501, NULL, + US"Invalid value for NOTIFY parameter"); + goto COMMAND_LOOP; + } + p = pp; + } + DEBUG(D_receive) debug_printf("DSN Flags: %x\n", flags); + } + } + + /* Unknown option. Stick back the terminator characters and break + the loop. An error for a malformed address will occur. */ + + else + { + DEBUG(D_receive) debug_printf("Invalid RCPT option: %s : %s\n", name, value); + name[-1] = ' '; + value[-1] = '='; + break; + } + } + #endif /* Apply SMTP rewriting then extract the working address. Don't allow "<>" as a recipient address */ @@ -3747,6 +4355,21 @@ if (user_msg == NULL) smtp_printf("250 Accepted\r\n"); else smtp_user_msg(US"250", user_msg); receive_add_recipient(recipient, -1); + + #ifdef EXPERIMENTAL_DSN + /* Set the dsn flags in the recipients_list */ + if (orcpt != NULL) + recipients_list[recipients_count-1].orcpt = orcpt; + else + recipients_list[recipients_count-1].orcpt = NULL; + + if (flags != 0) + recipients_list[recipients_count-1].dsn_flags = flags; + else + recipients_list[recipients_count-1].dsn_flags = 0; + DEBUG(D_receive) debug_printf("DSN: orcpt: %s flags: %d\n", recipients_list[recipients_count-1].orcpt, recipients_list[recipients_count-1].dsn_flags); + #endif + } /* The recipient was discarded */ @@ -4379,6 +5002,11 @@ done = 1; /* Pretend eof - drops connection */ break; + #ifdef EXPERIMENTAL_PROXY + case PROXY_FAIL_IGNORE_CMD: + smtp_printf("503 Command refused, required Proxy negotiation failed\r\n"); + break; + #endif default: if (unknown_command_count++ >= smtp_max_unknown_commands) @@ -4413,4 +5041,6 @@ return done - 2; /* Convert yield values */ } +/* vi: aw ai sw=2 +*/ /* End of smtp_in.c */ diff -Nru exim4-4.82/src/smtp_out.c exim4-4.84/src/smtp_out.c --- exim4-4.82/src/smtp_out.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/smtp_out.c 2014-08-09 12:44:29.000000000 +0000 @@ -352,6 +352,10 @@ va_end(ap); count = Ustrlen(big_buffer); +if (count > outblock->buffersize) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing " + "SMTP"); + if (count > outblock->buffersize - (outblock->ptr - outblock->buffer)) { rc = outblock->cmd_count; /* flush resets */ diff -Nru exim4-4.82/src/spam.c exim4-4.84/src/spam.c --- exim4-4.82/src/spam.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/spam.c 2014-08-09 12:44:29.000000000 +0000 @@ -20,7 +20,9 @@ int spam_rc = 0; uschar *prev_spamd_address_work = NULL; -int spam(uschar **listptr) { +int +spam(uschar **listptr) +{ int sep = 0; uschar *list = *listptr; uschar *user_name; @@ -286,7 +288,7 @@ "spam acl condition: %s on spamd socket", strerror(errno)); else { if (time(NULL) - start < SPAMD_TIMEOUT) - goto again; + goto again; log_write(0, LOG_MAIN|LOG_PANIC, "spam acl condition: timed out writing spamd socket"); } diff -Nru exim4-4.82/src/spf.c exim4-4.84/src/spf.c --- exim4-4.82/src/spf.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/spf.c 2014-08-09 12:44:29.000000000 +0000 @@ -3,7 +3,7 @@ *************************************************/ /* Experimental SPF support. - Copyright (c) Tom Kistner 2004 + Copyright (c) Tom Kistner 2004 - 2014 License: GPL */ /* Code for calling spf checks via libspf-alt. Called from acl.c. */ @@ -19,8 +19,10 @@ { US"fail", 3 }, { US"softfail", 4 }, { US"none", 5 }, - { US"err_temp", 6 }, - { US"err_perm", 7 } + { US"err_temp", 6 }, /* Deprecated Apr 2014 */ + { US"err_perm", 7 }, /* Deprecated Apr 2014 */ + { US"temperror", 6 }, /* RFC 4408 defined */ + { US"permerror", 7 } /* RFC 4408 defined */ }; SPF_server_t *spf_server = NULL; diff -Nru exim4-4.82/src/spool_in.c exim4-4.84/src/spool_in.c --- exim4-4.82/src/spool_in.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/spool_in.c 2014-08-09 12:44:29.000000000 +0000 @@ -285,14 +285,22 @@ #ifdef SUPPORT_TLS tls_in.certificate_verified = FALSE; tls_in.cipher = NULL; +tls_in.ourcert = NULL; +tls_in.peercert = NULL; tls_in.peerdn = NULL; tls_in.sni = NULL; +tls_in.ocsp = OCSP_NOT_REQ; #endif #ifdef WITH_CONTENT_SCAN spam_score_int = NULL; #endif +#ifdef EXPERIMENTAL_DSN +dsn_ret = 0; +dsn_envid = NULL; +#endif + /* Generate the full name and open the file. If message_subdir is already set, just look in the given directory. Otherwise, look in both the split and unsplit directories, as for the data file above. */ @@ -467,13 +475,24 @@ case 'd': if (Ustrcmp(p, "eliver_firsttime") == 0) deliver_firsttime = TRUE; + #ifdef EXPERIMENTAL_DSN + /* Check if the dsn flags have been set in the header file */ + else if (Ustrncmp(p, "sn_ret", 6) == 0) + { + dsn_ret= atoi(big_buffer + 8); + } + else if (Ustrncmp(p, "sn_envid", 8) == 0) + { + dsn_envid = string_copy(big_buffer + 11); + } + #endif break; case 'f': if (Ustrncmp(p, "rozen", 5) == 0) { deliver_freeze = TRUE; - deliver_frozen_at = Uatoi(big_buffer + 7); + sscanf(big_buffer+7, TIME_T_FMT, &deliver_frozen_at); } break; @@ -548,10 +567,18 @@ tls_in.certificate_verified = TRUE; else if (Ustrncmp(p, "ls_cipher", 9) == 0) tls_in.cipher = string_copy(big_buffer + 12); +#ifndef COMPILE_UTILITY + else if (Ustrncmp(p, "ls_ourcert", 10) == 0) + (void) tls_import_cert(big_buffer + 13, &tls_in.ourcert); + else if (Ustrncmp(p, "ls_peercert", 11) == 0) + (void) tls_import_cert(big_buffer + 14, &tls_in.peercert); +#endif else if (Ustrncmp(p, "ls_peerdn", 9) == 0) tls_in.peerdn = string_unprinting(string_copy(big_buffer + 12)); else if (Ustrncmp(p, "ls_sni", 6) == 0) tls_in.sni = string_unprinting(string_copy(big_buffer + 9)); + else if (Ustrncmp(p, "ls_ocsp", 7) == 0) + tls_in.ocsp = big_buffer[10] - '0'; break; #endif @@ -604,6 +631,10 @@ { int nn; int pno = -1; + #ifdef EXPERIMENTAL_DSN + int dsn_flags = 0; + uschar *orcpt = NULL; + #endif uschar *errors_to = NULL; uschar *p; @@ -646,6 +677,9 @@ ends with , where pno is the parent number for one_time addresses, and len is the length of the errors_to address (zero meaning none). + + Bit 02 indicates that, again reading from right to left, the data continues + with orcpt len(orcpt),dsn_flags */ while (isdigit(*p)) p--; @@ -676,6 +710,13 @@ else if (*p == '#') { int flags; + + #ifdef EXPERIMENTAL_DSN + #ifndef COMPILE_UTILITY + DEBUG(D_deliver) debug_printf("**** SPOOL_IN - Exim 4 standard format spoolfile\n"); + #endif /* COMPILE_UTILITY */ + #endif + (void)sscanf(CS p+1, "%d", &flags); if ((flags & 0x01) != 0) /* one_time data exists */ @@ -688,15 +729,54 @@ { p -= len; errors_to = string_copy(p); - } + } + } + + *(--p) = 0; /* Terminate address */ +#ifdef EXPERIMENTAL_DSN + if ((flags & 0x02) != 0) /* one_time data exists */ + { + int len; + while (isdigit(*(--p)) || *p == ',' || *p == '-'); + (void)sscanf(CS p+1, "%d,%d", &len, &dsn_flags); + *p = 0; + if (len > 0) + { + p -= len; + orcpt = string_copy(p); + } } *(--p) = 0; /* Terminate address */ +#endif /* EXPERIMENTAL_DSN */ + } +#ifdef EXPERIMENTAL_DSN + #ifndef COMPILE_UTILITY + else + { + DEBUG(D_deliver) debug_printf("**** SPOOL_IN - No additional fields\n"); + } + + if ((orcpt != NULL) || (dsn_flags != 0)) + { + DEBUG(D_deliver) debug_printf("**** SPOOL_IN - address: |%s| orcpt: |%s| dsn_flags: %d\n", + big_buffer, orcpt, dsn_flags); + } + if (errors_to != NULL) + { + DEBUG(D_deliver) debug_printf("**** SPOOL_IN - address: |%s| errorsto: |%s|\n", + big_buffer, errors_to); } + #endif /* COMPILE_UTILITY */ +#endif /* EXPERIMENTAL_DSN */ recipients_list[recipients_count].address = string_copy(big_buffer); recipients_list[recipients_count].pno = pno; recipients_list[recipients_count].errors_to = errors_to; + #ifdef EXPERIMENTAL_DSN + recipients_list[recipients_count].orcpt = orcpt; + recipients_list[recipients_count].dsn_flags = dsn_flags; + #endif } /* The remainder of the spool header file contains the headers for the message, @@ -799,4 +879,6 @@ return inheader? spool_read_hdrerror : spool_read_enverror; } +/* vi: aw ai sw=2 +*/ /* End of spool_in.c */ diff -Nru exim4-4.82/src/spool_out.c exim4-4.84/src/spool_out.c --- exim4-4.82/src/spool_out.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/spool_out.c 2014-08-09 12:44:29.000000000 +0000 @@ -210,7 +210,7 @@ if (allow_unqualified_recipient) fprintf(f, "-allow_unqualified_recipient\n"); if (allow_unqualified_sender) fprintf(f, "-allow_unqualified_sender\n"); if (deliver_firsttime) fprintf(f, "-deliver_firsttime\n"); -if (deliver_freeze) fprintf(f, "-frozen %d\n", deliver_frozen_at); +if (deliver_freeze) fprintf(f, "-frozen " TIME_T_FMT "\n", deliver_frozen_at); if (dont_deliver) fprintf(f, "-N\n"); if (host_lookup_deferred) fprintf(f, "-host_lookup_deferred\n"); if (host_lookup_failed) fprintf(f, "-host_lookup_failed\n"); @@ -229,9 +229,28 @@ #ifdef SUPPORT_TLS if (tls_in.certificate_verified) fprintf(f, "-tls_certificate_verified\n"); -if (tls_in.cipher != NULL) fprintf(f, "-tls_cipher %s\n", tls_in.cipher); -if (tls_in.peerdn != NULL) fprintf(f, "-tls_peerdn %s\n", string_printing(tls_in.peerdn)); -if (tls_in.sni != NULL) fprintf(f, "-tls_sni %s\n", string_printing(tls_in.sni)); +if (tls_in.cipher) fprintf(f, "-tls_cipher %s\n", tls_in.cipher); +if (tls_in.peercert) + { + (void) tls_export_cert(big_buffer, big_buffer_size, tls_in.peercert); + fprintf(f, "-tls_peercert %s\n", CS big_buffer); + } +if (tls_in.peerdn) fprintf(f, "-tls_peerdn %s\n", string_printing(tls_in.peerdn)); +if (tls_in.sni) fprintf(f, "-tls_sni %s\n", string_printing(tls_in.sni)); +if (tls_in.ourcert) + { + (void) tls_export_cert(big_buffer, big_buffer_size, tls_in.ourcert); + fprintf(f, "-tls_ourcert %s\n", CS big_buffer); + } +if (tls_in.ocsp) fprintf(f, "-tls_ocsp %d\n", tls_in.ocsp); +#endif + +#ifdef EXPERIMENTAL_DSN +/* Write the dsn flags to the spool header file */ +DEBUG(D_deliver) debug_printf("DSN: Write SPOOL :-dsn_envid %s\n", dsn_envid); +if (dsn_envid != NULL) fprintf(f, "-dsn_envid %s\n", dsn_envid); +DEBUG(D_deliver) debug_printf("DSN: Write SPOOL :-dsn_ret %d\n", dsn_ret); +if (dsn_ret != 0) fprintf(f, "-dsn_ret %d\n", dsn_ret); #endif /* To complete the envelope, write out the tree of non-recipients, followed by @@ -244,14 +263,34 @@ for (i = 0; i < recipients_count; i++) { recipient_item *r = recipients_list + i; - if (r->pno < 0 && r->errors_to == NULL) +#ifdef EXPERIMENTAL_DSN +DEBUG(D_deliver) debug_printf("DSN: Flags :%d\n", r->dsn_flags); +#endif + if (r->pno < 0 && r->errors_to == NULL + #ifdef EXPERIMENTAL_DSN + && r->dsn_flags == 0 + #endif + ) fprintf(f, "%s\n", r->address); else { uschar *errors_to = (r->errors_to == NULL)? US"" : r->errors_to; + #ifdef EXPERIMENTAL_DSN + /* for DSN SUPPORT extend exim 4 spool in a compatible way by + adding new values upfront and add flag 0x02 */ + uschar *orcpt = (r->orcpt == NULL)? US"" : r->orcpt; + fprintf(f, "%s %s %d,%d %s %d,%d#3\n", r->address, orcpt, Ustrlen(orcpt), r->dsn_flags, + errors_to, Ustrlen(errors_to), r->pno); + #else fprintf(f, "%s %s %d,%d#1\n", r->address, errors_to, Ustrlen(errors_to), r->pno); + #endif } + + #ifdef EXPERIMENTAL_DSN + DEBUG(D_deliver) debug_printf("DSN: **** SPOOL_OUT - address: |%s| errorsto: |%s| orcpt: |%s| dsn_flags: %d\n", + r->address, r->errors_to, r->orcpt, r->dsn_flags); + #endif } /* Put a blank line before the headers */ diff -Nru exim4-4.82/src/string.c exim4-4.84/src/string.c --- exim4-4.82/src/string.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/string.c 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* Miscellaneous string-handling functions. Some are not required for @@ -34,7 +34,7 @@ */ int -string_is_ip_address(uschar *s, int *maskptr) +string_is_ip_address(const uschar *s, int *maskptr) { int i; int yield = 4; @@ -44,7 +44,7 @@ if (maskptr != NULL) { - uschar *ss = s + Ustrlen(s); + const uschar *ss = s + Ustrlen(s); *maskptr = 0; if (s != ss && isdigit(*(--ss))) { @@ -304,7 +304,7 @@ /* Get a new block of store guaranteed big enough to hold the expanded string. */ -ss = store_get(length + nonprintcount * 4 + 1); +ss = store_get(length + nonprintcount * 3 + 1); /* Copy everying, escaping non printers. */ @@ -374,7 +374,8 @@ { if (*p == '\\') { - *q = string_interpret_escape(&p); + *q++ = string_interpret_escape(&p); + p++; } else { @@ -716,7 +717,8 @@ va_start(ap, format); if (!string_vformat(buffer, sizeof(buffer), format, ap)) log_write(0, LOG_MAIN|LOG_PANIC_DIE, - "string_sprintf expansion was longer than " SIZE_T_FMT, sizeof(buffer)); + "string_sprintf expansion was longer than " SIZE_T_FMT " (%s)", + sizeof(buffer), format); va_end(ap); return string_copy(buffer); } @@ -964,6 +966,50 @@ } #endif /* COMPILE_UTILITY */ + +#ifndef COMPILE_UTILITY +/************************************************ +* Add element to seperated list * +************************************************/ +/* This function is used to build a list, returning +an allocated null-terminated growable string. The +given element has any embedded seperator characters +doubled. + +Arguments: + list points to the start of the list that is being built, or NULL + if this is a new list that has no contents yet + sep list seperator charactoer + ele new lement to be appended to the list + +Returns: pointer to the start of the list, changed if copied for expansion. +*/ + +uschar * +string_append_listele(uschar * list, uschar sep, const uschar * ele) +{ +uschar * new = NULL; +int sz = 0, off = 0; +uschar * sp; + +if (list) + { + new = string_cat(new, &sz, &off, list, Ustrlen(list)); + new = string_cat(new, &sz, &off, &sep, 1); + } + +while((sp = Ustrchr(ele, sep))) + { + new = string_cat(new, &sz, &off, ele, sp-ele+1); + new = string_cat(new, &sz, &off, &sep, 1); + ele = sp+1; + } +new = string_cat(new, &sz, &off, ele, Ustrlen(ele)); +new[off] = '\0'; +return new; +} +#endif /* COMPILE_UTILITY */ + #ifndef COMPILE_UTILITY diff -Nru exim4-4.82/src/structs.h exim4-4.84/src/structs.h --- exim4-4.82/src/structs.h 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/structs.h 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ @@ -55,6 +55,8 @@ but also used when checking lists of hosts and when transporting. Looking up host addresses is done using this structure. */ +typedef enum {DS_UNK=-1, DS_NO, DS_YES} dnssec_status_t; + typedef struct host_item { struct host_item *next; uschar *name; /* Host name */ @@ -65,6 +67,7 @@ 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 @@ -282,6 +285,9 @@ BOOL verify_sender; /* Use this router when verifying a sender */ BOOL uid_set; /* Flag to indicate uid is set */ BOOL unseen; /* If TRUE carry on, even after success */ +#ifdef EXPERIMENTAL_DSN + BOOL dsn_lasthop; /* If TRUE, this router is a DSN endpoint */ +#endif int self_code; /* Encoded version of "self" */ uid_t uid; /* Fixed uid value */ @@ -485,7 +491,7 @@ #define af_cert_verified 0x01000000 /* delivered with verified TLS cert */ #define af_pass_message 0x02000000 /* pass message in bounces */ #define af_bad_reply 0x04000000 /* filter could not generate autoreply */ -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR # define af_prdr_used 0x08000000 /* delivery used SMTP PRDR */ #endif #define af_force_command 0x10000000 /* force_command in pipe transport */ @@ -540,13 +546,22 @@ #ifdef SUPPORT_TLS uschar *cipher; /* Cipher used for transport */ + void *ourcert; /* Certificate offered to peer, binary */ + void *peercert; /* Certificate from peer, binary */ uschar *peerdn; /* DN of server's certificate */ + int ocsp; /* OCSP status of peer cert */ #endif uschar *authenticator; /* auth driver name used by transport */ uschar *auth_id; /* auth "login" name used by transport */ uschar *auth_sndr; /* AUTH arg to SMTP MAIL, used by transport */ + #ifdef EXPERIMENTAL_DSN + uschar *dsn_orcpt; /* DSN orcpt value */ + int dsn_flags; /* DSN flags */ + int dsn_aware; /* DSN aware flag */ + #endif + uid_t uid; /* uid for transporting */ gid_t gid; /* gid for transporting */ diff -Nru exim4-4.82/src/tls.c exim4-4.84/src/tls.c --- exim4-4.82/src/tls.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/tls.c 2014-08-09 12:44:29.000000000 +0000 @@ -17,6 +17,7 @@ #include "exim.h" +#include "transports/smtp.h" /* This module is compiled only when it is specifically requested in the build-time configuration. However, some compilers don't like compiling empty @@ -85,6 +86,7 @@ #ifdef USE_GNUTLS #include "tls-gnu.c" +#include "tlscert-gnu.c" #define ssl_xfer_buffer (state_server.xfer_buffer) #define ssl_xfer_buffer_lwm (state_server.xfer_buffer_lwm) @@ -94,6 +96,7 @@ #else #include "tls-openssl.c" +#include "tlscert-openssl.c" #endif @@ -181,4 +184,154 @@ #endif /* SUPPORT_TLS */ +void +tls_modify_variables(tls_support * dest_tsp) +{ +modify_variable(US"tls_bits", &dest_tsp->bits); +modify_variable(US"tls_certificate_verified", &dest_tsp->certificate_verified); +modify_variable(US"tls_cipher", &dest_tsp->cipher); +modify_variable(US"tls_peerdn", &dest_tsp->peerdn); +#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS) +modify_variable(US"tls_sni", &dest_tsp->sni); +#endif +} + + +#ifdef SUPPORT_TLS +/************************************************ +* TLS certificate name operations * +************************************************/ + +/* Convert an rfc4514 DN to an exim comma-sep list. +Backslashed commas need to be replaced by doublecomma +for Exim's list quoting. We modify the given string +inplace. +*/ + +static void +dn_to_list(uschar * dn) +{ +uschar * cp; +for (cp = dn; *cp; cp++) + if (cp[0] == '\\' && cp[1] == ',') + *cp++ = ','; +} + + +/* Extract fields of a given type from an RFC4514- +format Distinguished Name. Return an Exim list. +NOTE: We modify the supplied dn string during operation. + +Arguments: + dn Distinguished Name string + mod string containing optional list-sep and + field selector match, comma-separated +Return: + allocated string with list of matching fields, + field type stripped +*/ + +uschar * +tls_field_from_dn(uschar * dn, uschar * mod) +{ +int insep = ','; +uschar outsep = '\n'; +uschar * ele; +uschar * match = NULL; +int len; +uschar * list = NULL; + +while ((ele = string_nextinlist(&mod, &insep, NULL, 0))) + if (ele[0] != '>') + match = ele; /* field tag to match */ + else if (ele[1]) + outsep = ele[1]; /* nondefault separator */ + +dn_to_list(dn); +insep = ','; +len = Ustrlen(match); +while ((ele = string_nextinlist(&dn, &insep, NULL, 0))) + if (Ustrncmp(ele, match, len) == 0 && ele[len] == '=') + list = string_append_listele(list, outsep, ele+len+1); +return list; +} + + +# ifdef EXPERIMENTAL_CERTNAMES +/* Compare a domain name with a possibly-wildcarded name. Wildcards +are restricted to a single one, as the first element of patterns +having at least three dot-separated elements. Case-independent. +Return TRUE for a match +*/ +static BOOL +is_name_match(const uschar * name, const uschar * pat) +{ +uschar * cp; +return *pat == '*' /* possible wildcard match */ + ? *++pat == '.' /* starts star, dot */ + && !Ustrchr(++pat, '*') /* has no more stars */ + && Ustrchr(pat, '.') /* and has another dot. */ + && (cp = Ustrchr(name, '.'))/* The name has at least one dot */ + && strcmpic(++cp, pat) == 0 /* and we only compare after it. */ + : !Ustrchr(pat+1, '*') + && strcmpic(name, pat) == 0; +} + +/* Compare a list of names with the dnsname elements +of the Subject Alternate Name, if any, and the +Subject otherwise. + +Arguments: + namelist names to compare + cert certificate + +Returns: + TRUE/FALSE +*/ + +BOOL +tls_is_name_for_cert(uschar * namelist, void * cert) +{ +uschar * altnames = tls_cert_subject_altname(cert, US"dns"); +uschar * subjdn; +uschar * certname; +int cmp_sep = 0; +uschar * cmpname; + +if ((altnames = tls_cert_subject_altname(cert, US"dns"))) + { + int alt_sep = '\n'; + while ((cmpname = string_nextinlist(&namelist, &cmp_sep, NULL, 0))) + { + uschar * an = altnames; + while ((certname = string_nextinlist(&an, &alt_sep, NULL, 0))) + if (is_name_match(cmpname, certname)) + return TRUE; + } + } + +else if ((subjdn = tls_cert_subject(cert, NULL))) + { + int sn_sep = ','; + + dn_to_list(subjdn); + while ((cmpname = string_nextinlist(&namelist, &cmp_sep, NULL, 0))) + { + uschar * sn = subjdn; + while ((certname = string_nextinlist(&sn, &sn_sep, NULL, 0))) + if ( *certname++ == 'C' + && *certname++ == 'N' + && *certname++ == '=' + && is_name_match(cmpname, certname) + ) + return TRUE; + } + } +return FALSE; +} +# endif /*EXPERIMENTAL_CERTNAMES*/ +#endif /*SUPPORT_TLS*/ + +/* vi: aw ai sw=2 +*/ /* End of tls.c */ diff -Nru exim4-4.82/src/tlscert-gnu.c exim4-4.84/src/tlscert-gnu.c --- exim4-4.82/src/tlscert-gnu.c 1970-01-01 00:00:00.000000000 +0000 +++ exim4-4.84/src/tlscert-gnu.c 2014-08-09 12:44:29.000000000 +0000 @@ -0,0 +1,451 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) Jeremy Harris 2014 */ + +/* This file provides TLS/SSL support for Exim using the GnuTLS library, +one of the available supported implementations. This file is #included into +tls.c when USE_GNUTLS has been set. +*/ + +#include +/* needed for cert checks in verification and DN extraction: */ +#include +/* needed to disable PKCS11 autoload unless requested */ +#if GNUTLS_VERSION_NUMBER >= 0x020c00 +# include +#endif + + +/***************************************************** +* Export/import a certificate, binary/printable +*****************************************************/ +int +tls_export_cert(uschar * buf, size_t buflen, void * cert) +{ +size_t sz = buflen; +void * reset_point = store_get(0); +int fail; +uschar * cp; + +if ((fail = gnutls_x509_crt_export((gnutls_x509_crt_t)cert, + GNUTLS_X509_FMT_PEM, buf, &sz))) + { + log_write(0, LOG_MAIN, "TLS error in certificate export: %s", + gnutls_strerror(fail)); + return 1; + } +if ((cp = string_printing(buf)) != buf) + { + Ustrncpy(buf, cp, buflen); + if (buf[buflen-1]) + fail = 1; + } +store_reset(reset_point); +return fail; +} + +int +tls_import_cert(const uschar * buf, void ** cert) +{ +void * reset_point = store_get(0); +gnutls_datum_t datum; +gnutls_x509_crt_t crt; +int fail = 0; + +gnutls_global_init(); +gnutls_x509_crt_init(&crt); + +datum.data = string_unprinting(US buf); +datum.size = Ustrlen(datum.data); +if ((fail = gnutls_x509_crt_import(crt, &datum, GNUTLS_X509_FMT_PEM))) + { + log_write(0, LOG_MAIN, "TLS error in certificate import: %s", + gnutls_strerror(fail)); + fail = 1; + } +else + *cert = (void *)crt; + +store_reset(reset_point); +return fail; +} + +void +tls_free_cert(void * cert) +{ +gnutls_x509_crt_deinit((gnutls_x509_crt_t) cert); +gnutls_global_deinit(); +} + +/***************************************************** +* Certificate field extraction routines +*****************************************************/ + +/* First, some internal service functions */ + +static uschar * +g_err(const char * tag, const char * from, int gnutls_err) +{ +expand_string_message = string_sprintf("%s: %s fail: %s\n", + from, tag, gnutls_strerror(gnutls_err)); +return NULL; +} + + +static uschar * +time_copy(time_t t, uschar * mod) +{ +uschar * cp; +struct tm * tp; +size_t len; + +if (mod && Ustrcmp(mod, "int") == 0) + return string_sprintf("%u", (unsigned)t); + +cp = store_get(32); +tp = gmtime(&t); +len = strftime(CS cp, 32, "%b %e %T %Y %Z", tp); +return len > 0 ? cp : NULL; +} + + +/**/ +/* Now the extractors, called from expand.c +Arguments: + cert The certificate + mod Optional modifiers for the operator + +Return: + Allocated string with extracted value +*/ + +uschar * +tls_cert_issuer(void * cert, uschar * mod) +{ +uschar * cp = NULL; +int ret; +size_t siz = 0; + +if ((ret = gnutls_x509_crt_get_issuer_dn(cert, cp, &siz)) + != GNUTLS_E_SHORT_MEMORY_BUFFER) + return g_err("gi0", __FUNCTION__, ret); + +cp = store_get(siz); +if ((ret = gnutls_x509_crt_get_issuer_dn(cert, cp, &siz)) < 0) + return g_err("gi1", __FUNCTION__, ret); + +return mod ? tls_field_from_dn(cp, mod) : cp; +} + +uschar * +tls_cert_not_after(void * cert, uschar * mod) +{ +return time_copy( + gnutls_x509_crt_get_expiration_time((gnutls_x509_crt_t)cert), + mod); +} + +uschar * +tls_cert_not_before(void * cert, uschar * mod) +{ +return time_copy( + gnutls_x509_crt_get_activation_time((gnutls_x509_crt_t)cert), + mod); +} + +uschar * +tls_cert_serial_number(void * cert, uschar * mod) +{ +uschar bin[50], txt[150]; +size_t sz = sizeof(bin); +uschar * sp; +uschar * dp; +int ret; + +if ((ret = gnutls_x509_crt_get_serial((gnutls_x509_crt_t)cert, + bin, &sz))) + return g_err("gs0", __FUNCTION__, ret); + +for(dp = txt, sp = bin; sz; dp += 2, sp++, sz--) + sprintf(dp, "%.2x", *sp); +for(sp = txt; sp[0]=='0' && sp[1]; ) sp++; /* leading zeroes */ +return string_copy(sp); +} + +uschar * +tls_cert_signature(void * cert, uschar * mod) +{ +uschar * cp1; +uschar * cp2; +uschar * cp3; +size_t len = 0; +int ret; + +if ((ret = gnutls_x509_crt_get_signature((gnutls_x509_crt_t)cert, cp1, &len)) + != GNUTLS_E_SHORT_MEMORY_BUFFER) + return g_err("gs0", __FUNCTION__, ret); + +cp1 = store_get(len*4+1); +if (gnutls_x509_crt_get_signature((gnutls_x509_crt_t)cert, cp1, &len) != 0) + return g_err("gs1", __FUNCTION__, ret); + +for(cp3 = cp2 = cp1+len; cp1 < cp2; cp3 += 3, cp1++) + sprintf(cp3, "%.2x ", *cp1); +cp3[-1]= '\0'; + +return cp2; +} + +uschar * +tls_cert_signature_algorithm(void * cert, uschar * mod) +{ +gnutls_sign_algorithm_t algo = + gnutls_x509_crt_get_signature_algorithm((gnutls_x509_crt_t)cert); +return algo < 0 ? NULL : string_copy(gnutls_sign_get_name(algo)); +} + +uschar * +tls_cert_subject(void * cert, uschar * mod) +{ +uschar * cp = NULL; +int ret; +size_t siz = 0; + +if ((ret = gnutls_x509_crt_get_dn(cert, cp, &siz)) + != GNUTLS_E_SHORT_MEMORY_BUFFER) + return g_err("gs0", __FUNCTION__, ret); + +cp = store_get(siz); +if ((ret = gnutls_x509_crt_get_dn(cert, cp, &siz)) < 0) + return g_err("gs1", __FUNCTION__, ret); + +return mod ? tls_field_from_dn(cp, mod) : cp; +} + +uschar * +tls_cert_version(void * cert, uschar * mod) +{ +return string_sprintf("%d", gnutls_x509_crt_get_version(cert)); +} + +uschar * +tls_cert_ext_by_oid(void * cert, uschar * oid, int idx) +{ +uschar * cp1 = NULL; +uschar * cp2; +uschar * cp3; +size_t siz = 0; +unsigned int crit; +int ret; + +ret = gnutls_x509_crt_get_extension_by_oid ((gnutls_x509_crt_t)cert, + oid, idx, cp1, &siz, &crit); +if (ret != GNUTLS_E_SHORT_MEMORY_BUFFER) + return g_err("ge0", __FUNCTION__, ret); + +cp1 = store_get(siz*4 + 1); + +ret = gnutls_x509_crt_get_extension_by_oid ((gnutls_x509_crt_t)cert, + oid, idx, cp1, &siz, &crit); +if (ret < 0) + return g_err("ge1", __FUNCTION__, ret); + +/* binary data, DER encoded */ + +/* just dump for now */ +for(cp3 = cp2 = cp1+siz; cp1 < cp2; cp3 += 3, cp1++) + sprintf(cp3, "%.2x ", *cp1); +cp3[-1]= '\0'; + +return cp2; +} + +uschar * +tls_cert_subject_altname(void * cert, uschar * mod) +{ +uschar * list = NULL; +int index; +size_t siz; +int ret; +uschar sep = '\n'; +uschar * tag = US""; +uschar * ele; +int match = -1; + +while (mod) + { + if (*mod == '>' && *++mod) sep = *mod++; + else if (Ustrcmp(mod, "dns")==0) { match = GNUTLS_SAN_DNSNAME; mod += 3; } + else if (Ustrcmp(mod, "uri")==0) { match = GNUTLS_SAN_URI; mod += 3; } + else if (Ustrcmp(mod, "mail")==0) { match = GNUTLS_SAN_RFC822NAME; mod += 4; } + else continue; + + if (*mod++ != ',') + break; + } + +for(index = 0;; index++) + { + siz = 0; + switch(ret = gnutls_x509_crt_get_subject_alt_name( + (gnutls_x509_crt_t)cert, index, NULL, &siz, NULL)) + { + case GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE: + return list; /* no more elements; normal exit */ + + case GNUTLS_E_SHORT_MEMORY_BUFFER: + break; + + default: + return g_err("gs0", __FUNCTION__, ret); + } + + ele = store_get(siz+1); + if ((ret = gnutls_x509_crt_get_subject_alt_name( + (gnutls_x509_crt_t)cert, index, ele, &siz, NULL)) < 0) + return g_err("gs1", __FUNCTION__, ret); + ele[siz] = '\0'; + + if ( match != -1 && match != ret /* wrong type of SAN */ + || Ustrlen(ele) != siz) /* contains a NUL */ + continue; + switch (ret) + { + case GNUTLS_SAN_DNSNAME: tag = US"DNS"; break; + case GNUTLS_SAN_URI: tag = US"URI"; break; + case GNUTLS_SAN_RFC822NAME: tag = US"MAIL"; break; + default: continue; /* ignore unrecognised types */ + } + list = string_append_listele(list, sep, + match == -1 ? string_sprintf("%s=%s", tag, ele) : ele); + } +/*NOTREACHED*/ +} + +uschar * +tls_cert_ocsp_uri(void * cert, uschar * mod) +{ +#if GNUTLS_VERSION_NUMBER >= 0x030000 +gnutls_datum_t uri; +int ret; +uschar sep = '\n'; +int index; +uschar * list = NULL; + +if (mod) + if (*mod == '>' && *++mod) sep = *mod++; + +for(index = 0;; index++) + { + ret = gnutls_x509_crt_get_authority_info_access((gnutls_x509_crt_t)cert, + index, GNUTLS_IA_OCSP_URI, &uri, NULL); + + if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) + return list; + if (ret < 0) + return g_err("gai", __FUNCTION__, ret); + + list = string_append_listele(list, sep, + string_copyn(uri.data, uri.size)); + } +/*NOTREACHED*/ + +#else + +expand_string_message = + string_sprintf("%s: OCSP support with GnuTLS requires version 3.0.0\n", + __FUNCTION__); +return NULL; + +#endif +} + +uschar * +tls_cert_crl_uri(void * cert, uschar * mod) +{ +int ret; +size_t siz; +uschar sep = '\n'; +int index; +uschar * list = NULL; +uschar * ele; + +if (mod) + if (*mod == '>' && *++mod) sep = *mod++; + +for(index = 0;; index++) + { + siz = 0; + switch(ret = gnutls_x509_crt_get_crl_dist_points( + (gnutls_x509_crt_t)cert, index, NULL, &siz, NULL, NULL)) + { + case GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE: + return list; + case GNUTLS_E_SHORT_MEMORY_BUFFER: + break; + default: + return g_err("gc0", __FUNCTION__, ret); + } + + ele = store_get(siz+1); + if ((ret = gnutls_x509_crt_get_crl_dist_points( + (gnutls_x509_crt_t)cert, index, ele, &siz, NULL, NULL)) < 0) + return g_err("gc1", __FUNCTION__, ret); + + ele[siz] = '\0'; + list = string_append_listele(list, sep, ele); + } +/*NOTREACHED*/ +} + + +/***************************************************** +* Certificate operator routines +*****************************************************/ +static uschar * +fingerprint(gnutls_x509_crt_t cert, gnutls_digest_algorithm_t algo) +{ +int ret; +size_t siz = 0; +uschar * cp; +uschar * cp2; +uschar * cp3; + +if ((ret = gnutls_x509_crt_get_fingerprint(cert, algo, NULL, &siz)) + != GNUTLS_E_SHORT_MEMORY_BUFFER) + return g_err("gf0", __FUNCTION__, ret); + +cp = store_get(siz*3+1); +if ((ret = gnutls_x509_crt_get_fingerprint(cert, algo, cp, &siz)) < 0) + return g_err("gf1", __FUNCTION__, ret); + +for (cp3 = cp2 = cp+siz; cp < cp2; cp++, cp3+=2) + sprintf(cp3, "%02X",*cp); +return cp2; +} + + +uschar * +tls_cert_fprt_md5(void * cert) +{ +return fingerprint((gnutls_x509_crt_t)cert, GNUTLS_DIG_MD5); +} + +uschar * +tls_cert_fprt_sha1(void * cert) +{ +return fingerprint((gnutls_x509_crt_t)cert, GNUTLS_DIG_SHA1); +} + +uschar * +tls_cert_fprt_sha256(void * cert) +{ +return fingerprint((gnutls_x509_crt_t)cert, GNUTLS_DIG_SHA256); +} + + +/* vi: aw ai sw=2 +*/ +/* End of tlscert-gnu.c */ diff -Nru exim4-4.82/src/tlscert-openssl.c exim4-4.84/src/tlscert-openssl.c --- exim4-4.82/src/tlscert-openssl.c 1970-01-01 00:00:00.000000000 +0000 +++ exim4-4.84/src/tlscert-openssl.c 2014-08-09 12:44:29.000000000 +0000 @@ -0,0 +1,457 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) Jeremy Harris 2014 */ + +/* This module provides TLS (aka SSL) support for Exim using the OpenSSL +library. It is #included into the tls.c file when that library is used. +*/ + + +/* Heading stuff */ + +#include +#include +#include +#include +#include + + +/***************************************************** +* Export/import a certificate, binary/printable +*****************************************************/ +int +tls_export_cert(uschar * buf, size_t buflen, void * cert) +{ +BIO * bp = BIO_new(BIO_s_mem()); +int fail; + +if ((fail = PEM_write_bio_X509(bp, (X509 *)cert) ? 0 : 1)) + log_write(0, LOG_MAIN, "TLS error in certificate export: %s", + ERR_error_string(ERR_get_error(), NULL)); +else + { + char * cp = CS buf; + int n; + buflen -= 2; + for(;;) + { + if ((n = BIO_gets(bp, cp, (int)buflen)) <= 0) break; + cp += n+1; + buflen -= n+1; + cp[-2] = '\\'; cp[-1] = 'n'; /* newline->"\n" */ + } /* compat with string_printing() */ + *cp = '\0'; + } + +BIO_free(bp); +return fail; +} + +int +tls_import_cert(const uschar * buf, void ** cert) +{ +void * reset_point = store_get(0); +const uschar * cp = string_unprinting(US buf); +BIO * bp; +X509 * x; +int fail = 0; + +bp = BIO_new_mem_buf(US cp, -1); +if (!(x = PEM_read_bio_X509(bp, NULL, 0, NULL))) + { + log_write(0, LOG_MAIN, "TLS error in certificate import: %s", + ERR_error_string(ERR_get_error(), NULL)); + fail = 1; + } +else + *cert = (void *)x; +BIO_free(bp); +store_reset(reset_point); +return fail; +} + +void +tls_free_cert(void * cert) +{ +X509_free((X509 *)cert); +} + + +/***************************************************** +* Certificate field extraction routines +*****************************************************/ + +/* First, some internal service functions */ + +static uschar * +badalloc(void) +{ +expand_string_message = US"allocation failure"; +return NULL; +} + +static uschar * +bio_string_copy(BIO * bp, int len) +{ +uschar * cp = US""; +len = len > 0 ? (int) BIO_get_mem_data(bp, &cp) : 0; +cp = string_copyn(cp, len); +BIO_free(bp); +return cp; +} + +static uschar * +bio_string_time_to_int(BIO * bp, int len) +{ +uschar * cp = US""; +struct tm t; +len = len > 0 ? (int) BIO_get_mem_data(bp, &cp) : 0; +/*XXX %Z might be glibc-specific? */ +(void) strptime(CS cp, "%b%t%e%t%T%t%Y%t%Z", &t); +BIO_free(bp); +/*XXX timegm might not be portable? */ +return string_sprintf("%u", (unsigned) timegm(&t)); +} + +static uschar * +asn1_time_copy(const ASN1_TIME * time, uschar * mod) +{ +BIO * bp = BIO_new(BIO_s_mem()); +int len; + +if (!bp) return badalloc(); + +len = ASN1_TIME_print(bp, time); +return mod && Ustrcmp(mod, "int") == 0 + ? bio_string_time_to_int(bp, len) + : bio_string_copy(bp, len); +} + +static uschar * +x509_name_copy(X509_NAME * name) +{ +BIO * bp = BIO_new(BIO_s_mem()); +int len_good; + +if (!bp) return badalloc(); + +len_good = + X509_NAME_print_ex(bp, name, 0, XN_FLAG_RFC2253) >= 0 + ? 1 : 0; +return bio_string_copy(bp, len_good); +} + +/**/ +/* Now the extractors, called from expand.c +Arguments: + cert The certificate + mod Optional modifiers for the operator + +Return: + Allocated string with extracted value +*/ + +uschar * +tls_cert_issuer(void * cert, uschar * mod) +{ +uschar * cp = x509_name_copy(X509_get_issuer_name((X509 *)cert)); +return mod ? tls_field_from_dn(cp, mod) : cp; +} + +uschar * +tls_cert_not_before(void * cert, uschar * mod) +{ +return asn1_time_copy(X509_get_notBefore((X509 *)cert), mod); +} + +uschar * +tls_cert_not_after(void * cert, uschar * mod) +{ +return asn1_time_copy(X509_get_notAfter((X509 *)cert), mod); +} + +uschar * +tls_cert_serial_number(void * cert, uschar * mod) +{ +uschar txt[256]; +BIO * bp = BIO_new(BIO_s_mem()); +int len; + +if (!bp) return badalloc(); + +len = i2a_ASN1_INTEGER(bp, X509_get_serialNumber((X509 *)cert)); +if (len < sizeof(txt)) + BIO_read(bp, txt, len); +else + len = 0; +BIO_free(bp); +return string_copynlc(txt, len); /* lowercase */ +} + +uschar * +tls_cert_signature(void * cert, uschar * mod) +{ +uschar * cp = NULL; +BIO * bp = BIO_new(BIO_s_mem()); + +if (!bp) return badalloc(); + +if (X509_print_ex(bp, (X509 *)cert, 0, + X509_FLAG_NO_HEADER | X509_FLAG_NO_VERSION | X509_FLAG_NO_SERIAL | + X509_FLAG_NO_SIGNAME | X509_FLAG_NO_ISSUER | X509_FLAG_NO_VALIDITY | + X509_FLAG_NO_SUBJECT | X509_FLAG_NO_PUBKEY | X509_FLAG_NO_EXTENSIONS | + /* X509_FLAG_NO_SIGDUMP is the missing one */ + X509_FLAG_NO_AUX) == 1) + { + long len = BIO_get_mem_data(bp, &cp); + + /* Strip leading "Signature Algorithm" line */ + while (*cp && *cp != '\n') { cp++; len--; } + + cp = string_copyn(cp+1, len-1); + } +BIO_free(bp); +return cp; +} + +uschar * +tls_cert_signature_algorithm(void * cert, uschar * mod) +{ +uschar * cp = NULL; +BIO * bp = BIO_new(BIO_s_mem()); + +if (!bp) return badalloc(); + +if (X509_print_ex(bp, (X509 *)cert, 0, + X509_FLAG_NO_HEADER | X509_FLAG_NO_VERSION | X509_FLAG_NO_SERIAL | + /* X509_FLAG_NO_SIGNAME is the missing one */ + X509_FLAG_NO_ISSUER | X509_FLAG_NO_VALIDITY | + X509_FLAG_NO_SUBJECT | X509_FLAG_NO_PUBKEY | X509_FLAG_NO_EXTENSIONS | + X509_FLAG_NO_SIGDUMP | X509_FLAG_NO_AUX) == 1) + { + long len = BIO_get_mem_data(bp, &cp); + + /* Strip leading " Signature Algorithm: " and trailing newline */ + while (*cp && *cp != ':') { cp++; len--; } + do { cp++; len--; } while (*cp && *cp == ' '); + if (cp[len-1] == '\n') len--; + + cp = string_copyn(cp, len); + } +BIO_free(bp); +return cp; +} + +uschar * +tls_cert_subject(void * cert, uschar * mod) +{ +uschar * cp = x509_name_copy(X509_get_subject_name((X509 *)cert)); +return mod ? tls_field_from_dn(cp, mod) : cp; +} + +uschar * +tls_cert_version(void * cert, uschar * mod) +{ +return string_sprintf("%d", X509_get_version((X509 *)cert)); +} + +uschar * +tls_cert_ext_by_oid(void * cert, uschar * oid, int idx) +{ +int nid = OBJ_create(CS oid, "", ""); +int nidx = X509_get_ext_by_NID((X509 *)cert, nid, idx); +X509_EXTENSION * ex = X509_get_ext((X509 *)cert, nidx); +ASN1_OCTET_STRING * adata = X509_EXTENSION_get_data(ex); +BIO * bp = BIO_new(BIO_s_mem()); +long len; +uschar * cp1; +uschar * cp2; +uschar * cp3; + +if (!bp) return badalloc(); + +M_ASN1_OCTET_STRING_print(bp, adata); +/* binary data, DER encoded */ + +/* just dump for now */ +len = BIO_get_mem_data(bp, &cp1); +cp3 = cp2 = store_get(len*3+1); + +while(len) + { + sprintf(CS cp2, "%.2x ", *cp1++); + cp2 += 3; + len--; + } +cp2[-1] = '\0'; + +return cp3; +} + +uschar * +tls_cert_subject_altname(void * cert, uschar * mod) +{ +uschar * list = NULL; +STACK_OF(GENERAL_NAME) * san = (STACK_OF(GENERAL_NAME) *) + X509_get_ext_d2i((X509 *)cert, NID_subject_alt_name, NULL, NULL); +uschar sep = '\n'; +uschar * tag = US""; +uschar * ele; +int match = -1; +int len; + +if (!san) return NULL; + +while (mod) + { + if (*mod == '>' && *++mod) sep = *mod++; + else if (Ustrcmp(mod, "dns")==0) { match = GEN_DNS; mod += 3; } + else if (Ustrcmp(mod, "uri")==0) { match = GEN_URI; mod += 3; } + else if (Ustrcmp(mod, "mail")==0) { match = GEN_EMAIL; mod += 4; } + else continue; + + if (*mod++ != ',') + break; + } + +while (sk_GENERAL_NAME_num(san) > 0) + { + GENERAL_NAME * namePart = sk_GENERAL_NAME_pop(san); + if (match != -1 && match != namePart->type) + continue; + switch (namePart->type) + { + case GEN_DNS: + tag = US"DNS"; + ele = ASN1_STRING_data(namePart->d.dNSName); + len = ASN1_STRING_length(namePart->d.dNSName); + break; + case GEN_URI: + tag = US"URI"; + ele = ASN1_STRING_data(namePart->d.uniformResourceIdentifier); + len = ASN1_STRING_length(namePart->d.uniformResourceIdentifier); + break; + case GEN_EMAIL: + tag = US"MAIL"; + ele = ASN1_STRING_data(namePart->d.rfc822Name); + len = ASN1_STRING_length(namePart->d.rfc822Name); + break; + default: + continue; /* ignore unrecognised types */ + } + if (ele[len]) /* not nul-terminated */ + ele = string_copyn(ele, len); + + if (strnlen(CS ele, len) == len) /* ignore any with embedded nul */ + list = string_append_listele(list, sep, + match == -1 ? string_sprintf("%s=%s", tag, ele) : ele); + } + +sk_GENERAL_NAME_free(san); +return list; +} + +uschar * +tls_cert_ocsp_uri(void * cert, uschar * mod) +{ +STACK_OF(ACCESS_DESCRIPTION) * ads = (STACK_OF(ACCESS_DESCRIPTION) *) + X509_get_ext_d2i((X509 *)cert, NID_info_access, NULL, NULL); +int adsnum = sk_ACCESS_DESCRIPTION_num(ads); +int i; +uschar sep = '\n'; +uschar * list = NULL; + +if (mod) + if (*mod == '>' && *++mod) sep = *mod++; + +for (i = 0; i < adsnum; i++) + { + ACCESS_DESCRIPTION * ad = sk_ACCESS_DESCRIPTION_value(ads, i); + + if (ad && OBJ_obj2nid(ad->method) == NID_ad_OCSP) + list = string_append_listele(list, sep, + ASN1_STRING_data(ad->location->d.ia5)); + } +return list; +} + +uschar * +tls_cert_crl_uri(void * cert, uschar * mod) +{ +STACK_OF(DIST_POINT) * dps = (STACK_OF(DIST_POINT) *) + X509_get_ext_d2i((X509 *)cert, NID_crl_distribution_points, + NULL, NULL); +DIST_POINT * dp; +int dpsnum = sk_DIST_POINT_num(dps); +int i; +uschar sep = '\n'; +uschar * list = NULL; + +if (mod) + if (*mod == '>' && *++mod) sep = *mod++; + +if (dps) for (i = 0; i < dpsnum; i++) + if ((dp = sk_DIST_POINT_value(dps, i))) + { + STACK_OF(GENERAL_NAME) * names = dp->distpoint->name.fullname; + GENERAL_NAME * np; + int nnum = sk_GENERAL_NAME_num(names); + int j; + + for (j = 0; j < nnum; j++) + if ( (np = sk_GENERAL_NAME_value(names, j)) + && np->type == GEN_URI + ) + list = string_append_listele(list, sep, + ASN1_STRING_data(np->d.uniformResourceIdentifier)); + } +return list; +} + + + +/***************************************************** +* Certificate operator routines +*****************************************************/ +static uschar * +fingerprint(X509 * cert, const EVP_MD * fdig) +{ +int j; +unsigned int n; +uschar md[EVP_MAX_MD_SIZE]; +uschar * cp; + +if (!X509_digest(cert,fdig,md,&n)) + { + expand_string_message = US"tls_cert_fprt: out of mem\n"; + return NULL; + } +cp = store_get(n*2+1); +for (j = 0; j < (int)n; j++) sprintf(CS cp+2*j, "%02X", md[j]); +return(cp); +} + +uschar * +tls_cert_fprt_md5(void * cert) +{ +return fingerprint((X509 *)cert, EVP_md5()); +} + +uschar * +tls_cert_fprt_sha1(void * cert) +{ +return fingerprint((X509 *)cert, EVP_sha1()); +} + +uschar * +tls_cert_fprt_sha256(void * cert) +{ +return fingerprint((X509 *)cert, EVP_sha256()); +} + + +/* vi: aw ai sw=2 +*/ +/* End of tlscert-openssl.c */ diff -Nru exim4-4.82/src/tls-gnu.c exim4-4.84/src/tls-gnu.c --- exim4-4.82/src/tls-gnu.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/tls-gnu.c 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* Copyright (c) Phil Pennock 2012 */ @@ -43,6 +43,14 @@ #if GNUTLS_VERSION_NUMBER >= 0x020c00 # include #endif +#if GNUTLS_VERSION_NUMBER < 0x030103 && !defined(DISABLE_OCSP) +# warning "GnuTLS library version too old; define DISABLE_OCSP in Makefile" +# define DISABLE_OCSP +#endif + +#ifndef DISABLE_OCSP +# include +#endif /* GnuTLS 2 vs 3 @@ -57,7 +65,12 @@ /* Values for verify_requirement */ -enum peer_verify_requirement { VERIFY_NONE, VERIFY_OPTIONAL, VERIFY_REQUIRED }; +enum peer_verify_requirement + { VERIFY_NONE, VERIFY_OPTIONAL, VERIFY_REQUIRED +#ifdef EXPERIMENTAL_CERTNAMES + ,VERIFY_WITHHOST +#endif + }; /* This holds most state for server or client; with this, we can set up an outbound TLS-enabled connection in an ACL callout, while not stomping all @@ -71,19 +84,20 @@ */ typedef struct exim_gnutls_state { - gnutls_session_t session; + gnutls_session_t session; gnutls_certificate_credentials_t x509_cred; - gnutls_priority_t priority_cache; + gnutls_priority_t priority_cache; enum peer_verify_requirement verify_requirement; - int fd_in; - int fd_out; - BOOL peer_cert_verified; - BOOL trigger_sni_changes; - BOOL have_set_peerdn; + int fd_in; + int fd_out; + BOOL peer_cert_verified; + BOOL trigger_sni_changes; + BOOL have_set_peerdn; const struct host_item *host; - uschar *peerdn; - uschar *ciphersuite; - uschar *received_sni; + gnutls_x509_crt_t peercert; + uschar *peerdn; + uschar *ciphersuite; + uschar *received_sni; const uschar *tls_certificate; const uschar *tls_privatekey; @@ -91,12 +105,16 @@ const uschar *tls_verify_certificates; const uschar *tls_crl; const uschar *tls_require_ciphers; + uschar *exp_tls_certificate; uschar *exp_tls_privatekey; - uschar *exp_tls_sni; uschar *exp_tls_verify_certificates; uschar *exp_tls_crl; uschar *exp_tls_require_ciphers; + uschar *exp_tls_ocsp_file; +#ifdef EXPERIMENTAL_CERTNAMES + uschar *exp_tls_verify_cert_hostnames; +#endif tls_support *tlsp; /* set in tls_init() */ @@ -111,7 +129,10 @@ NULL, NULL, NULL, VERIFY_NONE, -1, -1, FALSE, FALSE, FALSE, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, +#ifdef EXPERIMENTAL_CERTNAMES + NULL, +#endif NULL, NULL, 0, 0, 0, 0, }; @@ -173,18 +194,18 @@ #define expand_check_tlsvar(Varname) expand_check(state->Varname, US #Varname, &state->exp_##Varname) #if GNUTLS_VERSION_NUMBER >= 0x020c00 -#define HAVE_GNUTLS_SESSION_CHANNEL_BINDING -#define HAVE_GNUTLS_SEC_PARAM_CONSTANTS -#define HAVE_GNUTLS_RND +# define HAVE_GNUTLS_SESSION_CHANNEL_BINDING +# define HAVE_GNUTLS_SEC_PARAM_CONSTANTS +# define HAVE_GNUTLS_RND /* The security fix we provide with the gnutls_allow_auto_pkcs11 option * (4.82 PP/09) introduces a compatibility regression. The symbol simply * isn't available sometimes, so this needs to become a conditional * compilation; the sanest way to deal with this being a problem on * older OSes is to block it in the Local/Makefile with this compiler * definition */ -#ifndef AVOID_GNUTLS_PKCS11 -#define HAVE_GNUTLS_PKCS11 -#endif /* AVOID_GNUTLS_PKCS11 */ +# ifndef AVOID_GNUTLS_PKCS11 +# define HAVE_GNUTLS_PKCS11 +# endif /* AVOID_GNUTLS_PKCS11 */ #endif @@ -199,6 +220,10 @@ static int exim_sni_handling_cb(gnutls_session_t session); +#ifndef DISABLE_OCSP +static int server_ocsp_stapling_cb(gnutls_session_t session, void * ptr, + gnutls_datum_t * ocsp_response); +#endif @@ -285,12 +310,40 @@ * Set various Exim expansion vars * *************************************************/ +#define exim_gnutls_cert_err(Label) \ + do \ + { \ + if (rc != GNUTLS_E_SUCCESS) \ + { \ + DEBUG(D_tls) debug_printf("TLS: cert problem: %s: %s\n", \ + (Label), gnutls_strerror(rc)); \ + return rc; \ + } \ + } while (0) + +static int +import_cert(const gnutls_datum * cert, gnutls_x509_crt_t * crtp) +{ +int rc; + +rc = gnutls_x509_crt_init(crtp); +exim_gnutls_cert_err(US"gnutls_x509_crt_init (crt)"); + +rc = gnutls_x509_crt_import(*crtp, cert, GNUTLS_X509_FMT_DER); +exim_gnutls_cert_err(US"failed to import certificate [gnutls_x509_crt_import(cert)]"); + +return rc; +} + +#undef exim_gnutls_cert_err + + /* We set various Exim global variables from the state, once a session has been established. With TLS callouts, may need to change this to stack variables, or just re-call it with the server state after client callout has finished. -Make sure anything set here is inset in tls_getc(). +Make sure anything set here is unset in tls_getc(). Sets: tls_active fd @@ -298,15 +351,17 @@ tls_certificate_verified bool indicator tls_channelbinding_b64 for some SASL mechanisms tls_cipher a string + tls_peercert pointer to library internal tls_peerdn a string tls_sni a (UTF-8) string + tls_ourcert pointer to library internal Argument: state the relevant exim_gnutls_state_st * */ static void -extract_exim_vars_from_tls_state(exim_gnutls_state_st *state, BOOL is_server) +extract_exim_vars_from_tls_state(exim_gnutls_state_st * state) { gnutls_cipher_algorithm_t cipher; #ifdef HAVE_GNUTLS_SESSION_CHANNEL_BINDING @@ -314,18 +369,19 @@ int rc; gnutls_datum_t channel; #endif +tls_support * tlsp = state->tlsp; -state->tlsp->active = state->fd_out; +tlsp->active = state->fd_out; cipher = gnutls_cipher_get(state->session); /* returns size in "bytes" */ -state->tlsp->bits = gnutls_cipher_get_key_size(cipher) * 8; +tlsp->bits = gnutls_cipher_get_key_size(cipher) * 8; -state->tlsp->cipher = state->ciphersuite; +tlsp->cipher = state->ciphersuite; DEBUG(D_tls) debug_printf("cipher: %s\n", state->ciphersuite); -state->tlsp->certificate_verified = state->peer_cert_verified; +tlsp->certificate_verified = state->peer_cert_verified; /* note that tls_channelbinding_b64 is not saved to the spool file, since it's only available for use for authenticators while this TLS session is running. */ @@ -346,8 +402,17 @@ } #endif -state->tlsp->peerdn = state->peerdn; -state->tlsp->sni = state->received_sni; +/* peercert is set in peer_status() */ +tlsp->peerdn = state->peerdn; +tlsp->sni = state->received_sni; + +/* record our certificate */ + { + const gnutls_datum * cert = gnutls_certificate_get_ours(state->session); + gnutls_x509_crt_t crt; + + tlsp->ourcert = cert && import_cert(cert, &crt)==0 ? crt : NULL; + } } @@ -658,7 +723,7 @@ int cert_count; /* We check for tls_sni *before* expansion. */ -if (!state->host) +if (!host) /* server */ { if (!state->received_sni) { @@ -700,7 +765,7 @@ if ((state->exp_tls_certificate == NULL) || (*state->exp_tls_certificate == '\0')) { - if (state->host == NULL) + if (!host) return tls_error(US"no TLS server certificate is specified", NULL, NULL); else DEBUG(D_tls) debug_printf("TLS: no client certificate specified; okay\n"); @@ -745,6 +810,30 @@ DEBUG(D_tls) debug_printf("TLS: cert/key registered\n"); } /* tls_certificate */ + +/* Set the OCSP stapling server info */ + +#ifndef DISABLE_OCSP +if ( !host /* server */ + && tls_ocsp_file + ) + { + if (!expand_check(tls_ocsp_file, US"tls_ocsp_file", + &state->exp_tls_ocsp_file)) + return DEFER; + + /* Use the full callback method for stapling just to get observability. + More efficient would be to read the file once only, if it never changed + (due to SNI). Would need restart on file update, or watch datestamp. */ + + gnutls_certificate_set_ocsp_status_request_function(state->x509_cred, + server_ocsp_stapling_cb, state->exp_tls_ocsp_file); + + DEBUG(D_tls) debug_printf("Set OCSP response file %s\n", &state->exp_tls_ocsp_file); + } +#endif + + /* Set the trusted CAs file if one is provided, and then add the CRL if one is provided. Experiment shows that, if the certificate file is empty, an unhelpful error message is provided. However, if we just refrain from setting anything up @@ -996,15 +1085,15 @@ /* set SNI in client, only */ if (host) { - if (!expand_check(state->tlsp->sni, US"tls_out_sni", &state->exp_tls_sni)) + if (!expand_check(sni, US"tls_out_sni", &state->tlsp->sni)) return DEFER; - if (state->exp_tls_sni && *state->exp_tls_sni) + if (state->tlsp->sni && *state->tlsp->sni) { DEBUG(D_tls) - debug_printf("Setting TLS client SNI to \"%s\"\n", state->exp_tls_sni); - sz = Ustrlen(state->exp_tls_sni); + debug_printf("Setting TLS client SNI to \"%s\"\n", state->tlsp->sni); + sz = Ustrlen(state->tlsp->sni); rc = gnutls_server_name_set(state->session, - GNUTLS_NAME_DNS, state->exp_tls_sni, sz); + GNUTLS_NAME_DNS, state->tlsp->sni, sz); exim_gnutls_err_check(US"gnutls_server_name_set"); } } @@ -1072,7 +1161,6 @@ - /************************************************* * Extract peer information * *************************************************/ @@ -1154,7 +1242,7 @@ { DEBUG(D_tls) debug_printf("TLS: no certificate from peer (%p & %d)\n", cert_list, cert_list_size); - if (state->verify_requirement == VERIFY_REQUIRED) + if (state->verify_requirement >= VERIFY_REQUIRED) return tls_error(US"certificate verification failed", "no certificate received from peer", state->host); return OK; @@ -1166,23 +1254,29 @@ const char *ctn = gnutls_certificate_type_get_name(ct); DEBUG(D_tls) debug_printf("TLS: peer cert not X.509 but instead \"%s\"\n", ctn); - if (state->verify_requirement == VERIFY_REQUIRED) + if (state->verify_requirement >= VERIFY_REQUIRED) return tls_error(US"certificate verification not possible, unhandled type", ctn, state->host); return OK; } -#define exim_gnutls_peer_err(Label) do { \ - if (rc != GNUTLS_E_SUCCESS) { \ - DEBUG(D_tls) debug_printf("TLS: peer cert problem: %s: %s\n", (Label), gnutls_strerror(rc)); \ - if (state->verify_requirement == VERIFY_REQUIRED) { return tls_error((Label), gnutls_strerror(rc), state->host); } \ - return OK; } } while (0) +#define exim_gnutls_peer_err(Label) \ + do { \ + if (rc != GNUTLS_E_SUCCESS) \ + { \ + DEBUG(D_tls) debug_printf("TLS: peer cert problem: %s: %s\n", \ + (Label), gnutls_strerror(rc)); \ + if (state->verify_requirement >= VERIFY_REQUIRED) \ + return tls_error((Label), gnutls_strerror(rc), state->host); \ + return OK; \ + } \ + } while (0) -rc = gnutls_x509_crt_init(&crt); -exim_gnutls_peer_err(US"gnutls_x509_crt_init (crt)"); +rc = import_cert(&cert_list[0], &crt); +exim_gnutls_peer_err(US"cert 0"); + +state->tlsp->peercert = state->peercert = crt; -rc = gnutls_x509_crt_import(crt, &cert_list[0], GNUTLS_X509_FMT_DER); -exim_gnutls_peer_err(US"failed to import certificate [gnutls_x509_crt_import(cert 0)]"); sz = 0; rc = gnutls_x509_crt_get_dn(crt, NULL, &sz); if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER) @@ -1193,6 +1287,7 @@ dn_buf = store_get_perm(sz); rc = gnutls_x509_crt_get_dn(crt, CS dn_buf, &sz); exim_gnutls_peer_err(US"failed to extract certificate DN [gnutls_x509_crt_get_dn(cert 0)]"); + state->peerdn = dn_buf; return OK; @@ -1228,42 +1323,63 @@ *error = NULL; -rc = peer_status(state); -if (rc != OK) +if ((rc = peer_status(state)) != OK) { verify = GNUTLS_CERT_INVALID; - *error = "not supplied"; + *error = "certificate not supplied"; } else - { rc = gnutls_certificate_verify_peers2(state->session, &verify); - } /* Handle the result of verification. INVALID seems to be set as well as REVOKED, but leave the test for both. */ -if ((rc < 0) || (verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED)) != 0) +if (rc < 0 || + verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED) + ) { state->peer_cert_verified = FALSE; - if (*error == NULL) - *error = ((verify & GNUTLS_CERT_REVOKED) != 0) ? "revoked" : "invalid"; + if (!*error) + *error = verify & GNUTLS_CERT_REVOKED + ? "certificate revoked" : "certificate invalid"; DEBUG(D_tls) - debug_printf("TLS certificate verification failed (%s): peerdn=%s\n", + debug_printf("TLS certificate verification failed (%s): peerdn=\"%s\"\n", *error, state->peerdn ? state->peerdn : US""); - if (state->verify_requirement == VERIFY_REQUIRED) + if (state->verify_requirement >= VERIFY_REQUIRED) { - gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE); + gnutls_alert_send(state->session, + GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE); return FALSE; } DEBUG(D_tls) debug_printf("TLS verify failure overridden (host in tls_try_verify_hosts)\n"); } + else { +#ifdef EXPERIMENTAL_CERTNAMES + if (state->verify_requirement == VERIFY_WITHHOST) + { + int sep = 0; + uschar * list = state->exp_tls_verify_cert_hostnames; + uschar * name; + while (name = string_nextinlist(&list, &sep, NULL, 0)) + if (gnutls_x509_crt_check_hostname(state->tlsp->peercert, CS name)) + break; + if (!name) + { + DEBUG(D_tls) + debug_printf("TLS certificate verification failed: cert name mismatch\n"); + gnutls_alert_send(state->session, + GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE); + return FALSE; + } + } +#endif state->peer_cert_verified = TRUE; - DEBUG(D_tls) debug_printf("TLS certificate verified: peerdn=%s\n", + DEBUG(D_tls) debug_printf("TLS certificate verified: peerdn=\"%s\"\n", state->peerdn ? state->peerdn : US""); } @@ -1373,6 +1489,31 @@ +#ifndef DISABLE_OCSP + +static int +server_ocsp_stapling_cb(gnutls_session_t session, void * ptr, + gnutls_datum_t * ocsp_response) +{ +int ret; + +if ((ret = gnutls_load_file(ptr, ocsp_response)) < 0) + { + DEBUG(D_tls) debug_printf("Failed to load ocsp stapling file %s\n", + (char *)ptr); + tls_in.ocsp = OCSP_NOT_RESP; + return GNUTLS_E_NO_CERTIFICATE_STATUS; + } + +tls_in.ocsp = OCSP_VFY_NOT_TRIED; +return 0; +} + +#endif + + + + /* ------------------------------------------------------------------------ */ /* Exported functions */ @@ -1427,19 +1568,22 @@ if (verify_check_host(&tls_verify_hosts) == OK) { - DEBUG(D_tls) debug_printf("TLS: a client certificate will be required.\n"); + DEBUG(D_tls) + debug_printf("TLS: a client certificate will be required.\n"); state->verify_requirement = VERIFY_REQUIRED; gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE); } else if (verify_check_host(&tls_try_verify_hosts) == OK) { - DEBUG(D_tls) debug_printf("TLS: a client certificate will be requested but not required.\n"); + DEBUG(D_tls) + debug_printf("TLS: a client certificate will be requested but not required.\n"); state->verify_requirement = VERIFY_OPTIONAL; gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUEST); } else { - DEBUG(D_tls) debug_printf("TLS: a client certificate will not be requested.\n"); + DEBUG(D_tls) + debug_printf("TLS: a client certificate will not be requested.\n"); state->verify_requirement = VERIFY_NONE; gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_IGNORE); } @@ -1459,15 +1603,15 @@ if (!state->tlsp->on_connect) { smtp_printf("220 TLS go ahead\r\n"); - fflush(smtp_out); /*XXX JGH */ + fflush(smtp_out); } /* Now negotiate the TLS session. We put our own timer on it, since it seems that the GnuTLS library doesn't. */ gnutls_transport_set_ptr2(state->session, - (gnutls_transport_ptr)fileno(smtp_in), - (gnutls_transport_ptr)fileno(smtp_out)); + (gnutls_transport_ptr)(long) fileno(smtp_in), + (gnutls_transport_ptr)(long) fileno(smtp_out)); state->fd_in = fileno(smtp_in); state->fd_out = fileno(smtp_out); @@ -1501,22 +1645,17 @@ /* Verify after the fact */ -if (state->verify_requirement != VERIFY_NONE) +if ( state->verify_requirement != VERIFY_NONE + && !verify_certificate(state, &error)) { - if (!verify_certificate(state, &error)) + if (state->verify_requirement != VERIFY_OPTIONAL) { - if (state->verify_requirement == VERIFY_OPTIONAL) - { - DEBUG(D_tls) - debug_printf("TLS: continuing on only because verification was optional, after: %s\n", - error); - } - else - { - tls_error(US"certificate verification failed", error, NULL); - return FAIL; - } + tls_error(US"certificate verification failed", error, NULL); + return FAIL; } + DEBUG(D_tls) + debug_printf("TLS: continuing on only because verification was optional, after: %s\n", + error); } /* Figure out peer DN, and if authenticated, etc. */ @@ -1526,7 +1665,7 @@ /* Sets various Exim expansion variables; always safe within server */ -extract_exim_vars_from_tls_state(state, TRUE); +extract_exim_vars_from_tls_state(state); /* TLS has been set up. Adjust the input functions to read via TLS, and initialize appropriately. */ @@ -1555,14 +1694,7 @@ fd the fd of the connection host connected host (for messages) addr the first address (not used) - certificate certificate file - privatekey private key file - sni TLS SNI to send to remote host - verify_certs file for certificate verify - verify_crl CRL for verify - require_ciphers list of allowed ciphers or NULL - dh_min_bits minimum number of bits acceptable in server's DH prime - timeout startup timeout + ob smtp transport options Returns: OK/DEFER/FAIL (because using common functions), but for a client, DEFER and FAIL have the same meaning @@ -1571,58 +1703,116 @@ int tls_client_start(int fd, host_item *host, address_item *addr ARG_UNUSED, - uschar *certificate, uschar *privatekey, uschar *sni, - uschar *verify_certs, uschar *verify_crl, - uschar *require_ciphers, -#ifdef EXPERIMENTAL_OCSP - uschar *require_ocsp ARG_UNUSED, -#endif - int dh_min_bits, int timeout) + void *v_ob) { +smtp_transport_options_block *ob = v_ob; int rc; const char *error; exim_gnutls_state_st *state = NULL; +#ifndef DISABLE_OCSP +BOOL require_ocsp = verify_check_this_host(&ob->hosts_require_ocsp, + NULL, host->name, host->address, NULL) == OK; +BOOL request_ocsp = require_ocsp ? TRUE + : verify_check_this_host(&ob->hosts_request_ocsp, + NULL, host->name, host->address, NULL) == OK; +#endif DEBUG(D_tls) debug_printf("initialising GnuTLS as a client on fd %d\n", fd); -rc = tls_init(host, certificate, privatekey, - sni, verify_certs, verify_crl, require_ciphers, &state); -if (rc != OK) return rc; +if ((rc = tls_init(host, ob->tls_certificate, ob->tls_privatekey, + ob->tls_sni, ob->tls_verify_certificates, ob->tls_crl, + ob->tls_require_ciphers, &state)) != OK) + return rc; -if (dh_min_bits < EXIM_CLIENT_DH_MIN_MIN_BITS) { - DEBUG(D_tls) - debug_printf("WARNING: tls_dh_min_bits far too low, clamping %d up to %d\n", - dh_min_bits, EXIM_CLIENT_DH_MIN_MIN_BITS); - dh_min_bits = EXIM_CLIENT_DH_MIN_MIN_BITS; + int dh_min_bits = ob->tls_dh_min_bits; + if (dh_min_bits < EXIM_CLIENT_DH_MIN_MIN_BITS) + { + DEBUG(D_tls) + debug_printf("WARNING: tls_dh_min_bits far too low," + " clamping %d up to %d\n", + dh_min_bits, EXIM_CLIENT_DH_MIN_MIN_BITS); + dh_min_bits = EXIM_CLIENT_DH_MIN_MIN_BITS; + } + + DEBUG(D_tls) debug_printf("Setting D-H prime minimum" + " acceptable bits to %d\n", + dh_min_bits); + gnutls_dh_set_prime_bits(state->session, dh_min_bits); + } + +/* Stick to the old behaviour for compatibility if tls_verify_certificates is +set but both tls_verify_hosts and tls_try_verify_hosts are unset. Check only +the specified host patterns if one of them is defined */ + +if (( state->exp_tls_verify_certificates + && !ob->tls_verify_hosts + && !ob->tls_try_verify_hosts + ) + || + verify_check_host(&ob->tls_verify_hosts) == OK + ) + { +#ifdef EXPERIMENTAL_CERTNAMES + if (ob->tls_verify_cert_hostnames) + { + DEBUG(D_tls) + debug_printf("TLS: server cert incl. hostname verification required.\n"); + state->verify_requirement = VERIFY_WITHHOST; + if (!expand_check(ob->tls_verify_cert_hostnames, + US"tls_verify_cert_hostnames", + &state->exp_tls_verify_cert_hostnames)) + return FAIL; + if (state->exp_tls_verify_cert_hostnames) + DEBUG(D_tls) debug_printf("Cert hostname to check: \"%s\"\n", + state->exp_tls_verify_cert_hostnames); + } + else +#endif + { + DEBUG(D_tls) + debug_printf("TLS: server certificate verification required.\n"); + state->verify_requirement = VERIFY_REQUIRED; + } + gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE); } - -DEBUG(D_tls) debug_printf("Setting D-H prime minimum acceptable bits to %d\n", - dh_min_bits); -gnutls_dh_set_prime_bits(state->session, dh_min_bits); - -if (verify_certs == NULL) +else if (verify_check_host(&ob->tls_try_verify_hosts) == OK) { - DEBUG(D_tls) debug_printf("TLS: server certificate verification not required\n"); - state->verify_requirement = VERIFY_NONE; - /* we still ask for it, to log it, etc */ + DEBUG(D_tls) + debug_printf("TLS: server certificate verification optional.\n"); + state->verify_requirement = VERIFY_OPTIONAL; gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUEST); } else { - DEBUG(D_tls) debug_printf("TLS: server certificate verification required\n"); - state->verify_requirement = VERIFY_REQUIRED; - gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE); + DEBUG(D_tls) + debug_printf("TLS: server certificate verification not required.\n"); + state->verify_requirement = VERIFY_NONE; + gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_IGNORE); } -gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr)fd); +#ifndef DISABLE_OCSP + /* supported since GnuTLS 3.1.3 */ +if (request_ocsp) + { + DEBUG(D_tls) debug_printf("TLS: will request OCSP stapling\n"); + if ((rc = gnutls_ocsp_status_request_enable_client(state->session, + NULL, 0, NULL)) != OK) + return tls_error(US"cert-status-req", + gnutls_strerror(rc), state->host); + tls_out.ocsp = OCSP_NOT_RESP; + } +#endif + +gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr)(long) fd); state->fd_in = fd; state->fd_out = fd; +DEBUG(D_tls) debug_printf("about to gnutls_handshake\n"); /* There doesn't seem to be a built-in timeout on connection. */ sigalrm_seen = FALSE; -alarm(timeout); +alarm(ob->command_timeout); do { rc = gnutls_handshake(state->session); @@ -1642,14 +1832,45 @@ !verify_certificate(state, &error)) return tls_error(US"certificate verification failed", error, state->host); +#ifndef DISABLE_OCSP +if (require_ocsp) + { + DEBUG(D_tls) + { + gnutls_datum_t stapling; + gnutls_ocsp_resp_t resp; + gnutls_datum_t printed; + if ( (rc= gnutls_ocsp_status_request_get(state->session, &stapling)) == 0 + && (rc= gnutls_ocsp_resp_init(&resp)) == 0 + && (rc= gnutls_ocsp_resp_import(resp, &stapling)) == 0 + && (rc= gnutls_ocsp_resp_print(resp, GNUTLS_OCSP_PRINT_FULL, &printed)) == 0 + ) + { + debug_printf("%.4096s", printed.data); + gnutls_free(printed.data); + } + else + (void) tls_error(US"ocsp decode", gnutls_strerror(rc), state->host); + } + + if (gnutls_ocsp_status_request_is_checked(state->session, 0) == 0) + { + tls_out.ocsp = OCSP_FAILED; + return tls_error(US"certificate status check failed", NULL, state->host); + } + DEBUG(D_tls) debug_printf("Passed OCSP checking\n"); + tls_out.ocsp = OCSP_VFIED; + } +#endif + /* Figure out peer DN, and if authenticated, etc. */ -rc = peer_status(state); -if (rc != OK) return rc; +if ((rc = peer_status(state)) != OK) + return rc; /* Sets various Exim expansion variables; may need to adjust for ACL callouts */ -extract_exim_vars_from_tls_state(state, FALSE); +extract_exim_vars_from_tls_state(state); return OK; } @@ -1747,8 +1968,9 @@ state->tlsp->active = -1; state->tlsp->bits = 0; state->tlsp->certificate_verified = FALSE; - tls_channelbinding_b64 = NULL; /*XXX JGH */ + tls_channelbinding_b64 = NULL; state->tlsp->cipher = NULL; + state->tlsp->peercert = NULL; state->tlsp->peerdn = NULL; return smtp_getc(); @@ -2031,4 +2253,6 @@ gnutls_check_version(NULL)); } +/* vi: aw ai sw=2 +*/ /* End of tls-gnu.c */ diff -Nru exim4-4.82/src/tls-openssl.c exim4-4.84/src/tls-openssl.c --- exim4-4.82/src/tls-openssl.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/tls-openssl.c 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* Portions Copyright (c) The OpenSSL Project 1999 */ @@ -22,17 +22,22 @@ #include #include #include -#ifdef EXPERIMENTAL_OCSP -#include +#ifndef DISABLE_OCSP +# include #endif -#ifdef EXPERIMENTAL_OCSP -#define EXIM_OCSP_SKEW_SECONDS (300L) -#define EXIM_OCSP_MAX_AGE (-1L) +#ifndef DISABLE_OCSP +# define EXIM_OCSP_SKEW_SECONDS (300L) +# define EXIM_OCSP_MAX_AGE (-1L) #endif #if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT) -#define EXIM_HAVE_OPENSSL_TLSEXT +# define EXIM_HAVE_OPENSSL_TLSEXT +#endif + +#if !defined(EXIM_HAVE_OPENSSL_TLSEXT) && !defined(DISABLE_OCSP) +# warning "OpenSSL library version too old; define DISABLE_OCSP in Makefile" +# define DISABLE_OCSP #endif /* Structure for collecting random data for seeding. */ @@ -88,7 +93,7 @@ typedef struct tls_ext_ctx_cb { uschar *certificate; uschar *privatekey; -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP BOOL is_server; union { struct { @@ -97,7 +102,8 @@ OCSP_RESPONSE *response; } server; struct { - X509_STORE *verify_store; + X509_STORE *verify_store; /* non-null if status requested */ + BOOL verify_required; } client; } u_ocsp; #endif @@ -106,6 +112,10 @@ uschar *server_cipher_list; /* only passed down to tls_error: */ host_item *host; + +#ifdef EXPERIMENTAL_CERTNAMES + uschar * verify_cert_hostnames; +#endif } tls_ext_ctx_cb; /* should figure out a cleanup of API to handle state preserved per @@ -122,7 +132,7 @@ #ifdef EXIM_HAVE_OPENSSL_TLSEXT static int tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg); #endif -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP static int tls_server_stapling_cb(SSL *s, void *arg); #endif @@ -208,7 +218,7 @@ /* Extreme debug -#if defined(EXPERIMENTAL_OCSP) +#ifndef DISABLE_OCSP void x509_store_dump_cert_s_names(X509_STORE * store) { @@ -261,59 +271,103 @@ */ static int -verify_callback(int state, X509_STORE_CTX *x509ctx, tls_support *tlsp, BOOL *calledp, BOOL *optionalp) +verify_callback(int state, X509_STORE_CTX *x509ctx, + tls_support *tlsp, BOOL *calledp, BOOL *optionalp) { +X509 * cert = X509_STORE_CTX_get_current_cert(x509ctx); static uschar txt[256]; -X509_NAME_oneline(X509_get_subject_name(x509ctx->current_cert), - CS txt, sizeof(txt)); +X509_NAME_oneline(X509_get_subject_name(cert), CS txt, sizeof(txt)); if (state == 0) { log_write(0, LOG_MAIN, "SSL verify error: depth=%d error=%s cert=%s", - x509ctx->error_depth, - X509_verify_cert_error_string(x509ctx->error), + X509_STORE_CTX_get_error_depth(x509ctx), + X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509ctx)), txt); tlsp->certificate_verified = FALSE; *calledp = TRUE; - if (!*optionalp) return 0; /* reject */ + if (!*optionalp) + { + tlsp->peercert = X509_dup(cert); + return 0; /* reject */ + } DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in " "tls_try_verify_hosts)\n"); - return 1; /* accept */ } -if (x509ctx->error_depth != 0) +else if (X509_STORE_CTX_get_error_depth(x509ctx) != 0) { - DEBUG(D_tls) debug_printf("SSL verify ok: depth=%d cert=%s\n", - x509ctx->error_depth, txt); -#ifdef EXPERIMENTAL_OCSP + DEBUG(D_tls) debug_printf("SSL verify ok: depth=%d SN=%s\n", + X509_STORE_CTX_get_error_depth(x509ctx), txt); +#ifndef DISABLE_OCSP if (tlsp == &tls_out && client_static_cbinfo->u_ocsp.client.verify_store) { /* client, wanting stapling */ /* Add the server cert's signing chain as the one for the verification of the OCSP stapled information. */ if (!X509_STORE_add_cert(client_static_cbinfo->u_ocsp.client.verify_store, - x509ctx->current_cert)) + cert)) ERR_clear_error(); } #endif } else { - DEBUG(D_tls) debug_printf("SSL%s peer: %s\n", - *calledp ? "" : " authenticated", txt); +#ifdef EXPERIMENTAL_CERTNAMES + uschar * verify_cert_hostnames; +#endif + tlsp->peerdn = txt; - } + tlsp->peercert = X509_dup(cert); -/*XXX JGH: this looks bogus - we set "verified" first time through, which -will be for the root CS cert (calls work down the chain). Why should it -not be on the last call, where we're setting peerdn? +#ifdef EXPERIMENTAL_CERTNAMES + if ( tlsp == &tls_out + && ((verify_cert_hostnames = client_static_cbinfo->verify_cert_hostnames))) + /* client, wanting hostname check */ + +# if OPENSSL_VERSION_NUMBER >= 0x010100000L || OPENSSL_VERSION_NUMBER >= 0x010002000L +# ifndef X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS +# define X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS 0 +# endif + { + int sep = 0; + uschar * list = verify_cert_hostnames; + uschar * name; + int rc; + while ((name = string_nextinlist(&list, &sep, NULL, 0))) + if ((rc = X509_check_host(cert, name, 0, + X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS))) + { + if (rc < 0) + { + log_write(0, LOG_MAIN, "SSL verify error: internal error\n"); + name = NULL; + } + break; + } + if (!name) + { + log_write(0, LOG_MAIN, + "SSL verify error: certificate name mismatch: \"%s\"\n", txt); + return 0; /* reject */ + } + } +# else + if (!tls_is_name_for_cert(verify_cert_hostnames, cert)) + { + log_write(0, LOG_MAIN, + "SSL verify error: certificate name mismatch: \"%s\"\n", txt); + return 0; /* reject */ + } +# endif +#endif -To test: set up a chain anchored by a good root-CA but with a bad server cert. -Does certificate_verified get set? -*/ -if (!*calledp) tlsp->certificate_verified = TRUE; -*calledp = TRUE; + DEBUG(D_tls) debug_printf("SSL%s verify ok: depth=0 SN=%s\n", + *calledp ? "" : " authenticated", txt); + if (!*calledp) tlsp->certificate_verified = TRUE; + *calledp = TRUE; + } return 1; /* accept */ } @@ -382,14 +436,11 @@ if (!expand_check(dhparam, US"tls_dhparam", &dhexpanded)) return FALSE; -if (dhexpanded == NULL || *dhexpanded == '\0') - { +if (!dhexpanded || !*dhexpanded) bio = BIO_new_mem_buf(CS std_dh_prime_default(), -1); - } else if (dhexpanded[0] == '/') { - bio = BIO_new_file(CS dhexpanded, "r"); - if (bio == NULL) + if (!(bio = BIO_new_file(CS dhexpanded, "r"))) { tls_error(string_sprintf("could not read dhparams file %s", dhexpanded), host, US strerror(errno)); @@ -404,8 +455,7 @@ return TRUE; } - pem = std_dh_prime_named(dhexpanded); - if (!pem) + if (!(pem = std_dh_prime_named(dhexpanded))) { tls_error(string_sprintf("Unknown standard DH prime \"%s\"", dhexpanded), host, US strerror(errno)); @@ -414,8 +464,7 @@ bio = BIO_new_mem_buf(CS pem, -1); } -dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); -if (dh == NULL) +if (!(dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL))) { BIO_free(bio); tls_error(string_sprintf("Could not read tls_dhparams \"%s\"", dhexpanded), @@ -449,7 +498,7 @@ -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP /************************************************* * Load OCSP information into state * *************************************************/ @@ -566,24 +615,24 @@ } supply_response: -cbinfo->u_ocsp.server.response = resp; + cbinfo->u_ocsp.server.response = resp; return; bad: -if (running_in_test_harness) - { - extern char ** environ; - uschar ** p; - for (p = USS environ; *p != NULL; p++) - if (Ustrncmp(*p, "EXIM_TESTHARNESS_DISABLE_OCSPVALIDITYCHECK", 42) == 0) - { - DEBUG(D_tls) debug_printf("Supplying known bad OCSP response\n"); - goto supply_response; - } - } + if (running_in_test_harness) + { + extern char ** environ; + uschar ** p; + for (p = USS environ; *p != NULL; p++) + if (Ustrncmp(*p, "EXIM_TESTHARNESS_DISABLE_OCSPVALIDITYCHECK", 42) == 0) + { + DEBUG(D_tls) debug_printf("Supplying known bad OCSP response\n"); + goto supply_response; + } + } return; } -#endif /*EXPERIMENTAL_OCSP*/ +#endif /*!DISABLE_OCSP*/ @@ -645,7 +694,7 @@ "SSL_CTX_use_PrivateKey_file file=%s", expanded), cbinfo->host, NULL); } -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP if (cbinfo->is_server && cbinfo->u_ocsp.server.file != NULL) { if (!expand_check(cbinfo->u_ocsp.server.file, US"tls_ocsp_file", &expanded)) @@ -716,8 +765,7 @@ not confident that memcpy wouldn't break some internal reference counting. Especially since there's a references struct member, which would be off. */ -server_sni = SSL_CTX_new(SSLv23_server_method()); -if (!server_sni) +if (!(server_sni = SSL_CTX_new(SSLv23_server_method()))) { ERR_error_string(ERR_get_error(), ssl_errstring); DEBUG(D_tls) debug_printf("SSL_CTX_new() failed: %s\n", ssl_errstring); @@ -735,7 +783,7 @@ SSL_CTX_set_tlsext_servername_arg(server_sni, cbinfo); if (cbinfo->server_cipher_list) SSL_CTX_set_cipher_list(server_sni, CS cbinfo->server_cipher_list); -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP if (cbinfo->u_ocsp.server.file) { SSL_CTX_set_tlsext_status_cb(server_sni, tls_server_stapling_cb); @@ -751,8 +799,8 @@ rc = tls_expand_session_files(server_sni, cbinfo); if (rc != OK) return SSL_TLSEXT_ERR_NOACK; -rc = init_dh(server_sni, cbinfo->dhparam, NULL); -if (rc != OK) return SSL_TLSEXT_ERR_NOACK; +if (!init_dh(server_sni, cbinfo->dhparam, NULL)) + return SSL_TLSEXT_ERR_NOACK; DEBUG(D_tls) debug_printf("Switching SSL context.\n"); SSL_set_SSL_CTX(s, server_sni); @@ -764,7 +812,7 @@ -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP /************************************************* * Callback to handle OCSP Stapling * @@ -785,22 +833,22 @@ uschar *response_der; int response_der_len; -if (log_extra_selector & LX_tls_cipher) - log_write(0, LOG_MAIN, "[%s] Recieved OCSP stapling req;%s responding", - sender_host_address, cbinfo->u_ocsp.server.response ? "":" not"); -else - DEBUG(D_tls) debug_printf("Received TLS status request (OCSP stapling); %s response.", +DEBUG(D_tls) + debug_printf("Received TLS status request (OCSP stapling); %s response.", cbinfo->u_ocsp.server.response ? "have" : "lack"); +tls_in.ocsp = OCSP_NOT_RESP; if (!cbinfo->u_ocsp.server.response) return SSL_TLSEXT_ERR_NOACK; response_der = NULL; -response_der_len = i2d_OCSP_RESPONSE(cbinfo->u_ocsp.server.response, &response_der); +response_der_len = i2d_OCSP_RESPONSE(cbinfo->u_ocsp.server.response, + &response_der); if (response_der_len <= 0) return SSL_TLSEXT_ERR_NOACK; SSL_set_tlsext_status_ocsp_resp(server_ssl, response_der, response_der_len); +tls_in.ocsp = OCSP_VFIED; return SSL_TLSEXT_ERR_OK; } @@ -827,14 +875,18 @@ len = SSL_get_tlsext_status_ocsp_resp(s, &p); if(!p) { - if (log_extra_selector & LX_tls_cipher) - log_write(0, LOG_MAIN, "Received TLS status response, null content"); + /* Expect this when we requested ocsp but got none */ + if ( cbinfo->u_ocsp.client.verify_required + && log_extra_selector & LX_tls_cipher) + log_write(0, LOG_MAIN, "Received TLS status callback, null content"); else DEBUG(D_tls) debug_printf(" null\n"); - return 0; /* This is the fail case for require-ocsp; none from server */ + return cbinfo->u_ocsp.client.verify_required ? 0 : 1; } + if(!(rsp = d2i_OCSP_RESPONSE(NULL, &p, len))) { + tls_out.ocsp = OCSP_FAILED; if (log_extra_selector & LX_tls_cipher) log_write(0, LOG_MAIN, "Received TLS status response, parse error"); else @@ -844,6 +896,7 @@ if(!(bs = OCSP_response_get1_basic(rsp))) { + tls_out.ocsp = OCSP_FAILED; if (log_extra_selector & LX_tls_cipher) log_write(0, LOG_MAIN, "Received TLS status response, error parsing response"); else @@ -855,14 +908,12 @@ /* We'd check the nonce here if we'd put one in the request. */ /* However that would defeat cacheability on the server so we don't. */ - /* This section of code reworked from OpenSSL apps source; The OpenSSL Project retains copyright: Copyright (c) 1999 The OpenSSL Project. All rights reserved. */ { BIO * bp = NULL; - OCSP_CERTID *id; int status, reason; ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd; @@ -873,11 +924,13 @@ /* Use the chain that verified the server cert to verify the stapled info */ /* DEBUG(D_tls) x509_store_dump_cert_s_names(cbinfo->u_ocsp.client.verify_store); */ - if ((i = OCSP_basic_verify(bs, NULL, cbinfo->u_ocsp.client.verify_store, 0)) <= 0) + if ((i = OCSP_basic_verify(bs, NULL, + cbinfo->u_ocsp.client.verify_store, 0)) <= 0) { + tls_out.ocsp = OCSP_FAILED; BIO_printf(bp, "OCSP response verify failure\n"); ERR_print_errors(bp); - i = 0; + i = cbinfo->u_ocsp.client.verify_required ? 0 : 1; goto out; } @@ -889,39 +942,52 @@ if (sk_OCSP_SINGLERESP_num(sresp) != 1) { - log_write(0, LOG_MAIN, "OCSP stapling with multiple responses not handled"); + tls_out.ocsp = OCSP_FAILED; + log_write(0, LOG_MAIN, "OCSP stapling " + "with multiple responses not handled"); + i = cbinfo->u_ocsp.client.verify_required ? 0 : 1; goto out; } single = OCSP_resp_get0(bs, 0); - status = OCSP_single_get0_status(single, &reason, &rev, &thisupd, &nextupd); + status = OCSP_single_get0_status(single, &reason, &rev, + &thisupd, &nextupd); } - i = 0; DEBUG(D_tls) time_print(bp, "This OCSP Update", thisupd); DEBUG(D_tls) if(nextupd) time_print(bp, "Next OCSP Update", nextupd); - if (!OCSP_check_validity(thisupd, nextupd, EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE)) + if (!OCSP_check_validity(thisupd, nextupd, + EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE)) { + tls_out.ocsp = OCSP_FAILED; DEBUG(D_tls) ERR_print_errors(bp); log_write(0, LOG_MAIN, "Server OSCP dates invalid"); - goto out; + i = cbinfo->u_ocsp.client.verify_required ? 0 : 1; } - - DEBUG(D_tls) BIO_printf(bp, "Certificate status: %s\n", OCSP_cert_status_str(status)); - switch(status) + else { - case V_OCSP_CERTSTATUS_GOOD: - i = 1; - break; - case V_OCSP_CERTSTATUS_REVOKED: - log_write(0, LOG_MAIN, "Server certificate revoked%s%s", - reason != -1 ? "; reason: " : "", reason != -1 ? OCSP_crl_reason_str(reason) : ""); - DEBUG(D_tls) time_print(bp, "Revocation Time", rev); - i = 0; - break; - default: - log_write(0, LOG_MAIN, "Server certificate status unknown, in OCSP stapling"); - i = 0; - break; + DEBUG(D_tls) BIO_printf(bp, "Certificate status: %s\n", + OCSP_cert_status_str(status)); + switch(status) + { + case V_OCSP_CERTSTATUS_GOOD: + tls_out.ocsp = OCSP_VFIED; + i = 1; + break; + case V_OCSP_CERTSTATUS_REVOKED: + tls_out.ocsp = OCSP_FAILED; + log_write(0, LOG_MAIN, "Server certificate revoked%s%s", + reason != -1 ? "; reason: " : "", + reason != -1 ? OCSP_crl_reason_str(reason) : ""); + DEBUG(D_tls) time_print(bp, "Revocation Time", rev); + i = cbinfo->u_ocsp.client.verify_required ? 0 : 1; + break; + default: + tls_out.ocsp = OCSP_FAILED; + log_write(0, LOG_MAIN, + "Server certificate status unknown, in OCSP stapling"); + i = cbinfo->u_ocsp.client.verify_required ? 0 : 1; + break; + } } out: BIO_free(bp); @@ -930,7 +996,7 @@ OCSP_RESPONSE_free(rsp); return i; } -#endif /*EXPERIMENTAL_OCSP*/ +#endif /*!DISABLE_OCSP*/ @@ -938,8 +1004,8 @@ * Initialize for TLS * *************************************************/ -/* Called from both server and client code, to do preliminary initialization of -the library. +/* Called from both server and client code, to do preliminary initialization +of the library. We allocate and return a context structure. Arguments: host connected host, if client; NULL if server @@ -948,6 +1014,7 @@ privatekey private key ocsp_file file of stapling info (server); flag for require ocsp (client) addr address if client; NULL if server (for some randomness) + cbp place to put allocated context Returns: OK/DEFER/FAIL */ @@ -955,7 +1022,7 @@ static int tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate, uschar *privatekey, -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP uschar *ocsp_file, #endif address_item *addr, tls_ext_ctx_cb ** cbp) @@ -968,7 +1035,7 @@ cbinfo = store_malloc(sizeof(tls_ext_ctx_cb)); cbinfo->certificate = certificate; cbinfo->privatekey = privatekey; -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP if ((cbinfo->is_server = host==NULL)) { cbinfo->u_ocsp.server.file = ocsp_file; @@ -979,6 +1046,7 @@ cbinfo->u_ocsp.client.verify_store = NULL; #endif cbinfo->dhparam = dhparam; +cbinfo->server_cipher_list = NULL; cbinfo->host = host; SSL_load_error_strings(); /* basic set up */ @@ -1070,7 +1138,7 @@ #ifdef EXIM_HAVE_OPENSSL_TLSEXT if (host == NULL) /* server */ { -# ifdef EXPERIMENTAL_OCSP +# ifndef DISABLE_OCSP /* We check u_ocsp.server.file, not server.response, because we care about if the option exists, not what the current expansion might be, as SNI might change the certificate and OCSP file in use between now and the time the @@ -1086,7 +1154,7 @@ SSL_CTX_set_tlsext_servername_callback(*ctxp, tls_servername_cb); SSL_CTX_set_tlsext_servername_arg(*ctxp, cbinfo); } -# ifdef EXPERIMENTAL_OCSP +# ifndef DISABLE_OCSP else /* client */ if(ocsp_file) /* wanting stapling */ { @@ -1101,6 +1169,10 @@ # endif #endif +#ifdef EXPERIMENTAL_CERTNAMES +cbinfo->verify_cert_hostnames = NULL; +#endif + /* Set up the RSA callback */ SSL_CTX_set_tmp_rsa_callback(*ctxp, rsa_callback); @@ -1137,37 +1209,9 @@ yet reflect that. It should be a safe change anyway, even 0.9.8 versions have the accessor functions use const in the prototype. */ const SSL_CIPHER *c; -uschar *ver; - -switch (ssl->session->ssl_version) - { - case SSL2_VERSION: - ver = US"SSLv2"; - break; - - case SSL3_VERSION: - ver = US"SSLv3"; - break; +const uschar *ver; - case TLS1_VERSION: - ver = US"TLSv1"; - break; - -#ifdef TLS1_1_VERSION - case TLS1_1_VERSION: - ver = US"TLSv1.1"; - break; -#endif - -#ifdef TLS1_2_VERSION - case TLS1_2_VERSION: - ver = US"TLSv1.2"; - break; -#endif - - default: - ver = US"UNKNOWN"; - } +ver = (const uschar *)SSL_get_version(ssl); c = (const SSL_CIPHER *) SSL_get_current_cipher(ssl); SSL_CIPHER_get_bits(c, bits); @@ -1347,7 +1391,7 @@ the error. */ rc = tls_init(&server_ctx, NULL, tls_dhparam, tls_certificate, tls_privatekey, -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP tls_ocsp_file, #endif NULL, &server_static_cbinfo); @@ -1461,6 +1505,11 @@ debug_printf("Shared ciphers: %s\n", buf); } +/* Record the certificate we presented */ + { + X509 * crt = SSL_get_certificate(server_ssl); + tls_in.ourcert = crt ? X509_dup(crt) : NULL; + } /* Only used by the server-side tls (tls_in), including tls_getc. Client-side (tls_out) reads (seem to?) go via @@ -1495,15 +1544,7 @@ fd the fd of the connection host connected host (for messages) addr the first address - certificate certificate file - privatekey private key file - sni TLS SNI to send to remote host - verify_certs file for certificate verify - crl file containing CRL - require_ciphers list of allowed ciphers - dh_min_bits minimum number of bits acceptable in server's DH prime - (unused in OpenSSL) - timeout startup timeout + ob smtp transport options Returns: OK on success FAIL otherwise - note that tls_error() will not give DEFER @@ -1512,27 +1553,26 @@ int tls_client_start(int fd, host_item *host, address_item *addr, - uschar *certificate, uschar *privatekey, uschar *sni, - uschar *verify_certs, uschar *crl, - uschar *require_ciphers, -#ifdef EXPERIMENTAL_OCSP - uschar *hosts_require_ocsp, -#endif - int dh_min_bits ARG_UNUSED, int timeout) + void *v_ob) { +smtp_transport_options_block * ob = v_ob; static uschar txt[256]; uschar *expciphers; X509* server_cert; int rc; static uschar cipherbuf[256]; -#ifdef EXPERIMENTAL_OCSP -BOOL require_ocsp = verify_check_this_host(&hosts_require_ocsp, +#ifndef DISABLE_OCSP +BOOL require_ocsp = verify_check_this_host(&ob->hosts_require_ocsp, NULL, host->name, host->address, NULL) == OK; +BOOL request_ocsp = require_ocsp ? TRUE + : verify_check_this_host(&ob->hosts_request_ocsp, + NULL, host->name, host->address, NULL) == OK; #endif -rc = tls_init(&client_ctx, host, NULL, certificate, privatekey, -#ifdef EXPERIMENTAL_OCSP - require_ocsp ? US"" : NULL, +rc = tls_init(&client_ctx, host, NULL, + ob->tls_certificate, ob->tls_privatekey, +#ifndef DISABLE_OCSP + (void *)(long)request_ocsp, #endif addr, &client_static_cbinfo); if (rc != OK) return rc; @@ -1540,7 +1580,8 @@ tls_out.certificate_verified = FALSE; client_verify_callback_called = FALSE; -if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers)) +if (!expand_check(ob->tls_require_ciphers, US"tls_require_ciphers", + &expciphers)) return FAIL; /* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they @@ -1556,17 +1597,48 @@ return tls_error(US"SSL_CTX_set_cipher_list", host, NULL); } -rc = setup_certs(client_ctx, verify_certs, crl, host, FALSE, verify_callback_client); -if (rc != OK) return rc; +/* stick to the old behaviour for compatibility if tls_verify_certificates is + set but both tls_verify_hosts and tls_try_verify_hosts is not set. Check only + the specified host patterns if one of them is defined */ + +if ((!ob->tls_verify_hosts && !ob->tls_try_verify_hosts) || + (verify_check_host(&ob->tls_verify_hosts) == OK)) + { + if ((rc = setup_certs(client_ctx, ob->tls_verify_certificates, + ob->tls_crl, host, FALSE, verify_callback_client)) != OK) + return rc; + client_verify_optional = FALSE; -if ((client_ssl = SSL_new(client_ctx)) == NULL) return tls_error(US"SSL_new", host, NULL); +#ifdef EXPERIMENTAL_CERTNAMES + if (ob->tls_verify_cert_hostnames) + { + if (!expand_check(ob->tls_verify_cert_hostnames, + US"tls_verify_cert_hostnames", + &client_static_cbinfo->verify_cert_hostnames)) + return FAIL; + if (client_static_cbinfo->verify_cert_hostnames) + DEBUG(D_tls) debug_printf("Cert hostname to check: \"%s\"\n", + client_static_cbinfo->verify_cert_hostnames); + } +#endif + } +else if (verify_check_host(&ob->tls_try_verify_hosts) == OK) + { + if ((rc = setup_certs(client_ctx, ob->tls_verify_certificates, + ob->tls_crl, host, TRUE, verify_callback_client)) != OK) + return rc; + client_verify_optional = TRUE; + } + +if ((client_ssl = SSL_new(client_ctx)) == NULL) + return tls_error(US"SSL_new", host, NULL); SSL_set_session_id_context(client_ssl, sid_ctx, Ustrlen(sid_ctx)); SSL_set_fd(client_ssl, fd); SSL_set_connect_state(client_ssl); -if (sni) +if (ob->tls_sni) { - if (!expand_check(sni, US"tls_sni", &tls_out.sni)) + if (!expand_check(ob->tls_sni, US"tls_sni", &tls_out.sni)) return FAIL; if (tls_out.sni == NULL) { @@ -1587,18 +1659,22 @@ } } -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP /* Request certificate status at connection-time. If the server does OCSP stapling we will get the callback (set in tls_init()) */ -if (require_ocsp) +if (request_ocsp) + { SSL_set_tlsext_status_type(client_ssl, TLSEXT_STATUSTYPE_ocsp); + client_static_cbinfo->u_ocsp.client.verify_required = require_ocsp; + tls_out.ocsp = OCSP_NOT_RESP; + } #endif /* There doesn't seem to be a built-in timeout on connection. */ DEBUG(D_tls) debug_printf("Calling SSL_connect\n"); sigalrm_seen = FALSE; -alarm(timeout); +alarm(ob->command_timeout); rc = SSL_connect(client_ssl); alarm(0); @@ -1608,12 +1684,13 @@ DEBUG(D_tls) debug_printf("SSL_connect succeeded\n"); /* Beware anonymous ciphers which lead to server_cert being NULL */ +/*XXX server_cert is never freed... use X509_free() */ server_cert = SSL_get_peer_certificate (client_ssl); if (server_cert) { tls_out.peerdn = US X509_NAME_oneline(X509_get_subject_name(server_cert), CS txt, sizeof(txt)); - tls_out.peerdn = txt; + tls_out.peerdn = txt; /*XXX a static buffer... */ } else tls_out.peerdn = NULL; @@ -1621,6 +1698,12 @@ construct_cipher_name(client_ssl, cipherbuf, sizeof(cipherbuf), &tls_out.bits); tls_out.cipher = cipherbuf; +/* Record the certificate we presented */ + { + X509 * crt = SSL_get_certificate(client_ssl); + tls_out.ourcert = crt ? X509_dup(crt) : NULL; + } + tls_out.active = fd; return OK; } @@ -1934,6 +2017,11 @@ it can result in serious failures, including crashing with a SIGSEGV. So report the version found by the compiler and the run-time version. +Note: some OS vendors backport security fixes without changing the version +number/string, and the version date remains unchanged. The _build_ date +will change, so we can more usefully assist with version diagnosis by also +reporting the build date. + Arguments: a FILE* to print the results to Returns: nothing */ @@ -1942,9 +2030,13 @@ tls_version_report(FILE *f) { fprintf(f, "Library version: OpenSSL: Compile: %s\n" - " Runtime: %s\n", + " Runtime: %s\n" + " : %s\n", OPENSSL_VERSION_TEXT, - SSLeay_version(SSLEAY_VERSION)); + SSLeay_version(SSLEAY_VERSION), + SSLeay_version(SSLEAY_BUILT_ON)); +/* third line is 38 characters for the %s and the line is 73 chars long; +the OpenSSL output includes a "built on: " prefix already. */ } @@ -2252,4 +2344,6 @@ return TRUE; } +/* vi: aw ai sw=2 +*/ /* End of tls-openssl.c */ diff -Nru exim4-4.82/src/tod.c exim4-4.84/src/tod.c --- exim4-4.82/src/tod.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/tod.c 2014-08-09 12:44:29.000000000 +0000 @@ -59,7 +59,8 @@ { struct timeval tv; gettimeofday(&tv, NULL); - (void) sprintf(CS timebuf, "%ld%06ld", tv.tv_sec, tv.tv_usec ); /* Unix epoch/usec format */ + /* Unix epoch/usec format */ + (void) sprintf(CS timebuf, "%ld%06ld", tv.tv_sec, (long) tv.tv_usec ); return timebuf; } @@ -73,8 +74,8 @@ else if (type == tod_epoch) { - (void) sprintf(CS timebuf, "%d", (int)now); /* Unix epoch format */ - return timebuf; + (void) sprintf(CS timebuf, TIME_T_FMT, now); /* Unix epoch format */ + return timebuf; /* NB the above will be wrong if time_t is FP */ } else if (type == tod_zulu) diff -Nru exim4-4.82/src/transport.c exim4-4.84/src/transport.c --- exim4-4.82/src/transport.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/transport.c 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* General functions concerned with transportation, and generic options for all @@ -600,6 +600,177 @@ +/* Add/remove/rewwrite headers, and send them plus the empty-line sparator. + +Globals: + header_list + +Arguments: + addr (chain of) addresses (for extra headers), or NULL; + only the first address is used + fd file descriptor to write the message to + sendfn function for output + use_crlf turn NL into CR LF + rewrite_rules chain of header rewriting rules + rewrite_existflags flags for the rewriting rules + +Returns: TRUE on success; FALSE on failure. +*/ +BOOL +transport_headers_send(address_item *addr, int fd, uschar *add_headers, uschar *remove_headers, + BOOL (*sendfn)(int fd, uschar * s, int len, BOOL use_crlf), + BOOL use_crlf, rewrite_rule *rewrite_rules, int rewrite_existflags) +{ +header_line *h; + +/* Then the message's headers. Don't write any that are flagged as "old"; +that means they were rewritten, or are a record of envelope rewriting, or +were removed (e.g. Bcc). If remove_headers is not null, skip any headers that +match any entries therein. It is a colon-sep list; expand the items +separately and squash any empty ones. +Then check addr->p.remove_headers too, provided that addr is not NULL. */ + +for (h = header_list; h != NULL; h = h->next) if (h->type != htype_old) + { + int i; + uschar *list = remove_headers; + + BOOL include_header = TRUE; + + for (i = 0; i < 2; i++) /* For remove_headers && addr->p.remove_headers */ + { + if (list) + { + int sep = ':'; /* This is specified as a colon-separated list */ + uschar *s, *ss; + uschar buffer[128]; + while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))) + { + int len; + + if (i == 0) + if (!(s = expand_string(s)) && !expand_string_forcedfail) + { + errno = ERRNO_CHHEADER_FAIL; + return FALSE; + } + len = Ustrlen(s); + if (strncmpic(h->text, s, len) != 0) continue; + ss = h->text + len; + while (*ss == ' ' || *ss == '\t') ss++; + if (*ss == ':') break; + } + if (s != NULL) { include_header = FALSE; break; } + } + if (addr != NULL) list = addr->p.remove_headers; + } + + /* If this header is to be output, try to rewrite it if there are rewriting + rules. */ + + if (include_header) + { + if (rewrite_rules) + { + void *reset_point = store_get(0); + header_line *hh; + + if ((hh = rewrite_header(h, NULL, NULL, rewrite_rules, rewrite_existflags, FALSE))) + { + if (!sendfn(fd, hh->text, hh->slen, use_crlf)) return FALSE; + store_reset(reset_point); + continue; /* With the next header line */ + } + } + + /* Either no rewriting rules, or it didn't get rewritten */ + + if (!sendfn(fd, h->text, h->slen, use_crlf)) return FALSE; + } + + /* Header removed */ + + else + { + DEBUG(D_transport) debug_printf("removed header line:\n%s---\n", h->text); + } + } + +/* Add on any address-specific headers. If there are multiple addresses, +they will all have the same headers in order to be batched. The headers +are chained in reverse order of adding (so several addresses from the +same alias might share some of them) but we want to output them in the +opposite order. This is a bit tedious, but there shouldn't be very many +of them. We just walk the list twice, reversing the pointers each time, +but on the second time, write out the items. + +Headers added to an address by a router are guaranteed to end with a newline. +*/ + +if (addr) + { + int i; + header_line *hprev = addr->p.extra_headers; + header_line *hnext; + for (i = 0; i < 2; i++) + { + for (h = hprev, hprev = NULL; h != NULL; h = hnext) + { + hnext = h->next; + h->next = hprev; + hprev = h; + if (i == 1) + { + if (!sendfn(fd, h->text, h->slen, use_crlf)) return FALSE; + DEBUG(D_transport) + debug_printf("added header line(s):\n%s---\n", h->text); + } + } + } + } + +/* If a string containing additional headers exists it is a newline-sep +list. Expand each item and write out the result. This is done last so that +if it (deliberately or accidentally) isn't in header format, it won't mess +up any other headers. An empty string or a forced expansion failure are +noops. An added header string from a transport may not end with a newline; +add one if it does not. */ + +if (add_headers) + { + int sep = '\n'; + uschar * s; + + while ((s = string_nextinlist(&add_headers, &sep, NULL, 0))) + if (!(s = expand_string(s))) + { + if (!expand_string_forcedfail) + { errno = ERRNO_CHHEADER_FAIL; return FALSE; } + } + else + { + int len = Ustrlen(s); + if (len > 0) + { + if (!sendfn(fd, s, len, use_crlf)) return FALSE; + if (s[len-1] != '\n' && !sendfn(fd, US"\n", 1, use_crlf)) + return FALSE; + DEBUG(D_transport) + { + debug_printf("added header line:\n%s", s); + if (s[len-1] != '\n') debug_printf("\n"); + debug_printf("---\n"); + } + } + } + } + +/* Separate headers from body with a blank line */ + +return sendfn(fd, US"\n", 1, use_crlf); +} + + /************************************************* * Write the message * *************************************************/ @@ -666,7 +837,6 @@ { int written = 0; int len; -header_line *h; BOOL use_crlf = (options & topt_use_crlf) != 0; /* Initialize pointer in output buffer. */ @@ -747,154 +917,9 @@ were removed (e.g. Bcc). If remove_headers is not null, skip any headers that match any entries therein. Then check addr->p.remove_headers too, provided that addr is not NULL. */ - - if (remove_headers != NULL) - { - uschar *s = expand_string(remove_headers); - if (s == NULL && !expand_string_forcedfail) - { - errno = ERRNO_CHHEADER_FAIL; - return FALSE; - } - remove_headers = s; - } - - for (h = header_list; h != NULL; h = h->next) - { - int i; - uschar *list = NULL; - BOOL include_header; - - if (h->type == htype_old) continue; - - include_header = TRUE; - list = remove_headers; - - for (i = 0; i < 2; i++) /* For remove_headers && addr->p.remove_headers */ - { - if (list != NULL) - { - int sep = ':'; /* This is specified as a colon-separated list */ - uschar *s, *ss; - uschar buffer[128]; - while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) - != NULL) - { - int len = Ustrlen(s); - if (strncmpic(h->text, s, len) != 0) continue; - ss = h->text + len; - while (*ss == ' ' || *ss == '\t') ss++; - if (*ss == ':') break; - } - if (s != NULL) { include_header = FALSE; break; } - } - if (addr != NULL) list = addr->p.remove_headers; - } - - /* If this header is to be output, try to rewrite it if there are rewriting - rules. */ - - if (include_header) - { - if (rewrite_rules != NULL) - { - void *reset_point = store_get(0); - header_line *hh = - rewrite_header(h, NULL, NULL, rewrite_rules, rewrite_existflags, - FALSE); - if (hh != NULL) - { - if (!write_chunk(fd, hh->text, hh->slen, use_crlf)) return FALSE; - store_reset(reset_point); - continue; /* With the next header line */ - } - } - - /* Either no rewriting rules, or it didn't get rewritten */ - - if (!write_chunk(fd, h->text, h->slen, use_crlf)) return FALSE; - } - - /* Header removed */ - - else - { - DEBUG(D_transport) debug_printf("removed header line:\n%s---\n", - h->text); - } - } - - /* Add on any address-specific headers. If there are multiple addresses, - they will all have the same headers in order to be batched. The headers - are chained in reverse order of adding (so several addresses from the - same alias might share some of them) but we want to output them in the - opposite order. This is a bit tedious, but there shouldn't be very many - of them. We just walk the list twice, reversing the pointers each time, - but on the second time, write out the items. - - Headers added to an address by a router are guaranteed to end with a newline. - */ - - if (addr != NULL) - { - int i; - header_line *hprev = addr->p.extra_headers; - header_line *hnext; - for (i = 0; i < 2; i++) - { - for (h = hprev, hprev = NULL; h != NULL; h = hnext) - { - hnext = h->next; - h->next = hprev; - hprev = h; - if (i == 1) - { - if (!write_chunk(fd, h->text, h->slen, use_crlf)) return FALSE; - DEBUG(D_transport) - debug_printf("added header line(s):\n%s---\n", h->text); - } - } - } - } - - /* If a string containing additional headers exists, expand it and write - out the result. This is done last so that if it (deliberately or accidentally) - isn't in header format, it won't mess up any other headers. An empty string - or a forced expansion failure are noops. An added header string from a - transport may not end with a newline; add one if it does not. */ - - if (add_headers != NULL) - { - uschar *s = expand_string(add_headers); - if (s == NULL) - { - if (!expand_string_forcedfail) - { - errno = ERRNO_CHHEADER_FAIL; - return FALSE; - } - } - else - { - int len = Ustrlen(s); - if (len > 0) - { - if (!write_chunk(fd, s, len, use_crlf)) return FALSE; - if (s[len-1] != '\n' && !write_chunk(fd, US"\n", 1, use_crlf)) - return FALSE; - DEBUG(D_transport) - { - debug_printf("added header line(s):\n%s", s); - if (s[len-1] != '\n') debug_printf("\n"); - debug_printf("---\n"); - } - } - } - } - - /* Separate headers from body with a blank line */ - - if (!write_chunk(fd, US"\n", 1, use_crlf)) return FALSE; + if (!transport_headers_send(addr, fd, add_headers, remove_headers, &write_chunk, + use_crlf, rewrite_rules, rewrite_existflags)) + return FALSE; } /* If the body is required, ensure that the data for check strings (formerly @@ -950,24 +975,27 @@ * External interface to write the message, while signing it with DKIM and/or Domainkeys * ***************************************************************************************************/ -/* This function is a wrapper around transport_write_message(). It is only called - from the smtp transport if DKIM or Domainkeys support is compiled in. - The function sets up a replacement fd into a -K file, then calls the normal - function. This way, the exact bits that exim would have put "on the wire" will - end up in the file (except for TLS encapsulation, which is the very - very last thing). When we are done signing the file, send the - signed message down the original fd (or TLS fd). - -Arguments: as for internal_transport_write_message() above, with additional - arguments: - uschar *dkim_private_key DKIM: The private key to use (filename or plain data) - uschar *dkim_domain DKIM: The domain to use - uschar *dkim_selector DKIM: The selector to use. - uschar *dkim_canon DKIM: The canonalization scheme to use, "simple" or "relaxed" - uschar *dkim_strict DKIM: What to do if signing fails: 1/true => throw error - 0/false => send anyway - uschar *dkim_sign_headers DKIM: List of headers that should be included in signature - generation +/* This function is a wrapper around transport_write_message(). + It is only called from the smtp transport if DKIM or Domainkeys support + is compiled in. The function sets up a replacement fd into a -K file, + then calls the normal function. This way, the exact bits that exim would + have put "on the wire" will end up in the file (except for TLS + encapsulation, which is the very very last thing). When we are done + signing the file, send the signed message down the original fd (or TLS fd). + +Arguments: + as for internal_transport_write_message() above, with additional arguments: + uschar *dkim_private_key DKIM: The private key to use (filename or + plain data) + uschar *dkim_domain DKIM: The domain to use + uschar *dkim_selector DKIM: The selector to use. + uschar *dkim_canon DKIM: The canonalization scheme to use, + "simple" or "relaxed" + uschar *dkim_strict DKIM: What to do if signing fails: + 1/true => throw error + 0/false => send anyway + uschar *dkim_sign_headers DKIM: List of headers that should be included + in signature generation Returns: TRUE on success; FALSE (with errno) for any failure */ @@ -980,129 +1008,143 @@ uschar *dkim_selector, uschar *dkim_canon, uschar *dkim_strict, uschar *dkim_sign_headers ) { - int dkim_fd; - int save_errno = 0; - BOOL rc; - uschar dkim_spool_name[256]; - char sbuf[2048]; - int sread = 0; - int wwritten = 0; - uschar *dkim_signature = NULL; - off_t size = 0; - - if (!( ((dkim_private_key != NULL) && (dkim_domain != NULL) && (dkim_selector != NULL)) )) { - /* If we can't sign, just call the original function. */ - return transport_write_message(addr, fd, options, - size_limit, add_headers, remove_headers, - check_string, escape_string, rewrite_rules, - rewrite_existflags); - } - - (void)string_format(dkim_spool_name, 256, "%s/input/%s/%s-%d-K", - spool_directory, message_subdir, message_id, (int)getpid()); - dkim_fd = Uopen(dkim_spool_name, O_RDWR|O_CREAT|O_TRUNC, SPOOL_MODE); - if (dkim_fd < 0) - { - /* Can't create spool file. Ugh. */ - rc = FALSE; - save_errno = errno; - goto CLEANUP; - } +int dkim_fd; +int save_errno = 0; +BOOL rc; +uschar dkim_spool_name[256]; +char sbuf[2048]; +int sread = 0; +int wwritten = 0; +uschar *dkim_signature = NULL; +off_t size = 0; + +/* If we can't sign, just call the original function. */ + +if (!(dkim_private_key && dkim_domain && dkim_selector)) + return transport_write_message(addr, fd, options, + size_limit, add_headers, remove_headers, + check_string, escape_string, rewrite_rules, + rewrite_existflags); - /* Call original function */ - rc = transport_write_message(addr, dkim_fd, options, - size_limit, add_headers, remove_headers, - check_string, escape_string, rewrite_rules, - rewrite_existflags); +(void)string_format(dkim_spool_name, 256, "%s/input/%s/%s-%d-K", + spool_directory, message_subdir, message_id, (int)getpid()); - /* Save error state. We must clean up before returning. */ - if (!rc) - { - save_errno = errno; - goto CLEANUP; - } +if ((dkim_fd = Uopen(dkim_spool_name, O_RDWR|O_CREAT|O_TRUNC, SPOOL_MODE)) < 0) + { + /* Can't create spool file. Ugh. */ + rc = FALSE; + save_errno = errno; + goto CLEANUP; + } - if ( (dkim_private_key != NULL) && (dkim_domain != NULL) && (dkim_selector != NULL) ) { - /* Rewind file and feed it to the goats^W DKIM lib */ - lseek(dkim_fd, 0, SEEK_SET); - dkim_signature = dkim_exim_sign(dkim_fd, - dkim_private_key, - dkim_domain, - dkim_selector, - dkim_canon, - dkim_sign_headers); - if (dkim_signature == NULL) { - if (dkim_strict != NULL) { - uschar *dkim_strict_result = expand_string(dkim_strict); - if (dkim_strict_result != NULL) { - if ( (strcmpic(dkim_strict,US"1") == 0) || - (strcmpic(dkim_strict,US"true") == 0) ) { - /* Set errno to something halfway meaningful */ - save_errno = EACCES; - log_write(0, LOG_MAIN, "DKIM: message could not be signed, and dkim_strict is set. Deferring message delivery."); - rc = FALSE; - goto CLEANUP; - } - } +/* Call original function to write the -K file */ + +rc = transport_write_message(addr, dkim_fd, options, + size_limit, add_headers, remove_headers, + check_string, escape_string, rewrite_rules, + rewrite_existflags); + +/* Save error state. We must clean up before returning. */ +if (!rc) + { + save_errno = errno; + goto CLEANUP; + } + +if (dkim_private_key && dkim_domain && dkim_selector) + { + /* Rewind file and feed it to the goats^W DKIM lib */ + lseek(dkim_fd, 0, SEEK_SET); + dkim_signature = dkim_exim_sign(dkim_fd, + dkim_private_key, + dkim_domain, + dkim_selector, + dkim_canon, + dkim_sign_headers); + if (!dkim_signature) + { + if (dkim_strict) + { + uschar *dkim_strict_result = expand_string(dkim_strict); + if (dkim_strict_result) + if ( (strcmpic(dkim_strict,US"1") == 0) || + (strcmpic(dkim_strict,US"true") == 0) ) + { + /* Set errno to something halfway meaningful */ + save_errno = EACCES; + log_write(0, LOG_MAIN, "DKIM: message could not be signed," + " and dkim_strict is set. Deferring message delivery."); + rc = FALSE; + goto CLEANUP; + } } } - else { - int siglen = Ustrlen(dkim_signature); - while(siglen > 0) { - #ifdef SUPPORT_TLS - if (tls_out.active == fd) wwritten = tls_write(FALSE, dkim_signature, siglen); else - #endif - wwritten = write(fd,dkim_signature,siglen); - if (wwritten == -1) { - /* error, bail out */ - save_errno = errno; - rc = FALSE; - goto CLEANUP; - } - siglen -= wwritten; - dkim_signature += wwritten; + else + { + int siglen = Ustrlen(dkim_signature); + while(siglen > 0) + { +#ifdef SUPPORT_TLS + wwritten = tls_out.active == fd + ? tls_write(FALSE, dkim_signature, siglen) + : write(fd, dkim_signature, siglen); +#else + wwritten = write(fd, dkim_signature, siglen); +#endif + if (wwritten == -1) + { + /* error, bail out */ + save_errno = errno; + rc = FALSE; + goto CLEANUP; + } + siglen -= wwritten; + dkim_signature += wwritten; } } } - /* Fetch file positition (the size) */ - size = lseek(dkim_fd,0,SEEK_CUR); +/* Fetch file size */ +size = lseek(dkim_fd, 0, SEEK_END); - /* Rewind file */ - lseek(dkim_fd, 0, SEEK_SET); +/* Rewind file */ +lseek(dkim_fd, 0, SEEK_SET); #ifdef HAVE_LINUX_SENDFILE - /* We can use sendfile() to shove the file contents - to the socket. However only if we don't use TLS, - in which case theres another layer of indirection - before the data finally hits the socket. */ - if (tls_out.active != fd) - { - ssize_t copied = 0; - off_t offset = 0; - while((copied >= 0) && (offset= 0 && offset < size) + copied = sendfile(fd, dkim_fd, &offset, size - offset); + if (copied < 0) + { + save_errno = errno; + rc = FALSE; } + goto CLEANUP; + } #endif - /* Send file down the original fd */ - while((sread = read(dkim_fd,sbuf,2048)) > 0) +/* Send file down the original fd */ +while((sread = read(dkim_fd, sbuf, 2048)) > 0) + { + char *p = sbuf; + /* write the chunk */ + + while (sread) { - char *p = sbuf; - /* write the chunk */ - DKIM_WRITE: - #ifdef SUPPORT_TLS - if (tls_out.active == fd) wwritten = tls_write(FALSE, US p, sread); else - #endif - wwritten = write(fd,p,sread); +#ifdef SUPPORT_TLS + wwritten = tls_out.active == fd + ? tls_write(FALSE, US p, sread) + : write(fd, p, sread); +#else + wwritten = write(fd, p, sread); +#endif if (wwritten == -1) { /* error, bail out */ @@ -1110,28 +1152,24 @@ rc = FALSE; goto CLEANUP; } - if (wwritten < sread) - { - /* short write, try again */ - p += wwritten; - sread -= wwritten; - goto DKIM_WRITE; - } + p += wwritten; + sread -= wwritten; } + } - if (sread == -1) - { - save_errno = errno; - rc = FALSE; - goto CLEANUP; - } +if (sread == -1) + { + save_errno = errno; + rc = FALSE; + goto CLEANUP; + } - CLEANUP: - /* unlink -K file */ - (void)close(dkim_fd); - Uunlink(dkim_spool_name); - errno = save_errno; - return rc; +CLEANUP: +/* unlink -K file */ +(void)close(dkim_fd); +Uunlink(dkim_spool_name); +errno = save_errno; +return rc; } #endif @@ -1802,6 +1840,11 @@ argv = child_exec_exim(CEE_RETURN_ARGV, TRUE, &i, FALSE, 0); + #ifdef EXPERIMENTAL_DSN + /* Call with the dsn flag */ + if (smtp_use_dsn) argv[i++] = US"-MCD"; + #endif + if (smtp_authenticated) argv[i++] = US"-MCA"; #ifdef SUPPORT_TLS @@ -2157,4 +2200,6 @@ return TRUE; } +/* vi: aw ai sw=2 +*/ /* End of transport.c */ diff -Nru exim4-4.82/src/transports/appendfile.c exim4-4.84/src/transports/appendfile.c --- exim4-4.82/src/transports/appendfile.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/transports/appendfile.c 2014-08-09 12:44:29.000000000 +0000 @@ -1620,6 +1620,7 @@ if (ob->use_lockfile) { + /* cf. exim_lock.c */ lockname = string_sprintf("%s.lock", filename); hitchname = string_sprintf( "%s.%s.%08x.%08x", lockname, primary_hostname, (unsigned int)(time(NULL)), (unsigned int)getpid()); diff -Nru exim4-4.82/src/transports/lmtp.c exim4-4.84/src/transports/lmtp.c --- exim4-4.82/src/transports/lmtp.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/transports/lmtp.c 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2009 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ @@ -662,8 +662,14 @@ if (addr->transport_return != PENDING_OK) continue; if (lmtp_read_response(out, buffer, sizeof(buffer), '2', timeout)) + { addr->transport_return = OK; - + if ((log_extra_selector & LX_smtp_confirmation) != 0) + { + uschar *s = string_printing(buffer); + addr->message = (s == buffer)? (uschar *)string_copy(s) : s; + } + } /* If the response has failed badly, use it for all the remaining pending addresses and give up. */ diff -Nru exim4-4.82/src/transports/pipe.c exim4-4.84/src/transports/pipe.c --- exim4-4.82/src/transports/pipe.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/transports/pipe.c 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2009 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ diff -Nru exim4-4.82/src/transports/pipe.h exim4-4.84/src/transports/pipe.h --- exim4-4.82/src/transports/pipe.h 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/transports/pipe.h 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2009 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* Private structure for the private options. */ diff -Nru exim4-4.82/src/transports/smtp.c exim4-4.84/src/transports/smtp.c --- exim4-4.82/src/transports/smtp.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/transports/smtp.c 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ #include "../exim.h" @@ -55,6 +55,10 @@ (void *)offsetof(smtp_transport_options_block, dns_qualify_single) }, { "dns_search_parents", opt_bool, (void *)offsetof(smtp_transport_options_block, dns_search_parents) }, + { "dnssec_request_domains", opt_stringptr, + (void *)offsetof(smtp_transport_options_block, dnssec_request_domains) }, + { "dnssec_require_domains", opt_stringptr, + (void *)offsetof(smtp_transport_options_block, dnssec_require_domains) }, { "dscp", opt_stringptr, (void *)offsetof(smtp_transport_options_block, dscp) }, { "fallback_hosts", opt_stringptr, @@ -65,7 +69,7 @@ (void *)offsetof(smtp_transport_options_block, gethostbyname) }, #ifdef SUPPORT_TLS /* These are no longer honoured, as of Exim 4.80; for now, we silently - ignore; a later release will warn, and a later-still release will remove + ignore; 4.83 will warn, and a later-still release will remove these options, so that using them becomes an error. */ { "gnutls_require_kx", opt_stringptr, (void *)offsetof(smtp_transport_options_block, gnutls_require_kx) }, @@ -98,10 +102,14 @@ (void *)offsetof(smtp_transport_options_block, hosts_override) }, { "hosts_randomize", opt_bool, (void *)offsetof(smtp_transport_options_block, hosts_randomize) }, +#if defined(SUPPORT_TLS) && !defined(DISABLE_OCSP) + { "hosts_request_ocsp", opt_stringptr, + (void *)offsetof(smtp_transport_options_block, hosts_request_ocsp) }, +#endif { "hosts_require_auth", opt_stringptr, (void *)offsetof(smtp_transport_options_block, hosts_require_auth) }, #ifdef SUPPORT_TLS -# if defined EXPERIMENTAL_OCSP +# ifndef DISABLE_OCSP { "hosts_require_ocsp", opt_stringptr, (void *)offsetof(smtp_transport_options_block, hosts_require_ocsp) }, # endif @@ -110,7 +118,7 @@ #endif { "hosts_try_auth", opt_stringptr, (void *)offsetof(smtp_transport_options_block, hosts_try_auth) }, -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR { "hosts_try_prdr", opt_stringptr, (void *)offsetof(smtp_transport_options_block, hosts_try_prdr) }, #endif @@ -153,8 +161,16 @@ (void *)offsetof(smtp_transport_options_block, tls_sni) }, { "tls_tempfail_tryclear", opt_bool, (void *)offsetof(smtp_transport_options_block, tls_tempfail_tryclear) }, + { "tls_try_verify_hosts", opt_stringptr, + (void *)offsetof(smtp_transport_options_block, tls_try_verify_hosts) }, +#ifdef EXPERIMENTAL_CERTNAMES + { "tls_verify_cert_hostnames", opt_stringptr, + (void *)offsetof(smtp_transport_options_block,tls_verify_cert_hostnames)}, +#endif { "tls_verify_certificates", opt_stringptr, - (void *)offsetof(smtp_transport_options_block, tls_verify_certificates) } + (void *)offsetof(smtp_transport_options_block, tls_verify_certificates) }, + { "tls_verify_hosts", opt_stringptr, + (void *)offsetof(smtp_transport_options_block, tls_verify_hosts) } #endif #ifdef EXPERIMENTAL_TPDA ,{ "tpda_host_defer_action", opt_stringptr, @@ -184,10 +200,11 @@ NULL, /* serialize_hosts */ NULL, /* hosts_try_auth */ NULL, /* hosts_require_auth */ -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR NULL, /* hosts_try_prdr */ #endif -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP + US"*", /* hosts_request_ocsp */ NULL, /* hosts_require_ocsp */ #endif NULL, /* hosts_require_tls */ @@ -209,6 +226,8 @@ FALSE, /* gethostbyname */ TRUE, /* dns_qualify_single */ FALSE, /* dns_search_parents */ + NULL, /* dnssec_request_domains */ + NULL, /* dnssec_require_domains */ TRUE, /* delay_after_cutoff */ FALSE, /* hosts_override */ FALSE, /* hosts_randomize */ @@ -227,7 +246,12 @@ NULL, /* tls_verify_certificates */ EXIM_CLIENT_DH_DEFAULT_MIN_BITS, /* tls_dh_min_bits */ - TRUE /* tls_tempfail_tryclear */ + TRUE, /* tls_tempfail_tryclear */ + NULL, /* tls_verify_hosts */ + NULL /* tls_try_verify_hosts */ +# ifdef EXPERIMENTAL_CERTNAMES + ,NULL /* tls_verify_cert_hostnames */ +# endif #endif #ifndef DISABLE_DKIM ,NULL, /* dkim_canon */ @@ -242,6 +266,16 @@ #endif }; +#ifdef EXPERIMENTAL_DSN +/* some DSN flags for use later */ + +static int rf_list[] = {rf_notify_never, rf_notify_success, + rf_notify_failure, rf_notify_delay }; + +static uschar *rf_names[] = { "NEVER", "SUCCESS", "FAILURE", "DELAY" }; +#endif + + /* Local statics */ @@ -366,6 +400,15 @@ for them, but do not do any lookups at this time. */ host_build_hostlist(&(ob->fallback_hostlist), ob->fallback_hosts, FALSE); + +#ifdef SUPPORT_TLS +if ( ob->gnutls_require_kx + || ob->gnutls_require_mac + || ob->gnutls_require_proto) + log_write(0, LOG_MAIN, "WARNING: smtp transport options" + " gnutls_require_kx, gnutls_require_mac and gnutls_require_protocols" + " are obsolete\n"); +#endif } @@ -619,7 +662,7 @@ ? string_sprintf("%s: %s", addr->message, strerror(addr->basic_errno)) : string_copy(addr->message) : addr->basic_errno > 0 - ? string_copy(strerror(addr->basic_errno)) + ? string_copy(US strerror(addr->basic_errno)) : NULL; DEBUG(D_transport) @@ -1168,10 +1211,13 @@ BOOL esmtp = TRUE; BOOL pending_MAIL; BOOL pass_message = FALSE; -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR BOOL prdr_offered = FALSE; BOOL prdr_active; #endif +#ifdef EXPERIMENTAL_DSN +BOOL dsn_all_lasthop = TRUE; +#endif smtp_inblock inblock; smtp_outblock outblock; int max_rcpt = tblock->max_addresses; @@ -1182,7 +1228,7 @@ uschar *p; uschar buffer[4096]; uschar inbuffer[4096]; -uschar outbuffer[1024]; +uschar outbuffer[4096]; suppress_tls = suppress_tls; /* stop compiler warning when no TLS support */ @@ -1207,25 +1253,27 @@ /* Reset the parameters of a TLS session. */ -tls_in.bits = 0; -tls_in.cipher = NULL; /* for back-compatible behaviour */ -tls_in.peerdn = NULL; -#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS) -tls_in.sni = NULL; -#endif - tls_out.bits = 0; tls_out.cipher = NULL; /* the one we may use for this transport */ +tls_out.ourcert = NULL; +tls_out.peercert = NULL; tls_out.peerdn = NULL; #if defined(SUPPORT_TLS) && !defined(USE_GNUTLS) tls_out.sni = NULL; #endif +tls_out.ocsp = OCSP_NOT_REQ; + +/* Flip the legacy TLS-related variables over to the outbound set in case +they're used in the context of the transport. Don't bother resetting +afterward as we're in a subprocess. */ + +tls_modify_variables(&tls_out); #ifndef SUPPORT_TLS if (smtps) { - set_errno(addrlist, 0, US"TLS support not available", DEFER, FALSE); - return ERROR; + set_errno(addrlist, 0, US"TLS support not available", DEFER, FALSE); + return ERROR; } #endif @@ -1367,7 +1415,7 @@ PCRE_EOPT, NULL, 0) >= 0; #endif - #ifdef EXPERIMENTAL_PRDR + #ifndef DISABLE_PRDR prdr_offered = esmtp && (pcre_exec(regex_PRDR, NULL, CS buffer, Ustrlen(buffer), 0, PCRE_EOPT, NULL, 0) >= 0) && @@ -1433,20 +1481,7 @@ else TLS_NEGOTIATE: { - int rc = tls_client_start(inblock.sock, - host, - addrlist, - ob->tls_certificate, - ob->tls_privatekey, - ob->tls_sni, - ob->tls_verify_certificates, - ob->tls_crl, - ob->tls_require_ciphers, -#ifdef EXPERIMENTAL_OCSP - ob->hosts_require_ocsp, -#endif - ob->tls_dh_min_bits, - ob->command_timeout); + int rc = tls_client_start(inblock.sock, host, addrlist, ob); /* 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 @@ -1467,7 +1502,10 @@ if (addr->transport_return == PENDING_DEFER) { addr->cipher = tls_out.cipher; + addr->ourcert = tls_out.ourcert; + addr->peercert = tls_out.peercert; addr->peerdn = tls_out.peerdn; + addr->ocsp = tls_out.ocsp; } } } @@ -1576,7 +1614,7 @@ DEBUG(D_transport) debug_printf("%susing PIPELINING\n", smtp_use_pipelining? "" : "not "); -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR prdr_offered = esmtp && pcre_exec(regex_PRDR, NULL, CS buffer, Ustrlen(CS buffer), 0, PCRE_EOPT, NULL, 0) >= 0 && @@ -1587,6 +1625,13 @@ {DEBUG(D_transport) debug_printf("PRDR usable\n");} #endif +#ifdef EXPERIMENTAL_DSN + /* Note if the server supports DSN */ + smtp_use_dsn = esmtp && pcre_exec(regex_DSN, NULL, CS buffer, (int)Ustrlen(CS buffer), 0, + PCRE_EOPT, NULL, 0) >= 0; + DEBUG(D_transport) debug_printf("use_dsn=%d\n", smtp_use_dsn); +#endif + /* Note if the response to EHLO specifies support for the AUTH extension. If it has, check that this host is one we want to authenticate to, and do the business. The host name and address must be available when the @@ -1664,7 +1709,7 @@ while (*p) p++; } -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR prdr_active = FALSE; if (prdr_offered) { @@ -1684,6 +1729,38 @@ prdr_is_active: #endif +#ifdef EXPERIMENTAL_DSN +/* check if all addresses have lasthop flag */ +/* do not send RET and ENVID if true */ +dsn_all_lasthop = TRUE; +for (addr = first_addr; + address_count < max_rcpt && addr != NULL; + addr = addr->next) + if ((addr->dsn_flags & rf_dsnlasthop) != 1) + dsn_all_lasthop = FALSE; + +/* Add any DSN flags to the mail command */ + +if ((smtp_use_dsn) && (dsn_all_lasthop == FALSE)) + { + if (dsn_ret == dsn_ret_hdrs) + { + strcpy(p, " RET=HDRS"); + while (*p) p++; + } + else if (dsn_ret == dsn_ret_full) + { + strcpy(p, " RET=FULL"); + while (*p) p++; + } + if (dsn_envid != NULL) + { + string_format(p, sizeof(buffer) - (p-buffer), " ENVID=%s", dsn_envid); + while (*p) p++; + } + } +#endif + /* If an authenticated_sender override has been specified for this transport instance, expand it. If the expansion is forced to fail, and there was already an authenticated_sender for this message, the original value will be used. @@ -1746,18 +1823,66 @@ int count; BOOL no_flush; + #ifdef EXPERIMENTAL_DSN + if(smtp_use_dsn) + addr->dsn_aware = dsn_support_yes; + else + addr->dsn_aware = dsn_support_no; + #endif + if (addr->transport_return != PENDING_DEFER) continue; address_count++; no_flush = smtp_use_pipelining && (!mua_wrapper || addr->next != NULL); + #ifdef EXPERIMENTAL_DSN + /* Add any DSN flags to the rcpt command and add to the sent string */ + + p = buffer; + *p = 0; + + if ((smtp_use_dsn) && ((addr->dsn_flags & rf_dsnlasthop) != 1)) + { + if ((addr->dsn_flags & rf_dsnflags) != 0) + { + int i; + BOOL first = TRUE; + strcpy(p, " NOTIFY="); + while (*p) p++; + for (i = 0; i < 4; i++) + { + if ((addr->dsn_flags & rf_list[i]) != 0) + { + if (!first) *p++ = ','; + first = FALSE; + strcpy(p, rf_names[i]); + while (*p) p++; + } + } + } + + if (addr->dsn_orcpt != NULL) { + string_format(p, sizeof(buffer) - (p-buffer), " ORCPT=%s", + addr->dsn_orcpt); + while (*p) p++; + } + } + #endif + + /* Now send the RCPT command, and process outstanding responses when necessary. After a timeout on RCPT, we just end the function, leaving the yield as OK, because this error can often mean that there is a problem with just one address, so we don't want to delay the host. */ + #ifdef EXPERIMENTAL_DSN + count = smtp_write_command(&outblock, no_flush, "RCPT TO:<%s>%s%s\r\n", + transport_rcpt_address(addr, tblock->rcpt_include_affixes), igquotstr, buffer); + #else count = smtp_write_command(&outblock, no_flush, "RCPT TO:<%s>%s\r\n", transport_rcpt_address(addr, tblock->rcpt_include_affixes), igquotstr); + #endif + if (count < 0) goto SEND_FAILED; if (count > 0) { @@ -1900,7 +2025,7 @@ smtp_command = US"end of data"; -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR /* For PRDR we optionally get a partial-responses warning * followed by the individual responses, before going on with * the overall response. If we don't get the warning then deal @@ -1995,7 +2120,7 @@ address. For temporary errors, add a retry item for the address so that it doesn't get tried again too soon. */ -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR if (lmtp || prdr_active) #else if (lmtp) @@ -2006,7 +2131,7 @@ { if (errno != 0 || buffer[0] == 0) goto RESPONSE_FAILED; addr->message = string_sprintf( -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR "%s error after %s: %s", prdr_active ? "PRDR":"LMTP", #else "LMTP error after %s: %s", @@ -2020,7 +2145,7 @@ errno = ERRNO_DATA4XX; addr->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8; addr->transport_return = DEFER; -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR if (!prdr_active) #endif retry_add_item(addr, addr->address_retry_key, 0); @@ -2043,12 +2168,12 @@ addr->host_used = thost; addr->special_action = flag; addr->message = conf; -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR if (prdr_active) addr->flags |= af_prdr_used; #endif flag = '-'; -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR if (!prdr_active) #endif { @@ -2070,7 +2195,7 @@ } } -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR if (prdr_active) { /* PRDR - get the final, overall response. For any non-success @@ -2404,8 +2529,6 @@ #endif /* Close the socket, and return the appropriate value, first setting -continue_transport and continue_hostname NULL to prevent any other addresses -that may include the host from trying to re-use a continuation socket. This works because the NULL setting is passed back to the calling process, and remote_max_parallel is forced to 1 when delivering over an existing connection, @@ -2506,7 +2629,10 @@ addr->message = NULL; #ifdef SUPPORT_TLS addr->cipher = NULL; + addr->ourcert = NULL; + addr->peercert = NULL; addr->peerdn = NULL; + addr->ocsp = OCSP_NOT_REQ; #endif } return first_addr; @@ -2809,6 +2935,7 @@ rc = host_find_byname(host, NULL, flags, &canonical_name, TRUE); else rc = host_find_bydns(host, NULL, flags, NULL, NULL, NULL, + ob->dnssec_request_domains, ob->dnssec_require_domains, &canonical_name, NULL); /* Update the host (and any additional blocks, resulting from @@ -2908,6 +3035,9 @@ deliver_host = host->name; deliver_host_address = host->address; + lookup_dnssec_authenticated = host->dnssec == DS_YES ? US"yes" + : host->dnssec == DS_NO ? US"no" + : US""; /* Set up a string for adding to the retry key if the port number is not the standard SMTP port. A host may have its own port setting that overrides @@ -3421,4 +3551,6 @@ return TRUE; /* Each address has its status */ } +/* vi: aw ai sw=2 +*/ /* End of transport/smtp.c */ diff -Nru exim4-4.82/src/transports/smtp.h exim4-4.84/src/transports/smtp.h --- exim4-4.82/src/transports/smtp.h 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/transports/smtp.h 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* Private structure for the private options and other private data. */ @@ -21,10 +21,11 @@ uschar *serialize_hosts; uschar *hosts_try_auth; uschar *hosts_require_auth; -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR uschar *hosts_try_prdr; #endif -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP + uschar *hosts_request_ocsp; uschar *hosts_require_ocsp; #endif uschar *hosts_require_tls; @@ -46,13 +47,15 @@ BOOL gethostbyname; BOOL dns_qualify_single; BOOL dns_search_parents; + uschar *dnssec_request_domains; + uschar *dnssec_require_domains; BOOL delay_after_cutoff; BOOL hosts_override; BOOL hosts_randomize; BOOL keepalive; BOOL lmtp_ignore_quota; BOOL retry_include_ip_address; - #ifdef SUPPORT_TLS +#ifdef SUPPORT_TLS uschar *tls_certificate; uschar *tls_crl; uschar *tls_privatekey; @@ -64,18 +67,23 @@ uschar *tls_verify_certificates; int tls_dh_min_bits; BOOL tls_tempfail_tryclear; - #endif - #ifndef DISABLE_DKIM + uschar *tls_verify_hosts; + uschar *tls_try_verify_hosts; +# ifdef EXPERIMENTAL_CERTNAMES + uschar *tls_verify_cert_hostnames; +# endif +#endif +#ifndef DISABLE_DKIM uschar *dkim_domain; uschar *dkim_private_key; uschar *dkim_selector; uschar *dkim_canon; uschar *dkim_sign_headers; uschar *dkim_strict; - #endif - #ifdef EXPERIMENTAL_TPDA +#endif +#ifdef EXPERIMENTAL_TPDA uschar *tpda_host_defer_action; - #endif +#endif } smtp_transport_options_block; /* Data for reading the private options. */ diff -Nru exim4-4.82/src/verify.c exim4-4.84/src/verify.c --- exim4-4.82/src/verify.c 2013-10-25 00:46:27.000000000 +0000 +++ exim4-4.84/src/verify.c 2014-08-09 12:44:29.000000000 +0000 @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2009 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* Functions concerned with verifying things. The original code for callout @@ -373,10 +373,13 @@ { HDEBUG(D_verify) debug_printf("cannot callout via null transport\n"); } +else if (Ustrcmp(addr->transport->driver_name, "smtp") != 0) + log_write(0, LOG_MAIN|LOG_PANIC|LOG_CONFIG_FOR, "callout transport '%s': %s is non-smtp", + addr->transport->name, addr->transport->driver_name); else { smtp_transport_options_block *ob = - (smtp_transport_options_block *)(addr->transport->options_block); + (smtp_transport_options_block *)addr->transport->options_block; /* The information wasn't available in the cache, so we have to do a real callout and save the result in the cache for next time, unless no_cache is set, @@ -521,9 +524,6 @@ else active_hostname = s; } - deliver_host = deliver_host_address = NULL; - deliver_domain = save_deliver_domain; - /* Wait for initial response, and send HELO. The smtp_write_command() function leaves its command in big_buffer. This is used in error responses. Initialize it in case the connection is rejected. */ @@ -538,7 +538,7 @@ #endif if (!(done= smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout))) goto RESPONSE_FAILED; - + /* Not worth checking greeting line for ESMTP support */ if (!(esmtp = verify_check_this_host(&(ob->hosts_avoid_esmtp), NULL, host->name, host->address, NULL) != OK)) @@ -633,15 +633,12 @@ /* STARTTLS accepted or ssl-on-connect: try to negotiate a TLS session. */ else { - int rc = tls_client_start(inblock.sock, host, addr, - ob->tls_certificate, ob->tls_privatekey, - ob->tls_sni, - ob->tls_verify_certificates, ob->tls_crl, - ob->tls_require_ciphers, -#ifdef EXPERIMENTAL_OCSP - ob->hosts_require_ocsp, -#endif - ob->tls_dh_min_bits, callout); + int oldtimeout = ob->command_timeout; + int rc; + + ob->command_timeout = callout; + rc = tls_client_start(inblock.sock, host, addr, ob); + ob->command_timeout = oldtimeout; /* TLS negotiation failed; give an error. Try in clear on a new connection, if the options permit it for this host. */ @@ -694,13 +691,25 @@ done = TRUE; /* so far so good; have response to HELO */ - /*XXX the EHLO response would be analyzed here for IGNOREQUOTA, SIZE, PIPELINING, AUTH */ - /* If we haven't authenticated, but are required to, give up. */ + /*XXX the EHLO response would be analyzed here for IGNOREQUOTA, SIZE, PIPELINING */ - /*XXX "filter command specified for this transport" ??? */ - /* for now, transport_filter by cutthrough-delivery is not supported */ + /* For now, transport_filter by cutthrough-delivery is not supported */ /* Need proper integration with the proper transport mechanism. */ - + if (cutthrough_delivery) + { + if (addr->transport->filter_command) + { + cutthrough_delivery= FALSE; + HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of transport filter\n"); + } + #ifndef DISABLE_DKIM + if (ob->dkim_domain) + { + cutthrough_delivery= FALSE; + HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of DKIM signing\n"); + } + #endif + } SEND_FAILED: RESPONSE_FAILED: @@ -708,7 +717,6 @@ ; /* Clear down of the TLS, SMTP and TCP layers on error is handled below. */ - /* Failure to accept HELO is cached; this blocks the whole domain for all senders. I/O errors and defer responses are not cached. */ @@ -722,6 +730,7 @@ } } + /* If we haven't authenticated, but are required to, give up. */ /* Try to AUTH */ else done = smtp_auth(responsebuffer, sizeof(responsebuffer), @@ -745,6 +754,9 @@ smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout); + deliver_host = deliver_host_address = NULL; + deliver_domain = save_deliver_domain; + /* If the host does not accept MAIL FROM:<>, arrange to cache this information, but again, don't record anything for an I/O error or a defer. Do not cache rejections of MAIL when a non-empty sender has been used, because @@ -962,6 +974,7 @@ { cutthrough_fd= outblock.sock; /* We assume no buffer in use in the outblock */ cutthrough_addr = *addr; /* Save the address_item for later logging */ + cutthrough_addr.next = NULL; cutthrough_addr.host_used = store_get(sizeof(host_item)); cutthrough_addr.host_used->name = host->name; cutthrough_addr.host_used->address = host->address; @@ -1230,27 +1243,43 @@ } +/* fd and use_crlf args only to match write_chunk() */ +static BOOL +cutthrough_write_chunk(int fd, uschar * s, int len, BOOL use_crlf) +{ +uschar * s2; +while(s && (s2 = Ustrchr(s, '\n'))) + { + if(!cutthrough_puts(s, s2-s) || !cutthrough_put_nl()) + return FALSE; + s = s2+1; + } +return TRUE; +} + + /* Buffered send of headers. Return success boolean. */ /* Expands newlines to wire format (CR,NL). */ /* Also sends header-terminating blank line. */ BOOL cutthrough_headers_send( void ) { -header_line * h; -uschar * cp1, * cp2; - if(cutthrough_fd < 0) return FALSE; -for(h= header_list; h != NULL; h= h->next) - if(h->type != htype_old && h->text != NULL) - for (cp1 = h->text; *cp1 && (cp2 = Ustrchr(cp1, '\n')); cp1 = cp2+1) - if( !cutthrough_puts(cp1, cp2-cp1) - || !cutthrough_put_nl()) - return FALSE; +/* We share a routine with the mainline transport to handle header add/remove/rewrites, + but having a separate buffered-output function (for now) +*/ +HDEBUG(D_acl) debug_printf("----------- start cutthrough headers send -----------\n"); + +if (!transport_headers_send(&cutthrough_addr, cutthrough_fd, + cutthrough_addr.transport->add_headers, cutthrough_addr.transport->remove_headers, + &cutthrough_write_chunk, TRUE, + cutthrough_addr.transport->rewrite_rules, cutthrough_addr.transport->rewrite_existflags)) + return FALSE; -HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>>(nl)\n"); -return cutthrough_put_nl(); +HDEBUG(D_acl) debug_printf("----------- done cutthrough headers send ------------\n"); +return TRUE; } @@ -1542,13 +1571,7 @@ they're used in the context of a transport used by verification. Reset them at exit from this routine. */ -modify_variable(US"tls_bits", &tls_out.bits); -modify_variable(US"tls_certificate_verified", &tls_out.certificate_verified); -modify_variable(US"tls_cipher", &tls_out.cipher); -modify_variable(US"tls_peerdn", &tls_out.peerdn); -#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS) -modify_variable(US"tls_sni", &tls_out.sni); -#endif +tls_modify_variables(&tls_out); /* Save a copy of the sender address for re-instating if we change it to <> while verifying a sender address (a nice bit of self-reference there). */ @@ -1721,8 +1744,20 @@ string_is_ip_address(host->name, NULL) != 0) (void)host_find_byname(host, NULL, flags, &canonical_name, TRUE); else + { + uschar * d_request = NULL, * d_require = NULL; + if (Ustrcmp(addr->transport->driver_name, "smtp") == 0) + { + smtp_transport_options_block * ob = + (smtp_transport_options_block *) + addr->transport->options_block; + d_request = ob->dnssec_request_domains; + d_require = ob->dnssec_require_domains; + } + (void)host_find_bydns(host, NULL, flags, NULL, NULL, NULL, - &canonical_name, NULL); + d_request, d_require, &canonical_name, NULL); + } } } } @@ -2007,14 +2042,7 @@ the -bv or -bt case). */ out: - -modify_variable(US"tls_bits", &tls_in.bits); -modify_variable(US"tls_certificate_verified", &tls_in.certificate_verified); -modify_variable(US"tls_cipher", &tls_in.cipher); -modify_variable(US"tls_peerdn", &tls_in.peerdn); -#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS) -modify_variable(US"tls_sni", &tls_in.sni); -#endif +tls_modify_variables(&tls_in); return yield; } @@ -2144,6 +2172,41 @@ } +/************************************************* +* Check header names for 8-bit characters * +*************************************************/ + +/* This function checks for invalid charcters in header names. See +RFC 5322, 2.2. and RFC 6532, 3. + +Arguments: + msgptr where to put an error message + +Returns: OK + FAIL +*/ + +int +verify_check_header_names_ascii(uschar **msgptr) +{ +header_line *h; +uschar *colon, *s; + +for (h = header_list; h != NULL; h = h->next) + { + colon = Ustrchr(h->text, ':'); + for(s = h->text; s < colon; s++) + { + if ((*s < 33) || (*s > 126)) + { + *msgptr = string_sprintf("Invalid character in header \"%.*s\" found", + colon - h->text, h->text); + return FAIL; + } + } + } +return OK; +} /************************************************* * Check for blind recipients * @@ -3490,7 +3553,7 @@ /* In case this is the first time the DNS resolver is being used. */ -dns_init(FALSE, FALSE); +dns_init(FALSE, FALSE, FALSE); /*XXX dnssec? */ /* Loop through all the domains supplied, until something matches */ @@ -3661,4 +3724,6 @@ return FAIL; } +/* vi: aw ai sw=2 +*/ /* End of verify.c */ diff -Nru exim4-4.82/src/version.sh exim4-4.84/src/version.sh --- exim4-4.82/src/version.sh 2013-10-28 12:57:00.000000000 +0000 +++ exim4-4.84/src/version.sh 2014-08-11 13:12:36.000000000 +0000 @@ -1,4 +1,4 @@ # automatically generated file - see ../scripts/reversion -EXIM_RELEASE_VERSION="4.82" +EXIM_RELEASE_VERSION="4.84" EXIM_VARIANT_VERSION="" EXIM_COMPILE_NUMBER="1" diff -Nru exim4-4.82/util/proxy_protocol_client.pl exim4-4.84/util/proxy_protocol_client.pl --- exim4-4.82/util/proxy_protocol_client.pl 1970-01-01 00:00:00.000000000 +0000 +++ exim4-4.84/util/proxy_protocol_client.pl 2014-08-09 12:44:29.000000000 +0000 @@ -0,0 +1,250 @@ +#!/usr/bin/perl +# +# Copyright (C) 2014 Todd Lyons +# License GPLv2: GNU GPL version 2 +# +# +# This script emulates a proxy which uses Proxy Protocol to communicate +# to a backend server. It should be run from an IP which is configured +# to be a Proxy Protocol connection (or not, if you are testing error +# scenarios) because Proxy Protocol specs require not to fall back to a +# non-proxied mode. +# +# The script is interactive, so when you run it, you are expected to +# perform whatever conversation is required for the protocol being +# tested. It uses STDIN/STDOUT, so you can also pipe output to/from the +# script. It was originally written to test Exim's Proxy Protocol +# code, and it could be tested like this: +# +# swaks --pipe 'perl proxy_protocol_client.pl --server-ip +# host.internal.lan' --from user@example.com --to user@example.net +# +use strict; +use warnings; +use IO::Select; +use IO::Socket; +use Getopt::Long; +use Data::Dumper; + +my %opts; +GetOptions( \%opts, + 'help', + '6|ipv6', + 'dest-ip:s', + 'dest-port:i', + 'source-ip:s', + 'source-port:i', + 'server-ip:s', + 'server-port:i', + 'version:i' +); +&usage() if ($opts{help} || !$opts{'server-ip'}); + +my ($dest_ip,$source_ip,$dest_port,$source_port); +my %socket_map; +my $status_line = "Testing Proxy Protocol Version " . + ($opts{version} ? $opts{version} : '2') . + ":\n"; + +# All ip's and ports are in network byte order in version 2 mode, but are +# simple strings when in version 1 mode. The binary_pack_*() functions +# return the required data for the Proxy Protocol version being used. + +# Use provided source or fall back to www.mrball.net +$source_ip = $opts{'source-ip'} ? binary_pack_ip($opts{'source-ip'}) : + $opts{6} ? + binary_pack_ip("2001:470:d:367::50") : + binary_pack_ip("208.89.139.252"); +$source_port = $opts{'source-port'} ? + binary_pack_port($opts{'source-port'}) : + binary_pack_port(43118); + +$status_line .= "-> " if (!$opts{version} || $opts{version} == 2); + +# Use provided dest or fall back to mail.exim.org +$dest_ip = $opts{'dest-ip'} ? binary_pack_ip($opts{'dest-ip'}) : + $opts{6} ? + binary_pack_ip("2001:630:212:8:204:23ff:fed6:b664") : + binary_pack_ip("131.111.8.192"); +$dest_port = $opts{'dest-port'} ? + binary_pack_port($opts{'dest-port'}) : + binary_pack_port(25); + +# The IP and port of the Proxy Protocol backend real server being tested, +# don't binary pack it. +my $server_ip = $opts{'server-ip'}; +my $server_port = $opts{'server-port'} ? $opts{'server-port'} : 25; + +my $s = IO::Select->new(); # for socket polling + +sub generate_preamble { + my @preamble; + if (!$opts{version} || $opts{version} == 2) { + @preamble = ( + "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A", # 12 byte v2 header + "\x21", # top 4 bits declares v2 + # bottom 4 bits is command + $opts{6} ? "\x21" : "\x11", # inet6/4 and TCP (stream) + $opts{6} ? "\x00\x24" : "\x00\x0b", # 36 bytes / 12 bytes + $source_ip, + $dest_ip, + $source_port, + $dest_port + ); + } + else { + @preamble = ( + "PROXY", " ", # Request proxy mode + $opts{6} ? "TCP6" : "TCP4", " ", # inet6/4 and TCP (stream) + $source_ip, " ", + $dest_ip, " ", + $source_port, " ", + $dest_port, + "\x0d\x0a" + ); + $status_line .= join "", @preamble; + } + print "\n", $status_line, "\n"; + print "\n" if (!$opts{version} || $opts{version} == 2); + return @preamble; +} + +sub binary_pack_port { + my $port = shift(); + if ($opts{version} && $opts{version} == 1) { + return $port + if ($port && $port =~ /^\d+$/ && $port > 0 && $port < 65536); + die "Not a valid port: $port"; + } + $status_line .= $port." "; + $port = pack "S", $port; + return $port; +} + +sub binary_pack_ip { + my $ip = shift(); + if ( $ip =~ m/\./ && !$opts{6}) { + if (IP4_valid($ip)) { + return $ip if ($opts{version} && $opts{version} == 1); + $status_line .= $ip.":"; + $ip = pack "C*", split /\./, $ip; + } + else { die "Invalid IPv4: $ip"; } + } + elsif ($ip =~ m/:/ && $opts{6}) { + $ip = pad_ipv6($ip); + if (IP6_valid($ip)) { + return $ip if ($opts{version} && $opts{version} == 1); + $status_line .= $ip.":"; + $ip = pack "S>*", map hex, split /:/, $ip; + } + else { die "Invalid IPv6: $ip"; } + } + else { die "Mismatching IP families passed: $ip"; } + return $ip; +} + +sub pad_ipv6 { + my $ip = shift(); + my @ip = split /:/, $ip; + my $segments = scalar @ip; + return $ip if ($segments == 8); + $ip = ""; + for (my $count=1; $count <= $segments; $count++) { + my $block = $ip[$count-1]; + if ($block) { + $ip .= $block; + $ip .= ":" unless $count == $segments; + } + elsif ($count == 1) { + # Somebody passed us ::1, fix it, but it's not really valid + $ip = "0:"; + } + else { + $ip .= join ":", map "0", 0..(8-$segments); + $ip .= ":"; + } + } + return $ip; +} + +sub IP6_valid { + my $ip = shift; + $ip = lc($ip); + return 0 unless ($ip =~ /^[0-9a-f:]+$/); + my @ip = split /:/, $ip; + return 0 if (scalar @ip != 8); + return 1; +} + +sub IP4_valid { + my $ip = shift; + $ip =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/; + foreach ($1,$2,$3,$4){ + if ($_ <256 && $_ >0) {next;} + return 0; + } + return 1; +} + +sub go_interactive { + my $continue = 1; + while($continue) { + # Check for input on both ends, recheck every 5 sec + for my $socket ($s->can_read(5)) { + my $remote = $socket_map{$socket}; + my $buffer; + my $read = $socket->sysread($buffer, 4096); + if ($read) { + $remote->syswrite($buffer); + } + else { + $continue = 0; + } + } + } +} + +sub connect_stdin_to_proxy { + my $sock = new IO::Socket::INET( + PeerAddr => $server_ip, + PeerPort => $server_port, + Proto => 'tcp' + ); + + die "Could not create socket: $!\n" unless $sock; + # Add sockets to the Select group + $s->add(\*STDIN); + $s->add($sock); + # Tie the sockets together using this hash + $socket_map{\*STDIN} = $sock; + $socket_map{$sock} = \*STDOUT; + return $sock; +} + +sub usage { + chomp(my $prog = `basename $0`); + print <