Merge branch 'debian' into hcoop_489
authorClinton Ebadi <clinton@unknownlamer.org>
Fri, 23 Mar 2018 03:25:15 +0000 (23:25 -0400)
committerClinton Ebadi <clinton@unknownlamer.org>
Fri, 23 Mar 2018 03:25:15 +0000 (23:25 -0400)
359 files changed:
.ctags [new file with mode: 0644]
.gitignore
ACKNOWLEDGMENTS
Makefile
OS/Makefile-Base
OS/Makefile-CYGWIN
OS/Makefile-Default
OS/Makefile-FreeBSD
OS/Makefile-HP-UX
OS/Makefile-SunOS5
OS/os.c-BSDI [new file with mode: 0644]
OS/os.c-HP-UX [new file with mode: 0644]
OS/os.c-Linux
OS/os.c-SunOS5 [new file with mode: 0644]
OS/os.c-cygwin
OS/os.h-AIX
OS/os.h-BSDI
OS/os.h-DGUX
OS/os.h-Darwin
OS/os.h-DragonFly
OS/os.h-FreeBSD
OS/os.h-GNU
OS/os.h-GNUkFreeBSD
OS/os.h-GNUkNetBSD
OS/os.h-HI-OSF
OS/os.h-HI-UX
OS/os.h-HP-UX
OS/os.h-HP-UX-9
OS/os.h-IRIX
OS/os.h-IRIX6
OS/os.h-IRIX632
OS/os.h-IRIX65
OS/os.h-Linux
OS/os.h-NetBSD
OS/os.h-OSF1
OS/os.h-OpenBSD
OS/os.h-OpenUNIX
OS/os.h-QNX
OS/os.h-SCO
OS/os.h-SCO_SV
OS/os.h-SunOS4
OS/os.h-SunOS5
OS/os.h-SunOS5-hal
OS/os.h-ULTRIX
OS/os.h-UNIX_SV
OS/os.h-USG
OS/os.h-Unixware7
OS/os.h-cygwin
OS/os.h-mips
README.DSN
README.UPDATING
conf [new file with mode: 0644]
debian/EDITME.exim4-heavy.diff
debian/EDITME.exim4-light.diff
debian/EDITME.eximon.diff
debian/NEWS [moved from debian/exim4-config.NEWS with 53% similarity]
debian/README.Debian.xml
debian/changelog
debian/compat
debian/control
debian/copyright
debian/debconf/conf.d/acl/30_exim4-config_check_mail
debian/debconf/conf.d/acl/30_exim4-config_check_rcpt
debian/debconf/conf.d/acl/40_exim4-config_check_data
debian/debconf/conf.d/main/01_exim4-config_listmacrosdefs
debian/debconf/conf.d/main/02_exim4-config_options
debian/debconf/conf.d/main/03_exim4-config_tlsoptions
debian/debconf/conf.d/rewrite/31_exim4-config_rewriting
debian/debconf/conf.d/router/100_exim4-config_domain_literal
debian/debconf/conf.d/router/500_exim4-config_hubuser
debian/debconf/conf.d/transport/30_exim4-config_remote_smtp
debian/debconf/conf.d/transport/30_exim4-config_remote_smtp_smarthost
debian/debconf/update-exim4.conf
debian/example.conf.md5
debian/exim4-base.NEWS [deleted file]
debian/exim4-base.exim4.init
debian/exim4-base.install
debian/exim4-base.postinst
debian/exim4-base.postrm
debian/exim4-config.postinst
debian/exim4-daemon-heavy-dbg.links [new file with mode: 0644]
debian/exim4-daemon-heavy.NEWS [deleted file]
debian/exim4-daemon-light-dbg.links [new file with mode: 0644]
debian/exim4-daemon-light.NEWS [deleted file]
debian/exim4-daemon-light.postinst
debian/exim4-daemon-light.prerm
debian/exim4-dbg.links [new file with mode: 0644]
debian/exim4_refresh_gnutls-params [new file with mode: 0755]
debian/eximon4.links [new file with mode: 0644]
debian/gnutls-params-2048 [new file with mode: 0644]
debian/manpages/exim4-config_files.5
debian/manpages/update-exim4.conf.8
debian/minimaltest
debian/patches/31_eximmanpage.dpatch
debian/patches/32_exim4.dpatch
debian/patches/35_install.dpatch
debian/patches/40_reproducible_build.diff [new file with mode: 0644]
debian/patches/50_localscan_dlopen.dpatch
debian/patches/66_enlarge-dh-parameters-size.dpatch [deleted file]
debian/patches/67_unnecessaryCopt.diff
debian/patches/70_remove_exim-users_references.dpatch
debian/patches/78_Disable-chunking-BDAT-by-default.patch [new file with mode: 0644]
debian/patches/79_CVE-2017-1000369.patch [new file with mode: 0644]
debian/patches/80_Avoid-release-of-store-if-there-have-been-later-allo.patch [new file with mode: 0644]
debian/patches/80_mime_empty_charset.diff [deleted file]
debian/patches/81_Chunking-do-not-treat-the-first-lonely-dot-special.-.patch [new file with mode: 0644]
debian/patches/81_buffer-overrun-in-spam-acl.diff [deleted file]
debian/patches/82_Fix-base64d-buffer-size-CVE-2018-6789.patch [new file with mode: 0644]
debian/patches/82_quoted-or-r-2047-encoded.diff [deleted file]
debian/patches/83_Remove-limit-on-remove_headers-item-size.-Bug-1533.patch [deleted file]
debian/patches/84_Fix-truncation-of-items-in-headers_remove-lists-this.patch [deleted file]
debian/patches/series
debian/rules
debian/script
debian/source/include-binaries [deleted file]
debian/source/lintian-overrides [deleted file]
debian/update-exim4defaults
debian/upstream-signing-key.pgp [deleted file]
debian/upstream/signing-key.asc [new file with mode: 0644]
debian/watch
doc/ChangeLog
doc/DANE-draft-notes [new file with mode: 0644]
doc/Exim3.upgrade
doc/Exim4.upgrade
doc/NewStuff
doc/OptionLists.txt
doc/README.SIEVE
doc/cve-2016-9663 [new file with mode: 0644]
doc/dbm.discuss.txt
doc/exim.8
doc/experimental-spec.txt
doc/filter.txt
doc/openssl.txt [new file with mode: 0644]
doc/spec.txt
exim_monitor/em_StripChart.c
exim_monitor/em_TextPop.c
exim_monitor/em_globals.c
exim_monitor/em_hdr.h
exim_monitor/em_log.c
exim_monitor/em_main.c
exim_monitor/em_menu.c
exim_monitor/em_queue.c
exim_monitor/em_strip.c
exim_monitor/em_xs.c
scripts/Configure-Makefile
scripts/MakeLinks
scripts/exim_install
scripts/lookups-Makefile
scripts/reversion
scripts/source_checks
src/EDITME
src/acl.c
src/auths/Makefile
src/auths/auth-spa.c
src/auths/b64decode.c [deleted file]
src/auths/b64encode.c [deleted file]
src/auths/call_pam.c
src/auths/call_pwcheck.c
src/auths/call_radius.c
src/auths/cram_md5.c
src/auths/cyrus_sasl.c
src/auths/dovecot.c
src/auths/get_data.c
src/auths/get_no64_data.c
src/auths/gsasl_exim.c
src/auths/heimdal_gssapi.c
src/auths/plaintext.c
src/auths/spa.c
src/auths/tls.c [new file with mode: 0644]
src/auths/tls.h [new file with mode: 0644]
src/base64.c [new file with mode: 0644]
src/blob.h [new file with mode: 0644]
src/buildconfig.c
src/child.c
src/cnumber.h [deleted file]
src/config.h.defaults
src/configure.default
src/convert4r3.src
src/convert4r4.src
src/crypt16.c
src/daemon.c
src/dane-gnu.c [new file with mode: 0644]
src/dane-openssl.c [new file with mode: 0644]
src/dane.c [new file with mode: 0644]
src/danessl.h [new file with mode: 0644]
src/dbfn.c
src/dbfunctions.h
src/dbstuff.h
src/dcc.c
src/debug.c
src/deliver.c
src/demime.c [deleted file]
src/demime.h [deleted file]
src/dkim.c
src/dkim.h
src/dmarc.c
src/dmarc.h
src/dns.c
src/drtables.c
src/enq.c
src/environment.c [new file with mode: 0644]
src/exicyclog.src
src/exigrep.src
src/exim.c
src/exim.h
src/exim_checkaccess.src
src/exim_dbmbuild.c
src/exim_dbutil.c
src/exim_lock.c
src/eximon.src
src/eximstats.src
src/exinext.src
src/exipick.src
src/exiqgrep.src
src/exiqsumm.src
src/expand.c
src/filter.c
src/functions.h
src/globals.c
src/globals.h
src/hash.c [moved from src/auths/sha1.c with 88% similarity]
src/hash.h [new file with mode: 0644]
src/header.c
src/host.c
src/imap_utf7.c [new file with mode: 0644]
src/ip.c
src/local_scan.h
src/log.c
src/lookupapi.h
src/lookups/Makefile
src/lookups/README
src/lookups/cdb.c
src/lookups/dbmdb.c
src/lookups/dnsdb.c
src/lookups/dsearch.c
src/lookups/ibase.c
src/lookups/ldap.c
src/lookups/ldap.h
src/lookups/lf_functions.h
src/lookups/lf_quote.c
src/lookups/lf_sqlperform.c
src/lookups/lmdb.c [new file with mode: 0644]
src/lookups/lsearch.c
src/lookups/mysql.c
src/lookups/nis.c
src/lookups/nisplus.c
src/lookups/oracle.c
src/lookups/passwd.c
src/lookups/pgsql.c
src/lookups/redis.c
src/lookups/spf.c
src/lookups/sqlite.c
src/lookups/testdb.c
src/lookups/whoson.c
src/lss.c
src/macros.h
src/malware.c
src/match.c
src/memcheck.h
src/mime.c
src/mime.h
src/moan.c
src/mytypes.h
src/os.c
src/osfunctions.h
src/parse.c
src/pdkim/Makefile
src/pdkim/README
src/pdkim/base64.c [deleted file]
src/pdkim/base64.h [deleted file]
src/pdkim/bignum.c [deleted file]
src/pdkim/bignum.h [deleted file]
src/pdkim/bn_mul.h [deleted file]
src/pdkim/config.h [new file with mode: 0644]
src/pdkim/crypt_ver.h [new file with mode: 0644]
src/pdkim/pdkim.c
src/pdkim/pdkim.h
src/pdkim/pdkim_hash.h [new file with mode: 0644]
src/pdkim/rsa.c
src/pdkim/rsa.h
src/pdkim/sha1.c [deleted file]
src/pdkim/sha1.h [deleted file]
src/pdkim/sha2.c [deleted file]
src/pdkim/sha2.h [deleted file]
src/perl.c
src/queue.c
src/rda.c
src/readconf.c
src/receive.c
src/regex.c
src/retry.c
src/rewrite.c
src/rfc2047.c
src/route.c
src/routers/README
src/routers/accept.c
src/routers/dnslookup.c
src/routers/dnslookup.h
src/routers/ipliteral.c
src/routers/iplookup.c
src/routers/manualroute.c
src/routers/queryprogram.c
src/routers/redirect.c
src/routers/rf_change_domain.c
src/routers/rf_functions.h
src/routers/rf_get_errors_address.c
src/routers/rf_get_munge_headers.c
src/routers/rf_lookup_hostlist.c
src/routers/rf_queue_add.c
src/search.c
src/setenv.c [new file with mode: 0644]
src/sha_ver.h [new file with mode: 0644]
src/sieve.c
src/smtp_in.c
src/smtp_out.c
src/spam.c
src/spam.h
src/spf.c
src/spf.h
src/spool_in.c
src/spool_mbox.c
src/spool_out.c
src/srs.c
src/std-crypto.c
src/store.c
src/string.c
src/structs.h
src/tls-gnu.c
src/tls-openssl.c
src/tls.c
src/tlscert-gnu.c
src/tlscert-openssl.c
src/tod.c
src/transport-filter.src
src/transport.c
src/transports/Makefile
src/transports/appendfile.c
src/transports/autoreply.c
src/transports/lmtp.c
src/transports/pipe.c
src/transports/queuefile.c [new file with mode: 0644]
src/transports/queuefile.h [new file with mode: 0644]
src/transports/smtp.c
src/transports/smtp.h
src/transports/smtp_socks.c [new file with mode: 0644]
src/transports/tf_maildir.c
src/tree.c
src/utf8.c [new file with mode: 0644]
src/valgrind.h
src/verify.c
src/version.sh
util/.gitignore [new file with mode: 0644]
util/chunking_fixqueue_finalnewlines.pl [new file with mode: 0755]
util/cramtest.pl
util/gen_pkcs3.c
util/mkcdb.pl
util/ocsp_fetch.pl
util/proxy_protocol_client.pl
util/ratelimit.pl

diff --git a/.ctags b/.ctags
new file mode 100644 (file)
index 0000000..c764086
--- /dev/null
+++ b/.ctags
@@ -0,0 +1,2 @@
+--recurse
+--exclude=build-*
index 7839e97..8965c11 100644 (file)
@@ -1,4 +1,4 @@
 Local
 build-*
 tags
-cscope.out
+cscope.*
index 1c4a934..2e1ede0 100644 (file)
@@ -350,7 +350,7 @@ John Jetmore
 Tom Kistner               DKIM. Content scanning. SPA.
 Todd Lyons
 Nigel Metheringham        Transitioning out of Default Victim status.
-Phil Pennock              Release Coordinator. Breaks lots of things.
+Phil Pennock              Mostly idle; some security bits still.
 David Woodhouse           Dynamic modules. Security.
 
 
@@ -449,6 +449,7 @@ Jan Srzednicki            Patch improving Dovecot authenticator
 Samuel Thibault           Patch fixing IPv6 interface address detection on Hurd
 Martin Tscholak           Reported issue with TLS anonymous ciphersuites
 Stephen Usher             Patch fixing use of Oracle's LDAP libraries on Solaris
+Jasper Wallace            Patch for LibreSSL compatibility
 Holger Weiß               Patch leting ${run} return more data than OS pipe
                             buffer size
 Moritz Wilhelmy           Pointed out PCRE_PRERELEASE glitch
index 99f4ab3..2a100bb 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -2,7 +2,7 @@
 # appropriate links, and then creating and running the main makefile in that
 # directory.
 
-# Copyright (c) University of Cambridge, 1995 - 2014
+# Copyright (c) University of Cambridge, 1995 - 2015
 # See the file NOTICE for conditions of use and distribution.
 
 # IRIX make uses the shell that is in the SHELL variable, which often defaults
@@ -19,8 +19,14 @@ RM_COMMAND=/bin/rm
 # provide an override for the OS type and architecture type; they still have
 # to be used for the OS-specific files. To override them, you can set the
 # shell variables OSTYPE and ARCHTYPE when running make.
+#
+# EXIM_BUILD_SUFFIX should be used to enable parallel builds on a file
+# system shared among different Linux distros (same os-type, same
+# arch-type). The ../test/runtest script is expected to honour the
+# EXIM_BUILD_SUFFIX when searching the Exim binary.
+# NOTE: EXIM_BUILD_SUFFIX is considered *experimental*.
 
-buildname=$${build:-`$(SHELL) scripts/os-type`-`$(SHELL) scripts/arch-type`}
+buildname=$${build:-`$(SHELL) scripts/os-type`-`$(SHELL) scripts/arch-type`}$${EXIM_BUILD_SUFFIX:+.$$EXIM_BUILD_SUFFIX}
 
 # The default target checks for the existence of Local/Makefile, that the main
 # makefile is built and up-to-date, and then it runs it.
@@ -28,6 +34,14 @@ buildname=$${build:-`$(SHELL) scripts/os-type`-`$(SHELL) scripts/arch-type`}
 all: Local/Makefile configure
        @cd build-$(buildname); $(MAKE) SHELL=$(SHELL) $(MFLAGS)
 
+
+# This pair for the convenience of of the Debian maintainers
+exim: Local/Makefile configure
+       @cd build-$(buildname); $(MAKE) SHELL=$(SHELL) $(MFLAGS) exim
+utils: Local/Makefile configure
+       @cd build-$(buildname); $(MAKE) SHELL=$(SHELL) $(MFLAGS) utils
+
+
 Local/Makefile:
        @echo ""
        @echo "*** Please create Local/Makefile by copying src/EDITME and making"
@@ -90,9 +104,10 @@ distclean:; $(RM_COMMAND) -rf build-* cscope*
 cscope.files: FRC
        echo "-q" > $@
        echo "-p3" >> $@
-       find src Local OS -name "*.[cshyl]" -print \
+       find src Local OS exim_monitor -name "*.[cshyl]" -print \
                    -o -name "os.h*" -print \
                    -o -name "*akefile*" -print \
+                   -o -name config.h.defaults -print \
                    -o -name EDITME -print >> $@
        ls OS/* >> $@
 
index 87a8037..f6b42f3 100644 (file)
@@ -1,12 +1,15 @@
 # This file is the basis of the main makefile for Exim and friends. The
 # makefile at the top level arranges to build the main makefile by calling
 # scripts/Configure-Makefile from within the build directory. This
-# concatentates the configuration settings from Local/Makefile and other,
+# concatenates the configuration settings from Local/Makefile and other,
 # optional, Local/* files at the front of this file, to create Makefile in the
 # build directory.
+#
+# Copyright (c) The Exim Maintainers 2016
 
 SHELL      = $(MAKE_SHELL)
 SCRIPTS    = ../scripts
+O          = ../OS
 EDITME     = ../Local/Makefile
 EXIMON_EDITME = ../Local/eximon.conf
 
@@ -32,10 +35,10 @@ FE       = $(FULLECHO)
 # 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:       allexim
-config:    $(EDITME) checklocalmake Makefile os.h os.c config.h version.h
+all:       utils exim
+config:    $(EDITME) checklocalmake Makefile os.c config.h version.h
 
-checklocalmake:
+checklocalmake: 
        @if $(SHELL) $(SCRIPTS)/newer $(EDITME)-$(OSTYPE) $(EDITME) || \
          $(SHELL) $(SCRIPTS)/newer $(EDITME)-$(ARCHTYPE) $(EDITME) || \
          $(SHELL) $(SCRIPTS)/newer $(EDITME)-$(OSTYPE)-$(ARCHTYPE) $(EDITME); \
@@ -76,12 +79,29 @@ Makefile: ../OS/Makefile-Base ../OS/Makefile-Default \
 
 # Build (link) the os.h file
 
-os.h:
+os.h:  $(SCRIPTS)/Configure-os.h \
+       $(O)/os.h-AIX           $(O)/os.h-BSDI  $(O)/os.h-cygwin \
+       $(O)/os.h-Darwin        $(O)/os.h-DGUX  $(O)/os.h-DragonFly \
+       $(O)/os.h-FreeBSD       $(O)/os.h-GNU   $(O)/os.h-GNUkFreeBSD \
+       $(O)/os.h-GNUkNetBSD    $(O)/os.h-HI-OSF \
+       $(O)/os.h-HI-UX         $(O)/os.h-HP-UX $(O)/os.h-HP-UX-9 \
+       $(O)/os.h-IRIX          $(O)/os.h-IRIX6 $(O)/os.h-IRIX632 \
+       $(O)/os.h-IRIX65        $(O)/os.h-Linux $(O)/os.h-mips \
+       $(O)/os.h-NetBSD        $(O)/os.h-NetBSD-a.out \
+       $(O)/os.h-OpenBSD       $(O)/os.h-OpenUNIX      $(O)/os.h-OSF1 \
+       $(O)/os.h-QNX           $(O)/os.h-SCO           $(O)/os.h-SCO_SV \
+       $(O)/os.h-SunOS4        $(O)/os.h-SunOS5        $(O)/os.h-SunOS5-hal \
+       $(O)/os.h-ULTRIX        $(O)/os.h-UNIX_SV \
+       $(O)/os.h-Unixware7     $(O)/os.h-USG
        $(SHELL) $(SCRIPTS)/Configure-os.h
 
 # Build the os.c file
 
-os.c:   ../src/os.c
+os.c:   ../src/os.c \
+       $(SCRIPTS)/Configure-os.c \
+       $(O)/os.c-cygwin        $(O)/os.c-GNU   $(O)/os.c-HI-OSF \
+       $(O)/os.c-IRIX          $(O)/os.c-IRIX6 $(O)/os.c-IRIX632 \
+       $(O)/os.c-IRIX65        $(O)/os.c-Linux $(O)/os.c-OSF1
        $(SHELL) $(SCRIPTS)/Configure-os.c
 
 # Build the config.h file.
@@ -95,19 +115,16 @@ config.h: Makefile buildconfig ../src/config.h.defaults $(EDITME)
 # 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 config allexim buildauths buildlookups buildpdkim buildrouters \
+.PHONY: all config utils \
+       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: $(EXIM_MONITOR) exicyclog exinext exiwhat \
+utils: $(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 \
-        exim
+        exim_dbmbuild exim_dumpdb exim_fixdb exim_tidydb exim_lock
 
 
 # Targets for special-purpose configuration header builders
@@ -262,6 +279,7 @@ exipick: Makefile ../src/exipick.src
        @rm -f exipick
        @sed -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
          -e "s?SPOOL_DIRECTORY?$(SPOOL_DIRECTORY)?" \
+         -e "s?BIN_DIRECTORY?$(BIN_DIRECTORY)?" \
          ../src/exipick.src > exipick-t
        @mv exipick-t exipick
        @chmod a+x exipick
@@ -297,25 +315,32 @@ convert4r4: Makefile ../src/convert4r4.src
 # are thrown away by the linker.
 
 OBJ_WITH_CONTENT_SCAN = malware.o mime.o regex.o spam.o spool_mbox.o
-OBJ_WITH_OLD_DEMIME = demime.o
-OBJ_EXPERIMENTAL = bmi_spam.o spf.o srs.o dcc.o dmarc.o
+OBJ_EXPERIMENTAL = bmi_spam.o \
+                               dane.o \
+                               dcc.o \
+                               dmarc.o \
+                               imap_utf7.o \
+                               spf.o \
+                               srs.o \
+                               utf8.o
 
 # Targets for final binaries; the main one has a build number which is
 # updated each time. We don't bother with that for the auxiliaries.
 
 OBJ_LOOKUPS = lookups/lf_quote.o lookups/lf_check_file.o lookups/lf_sqlperform.o
 
-OBJ_EXIM = acl.o child.o crypt16.o daemon.o dbfn.o debug.o deliver.o \
+OBJ_EXIM = acl.o base64.o child.o crypt16.o daemon.o dbfn.o debug.o deliver.o \
         directory.o dns.o drtables.o enq.o exim.o expand.o filter.o \
-        filtertest.o globals.o dkim.o \
+        filtertest.o globals.o dkim.o hash.o \
         header.o host.o ip.o log.o lss.o match.o moan.o \
         os.o parse.o queue.o \
         rda.o readconf.o receive.o retry.o rewrite.o rfc2047.o \
         route.o search.o sieve.o smtp_in.o smtp_out.o spool_in.o spool_out.o \
         std-crypto.o store.o string.o tls.o tod.o transport.o tree.o verify.o \
+        environment.o \
         $(OBJ_LOOKUPS) \
         local_scan.o $(EXIM_PERL) $(OBJ_WITH_CONTENT_SCAN) \
-        $(OBJ_WITH_OLD_DEMIME) $(OBJ_EXPERIMENTAL)
+        $(OBJ_EXPERIMENTAL)
 
 exim:   buildlookups buildauths pdkim/pdkim.a \
         buildrouters buildtransports \
@@ -385,7 +410,7 @@ exim_tidydb: $(OBJ_TIDYDB)
 
 exim_dbmbuild: exim_dbmbuild.o
        @echo "$(LNCC) -o exim_dbmbuild"
-       $(FE)$(LNCC) -o exim_dbmbuild $(LFLAGS) exim_dbmbuild.o \
+       $(FE)$(LNCC) $(CFLAGS) $(INCLUDE) -o exim_dbmbuild $(LFLAGS) exim_dbmbuild.o \
          $(LIBS) $(EXTRALIBS) $(DBMLIB)
        @if [ x"$(STRIP_COMMAND)" != x"" ]; then \
          echo $(STRIP_COMMAND) exim_dbmbuild; \
@@ -396,7 +421,7 @@ exim_dbmbuild: exim_dbmbuild.o
 
 # The utility for locking a mailbox while messing around with it
 
-exim_lock: exim_lock.c
+exim_lock: exim_lock.c os.h
        @echo "$(CC) exim_lock.c"
        $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) exim_lock.c
        @echo "$(LNCC) -o exim_lock"
@@ -423,7 +448,13 @@ MONBIN = em_StripChart.o $(EXIMON_TEXTPOP) em_globals.o em_init.o \
 # The complete modules list also includes some specially compiled versions of
 # code from the main Exim source tree.
 
-OBJ_MONBIN = util-spool_in.o util-store.o util-string.o tod.o tree.o $(MONBIN)
+OBJ_MONBIN = util-spool_in.o \
+            util-store.o \
+            util-string.o \
+            util-queue.o \
+            tod.o \
+            tree.o \
+            $(MONBIN)
 
 eximon.bin: $(EXIMON_EDITME) eximon $(OBJ_MONBIN) \
             ../exim_monitor/em_version.c
@@ -432,7 +463,7 @@ eximon.bin: $(EXIMON_EDITME) eximon $(OBJ_MONBIN) \
          $(CFLAGS) $(XINCLUDE) -I. ../exim_monitor/em_version.c
        @echo "$(LNCC) -o eximon.bin"
        $(FE)$(PURIFY) $(LNCC) -o eximon.bin em_version.o $(LFLAGS) $(XLFLAGS) \
-       $(OBJ_MONBIN) -lXaw -lXmu -lXt -lXext -lX11 $(PCRE_LIBS) \
+         $(OBJ_MONBIN) -lXaw -lXmu -lXt -lXext -lX11 $(PCRE_LIBS) \
          $(LIBS) $(LIBS_EXIMON) $(EXTRALIBS) $(EXTRALIBS_EXIMON) -lc
        @if [ x"$(STRIP_COMMAND)" != x"" ]; then \
          echo $(STRIP_COMMAND) eximon.bin; \
@@ -443,13 +474,36 @@ eximon.bin: $(EXIMON_EDITME) eximon $(OBJ_MONBIN) \
 
 
 # Compile step for most of the exim modules. HDRS is a list of headers
-# which cause everthing to be rebuilt. PHDRS is the same, for the use
+# which cause everything to be rebuilt. PHDRS is the same, for the use
 # of routers, transports, and authenticators. I can't find a way of doing this
 # in one. This list is overkill, but it doesn't really take much time to
 # rebuild Exim on a modern computer.
 
-HDRS  =    config.h    dbfunctions.h    dbstuff.h    exim.h    functions.h    globals.h    local_scan.h    macros.h    mytypes.h    structs.h
-PHDRS = ../config.h ../dbfunctions.h ../dbstuff.h ../exim.h ../functions.h ../globals.h ../local_scan.h ../macros.h ../mytypes.h ../structs.h
+HDRS  =        blob.h \
+       config.h \
+       dbfunctions.h \
+       dbstuff.h \
+       exim.h \
+       functions.h \
+       globals.h \
+       hash.h \
+       local_scan.h \
+       macros.h \
+       mytypes.h \
+       sha_ver.h \
+       structs.h \
+       os.h
+PHDRS = ../config.h \
+       ../dbfunctions.h \
+       ../dbstuff.h \
+       ../exim.h \
+       ../functions.h \
+       ../globals.h \
+       ../local_scan.h \
+       ../macros.h \
+       ../mytypes.h \
+       ../structs.h \
+       ../os.h
 
 .SUFFIXES: .o .c
 .c.o:;  @echo "$(CC) $*.c"
@@ -520,6 +574,10 @@ util-string.o:   $(HDRS) string.c
        @echo "$(CC) -DCOMPILE_UTILITY string.c"
        $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) -DCOMPILE_UTILITY -o util-string.o string.c
 
+util-queue.o:   $(HDRS) queue.c
+       @echo "$(CC) -DCOMPILE_UTILITY queue.c"
+       $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) -DCOMPILE_UTILITY -o util-queue.o queue.c
+
 util-os.o:       $(HDRS) os.c
        @echo "$(CC) -DCOMPILE_UTILITY os.c"
        $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) \
@@ -538,6 +596,7 @@ local_scan.o:    config local_scan.h ../$(LOCAL_SCAN_SOURCE)
 # Dependencies for the "ordinary" exim modules
 
 acl.o:           $(HDRS) acl.c
+base64.o:        $(HDRS) mime.h base64.c
 child.o:         $(HDRS) child.c
 crypt16.o:       $(HDRS) crypt16.c
 daemon.o:        $(HDRS) daemon.c
@@ -549,9 +608,11 @@ dns.o:           $(HDRS) dns.c
 enq.o:           $(HDRS) enq.c
 exim.o:          $(HDRS) exim.c
 expand.o:        $(HDRS) expand.c
+environment.o:   $(HDRS) environment.c
 filter.o:        $(HDRS) filter.c
 filtertest.o:    $(HDRS) filtertest.c
 globals.o:       $(HDRS) globals.c
+hash.o:          $(HDRS) hash.c
 header.o:        $(HDRS) header.c
 host.o:          $(HDRS) host.c
 ip.o:            $(HDRS) ip.c
@@ -559,7 +620,7 @@ log.o:           $(HDRS) log.c
 lss.o:           $(HDRS) lss.c
 match.o:         $(HDRS) match.c
 moan.o:          $(HDRS) moan.c
-os.o:            $(HDRS) os.c
+os.o:            $(HDRS) $(OS_C_INCLUDES) os.c
 parse.o:         $(HDRS) parse.c
 queue.o:         $(HDRS) queue.c
 rda.o:           $(HDRS) rda.c
@@ -578,34 +639,34 @@ spool_out.o:     $(HDRS) spool_out.c
 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 tlscert-gnu.c tls-openssl.c tlscert-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
-verify.o:        $(HDRS) verify.c
-dkim.o:          $(HDRS) dkim.c
+verify.o:        $(HDRS) transports/smtp.h verify.c
+dkim.o:          $(HDRS) pdkim/pdkim.h dkim.c
 
 # Dependencies for WITH_CONTENT_SCAN modules
 
 malware.o:       $(HDRS) malware.c
-mime.o:          $(HDRS) mime.c
+mime.o:          $(HDRS) mime.h mime.c
 regex.o:         $(HDRS) regex.c
 spam.o:          $(HDRS) spam.c
 spool_mbox.o:    $(HDRS) spool_mbox.c
 
 
-# Dependencies for WITH_OLD_DEMIME modules
-
-demime.o:        $(HDRS) demime.c
-
-
 # Dependencies for EXPERIMENTAL_* modules
 
-bmi_spam.o:      $(HDRS) bmi_spam.c
-spf.o:           $(HDRS) spf.h spf.c
-srs.o:           $(HDRS) srs.h srs.c
-dcc.o:           $(HDRS) dcc.h dcc.c
-dmarc.o:         $(HDRS) dmarc.h dmarc.c
+bmi_spam.o:    $(HDRS) bmi_spam.c
+dane.o:                $(HDRS) dane.c dane-gnu.c dane-openssl.c
+dcc.o:         $(HDRS) dcc.h dcc.c
+dmarc.o:       $(HDRS) pdkim/pdkim.h dmarc.h dmarc.c
+imap_utf7.o:   $(HDRS) imap_utf7.c
+spf.o:         $(HDRS) spf.h spf.c
+srs.o:         $(HDRS) srs.h srs.c
+utf8.o:                $(HDRS) utf8.c
 
 # The module containing tables of available lookups, routers, auths, and
 # transports must be rebuilt if any of them are. However, because the makefiles
@@ -716,12 +777,13 @@ sa-os.o:         $(HDRS) os.c
 # These are the test targets themselves
 
 test_dbfn:   config.h dbfn.c dummies.o sa-globals.o sa-os.o store.o \
-              string.o tod.o version.o
+              string.o tod.o version.o utf8.o
        $(CC) -c $(CFLAGS) $(INCLUDE) -DSTAND_ALONE dbfn.c
+       $(CC) -c $(CFLAGS) $(INCLUDE) -DCOMPILE_UTILITY store.c
        $(LNCC) -o test_dbfn $(LFLAGS) dbfn.o \
          dummies.o sa-globals.o sa-os.o store.o string.o \
-         tod.o version.o $(LIBS) $(DBMLIB)
-       rm -f dbfn.o
+         tod.o version.o utf8.o $(LIBS) $(DBMLIB) $(LDFLAGS)
+       rm -f dbfn.o store.o
 
 test_host:   config.h child.c host.c dns.c dummies.c sa-globals.o os.o \
               store.o string.o tod.o tree.o
@@ -734,23 +796,24 @@ test_host:   config.h child.c host.c dns.c dummies.c sa-globals.o os.o \
          tod.o tree.o $(LIBS) $(LIBRESOLV)
        rm -f child.o dummies.o host.o dns.o
 
-test_os:     os.h os.c dummies.o sa-globals.o store.o string.o tod.o
+test_os:     os.h os.c dummies.o sa-globals.o store.o string.o tod.o utf8.o
        $(CC) -c $(CFLAGS) $(INCLUDE) -DSTAND_ALONE os.c
        $(LNCC) -o test_os $(LFLAGS) os.o dummies.o \
-         sa-globals.o store.o string.o tod.o $(LIBS)
+         sa-globals.o store.o string.o tod.o utf8.o $(LIBS) $(LDFLAGS)
        rm -f os.o
 
 test_parse:  config.h parse.c dummies.o sa-globals.o \
-            store.o string.o tod.o version.o
+            store.o string.o tod.o version.o utf8.o
        $(CC) -c $(CFLAGS) $(INCLUDE) -DSTAND_ALONE parse.c
        $(LNCC) -o test_parse $(LFLAGS) parse.o \
-         dummies.o sa-globals.o store.o string.o tod.o version.o
+         dummies.o sa-globals.o store.o string.o tod.o version.o \
+         utf8.o $(LDFLAGS)
        rm -f parse.o
 
-test_string: config.h string.c dummies.o sa-globals.o store.o tod.o
+test_string: config.h string.c dummies.o sa-globals.o store.o tod.o utf8.o
        $(CC) -c $(CFLAGS) $(INCLUDE) -DSTAND_ALONE string.c
        $(LNCC) -o test_string $(LFLAGS) -DSTAND_ALONE string.o \
-         dummies.o sa-globals.o store.o tod.o $(LIBS)
+         dummies.o sa-globals.o store.o tod.o utf8.o $(LIBS) $(LDFLAGS)
        rm -f string.o
 
 # End
index 774fa4f..006e9fe 100644 (file)
@@ -2,8 +2,10 @@
 
 # This file provided by Pierre A. Humblet <Pierre.Humblet@ieee.org>
 
+HAVE_IPV6 = yes
 HAVE_ICONV = yes
-CFLAGS= -g -Wall -O2
+# Use c99 to have %z 
+CFLAGS= -g -Wall -std=c99 -U __STRICT_ANSI__
 LIBS= -lcrypt -lresolv
 LIBS_EXIM= -liconv
 EXIWHAT_PS_ARG=-as
@@ -24,7 +26,7 @@ LIBS_EXIM +=../Local/exim_res.o
 
 
 ##################################################
-# The following is normaly set in local/Makefile.
+# The following is normally set in local/Makefile.
 # Makefile.cygwin provides defaults with which the
 # precompiled version is built
 ##################################################
@@ -78,7 +80,6 @@ LOOKUP_PASSWD=yes
 LDAP_LIB_TYPE=OPENLDAP2
 LOOKUP_LIBS=-lldap -llber
 
-# WITH_OLD_DEMIME=yes
 WITH_CONTENT_SCAN=yes
 
 # It is important to define these variables but the values are always overridden
@@ -98,17 +99,15 @@ ZCAT_COMMAND=/usr/bin/zcat
 SUPPORT_PAM=yes
 CFLAGS += -DINCLUDE_PAM -I ../pam -I ../../pam
 
-APPENDFILE_MODE       = 0644     # default if no ntsec
-APPENDFILE_DIRECTORY_MODE = 0777
-APPENDFILE_LOCKFILE_MODE = 0666
-EXIMDB_DIRECTORY_MODE    = 0777
+# All modes are in octal and must start with 0
+EXIMDB_DIRECTORY_MODE    = 01777
 EXIMDB_MODE              = 0666
 EXIMDB_LOCKFILE_MODE     = 0666
-INPUT_DIRECTORY_MODE  = 0777
-LOG_DIRECTORY_MODE    = 0777
+INPUT_DIRECTORY_MODE  = 01777
+LOG_DIRECTORY_MODE    = 01777
 LOG_MODE              = 0666
-MSGLOG_DIRECTORY_MODE = 0777
-SPOOL_DIRECTORY_MODE  = 0777
-SPOOL_MODE            = 0666
+MSGLOG_DIRECTORY_MODE = 01777
+SPOOL_DIRECTORY_MODE  = 01777
+SPOOL_MODE            = 0600
 
 # End
index 60d5ea8..b3990fe 100644 (file)
@@ -186,14 +186,6 @@ EXIWHAT_KILL_SIGNAL=-USR1
 
 # IPV6_USE_INET_PTON=yes
 
-# Setting the next option brings in support for A6 DNS records for IPV6. These
-# were at one time expected to supplant AAAA records, but were eventually
-# rejected. The code remains in Exim, but has not been compiled or tested for
-# quite some time. Do not set this unless you know what you are doing.
-
-# SUPPORT_A6=yes
-
-
 # HOSTNAME_COMMAND contains the path to the "hostname" command, which varies
 # from OS to OS. This is used when building the Exim monitor script only. (See
 # also BASENAME_COMMAND.) If HOSTNAME_COMMAND is set to "look_for_it" then the
@@ -294,7 +286,7 @@ LOCAL_SCAN_SOURCE=src/local_scan.c
 
 #############################################################################
 # The following definitions are relevant only when compiling the Exim monitor
-# program, which requires an X11 display. See the varible EXIM_MONITOR in
+# program, which requires an X11 display. See the variable EXIM_MONITOR in
 # src/EDITME for how to suppress this compilation.
 
 # X11 contains the location of the X11 libraries and include files.
index ebb116b..7c6c064 100644 (file)
@@ -6,12 +6,16 @@ CHOWN_COMMAND=/usr/sbin/chown
 STRIP_COMMAND=/usr/bin/strip
 CHMOD_COMMAND=/bin/chmod
 
+# FreeBSD Ports no longer insert compatibility symlinks into /usr/bin for
+# scripting languages which traditionally have had them.
+PERL_COMMAND=/usr/local/bin/perl
+
 HAVE_SA_LEN=YES
 
 # crypt() is in a separate library
 LIBS=-lcrypt -lm -lutil
 
-# Dynamicly loaded modules need to be built with -fPIC
+# Dynamically loaded modules need to be built with -fPIC
 CFLAGS_DYNAMIC=-shared -rdynamic -fPIC
 
 # FreeBSD always ships with Berkeley DB
index 073d67a..ea35144 100644 (file)
@@ -22,4 +22,6 @@ EXIMON_TEXTPOP=
 DBMLIB=-lndbm
 RANLIB=@true
 
+OS_C_INCLUDES=setenv.c
+
 # End
index e60a6c0..568e99f 100644 (file)
@@ -19,4 +19,6 @@ XINCLUDE=-I$(X11)/include
 XLFLAGS=-L$(X11)/lib -R$(X11)/lib
 X11LIB=$(X11)/lib
 
+OS_C_INCLUDES=setenv.c
+
 # End
diff --git a/OS/os.c-BSDI b/OS/os.c-BSDI
new file mode 100644 (file)
index 0000000..3cef2ac
--- /dev/null
@@ -0,0 +1,19 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) 2016 Heiko Schlittermann <hs@schlittermann.de> */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* BSDI-specific code. This is concatenated onto the generic
+src/os.c file. */
+
+#ifndef OS_UNSETENV
+#define OS_UNSETENV
+
+int
+os_unsetenv(const unsigned char * name)
+{
+unsetenv((char *)name);
+return 0;
+}
diff --git a/OS/os.c-HP-UX b/OS/os.c-HP-UX
new file mode 100644 (file)
index 0000000..fdd8708
--- /dev/null
@@ -0,0 +1,16 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 2016 */
+/* Copyright (c) Jeremy Harris 2016 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* HP-UX-specific code. This is concatenated onto the generic
+src/os.c file. */
+
+#ifndef COMPILE_UTILITY
+# include "setenv.c"
+#endif
+
+/* End of os.c-SunHP-UX */
index df0dff9..4bca776 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1997 - 2001 */
+/* Copyright (c) University of Cambridge 1997 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Linux-specific code. This is concatenated onto the generic
diff --git a/OS/os.c-SunOS5 b/OS/os.c-SunOS5
new file mode 100644 (file)
index 0000000..1624869
--- /dev/null
@@ -0,0 +1,16 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 2016 */
+/* Copyright (c) Jeremy Harris 2016 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* Solaris-specific code. This is concatenated onto the generic
+src/os.c file. */
+
+#if defined(MISSING_UNSETENV_3) && !defined(COMPILE_UTILITY)
+# include "setenv.c"
+#endif
+
+/* End of os.c-SunOS5 */
index ea17a43..c9464aa 100644 (file)
@@ -2,8 +2,8 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Cygwin-specific code. December 2002
-   This is concatenated onto the generic src/os.c file.
+/* Cygwin-specific code. December 2002. Updated Jan 2015.
+   This is prefixed to the src/os.c file.
 
    This code was supplied by Pierre A. Humblet <Pierre.Humblet@ieee.org>
 */
@@ -18,23 +18,12 @@ int cygwin_mkdir( const char *path, mode_t mode )
   return mkdir(p, mode);
 }
 
-/* We have strsignal but cannot use #define
-   because types don't match */
-#define OS_STRSIGNAL /* src/os.c need not provide it */
-char * os_strsignal(int sig)
-{
-  return (char *) strsignal(sig);
-}
-
 #ifndef COMPILE_UTILITY /* Utilities don't need special code */
-#ifdef INCLUDE_MINIRES
-#include "../minires/minires.c"
-#include "../minires/os-interface.c"
-#endif
 
 #ifdef INCLUDE_PAM
 #include "../pam/pam.c"
 #endif
+#include <alloca.h>
 
 unsigned int cygwin_WinVersion;
 
@@ -47,23 +36,25 @@ unsigned int cygwin_WinVersion;
 #endif
 
 #include <windows.h>
+#include <ntstatus.h>
+#include <lmcons.h>
+
 #define EqualLuid(Luid1, Luid2) \
   ((Luid1.LowPart == Luid2.LowPart) && (Luid1.HighPart == Luid2.HighPart))
 #include <sys/cygwin.h>
 
 /* Special static variables */
 static BOOL cygwin_debug = FALSE;
-static int privileged = 1; /* when not privileged, setuid = noop */
+static int fakesetugid = 1; /* when not privileged, setugid = noop */
 
 #undef setuid
 int cygwin_setuid(uid_t uid )
 {
-  int res;
-  if (privileged <= 0) return 0;
-  else {
+  int res = 0;
+  if (fakesetugid == 0) { 
     res = setuid(uid);
     if (cygwin_debug)
-      fprintf(stderr, "setuid %lu %lu %d pid: %d\n",
+      fprintf(stderr, "setuid %u %u %d pid: %d\n",
               uid, getuid(),res, getpid());
   }
   return res;
@@ -72,12 +63,11 @@ int cygwin_setuid(uid_t uid )
 #undef setgid
 int cygwin_setgid(gid_t gid )
 {
-  int res;
-  if (privileged <= 0) return 0;
-  else {
+  int res = 0;
+  if (fakesetugid == 0) { 
     res = setgid(gid);
     if (cygwin_debug)
-      fprintf(stderr, "setgid %lu %lu %d pid: %d\n",
+      fprintf(stderr, "setgid %u %u %d pid: %d\n",
               gid, getgid(), res, getpid());
   }
   return res;
@@ -97,8 +87,8 @@ static void cygwin_setpriority()
    Next byte: 0
    Next byte: minor version of OS
    Low  byte: major version of OS (3 or 4 for for NT, 5 for 2000 and XP) */
-#define VERSION_IS_58M(x) (x & 0x80000000) /* 95, 98, Me   */
-#define VERSION_IS_NT(x)  ((x & 0XFF) < 5) /* NT 4 or 3.51 */
+//#define VERSION_IS_58M(x) (x & 0x80000000) /* 95, 98, Me   */
+//#define VERSION_IS_NT(x)  ((x & 0XFF) < 5) /* NT 4 or 3.51 */
 
 /*
   Routine to find if process or thread is privileged
@@ -106,7 +96,6 @@ static void cygwin_setpriority()
 
 enum {
   CREATE_BIT = 1,
-  RESTORE_BIT = 2
 };
 
 static DWORD get_privileges ()
@@ -132,15 +121,12 @@ static DWORD get_privileges ()
     for (i = 0; i < privs->PrivilegeCount; i++) {
       if (EqualLuid(privs->Privileges[i].Luid, cluid))
         ret |= CREATE_BIT;
-      else if (EqualLuid(privs->Privileges[i].Luid, rluid))
-        ret |= RESTORE_BIT;
-      else continue;
-      if (ret == (CREATE_BIT | RESTORE_BIT))
+      if (ret == (CREATE_BIT))
         break;
     }
   }
   else
-    fprintf(stderr, "has_create_token_privilege %ld\n", GetLastError());
+    fprintf(stderr, "has_create_token_privilege %u\n", GetLastError());
 
   if (hToken)
     CloseHandle(hToken);
@@ -148,17 +134,18 @@ static DWORD get_privileges ()
   return ret;
 }
 
-/* We use a special routine to initialize
-    cygwin_init is called from the OS_INIT macro in main(). */
-
-void cygwin_init(int argc, char ** argv, void * rup,
-                 void * eup, void * egp, void * cup, void * cgp)
+/* 
+  We use cygwin_premain to fake a few things 
+       and to provide some debug info 
+*/
+void cygwin_premain2(int argc, char ** argv, struct per_process * ptr)
 {
-  int i;
+  int i, res, is_daemon = 0, is_spoolwritable, is_privileged, is_eximuser;
   uid_t myuid, systemuid;
   gid_t mygid, adminsgid;
-  struct passwd * pwp;
-  char *cygenv, win32_path[MAX_PATH];
+  struct passwd * pwp = NULL;
+  struct stat buf;
+  char *cygenv;
   SID(1, SystemSid, SECURITY_LOCAL_SYSTEM_RID);
   SID(2, AdminsSid, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS);
   DWORD priv_flags;
@@ -174,77 +161,103 @@ void cygwin_init(int argc, char ** argv, void * rup,
   for (i = 1; i < argc; i++) {
     if (argv[i][0] == '-') {
       if (argv[i][1] == 'c') {
+        ssize_t size;
+        wchar_t *win32_path;
         argv[i][1] = 'n';  /* Replace -c by -n */
         cygwin_debug = TRUE;
-        fprintf(stderr, "CYGWIN = \"%s\".", cygenv);
-        cygwin_conv_to_win32_path("/", win32_path);
-        fprintf(stderr, " Root / mapped to %s.\n", win32_path);
+        fprintf(stderr, "CYGWIN = \"%s\".\n", cygenv);
+        if (((size = cygwin_conv_path(CCP_POSIX_TO_WIN_W,"/", win32_path, 0)) > 0)
+        && ((win32_path = malloc(size)) != NULL)
+         && (cygwin_conv_path(CCP_POSIX_TO_WIN_W,"/", win32_path, size) == 0)) {
+               fprintf(stderr, " Root / mapped to %ls.\n", win32_path);
+               free(win32_path);
+       }
       }
-      else if (argv[i][1] == 'b' && argv[i][2] == 'd')
+      else if (argv[i][1] == 'b' && argv[i][2] == 'd') {
+        is_daemon = 1;
         cygwin_setpriority();
     }
   }
-  if (VERSION_IS_58M(cygwin_WinVersion)) {
-    * (uid_t *) rup = myuid;  /* Pretend we are root */
-    * (uid_t *) eup = myuid;  /* ... and exim */
-    * (gid_t *) egp = mygid;
-    return;
   }
+
   /* Nt/2000/XP
-     We initially set the exim uid & gid to those of the "real exim",
+     We initially set the exim uid & gid to those of the "exim user",
        or to the root uid (SYSTEM) and exim gid (ADMINS),
      If privileged, we setuid to those.
      We always set the configure uid to the system uid.
      We always set the root uid to the real uid
-       to avoid useless execs following forks.
+       to allow exim imposed restrictions (bypassable by recompiling)
+       and to avoid exec that cause loss of privilege
      If not privileged and unable to chown,
        we set the exim uid to our uid.
-     If unprivileged, we fake all subsequent setuid. */
+     If unprivileged and /var/spool/exim is writable and not running as listening daemon, 
+       we fake all subsequent setuid. */
+
+  /* Get the system and admins uid from their sids */
+  if ((systemuid = cygwin_internal(CW_GET_UID_FROM_SID, & SystemSid)) == -1) {
+       fprintf(stderr, "Cannot map System sid. Aborting\n");
+       exit(1);
+  }
+  if ((adminsgid = cygwin_internal(CW_GET_GID_FROM_SID, & AdminsSid)) == -1) {
+       fprintf(stderr, "Cannot map Admins sid. Aborting\n");
+       exit(1);
+  }
 
   priv_flags = get_privileges ();
-  privileged = !!(priv_flags & CREATE_BIT);
-
-  /* Get the system and admins uid from their sids,
-     or use the default values from the Makefile. */
-  if ((systemuid = cygwin_internal(CW_GET_UID_FROM_SID, & SystemSid)) == -1)
-    systemuid = * (uid_t *) eup;
-  if ((adminsgid = cygwin_internal(CW_GET_GID_FROM_SID, & AdminsSid)) == -1)
-    adminsgid = * (gid_t *) egp;
-
-  if ((pwp = getpwnam("exim")) != NULL) {
-    * (uid_t *) eup = pwp->pw_uid;  /* Set it according to passwd */
-    * (gid_t *) egp = pwp->pw_gid;
+  is_privileged = !!(priv_flags & CREATE_BIT);
+
+  /* Call getpwnam for account exim after getting the local exim name */
+  char exim_username[DNLEN + UNLEN + 2];
+  if (cygwin_internal(CW_CYGNAME_FROM_WINNAME, "exim", exim_username, sizeof exim_username) != 0)
+     pwp = getpwnam (exim_username);
+
+  /* If cannot setuid to exim or and is not the daemon (which is assumed to be
+     able to chown or to be the exim user) set the exim ugid to our ugid to avoid
+     chown failures after creating files and to be able to setuid to exim in 
+     exim.c ( "privilege not needed" ). */
+  if ((is_privileged == 0) && (!is_daemon)) {
+    exim_uid = myuid;
+    exim_gid = mygid;
+  }
+  else if (pwp != NULL) {
+    exim_uid = pwp->pw_uid;  /* Set it according to passwd */
+    exim_gid = pwp->pw_gid;
+    is_eximuser = 1;
   }
   else {
-    * (uid_t *) eup = systemuid;
-    * (gid_t *) egp = adminsgid;
+    exim_uid = systemuid;
+    exim_gid = adminsgid;
+    is_eximuser = 0;
   }
 
-  /* Set the configuration uid and gid to the system uid and admins gid.
-     Note that exim uid is also accepted as owner of exim.conf. */
-  * (uid_t *) cup = systemuid;
-  * (gid_t *) cgp = adminsgid;
+  res = stat("/var/spool/exim", &buf);
+  /* Check if writable (and can be stat) */
+  is_spoolwritable = ((res == 0) && ((buf.st_mode & S_IWOTH) != 0));
+
+  fakesetugid = (is_privileged == 0) && (is_daemon == 0) && (is_spoolwritable == 1);
 
-  if (privileged) {             /* Can setuid */
-    if (cygwin_setgid(* (gid_t *) egp) /* Setuid to exim */
-        || cygwin_setuid(* (uid_t *) eup))
-      privileged = -1;          /* Problem... Perhaps not in 544 */
+  if (is_privileged) {             /* Can setuid */
+     if (cygwin_setgid(exim_gid) /* Setuid to exim */
+         || cygwin_setuid(exim_uid)) {
+          fprintf(stderr, "Unable to setuid/gid to exim. priv_flags: %x\n", priv_flags);
+          exit(0);          /* Problem... Perhaps not in 544 */
+     }
   }
 
-  /* Pretend we are root to avoid useless execs.
-     We are limited by file access rights */
-  * (uid_t *) rup = getuid ();
+  /* Set the configuration file uid and gid to the system uid and admins gid. */
+  config_uid = systemuid;
+  config_gid = adminsgid;
 
-  /* If we have not setuid to exim and cannot chown,
-     set the exim uid to our uid to avoid chown failures */
-  if (privileged <= 0 && !(priv_flags & RESTORE_BIT))
-    * (uid_t *) eup = * (uid_t *) rup;
+  /* Pretend we are root to avoid useless exec
+     and avoid exim set limitations.
+     We are limited by file access rights */
+  root_uid = getuid ();
 
   if (cygwin_debug) {
-    fprintf(stderr, "Starting uid %ld, gid %ld, ntsec %lu, privileged %d.\n",
-            myuid, mygid, cygwin_internal(CW_CHECK_NTSEC, NULL), privileged);
-    fprintf(stderr, "root_uid %ld, exim_uid %ld, exim_gid %ld, config_uid %ld, config_gid %ld.\n",
-            * (uid_t *) rup, * (uid_t *) eup, * (gid_t *) egp, * (uid_t *) cup, * (gid_t *) cgp);
+    fprintf(stderr, "Starting uid %u, gid %u, priv_flags %x, is_privileged %d, is_daemon %d, is_spoolwritable %d.\n",
+            myuid, mygid, priv_flags, is_privileged, is_daemon, is_spoolwritable);
+    fprintf(stderr, "root_uid %u, exim_uid %u, exim_gid %u, config_uid %u, config_gid %u, is_eximuser %d.\n",
+            root_uid, exim_uid, exim_gid, config_uid, config_gid, is_eximuser);
   }
   return;
 }
@@ -253,24 +266,15 @@ void cygwin_init(int argc, char ** argv, void * rup,
 #define OS_LOAD_AVERAGE /* src/os.c need not provide it */
 
 /*****************************************************************
- *
  Functions for average load measurements
 
- There are two methods, which work only on NT.
+ Uses NtQuerySystemInformation.
+ This requires definitions that are not part of
+ standard include files.
 
- The first one uses the HKEY_PERFORMANCE_DATA registry to
- get performance data. It is complex but well documented
- and works on all NT versions.
-
- The second one uses NtQuerySystemInformation.
- Its use is discouraged starting with WinXP.
-
- Until 4.43, the Cygwin port of exim was using the first
- method.
-
-*****************************************************************/
-#define PERF_METHOD2
+ This is discouraged starting with WinXP.
 
+*************************************************************/
 /* Structure to compute the load average efficiently */
 typedef struct {
   DWORD Lock;
@@ -279,11 +283,6 @@ typedef struct {
   unsigned long long LastCounter; /* Last measurement counter */
   unsigned long long PerfFreq;    /* Perf counter frequency */
   int LastLoad;                   /* Last reported load, or -1 */
-#ifdef PERF_METHOD1
-  PPERF_DATA_BLOCK PerfData;      /* Pointer to a buffer to get the data */
-  DWORD BufferSize;               /* Size of PerfData */
-  LPSTR * NamesArray;             /* Temporary (malloc) buffer for index */
-#endif
 } cygwin_perf_t;
 
 static struct {
@@ -292,317 +291,6 @@ static struct {
    cygwin_perf_t *perf;
 } cygwin_load = {NULL, 0, NULL};
 
-#ifdef PERF_METHOD1
-/*************************************************************
- METHOD 1
-
- Obtaining statistics in Windows is done at a low level by
- calling registry functions, in particular the key
- HKEY_PERFORMANCE_DATA on NT and successors.
- Something equivalent exists on Win95, see Microsoft article
- HOWTO: Access the Performance Registry Under Windows 95 (KB 174631)
- but it is not implemented here.
-
- The list of objects to be polled is specified in the string
- passed to RegQueryValueEx in ReadStat() below.
- On NT, all objects are polled even if info about only one is
- required. This is fixed in Windows 2000. See articles
- INFO: Perflib Calling Close Procedure in Windows 2000 (KB 270127)
- INFO: Performance Data Changes Between Windows NT 4.0 and Windows
- 2000 (KB 296523)
-
- It is unclear to me how the counters are primarily identified.
- Whether it's by name strings or by the offset of their strings
- as mapped in X:\Winnt\system32\perfc009.dat [or equivalently as
- reported by the registry functions in GetNameStrings( ) below].
- Microsoft documentation seems to say that both methods should
- work.
-
- In the interest of speed and language independence, the main
- code below relies on offsets. However if debug is enabled, the
- code verifies that the names of the corresponding strings are
- as expected.
-
-*****************************************************************/
-
-/* Object and counter indices and names */
-#define PROCESSOR_OBJECT_INDEX 238
-#define PROCESSOR_OBJECT_STRING "238"
-#define PROCESSOR_OBJECT_NAME "Processor"
-#define PROCESSOR_TIME_COUNTER 6
-#define PROCESSOR_TIME_NAME "% Processor Time"
-
-#define BYTEINCREMENT 800    /* Block to add to PerfData */
-
-/*****************************************************************
- *
- Macros to navigate through the performance data.
-
- *****************************************************************/
-#define FirstObject(PerfData)\
-  ((PPERF_OBJECT_TYPE)((PBYTE)PerfData + PerfData->HeaderLength))
-#define NextObject(PerfObj)\
-  ((PPERF_OBJECT_TYPE)((PBYTE)PerfObj + PerfObj->TotalByteLength))
-#define ObjectCounterBlock(PerfObj)\
-  ((PPERF_COUNTER_BLOCK)(PBYTE)PerfObj + PerfObj->DefinitionLength )
-#define FirstInstance(PerfObj )\
-  ((PPERF_INSTANCE_DEFINITION)((PBYTE)PerfObj + PerfObj->DefinitionLength))
-#define InstanceCounterBlock(PerfInst)\
-  ((PPERF_COUNTER_BLOCK) ((PBYTE)PerfInst + PerfInst->ByteLength ))
-#define NextInstance(PerfInst )\
-  ((PPERF_INSTANCE_DEFINITION)((PBYTE)InstanceCounterBlock(PerfInst) + \
-        InstanceCounterBlock(PerfInst)->ByteLength) )
-#define FirstCounter(PerfObj)\
-  ((PPERF_COUNTER_DEFINITION) ((PBYTE)PerfObj + PerfObj->HeaderLength))
-#define NextCounter(PerfCntr)\
-  ((PPERF_COUNTER_DEFINITION)((PBYTE)PerfCntr + PerfCntr->ByteLength))
-
-/*****************************************************************
- *
- Load the counter and object names from the registry
- to cygwin_load.perf->NameStrings
- and index them in cygwin_load.perf->NamesArray
-
- NameStrings seems to be taken from the file
- X:\Winnt\system32\perfc009.dat
-
- This is used only for name verification during initialization,
- if DEBUG(D_load) is TRUE.
-
-*****************************************************************/
-static BOOL GetNameStrings( )
-{
-  HKEY hKeyPerflib;      // handle to registry key
-  DWORD dwArraySize;     // size for array
-  DWORD dwNamesSize;     // size for strings
-  LPSTR lpCurrentString; // pointer for enumerating data strings
-  DWORD dwCounter;       // current counter index
-  LONG  res;
-
-  /* Get the number of Counter items into dwArraySize. */
-  if ((res = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
-                           "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Perflib",
-                           0,
-                           KEY_QUERY_VALUE, /* KEY_READ, */
-                           &hKeyPerflib))
-      != ERROR_SUCCESS) {
-    DEBUG(D_load) debug_printf("RegOpenKeyEx (1): error %ld (Windows)\n", res);
-    return FALSE;
-  }
-  dwNamesSize = sizeof(dwArraySize); /* Temporary reuse */
-  if ((res = RegQueryValueEx( hKeyPerflib,
-                              "Last Counter",
-                              NULL,
-                              NULL,
-                              (LPBYTE) &dwArraySize,
-                              &dwNamesSize ))
-      != ERROR_SUCCESS) {
-    DEBUG(D_load) debug_printf("RegQueryValueEx (1): error %ld (Windows)\n", res);
-    return FALSE;
-  }
-  RegCloseKey( hKeyPerflib );
-  /* Open the key containing the counter and object names. */
-  if ((res = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
-                           "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Perflib\\009",
-                           0,
-                           KEY_READ,
-                           &hKeyPerflib))
-      != ERROR_SUCCESS) {
-    DEBUG(D_load) debug_printf("RegOpenKeyEx (2): error %ld (Windows)\n", res);
-    return FALSE;
-  }
-  /* Get the size of the Counter value in the key
-     and then read the value in the tail of NamesArray */
-  dwNamesSize = 0;
-  lpCurrentString = NULL;
-  while (1) {
-    res = RegQueryValueEx( hKeyPerflib,
-                           "Counter",
-                           NULL,
-                           NULL,
-                           (unsigned char *) lpCurrentString,
-                           &dwNamesSize);
-    if ((res == ERROR_SUCCESS) && /* Bug (NT 4.0): SUCCESS was returned on first call */
-        (cygwin_load.perf->NamesArray != NULL)) break;
-    if ((res == ERROR_SUCCESS) || /* but cygwin_load.perf->NamesArrays == NULL */
-        (res == ERROR_MORE_DATA)) {
-      /* Allocate memory BOTH for the names array and for the counter and object names */
-      if ((cygwin_load.perf->NamesArray =
-           (LPSTR *) malloc( (dwArraySize + 1) * sizeof(LPSTR) + dwNamesSize * sizeof(CHAR)))
-          != NULL) {
-        /* Point to area for the counter and object names */
-        lpCurrentString = (LPSTR) & cygwin_load.perf->NamesArray[dwArraySize + 1];
-        continue;
-      }
-      DEBUG(D_load) debug_printf("Malloc: errno %d (%s)\n", errno, strerror(errno));
-    }
-    else { /* Serious error */
-      DEBUG(D_load) debug_printf("RegQueryValueEx (2): error %ld (Windows)\n", res);
-    }
-    return FALSE;
-  }
-  RegCloseKey( hKeyPerflib );
-  /* Index the names into an array. */
-  while (*lpCurrentString) {
-    dwCounter = atol( lpCurrentString );
-    lpCurrentString += (lstrlen(lpCurrentString)+1);
-    cygwin_load.perf->NamesArray[dwCounter] = lpCurrentString;
-    lpCurrentString += (strlen(lpCurrentString)+1);
-  }
-  return TRUE;
-}
-
-/*****************************************************************
- *
- Find the value of the Processor Time counter
-
-*****************************************************************/
-static BOOL ReadTimeCtr(PPERF_OBJECT_TYPE PerfObj,
-                        PPERF_COUNTER_DEFINITION CurCntr,
-                        PPERF_COUNTER_BLOCK PtrToCntr,
-                        unsigned long long * TimePtr){
-  int j;
-  /* Scan all counters. */
-  for( j = 0; j < PerfObj->NumCounters; j++ ) {
-    if (CurCntr->CounterNameTitleIndex == PROCESSOR_TIME_COUNTER) {
-      /* Verify it is really the proc time counter */
-      if ((CurCntr->CounterType != PERF_100NSEC_TIMER_INV) || /* Wrong type */
-          ((cygwin_load.perf->NamesArray != NULL) &&                  /* Verify name */
-           (strcmp(cygwin_load.perf->NamesArray[CurCntr->CounterNameTitleIndex],
-                   PROCESSOR_TIME_NAME)))) {
-        log_write(0, LOG_MAIN|LOG_PANIC,
-                  "Incorrect Perf counter type or name %x %s",
-                  (unsigned) CurCntr->CounterType,
-                  cygwin_load.perf->NamesArray[CurCntr->CounterNameTitleIndex]);
-        return FALSE;
-      }
-      *TimePtr += *(unsigned long long int *) ((PBYTE) PtrToCntr + CurCntr->CounterOffset);
-      return TRUE; /* return TRUE as soon as we found the counter */
-    }
-    /* Get the next counter. */
-    CurCntr = NextCounter( CurCntr );
-  }
-  return FALSE;
-}
-
-/*****************************************************************
- *
- ReadStat()
- Measures current Time100ns and IdleCount
- Return TRUE if success.
-
- *****************************************************************/
-static BOOL ReadStat(unsigned long long int *Time100nsPtr,
-                     unsigned long long int * IdleCountPtr)
-{
-  PPERF_OBJECT_TYPE PerfObj;
-  PPERF_INSTANCE_DEFINITION PerfInst;
-  PPERF_COUNTER_DEFINITION PerfCntr;
-  PPERF_COUNTER_BLOCK PtrToCntr;
-  DWORD i, k, res;
-
-  /* Get the performance data for the Processor object
-     There is no need to open a key.
-     We may need to blindly increase the buffer size.
-     BufferSize does not return info but may be changed */
-  while (1) {
-    DWORD BufferSize = cygwin_load.perf->BufferSize;
-    res = RegQueryValueEx( HKEY_PERFORMANCE_DATA,
-                           PROCESSOR_OBJECT_STRING,
-                           NULL,
-                           NULL,
-                           (LPBYTE) cygwin_load.perf->PerfData,
-                           &BufferSize );
-    if (res == ERROR_SUCCESS) break;
-    if (res == ERROR_MORE_DATA ) {
-      /* Increment if necessary to get a buffer that is big enough. */
-      cygwin_load.perf->BufferSize += BYTEINCREMENT;
-      if ((cygwin_load.perf->PerfData =
-           (PPERF_DATA_BLOCK) realloc( cygwin_load.perf->PerfData, cygwin_load.perf->BufferSize ))
-          != NULL) continue;
-      DEBUG(D_load) debug_printf("Malloc: errno %d (%s)\n", errno, strerror(errno));
-    }
-    else { /* Serious error */
-      DEBUG(D_load) debug_printf("RegQueryValueEx (3): error %ld (Windows)\n", res);
-    }
-    return FALSE;
-  }
-  /* Initialize the counters */
-  *Time100nsPtr = 0;
-  *IdleCountPtr = 0;
-  /* We should only have one object, but write general code just in case. */
-  PerfObj = FirstObject( cygwin_load.perf->PerfData );
-  for( i = 0; i < cygwin_load.perf->PerfData->NumObjectTypes; i++ ) {
-    /* We are only interested in the processor object */
-    if ( PerfObj->ObjectNameTitleIndex == PROCESSOR_OBJECT_INDEX) {
-      /* Possibly verify it is really the Processor object. */
-      if ((cygwin_load.perf->NamesArray != NULL) &&
-          (strcmp(cygwin_load.perf->NamesArray[PerfObj->ObjectNameTitleIndex],
-                  PROCESSOR_OBJECT_NAME))) {
-        log_write(0, LOG_MAIN|LOG_PANIC,
-                  "Incorrect Perf object name %s",
-                  cygwin_load.perf->NamesArray[PerfObj->ObjectNameTitleIndex]);
-        return FALSE;
-      }
-      /* Get the first counter */
-      PerfCntr = FirstCounter( PerfObj );
-      /* See if the object has instances.
-         It should, but write general code. */
-      if( PerfObj->NumInstances != PERF_NO_INSTANCES ) {
-        PerfInst = FirstInstance( PerfObj );
-        for( k = 0; k < PerfObj->NumInstances; k++ ) {
-          /* There can be several processors.
-             Accumulate both the Time100ns and the idle counter.
-             Starting with Win2000 there is an instance named "_Total".
-             Do not use it.     We only use instances with a single
-             character in the name.
-             If we examine the object names, we also look at the instance
-             names and their lengths and issue reports */
-          if ( cygwin_load.perf->NamesArray != NULL) {
-            CHAR ascii[30]; /* The name is in unicode */
-            wsprintf(ascii,"%.29lS",
-                     (char *)((PBYTE)PerfInst + PerfInst->NameOffset));
-            log_write(0, LOG_MAIN,
-                      "Perf: Found processor instance \"%s\", length %d",
-                      ascii, PerfInst->NameLength);
-            if ((PerfInst->NameLength != 4) &&
-                (strcmp(ascii, "_Total") != 0)) {
-              log_write(0, LOG_MAIN|LOG_PANIC,
-                        "Perf: WARNING: Unexpected processor instance name");
-              return FALSE;
-            }
-          }
-          if (PerfInst->NameLength == 4) {
-            *Time100nsPtr += cygwin_load.perf->PerfData->PerfTime100nSec.QuadPart;
-            PtrToCntr = InstanceCounterBlock(PerfInst);
-            if (! ReadTimeCtr(PerfObj, PerfCntr, PtrToCntr, IdleCountPtr)) {
-              return FALSE;
-            }
-          }
-          PerfInst = NextInstance( PerfInst );
-        }
-        return (*Time100nsPtr != 0); /* Something was read */
-      }
-      else { /* No instance, just the counter data */
-        *Time100nsPtr = cygwin_load.perf->PerfData->PerfTime100nSec.QuadPart;
-        PtrToCntr = ObjectCounterBlock(PerfObj);
-        return ReadTimeCtr(PerfObj, PerfCntr, PtrToCntr, IdleCountPtr);
-      }
-    }
-    PerfObj = NextObject( PerfObj );
-  }
-  return FALSE; /* Did not find the Processor object */
-}
-
-#elif defined(PERF_METHOD2)
-
-/*************************************************************
-  METHOD 2
-
-  Uses NtQuerySystemInformation.
-  This requires definitions that are not part of
-  standard include files.
-*************************************************************/
 #include <ntdef.h>
 
 typedef enum _SYSTEM_INFORMATION_CLASS
@@ -669,10 +357,9 @@ static BOOL LoadNtdll()
     return TRUE;
 
   DEBUG(D_load)
-    debug_printf("perf: load: %ld (Windows)\n", GetLastError());
+    debug_printf("perf: load: %u (Windows)\n", GetLastError());
   return FALSE;
 }
-
 /*****************************************************************
  *
  ReadStat()
@@ -694,7 +381,7 @@ static BOOL ReadStat(unsigned long long int *Time100nsPtr,
                                       (PVOID) &sbi, sizeof sbi, NULL))
       != STATUS_SUCCESS) {
     DEBUG(D_load)
-      debug_printf("Perf: NtQuerySystemInformation: %lu (Windows)\n",
+      debug_printf("Perf: NtQuerySystemInformation: %u (Windows)\n",
                    RtlNtStatusToDosError(ret));
   }
   else if (!(spt = (PSYSTEM_PROCESSOR_TIMES) alloca(sizeof(spt[0]) * sbi.NumberProcessors))) {
@@ -705,7 +392,7 @@ static BOOL ReadStat(unsigned long long int *Time100nsPtr,
                                            sizeof spt[0] * sbi.NumberProcessors, NULL))
            != STATUS_SUCCESS) {
     DEBUG(D_load)
-      debug_printf("Perf: NtQuerySystemInformation: %lu (Windows)\n",
+      debug_printf("Perf: NtQuerySystemInformation: %u (Windows)\n",
                    RtlNtStatusToDosError(ret));
   }
   else {
@@ -719,7 +406,6 @@ static BOOL ReadStat(unsigned long long int *Time100nsPtr,
   }
   return FALSE;
 }
-#endif /* PERF_METHODX */
 
 /*****************************************************************
  *
@@ -736,14 +422,6 @@ static void InitLoadAvg(cygwin_perf_t *this)
   QueryPerformanceFrequency((LARGE_INTEGER *)& this->PerfFreq);
   QueryPerformanceCounter((LARGE_INTEGER *)& this->LastCounter);
 
-#ifdef PERF_METHOD1
-  DEBUG(D_load) {
-    /* Get the name strings through the registry
-       to verify that the object and counter numbers
-       have the names we expect */
-    success = GetNameStrings();
-  }
-#endif
   /* Get initial values for Time100ns and IdleCount */
   success = success
             && ReadStat( & this->Time100ns,
@@ -754,13 +432,6 @@ static void InitLoadAvg(cygwin_perf_t *this)
     log_write(0, LOG_MAIN, "Cannot obtain Load Average");
     this->LastLoad = -1;
   }
-#ifdef PERF_METHOD1
-  /* Free the buffer created for debug name verification */
-  if (this->NamesArray != NULL) {
-    free(this->NamesArray);
-    this->NamesArray = NULL;
-  }
-#endif
 }
 
 
@@ -791,24 +462,22 @@ int os_getloadavg()
     BOOL new;
     cygwin_load.pid = newpid;
 
-#ifdef PERF_METHOD2
     if (!LoadNtdll()) {
       log_write(0, LOG_MAIN, "Cannot obtain Load Average");
       cygwin_load.perf = NULL;
       return -1;
     }
-#endif
 
     if ((new = !cygwin_load.handle)) {
       cygwin_load.handle = CreateFileMapping (INVALID_HANDLE_VALUE, &sa, PAGE_READWRITE,
                                               0, sizeof(cygwin_perf_t), NULL);
       DEBUG(D_load)
-        debug_printf("Perf: CreateFileMapping: handle %x\n", (unsigned) cygwin_load.handle);
+        debug_printf("Perf: CreateFileMapping: handle %p\n", (void *) cygwin_load.handle);
     }
     cygwin_load.perf = (cygwin_perf_t *) MapViewOfFile (cygwin_load.handle,
                                                         FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
     DEBUG(D_load)
-      debug_printf("Perf: MapViewOfFile: addr %x\n", (unsigned) cygwin_load.perf);
+      debug_printf("Perf: MapViewOfFile: addr %p\n", (void *) cygwin_load.perf);
     if (new && cygwin_load.perf)
       InitLoadAvg(cygwin_load.perf);
   }
index f3a84f2..5cd4501 100644 (file)
@@ -20,4 +20,8 @@
 
 typedef struct flock flock_t;
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
+
 /* End */
index cd91936..a1705ec 100644 (file)
@@ -5,7 +5,11 @@
 #define HAVE_MMAP
 #define HAVE_SYS_MOUNT_H
 #define SIOCGIFCONF_GIVES_ADDR
+#define OS_UNSETENV
 
 typedef struct flock flock_t;
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
index 838ddd9..9040f0e 100644 (file)
@@ -22,4 +22,7 @@ forego the detection of some source-routing based IP attacks. */
 
 #define NO_IP_OPTIONS
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
index 559003f..f408740 100644 (file)
@@ -42,4 +42,7 @@ updating Exim to use the newer interface. */
 #define OFF_T_FMT "%lld"
 #define LONGLONG_T long int
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
index 669bb23..4c2f1d5 100644 (file)
@@ -7,4 +7,7 @@
 
 typedef struct flock flock_t;
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
index c5ed042..bf43e0a 100644 (file)
 
 typedef struct flock flock_t;
 
+/* iconv arg2 type: libiconv in Ports uses "const char* * inbuf" and was
+ * traditionally the only approach available.  The iconv functionality
+ * in libc is "char ** restrict src".
+ *
+ * <https://www.freebsd.org/doc/en/books/porters-handbook/using-iconv.html>
+ * says that libc has iconv since 2013, in 10-CURRENT.  FreeBSD man-pages
+ * shows it included in 10.0-RELEASE.  Writing this in 2017, 10.3 is the
+ * oldest supported release, so we should assume non-libiconv by default.
+ * (Actually, people still using old releases past EOL; we shouldn't support
+ * them but I don't want to deal with howls of complaints because we dare
+ * to not support the unsupported, so guard this on FreeBSD 10+)
+ *
+ * Thus we no longer override iconv.
+ *
+ * However, if libiconv is installed, and anything adds /usr/local/include
+ * to include-path (likely) then we'll get that.  So define a variable
+ * which makes the libiconv try to not interfere with OS iconv.
+ */
+#if __FreeBSD__ >= 10
+# define LIBICONV_PLUG
+#endif
+/* for more specific version constraints, include <sys/param.h> and look at
+ * __FreeBSD_version */
+
 /* End */
index cc4da0e..4499316 100644 (file)
@@ -17,4 +17,7 @@ typedef struct flock flock_t;
 
 /* Hurd-specific bits below */
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
index e60690f..ab35031 100644 (file)
@@ -19,4 +19,7 @@ typedef struct flock flock_t;
 #define HAVE_SYS_MOUNT_H
 #define SIOCGIFCONF_GIVES_ADDR
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
index 121f2d3..bc3bc25 100644 (file)
@@ -19,4 +19,7 @@ typedef struct flock flock_t;
 #define HAVE_SYS_MOUNT_H
 #define SIOCGIFCONF_GIVES_ADDR
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
index 76bd429..0f50fb6 100644 (file)
@@ -6,4 +6,7 @@ typedef struct flock           flock_t;
 #define F_FREESP               O_TRUNC
 #define DN_EXPAND_ARG4_TYPE    u_char *
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
index 97b83ed..f3df963 100644 (file)
@@ -15,4 +15,7 @@
 
 typedef struct flock flock_t;
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
index 87e4dfc..4998734 100644 (file)
@@ -1,6 +1,5 @@
 /* Exim: OS-specific C header file for HP-UX versions greater than 9 */
 
-#define ICONV_ARG2_TYPE char **
 #define EXIM_SOCKLEN_T size_t
 
 #define LOAD_AVG_NEEDS_ROOT
@@ -24,4 +23,12 @@ typedef struct __res_state *res_state;
 
 #define strtoll(a,b,c) strtoimax(a,b,c)
 
+/* Determined by sockaddr_un */
+
+struct sockaddr_storage
+{
+  short ss_family;
+  char __ss_padding[92];
+};
+
 /* End */
index dab965e..5a260d6 100644 (file)
@@ -17,4 +17,7 @@
 
 typedef struct flock flock_t;
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
index ac5a6b3..1d4bf46 100644 (file)
@@ -14,7 +14,4 @@
 #define F_FAVAIL        f_favail
 #define vfork fork
 
-/* Other OS have "const" in here */
-#define ICONV_ARG2_TYPE char **
-
 /* End */
index c41a234..bf30767 100644 (file)
@@ -13,7 +13,4 @@
 #define F_FAVAIL        f_favail
 #define vfork fork
 
-/* Other OS have "const" in here */
-#define ICONV_ARG2_TYPE char **
-
 /* End */
index 0196931..90f1c58 100644 (file)
@@ -15,7 +15,4 @@
 #define F_FAVAIL        f_favail
 #define vfork fork
 
-/* Other OS have "const" in here */
-#define ICONV_ARG2_TYPE char **
-
 /* End */
index 683c66a..4b248fe 100644 (file)
@@ -13,7 +13,4 @@
 #define F_FAVAIL        f_favail
 #define vfork fork
 
-/* Other OS have "const" in here */
-#define ICONV_ARG2_TYPE char **
-
 /* End */
index 3fead17..cc1cef9 100644 (file)
@@ -44,9 +44,6 @@ storage" as quickly as Exim thinks they are. */
 
 #define NEED_SYNC_DIRECTORY
 
-/* Other OS have "const" in here */
-#define ICONV_ARG2_TYPE char **
-
 #define os_find_running_interfaces os_find_running_interfaces_linux
 
 /* Need a prototype for the Linux-specific function. The structure hasn't
@@ -68,5 +65,15 @@ then change the 0 to 1 in the next block. */
 # define LLONG_MAX LONG_LONG_MAX
 #endif
 
+#if _POSIX_C_SOURCE >= 200809L || _ATFILE_SOUCE
+# define EXIM_HAVE_OPENAT
+#endif
+
+#include <netinet/tcp.h>       /* for TCP_FASTOPEN */
+#include <sys/socket.h>                /* for MSG_FASTOPEN */
+#if defined(TCP_FASTOPEN) && !defined(MSG_FASTOPEN)
+# define MSG_FASTOPEN 0x20000000
+#endif
+
 
 /* End */
index 19a8ac0..d2d3e0d 100644 (file)
@@ -22,4 +22,7 @@ typedef struct flock flock_t;
 #define HAVE_SYS_STATVFS_H
 #endif
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
index f04a5b7..6b5fa49 100644 (file)
@@ -13,7 +13,4 @@ changed. */
 /* Still not "socklen_t", which is the most common setting */
 #define EXIM_SOCKLEN_T       int
 
-/* The default for this is "const char **" */
-#define ICONV_ARG2_TYPE      char **
-
 /* End */
index 55bade6..5d55a96 100644 (file)
@@ -5,6 +5,13 @@
 #define HAVE_SYS_MOUNT_H
 #define SIOCGIFCONF_GIVES_ADDR
 #define HAVE_ARC4RANDOM
+/* In May 2014, OpenBSD 5.5 was released which cleaned up the arc4random_* API
+   which removed the arc4random_stir() function. Set NOT_HAVE_ARC4RANDOM_STIR
+   if the version released is past that point. */
+#include <sys/param.h>
+#if OpenBSD >= 201405
+#define NOT_HAVE_ARC4RANDOM_STIR
+#endif
 
 typedef struct flock flock_t;
 
@@ -13,4 +20,11 @@ typedef struct flock flock_t;
 
 typedef struct __res_state *res_state;
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
+#ifndef EPROTO
+# define EPROTO 71
+#endif
+
 /* End */
index 90be8d5..67d1063 100644 (file)
@@ -13,4 +13,7 @@
 #define _SVID3
 #define NEED_H_ERRNO
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
index 106b0a6..798f799 100644 (file)
@@ -18,4 +18,7 @@ doesn't have/need this header file. From Karsten P. Hoffmann. */
 
 extern int h_errno;
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
index 07d21bd..e5e915e 100644 (file)
@@ -15,4 +15,7 @@
 #define _SVID3
 #define NEED_H_ERRNO
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
index 486fcbe..0ca29f7 100644 (file)
@@ -13,4 +13,7 @@
 #define _SVID3
 #define NEED_H_ERRNO
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
index b0deefc..6555620 100644 (file)
@@ -33,4 +33,7 @@ flag causes this to get done in exim.h. */
 
 #define FUDGE_GETC_AND_FRIENDS
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
index 8bc0799..dfbd8f1 100644 (file)
@@ -28,4 +28,24 @@ it seems. */
 
 #define PAM_CONVERSE_ARG2_TYPE  struct pam_message
 
+
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
+#if _POSIX_C_SOURCE < 200112L
+# define MISSING_UNSETENV_3
+#endif
+
+
+/* SunOS5 doesn't accept getcwd(NULL, 0) to auto-allocate
+a buffer */
+
+#define OS_GETCWD
+
+
+#ifndef MIN
+# define MIN(a,b) (((a)<(b))?(a):(b))
+# define MAX(a,b) (((a)>(b))?(a):(b))
+#endif
+
 /* End */
index 044e09b..cd9e877 100644 (file)
@@ -8,4 +8,7 @@
 #define LOAD_AVG_SYMBOL       "avenrun_1min"
 #define LOAD_AVG_FIELD         value.ul
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
index 9985af2..08db5ae 100644 (file)
@@ -12,4 +12,7 @@ a minority operating system, easiest just to say "no" until someone asks. */
 #define NO_OPENLOG
 typedef struct flock flock_t;
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
index 9ad824a..4943a07 100644 (file)
@@ -19,4 +19,7 @@
 #define _SVID3
 #define NEED_H_ERRNO
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
index 1c780ee..e769220 100644 (file)
@@ -13,4 +13,7 @@
 #define _SVID3
 #define NEED_H_ERRNO
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
index 1592094..4d3ed42 100644 (file)
@@ -2,7 +2,6 @@
 
 #define NO_SYSEXITS
 
-#define ICONV_ARG2_TYPE char **
 #define EXIM_SOCKLEN_T size_t
 
 #define LOAD_AVG_NEEDS_ROOT
index 740300a..6ef59e0 100644 (file)
@@ -1,19 +1,7 @@
 /* Exim: OS-specific C header file for Cygwin */
 
-/* This code was supplied by Pierre A. Humblet <Pierre.Humblet@ieee.org> */
-
-/* Define the OS_INIT macro that we insert in exim.c:main()
-   to set the root and exim uid depending on the system */
-/* We use a special routine to initialize */
-void cygwin_init(int, char **, void *, void *, void *, void *, void *);
-#define OS_INIT\
-  cygwin_init(argc, (char **) argv, &root_uid, &exim_uid, &exim_gid, &config_uid, &config_gid);
-
-/* We need a special mkdir that
-   allows names starting with // */
-#include <sys/stat.h> /* Do not redefine mkdir in sys/stat.h */
-int cygwin_mkdir( const char *_path, mode_t __mode );
-#define mkdir cygwin_mkdir /* redefine mkdir elsewhere */
+/* This code was supplied by Pierre A. Humblet <Pierre.Humblet@ieee.org>
+   December 2002. Updated Jan 2015. */
 
 /* Redefine the set*id calls to run when faking root */
 #include <unistd.h>   /* Do not redefine in unitsd.h */
@@ -22,8 +10,8 @@ int cygwin_setgid(gid_t gid );
 #define setuid cygwin_setuid
 #define setgid cygwin_setgid
 
-extern unsigned int cygwin_WinVersion;
-
+#define os_strsignal strsignal
+#define OS_STRSIGNAL
 #define BASE_62 36  /* Windows aliases lower and upper cases in filenames.
                        Consider reducing MAX_LOCALHOST_NUMBER */
 #define CRYPT_H
@@ -31,7 +19,6 @@ extern unsigned int cygwin_WinVersion;
 #define HAVE_SYS_VFS_H
 #define NO_IP_VAR_H
 #define NO_IP_OPTIONS
-#define F_FREESP     O_TRUNC
 /* Defining LOAD_AVG_NEEDS_ROOT causes an initial
    call to os_getloadavg. In our case this is beneficial
    because it initializes the counts */
@@ -48,4 +35,7 @@ struct  { \
   DWORD SubAuthority[n]; \
 } name = { SID_REVISION, n, {SECURITY_NT_AUTHORITY}, {sid}}
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
index 79f3ff2..325e3a1 100644 (file)
@@ -21,4 +21,7 @@ extern char *strerror(int);
 extern int   sys_nerr;
 extern char *sys_errlist[];
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
index 68d1641..d700dd0 100644 (file)
@@ -113,7 +113,7 @@ ChangeLog
 
                 *) dsn_process switch removed
                   *) every router "processes" DSN by default
-                  *) there is no possibilty to "gag" DSN anymore since this violates RFC
+                  *) there is no possibility 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 
index 590642f..05b3d9d 100644 (file)
@@ -26,6 +26,39 @@ The rest of this document contains information about changes in 4.xx releases
 that might affect a running system.
 
 
+Exim version 4.89
+-----------------
+
+ * SMTP CHUNKING in Exim 4.88 did not ensure that received mails had a final
+   newline; attempts to deliver such messages onwards to non-chunking hosts
+   would probably hang, as Exim does not insert the newline before a ".".
+   In 4.89, the newline is added upon receipt.  For already-received messages
+   in your queue, try util/chunking_fixqueue_finalnewlines.pl
+   to walk the queue, fixing any affected messages.  Note that because a
+   delivery attempt will be hanging, attempts to lock the messages for fixing
+   them will stall; stopping all queue-runners temporarily is recommended.
+
+ * OpenSSL: oldest supported release series is now 1.0.2, which is the oldest
+   supported by the OpenSSL project.  If you can build Exim with an older
+   release series, congratulations.  If you can't, then upgrade.
+   The file doc/openssl.txt contains instructions for installing a current
+   OpenSSL outside the system library paths and building Exim to use it.
+
+ * FreeBSD: we now always use the system iconv in libc, as all versions of
+   FreeBSD supported by the FreeBSD project provide this functionality.
+
+
+Exim version 4.88
+-----------------
+
+ * The "demime" ACL condition, deprecated for the past 10 years, has
+   now been removed.
+
+ * Old GnuTLS configuration options "gnutls_require_kx", "gnutls_require_mac",
+   and "gnutls_require_protocols" have now been removed.  (Inoperative from
+   4.80, per below; logging warnings since 4.83, again per below).
+
+
 Exim version 4.83
 -----------------
 
@@ -530,7 +563,7 @@ Version 4.23
 3. Version 4.23 saves the contents of the ACL variables with the message, so
    that they can be used later. If one of these variables contains a newline,
    there will be a newline character in the spool that will not be interpreted
-   correctely by a previous version of Exim. (Exim ignores keyed spool file
+   correctly by a previous version of Exim. (Exim ignores keyed spool file
    items that it doesn't understand - precisely for this kind of problem - but
    it expects them all to be on one line.)
 
diff --git a/conf b/conf
new file mode 100644 (file)
index 0000000..1619c0d
--- /dev/null
+++ b/conf
@@ -0,0 +1,2 @@
+perl_startup = $| = 1; print "<${^TAINT}>\n";
+perl_taintmode = yes
index b269d02..136ca61 100644 (file)
@@ -1,6 +1,6 @@
---- EDITME.exim4-light 2012-05-18 20:11:24.000000000 +0200
-+++ EDITME.exim4-heavy 2012-05-18 20:13:56.000000000 +0200
-@@ -212,7 +212,7 @@ ROUTER_REDIRECT=yes
+--- EDITME.exim4-light 2017-03-04 11:15:58.309895066 +0100
++++ EDITME.exim4-heavy 2017-03-04 11:17:12.616522005 +0100
+@@ -212,7 +212,7 @@
  
  # This one is very special-purpose, so is not included by default.
  
@@ -9,7 +9,7 @@
  
  
  #------------------------------------------------------------------------------
-@@ -244,7 +244,7 @@ TRANSPORT_LMTP=yes
+@@ -244,7 +244,7 @@
  
  SUPPORT_MAILDIR=yes
  SUPPORT_MAILSTORE=yes
@@ -18,7 +18,7 @@
  
  
  #------------------------------------------------------------------------------
-@@ -300,14 +300,14 @@ LOOKUP_DNSDB=yes
+@@ -305,15 +305,15 @@
  LOOKUP_CDB=yes
  LOOKUP_DSEARCH=yes
  # LOOKUP_IBASE=yes
  # LOOKUP_ORACLE=yes
  LOOKUP_PASSWD=yes
 -# LOOKUP_PGSQL=yes
--# LOOKUP_SQLITE=yes
 +LOOKUP_PGSQL=yes
+ # LOOKUP_REDIS=yes
+-# LOOKUP_SQLITE=yes
 +LOOKUP_SQLITE=yes
  # LOOKUP_SQLITE_PC=sqlite3
  # LOOKUP_WHOSON=yes
  
-@@ -328,7 +328,7 @@ LOOKUP_PASSWD=yes
+@@ -334,7 +334,7 @@
  # with Solaris 7 onwards. Uncomment whichever of these you are using.
  
  # LDAP_LIB_TYPE=OPENLDAP1
@@ -46,7 +47,7 @@
  # LDAP_LIB_TYPE=NETSCAPE
  # LDAP_LIB_TYPE=SOLARIS
  
-@@ -366,6 +366,9 @@ LOOKUP_PASSWD=yes
+@@ -373,6 +373,9 @@
  # LOOKUP_LIBS=-L/usr/local/lib -lldap -llber -lmysqlclient -lpq -lgds -lsqlite3
  
  
@@ -56,7 +57,7 @@
  #------------------------------------------------------------------------------
  # Compiling the Exim monitor: If you want to compile the Exim monitor, a
  # program that requires an X11 display, then EXIM_MONITOR should be set to the
-@@ -374,7 +377,7 @@ LOOKUP_PASSWD=yes
+@@ -381,7 +384,7 @@
  # files are defaulted in the OS/Makefile-Default file, but can be overridden in
  # local OS-specific make files.
  
  
  
  #------------------------------------------------------------------------------
-@@ -384,14 +387,14 @@ EXIM_MONITOR=eximon.bin
+@@ -391,7 +394,7 @@
  # and the MIME ACL. Please read the documentation to learn more about these
  # features.
  
 -# WITH_CONTENT_SCAN=yes
 +WITH_CONTENT_SCAN=yes
  
- # If you want to use the deprecated "demime" condition in the DATA ACL,
- # uncomment the line below. Doing so will also explicitly turn on the
- # WITH_CONTENT_SCAN option. If possible, use the MIME ACL instead of
- # the "demime" condition.
--# WITH_OLD_DEMIME=yes
-+WITH_OLD_DEMIME=yes
+ #------------------------------------------------------------------------------
  # If you're using ClamAV and are backporting fixes to an old version, instead
- # of staying current (which is the more usual approach) then you may need to
-@@ -578,14 +581,14 @@ WHITELIST_D_MACROS=OUTGOING
+@@ -627,16 +630,16 @@
  # configuration to make use of the mechanism(s) selected.
  
  AUTH_CRAM_MD5=yes
  # AUTH_GSASL_PC=libgsasl
  # AUTH_HEIMDAL_GSSAPI=yes
  # AUTH_HEIMDAL_GSSAPI_PC=heimdal-gssapi
+ # AUTH_HEIMDAL_GSSAPI_PC=heimdal-gssapi heimdal-krb5
  AUTH_PLAINTEXT=yes
 -# AUTH_SPA=yes
+-# AUTH_TLS=yes
 +AUTH_SPA=yes
++AUTH_TLS=yes
  
- #------------------------------------------------------------------------------
-@@ -595,7 +598,7 @@ AUTH_PLAINTEXT=yes
+ # Heimdal through 1.5 required pkg-config 'heimdal-gssapi'; Heimdal 7.1
+ # requires multiple pkg-config files to work with Exim, so the second example
+@@ -649,7 +652,7 @@
  # Similarly for GNU SASL, unless pkg-config is used via AUTH_GSASL_PC.
  # Ditto for AUTH_HEIMDAL_GSSAPI(_PC).
  
  # AUTH_LIBS=-lgsasl
  # AUTH_LIBS=-lgssapi -lheimntlm -lkrb5 -lhx509 -lcom_err -lhcrypto -lasn1 -lwind -lroken -lcrypt
  
-@@ -830,7 +833,7 @@ ZCAT_COMMAND=/bin/zcat
+@@ -923,7 +926,7 @@
  # (version 5.004 or later) installed, set EXIM_PERL to perl.o. Using embedded
  # Perl costs quite a lot of resources. Only do this if you really need it.
  
  
  
  #------------------------------------------------------------------------------
-@@ -840,7 +843,7 @@ ZCAT_COMMAND=/bin/zcat
+@@ -933,7 +936,7 @@
  # that the local_scan API is made available by the linker. You may also need
  # to add -ldl to EXTRALIBS so that dlopen() is available to Exim.
  
  
  
  #------------------------------------------------------------------------------
-@@ -850,11 +853,11 @@ ZCAT_COMMAND=/bin/zcat
+@@ -943,11 +946,11 @@
  # support, which is intended for use in conjunction with the SMTP AUTH
  # facilities, is included only when requested by the following setting:
  
  
  
  #------------------------------------------------------------------------------
-@@ -1174,7 +1177,7 @@ TMPDIR="/tmp"
+@@ -961,7 +964,7 @@
+ # If you may want to use inbound (server-side) proxying, using Proxy Protocol,
+ # uncomment the line below.
+-# SUPPORT_PROXY=yes
++SUPPORT_PROXY=yes
+ #------------------------------------------------------------------------------
+@@ -1299,7 +1302,7 @@
  # local part) can be increased by changing this value. It should be set to
  # a multiple of 16.
  
index fb495bb..4b492cd 100644 (file)
@@ -1,5 +1,5 @@
---- src/EDITME 2012-05-18 19:51:52.000000000 +0200
-+++ EDITME.exim4-light 2012-05-18 19:56:25.000000000 +0200
+--- src/EDITME 2017-02-12 14:19:37.000000000 +0000
++++ EDITME.exim4-light 2017-02-12 14:22:15.062382937 +0000
 @@ -98,7 +98,7 @@
  # /usr/local/sbin. The installation script will try to create this directory,
  # and any superior directories, if they do not exist.
@@ -9,7 +9,7 @@
  
  
  #------------------------------------------------------------------------------
-@@ -114,7 +114,7 @@ BIN_DIRECTORY=/usr/exim/bin
+@@ -114,7 +114,7 @@
  # don't exist. It will also install a default runtime configuration if this
  # file does not exist.
  
@@ -18,7 +18,7 @@
  
  # It is possible to specify a colon-separated list of files for CONFIGURE_FILE.
  # In this case, Exim will use the first of them that exists when it is run.
-@@ -131,7 +131,7 @@ CONFIGURE_FILE=/usr/exim/configure
+@@ -131,7 +131,7 @@
  # deliveries. (Local deliveries run as various non-root users, typically as the
  # owner of a local mailbox.) Specifying these values as root is not supported.
  
@@ -27,7 +27,7 @@
  
  # If you specify EXIM_USER as a name, this is looked up at build time, and the
  # uid number is built into the binary. However, you can specify that this
-@@ -153,6 +153,7 @@ EXIM_USER=
+@@ -153,6 +153,7 @@
  # you want to use a group other than the default group for the given user.
  
  # EXIM_GROUP=
@@ -35,7 +35,7 @@
  
  # Many sites define a user called "exim", with an appropriate default group,
  # and use
-@@ -173,7 +174,7 @@ EXIM_USER=
+@@ -173,7 +174,7 @@
  
  # Almost all installations choose this:
  
@@ -44,7 +44,7 @@
  
  
  
-@@ -232,7 +233,7 @@ TRANSPORT_SMTP=yes
+@@ -232,7 +233,7 @@
  # This one is special-purpose, and commonly not required, so it is not
  # included by default.
  
@@ -53,7 +53,7 @@
  
  
  #------------------------------------------------------------------------------
-@@ -241,8 +242,8 @@ TRANSPORT_SMTP=yes
+@@ -241,8 +242,8 @@
  # MBX, is included only when requested. If you do not know what this is about,
  # leave these settings commented out.
  
@@ -64,7 +64,7 @@
  # SUPPORT_MBX=yes
  
  
-@@ -296,15 +297,15 @@ LOOKUP_DBM=yes
+@@ -301,15 +302,15 @@
  LOOKUP_LSEARCH=yes
  LOOKUP_DNSDB=yes
  
@@ -82,9 +82,9 @@
 -# LOOKUP_PASSWD=yes
 +LOOKUP_PASSWD=yes
  # LOOKUP_PGSQL=yes
+ # LOOKUP_REDIS=yes
  # LOOKUP_SQLITE=yes
- # LOOKUP_SQLITE_PC=sqlite3
-@@ -528,7 +529,7 @@ FIXED_NEVER_USERS=root
+@@ -577,7 +578,7 @@
  # CONFIGURE_OWNER setting, to specify a configuration file which is listed in
  # the TRUSTED_CONFIG_LIST file, then root privileges are not dropped by Exim.
  
@@ -93,7 +93,7 @@
  
  
  #------------------------------------------------------------------------------
-@@ -564,6 +565,9 @@ FIXED_NEVER_USERS=root
+@@ -613,6 +614,9 @@
  
  # WHITELIST_D_MACROS=TLS:SPOOL
  
  #------------------------------------------------------------------------------
  # Exim has support for the AUTH (authentication) extension of the SMTP
  # protocol, as defined by RFC 2554. If you don't know what SMTP authentication
-@@ -573,14 +577,14 @@ FIXED_NEVER_USERS=root
+@@ -622,7 +626,7 @@
  # included in the Exim binary. You will then need to set up the run time
  # configuration to make use of the mechanism(s) selected.
  
  # AUTH_CYRUS_SASL=yes
  # AUTH_DOVECOT=yes
  # AUTH_GSASL=yes
- # AUTH_GSASL_PC=libgsasl
+@@ -630,7 +634,7 @@
  # AUTH_HEIMDAL_GSSAPI=yes
  # AUTH_HEIMDAL_GSSAPI_PC=heimdal-gssapi
+ # AUTH_HEIMDAL_GSSAPI_PC=heimdal-gssapi heimdal-krb5
 -# AUTH_PLAINTEXT=yes
 +AUTH_PLAINTEXT=yes
  # AUTH_SPA=yes
+ # AUTH_TLS=yes
  
-@@ -602,7 +606,7 @@ FIXED_NEVER_USERS=root
+@@ -656,7 +660,7 @@
  # one that is set in the headers_charset option. The default setting is
  # defined by this setting:
  
  
  # If you are going to make use of $header_xxx expansions in your configuration
  # file, or if your users are going to use them in filter files, and the normal
-@@ -684,7 +688,7 @@ HEADERS_CHARSET="ISO-8859-1"
+@@ -745,7 +749,7 @@
  # leave these settings commented out.
  
  # This setting is required for any TLS support (either OpenSSL or GnuTLS)
  
  # Uncomment one of these settings if you are using OpenSSL; pkg-config vs not
  # USE_OPENSSL_PC=openssl
-@@ -692,9 +696,9 @@ HEADERS_CHARSET="ISO-8859-1"
+@@ -753,9 +757,9 @@
  
  # Uncomment the first and either the second or the third of these if you
  # are using GnuTLS.  If you have pkg-config, then the second, else the third.
 -# TLS_LIBS=-lgnutls -ltasn1 -lgcrypt
 +TLS_LIBS=-lgnutls
  
- # If you are running Exim as a server, note that just building it with TLS
- # support is not all you need to do. You also need to set up a suitable
-@@ -775,6 +779,7 @@ CFLAGS += -fvisibility=hidden
+ # If using GnuTLS older than 2.10 and using pkg-config then note that Exim's
+ # build process will require libgcrypt-config to exist in your $PATH.  A
+@@ -847,6 +851,7 @@
  # to form the final file names. Some installations may want something like this:
  
  # LOG_FILE_PATH=/var/log/exim_%slog
  
  # which results in files with names /var/log/exim_mainlog, etc. The directory
  # in which the log files are placed must exist; Exim does not try to create
-@@ -823,7 +828,7 @@ EXICYCLOG_MAX=10
+@@ -895,7 +900,7 @@
  # files. Both the name of the command and the suffix that it adds to files
  # need to be defined here. See also the EXICYCLOG_MAX configuration.
  
  COMPRESS_SUFFIX=gz
  
  
-@@ -831,7 +836,7 @@ COMPRESS_SUFFIX=gz
- # If the exigrep utility is fed compressed log files, it tries to uncompress
- # them using this command.
+@@ -910,7 +915,7 @@
+ # ZCAT_COMMAND=zcat
+ #
+ # Or specify the full pathname:
 -ZCAT_COMMAND=/usr/bin/zcat
-+ZCAT_COMMAND=/bin/zcat
++ZCAT_COMMAND=zcat
  
  #------------------------------------------------------------------------------
-@@ -864,6 +869,7 @@ ZCAT_COMMAND=/usr/bin/zcat
+ # Compiling in support for embedded Perl: If you want to be able to
+@@ -942,6 +947,7 @@
  
  # You probably need to add -lpam to EXTRALIBS, and in some releases of
  # GNU/Linux -ldl is also needed.
  
  
  #------------------------------------------------------------------------------
-@@ -930,6 +936,8 @@ ZCAT_COMMAND=/usr/bin/zcat
+@@ -950,7 +956,7 @@
+ # If you may want to use outbound (client-side) proxying, using Socks5,
+ # uncomment the line below.
+-# SUPPORT_SOCKS=yes
++SUPPORT_SOCKS=yes
+ # If you may want to use inbound (server-side) proxying, using Proxy Protocol,
+ # uncomment the line below.
+@@ -1038,6 +1044,8 @@
  
  # CYRUS_SASLAUTHD_SOCKET=/var/state/saslauthd/mux
  
  
  #------------------------------------------------------------------------------
  # TCP wrappers: If you want to use tcpwrappers from within Exim, uncomment
-@@ -1233,6 +1241,7 @@ TMPDIR="/tmp"
+@@ -1343,6 +1351,7 @@
  # file can be specified here. Some installations may want something like this:
  
  # PID_FILE_PATH=/var/lock/exim.pid
-+PID_FILE_PATH=/var/run/exim4/exim.pid
++PID_FILE_PATH=/run/exim4/exim.pid
  
  # If PID_FILE_PATH is not defined, Exim writes a file in its spool directory
  # using the name "exim-daemon.pid".
-@@ -1266,6 +1275,7 @@ TMPDIR="/tmp"
+@@ -1376,6 +1385,7 @@
  # messages become "invisible" to the normal management tools.
  
  # SUPPORT_MOVE_FROZEN_MESSAGES=yes
  
  
  #------------------------------------------------------------------------------
-@@ -1304,3 +1314,6 @@ TMPDIR="/tmp"
+@@ -1414,3 +1424,6 @@
  # ENABLE_DISABLE_FSYNC=yes
  
  # End of EDITME for Exim 4.
index d3d02a5..672f641 100644 (file)
@@ -1,5 +1,5 @@
---- exim_monitor/EDITME        2012-05-18 05:04:36.000000000 +0200
-+++ EDITME.eximon      2012-05-18 19:53:04.000000000 +0200
+--- exim_monitor/EDITME        2017-02-12 00:58:50.000000000 +0000
++++ EDITME.eximon      2017-02-12 14:19:40.765243359 +0000
 @@ -1,6 +1,7 @@
  ##################################################
  #                The Exim Monitor                #
similarity index 53%
rename from debian/exim4-config.NEWS
rename to debian/NEWS
index 5fbd981..ef106e2 100644 (file)
@@ -1,3 +1,143 @@
+exim4 (4.87-3) unstable; urgency=medium
+
+  Starting with 4.87~RC1-1 exim will not accept or send out messages with
+  physical lines longer than 998 characters by SMTP DATA. Delivery of such
+  RFC-violating message might fail and subsequently cause routing errors and
+  loss of legitimate mail.  See <https://bugs.exim.org/show_bug.cgi?id=1684>.
+  This limit can be disabled by setting the macro
+  IGNORE_SMTP_LINE_LENGTH_LIMIT.
+
+ -- Andreas Metzler <ametzler@debian.org>  Sun, 08 May 2016 14:03:10 +0200
+
+exim4 (4.87-2) unstable; urgency=medium
+
+  exim4-daemon heavy does not support the "demime" ACL condition
+  (WITH_OLD_DEMIME) anymore. It was superceded by the acl_smtp_mime ACL and
+  will not be part of the next upstream release.
+
+ -- Andreas Metzler <ametzler@debian.org>  Sat, 30 Apr 2016 13:38:29 +0200
+
+exim4 (4.87~RC6-3) unstable; urgency=medium
+
+  As part of the fix for CVE-2016-1531 updated Exim versions clean
+  the complete execution environment by default, affecting Exim and
+  subprocesses such as routers calling other programs, and thus may break
+  existing installations. New configuration options (keep_environment,
+  add_environment) were introduced to adjust this behavior. Because of the
+  possible breakage Exim will show a runtime warning if keep_environment is
+  not set.
+
+  The Debian exim4 configuration does not rely on specific environment
+  variables and therefore sets 'keep_environment =' (i.e confirm empty
+  environment).
+
+  Users of custom Exim configurations will need to check whether their setup
+  continues to work with the abovementioned upstream change and modify the
+  Exim environment as needed otherwise. If the setup works fine with empty
+  environment it is still necessary to set the main configuration option
+  "keep_environment =" to quiet the runtime warning.
+
+  See <https://exim.org/static/doc/CVE-2016-1531.txt> for details.
+
+ -- Andreas Metzler <ametzler@debian.org>  Wed, 23 Mar 2016 18:44:22 +0100
+
+exim4 (4.80~rc6-1) experimental; urgency=low
+
+  Upstream's handling of GnuTLS DH parameters has changed, hardcoded
+  parameters (from RFCs are used by default. See
+  /usr/share/doc/exim4-base/README.UPDATING* for details. Stop shipping
+  /usr/share/exim4/exim4_refresh_gnutls-params /usr/share/exim4/timeout.pl
+  and /var/spool/exim4/gnutls-params-2236.
+
+ -- Andreas Metzler <ametzler@debian.org>  Sun, 27 May 2012 18:46:48 +0200
+
+exim4 (4.80~rc2-1) experimental; urgency=low
+
+  Ldap lookups returning multi-valued attributes now separate the attributes
+  with only a comma, not a comma-space sequence.
+
+  The GnuTLS support has been mostly rewritten. exim main configuration
+  options gnutls_require_kx, gnutls_require_mac and gnutls_require_protocols,
+  are no longer supported. (They are ignored if present now, but will trigger
+  an error in later releases.) Their functionality is entirely subsumed into
+  tls_require_ciphers.  In turn, tls_require_ciphers is no longer an Exim list
+  and is not parsed by Exim, but is instead given to gnutls_priority_init(3).
+
+  See /exim4-base/usr/share/doc/exim4-base/README.UPDATING.gz for details.
+
+ -- Andreas Metzler <ametzler@debian.org>  Sat, 22 Oct 2011 19:16:58 +0200
+
+exim4 (4.77~rc4-1) experimental; urgency=low
+
+  Exim no longer performs string expansion on the second string of
+  the match_* expansion conditions: "match_address", "match_domain",
+  "match_ip" & "match_local_part". Named lists can still be used.
+
+  The previous behavior made it too easy to create (remotely) vulnerable
+  configurations. A more detailed rationale and explanation can be found on 
+  https://lists.exim.org/lurker/message/20111003.122326.fbcf32b7.en.html
+
+ -- Andreas Metzler <ametzler@debian.org>  Thu, 05 Oct 2011 19:22:52 +0200
+
+exim4 (4.72-3) unstable; urgency=low
+
+  Exim versions up to and including 4.72 are vulnerable to CVE-2010-4345.
+  This is a privilege escalation issue that allows the exim user to gain
+  root privileges by specifying an alternate configuration file using the -C
+  option. The macro override facility (-D) might also be misused for this
+  purpose.
+
+  In reaction to this security vulnerability upstream has made a number of
+  user visible changes. This package includes these changes.
+  ---------------------------------------------------------
+  If exim is invoked with the -C or -D option the daemon will not regain
+  root privileges though re-execution. This is usually necessary for local
+  delivery, though. Therefore it is generally not possible anymore to run an
+  exim daemon with -D or -C options.
+
+  However this version of exim has been built with
+  TRUSTED_CONFIG_LIST=/etc/exim4/trusted_configs. TRUSTED_CONFIG_LIST
+  defines a list of configuration files which are trusted; if a config file
+  is owned by root and matches a pathname in the list, then it may be
+  invoked by the Exim build-time user without Exim relinquishing root
+  privileges.
+
+  As a hotfix to not break existing installations of mailscanner we have
+  also set WHITELIST_D_MACROS=OUTGOING. i.e. it is still possible to start
+  exim with -DOUTGOING while being able to do local deliveries.
+
+  If you previously were using -D switches you will need to change your
+  setup to use a separate configuration file. The ".include" mechanism
+  makes this easy.
+  ---------------------------------------------------------
+  The system filter is run as exim_user instead of root by default. If your
+  setup requies root privileges when running the system filter you will
+  need to set the system_filter_user exim main configuration option.
+  ---------------------------------------------------------
+
+ -- Andreas Metzler <ametzler@debian.org>  Sat, 18 Dec 2010 18:57:16 +0100
+
+exim4 (4.69-4) unstable; urgency=low
+
+  In reaction to #475194, the size of the Diffie-Hellman parameters
+  used by exim was increased to 2048, which is GnuTLS's default.
+  
+  Since periodically regenerating the Diffie-Hellman parameters
+  doesn't increase security that much (they're sent in clear text in the
+  TLS handshake, and some protocols even have hardcoded them in the
+  standard document), and automatically generating 2048 bits
+  Diffie-Hellman parameters can take a long time, this has been disabled
+  in the Exim4 packages starting with 4.69-4. All exim installations
+  will thus run with the Diffie-Hellman parameters shipped in the
+  package by default.
+  
+  Really, really paranoid people with sufficiently fast machines will
+  want to set up a cron job calling
+  /usr/share/exim4/exim4_refresh_gnutls-params manually - suggested
+  interval is weekly or monthly.
+
+ -- Marc Haber <mh+debian-packages@zugschlus.de>  Sun, 27 Apr 2008 09:14:32 +0200
+
 exim4 (4.68-1) unstable; urgency=low
 
     In order to fix #420217, the handling of incoming messages to
@@ -193,6 +333,17 @@ exim4 (4.62-1) unstable; urgency=low
 
  -- Marc Haber <mh+debian-packages@zugschlus.de>  Sat, 29 Apr 2006 22:36:31 +0000
 
+exim4 (4.60-2) unstable; urgency=low
+
+    The exim4 daemon packages now include a symlink from
+    /usr/sbin/exim4 to /usr/sbin/exim. This can break exim 3 cron and
+    init scripts if the last exim 3 you had installed was any earlier
+    than 3.36-5 and the conffiles from your exim 3 package are still
+    around. Be sure to have any exim 4 earlier than 3.36-5 _purged_
+    (not removed) before installing this package.
+
+ -- Marc Haber <mh+debian-packages@zugschlus.de>  Wed, 24 Jan 2006 14:58:08 +0100
+
 exim4 (4.50-5) unstable; urgency=low
 
     mailname, the local name of the system used to qualify senders and
@@ -222,6 +373,13 @@ exim4 (4.34-1) unstable; urgency=low
 
  -- Andreas Metzler <ametzler@debian.org>  Wed, 12 May 2004 13:42:23 +0200
 
+exim4 (4.31-2) unstable; urgency=low
+
+    The local_scan perl-plugin has been removed because upstream
+    development has stopped. (am)
+
+ -- Andreas Metzler <ametzler@debian.org>  Mon,  5 Apr 2004 15:55:12 +0200
+
 exim4 (4.30-5) unstable; urgency=low
 
     (Re)introduce /etc/exim4/exim4.conf.template as alternative to the
@@ -231,6 +389,58 @@ exim4 (4.30-5) unstable; urgency=low
 
  -- Andreas Metzler <ametzler@debian.org>  Sun, 11 Jan 2004 13:03:43 +0100
 
+exim4 (4.30-1) unstable; urgency=low
+
+  * Exim now runs under its own uid (Debian-exim) instead of using mail:mail.
+  
+    WARNING: You cannot downgrade this version to an older one without
+    manual chown|chrgrp all files owned by Debian-exim to mail.
+    
+    Securitywise this is a tradeoff:
+    - if exim is SUID root and runs without deliver_drop_privilege you win:
+      exim's internal data in /var/spool/exim4 is not open to attacks by
+      bugs in programs SGID mail (mail delivery agents like deliver or
+      procmail, or MUAs like pine) anymore. This is Debian's default setup.
+    - OTOH if you need to be able to make local deliveries to /var/mail and
+      want to run exim with reduced priviledge you have some additional work
+      to do:
+      * Use an SGID MDA for the actual delivery (I suggest maildrop.)
+      * Make changes to run exim4 under group mail:
+        - exim_group=mail.
+        - Hack: make Debian-exim a group with gid=8, i.e. an alias for
+          the mail group, _before_ you make the upgrade. (groupadd -o -g 8
+          Debian-exim)
+
+ -- Andreas Metzler <ametzler@debian.org>  Sun,  7 Dec 2003 13:59:46 +0100
+
+exim4 (4.24-1) unstable; urgency=low
+
+  * This version of exim cannot run deliveries as root anymore, see change
+    5a for exim 4.23 in /usr/share/doc/exim4-base/changelog.gz. If you
+    don't redirect mail for root via /etc/aliases to a nonpriviledged
+    account the mail will be delivered to /var/mail/mail with permissions
+    0600 and owner mail:mail.
+
+ -- Andreas Metzler <ametzler@debian.org>  Fri,  3 Oct 2003 18:11:17 +0200
+exim4 (4.22-2) unstable; urgency=low
+
+    Include exiscan-acl patch http://duncanthrax.net/exiscan-acl/ in
+    -heavy and -custom for easy integration of content-scanning and
+    invoking spamassassin at SMTP time.
+
+ -- Andreas Metzler <ametzler@debian.org>  Wed, 27 Aug 2003 12:50:59 +0200
+
+exim4 (4.22-1) unstable; urgency=low
+
+  * The way that the $h_ (and $header_) expansions work has been changed
+    by the addition of RFC 2047 decoding. See the main documentation (the
+    NewStuff file until release 4.30, then the manual) for full details.
+
+    Exim shipped with Debian defaults to HEADER_DECODE_TO="UTF-8"
+
+ -- Andreas Metzler <ametzler@debian.org>  Mon, 18 Aug 2003 16:51:47 +0200
+
 exim4 (4.20-2) unstable; urgency=low
 
     Rewriting now uses /etc/email-addresses instead of 
index 30c5961..8fa7422 100644 (file)
       please be familiar with how Exim works. At minimum, have read this
       README file and the manpages delivered with the Debian Exim 4
       packages, and <filename>/usr/share/doc/exim4-base/spec.txt.gz</filename>
-      chapters 3 and 6. <filename>spec.txt.gz</filename> is an excellent
-      reference.
+      chapters <phrase>"How Exim receives and delivers mail"</phrase> and
+      <phrase>"The Exim run time configuration file"</phrase>.
+      <filename>spec.txt.gz</filename> is an excellent reference.
     </para>
     <para>
       Please note that while most free-form fields in the
                list extra care needs to be taken in this case. 
                <emphasis>Unresolvable names in the host list will break
                relaying.</emphasis> See
-               <ulink url="http://www.exim.org/exim-html-current/doc/html/spec_html/ch-domain_host_address_and_local_part_lists.html">
-                       Exim specification - chapter Domain, host, address, and
-                       local part lists
-               </ulink> and the exim4-config_files man page.
+               Exim specification chapter <phrase>"Domain, host, address, and
+               local part lists"</phrase>
+               and the exim4-config_files man page.
              </para>
           </section>
          <section> <title>IP address or host name of the outgoing
              <para>
                Multiple smarthost entries are permitted, semicolon
                separated. Each of the hosts is tried, in the order
-               specified (See Exim specification, chapter 20.5).
+               specified (See Exim specification, chapter
+               <phrase>"The manualroute router"</phrase>, section
+               <phrase>"How the list of hosts is used"</phrase>.)
              </para>
           </section>
          <section> <title>Hide local mail name in outgoing mail</title>
        setting macros. That way, you can switch on and off certain
        parts of the default configuration and/or override values set
         in Debconf without having to touch the dpkg-conffiles. While
-       touching dpkg-conffiles itself is explitly allowed and wanted,
+       touching dpkg-conffiles itself is explicitly allowed and wanted,
        it can be quite a nuisance to be asked on package upgrade
        whether one wants to use the locally changed file or the
        file changed by the package maintainer.
       <para>
        into the appropriate file. For more detailed discussion of the
        general macro mechanism, see the Exim specification, chapter
-       6.4, for details how macro expansion works.
+       <phrase>"The Exim run time configuration file"</phrase>, for
+       details how macro expansion works.
       </para>
       </section>
       <section>        <title>How does this work?</title>
           (most prominent example being nearly all versions of Microsoft
          Outlook and Outlook Express, and Incredimail) insist on doing
          TLS on connect on Port 465. If you need to support these, set
-         SMTPLISTENEROPTIONS='-oX 465:25 -oP /var/run/exim4/exim.pid'
+         SMTPLISTENEROPTIONS='-oX 465:25 -oP /run/exim4/exim.pid'
          in <filename>/etc/default/exim4</filename> and
          "tls_on_connect_ports=465" in the main configuration section.
        </para>
           job will malfunction.
        </para>
        <para>
-         It might be appropriate to add "+tls_cipher +tls_peerdn" to
+         It might be appropriate to add "+tls_cipher" to
          any log_selector statement you might already have, or to add a
          log_selector statement setting these two options in a local
-         configuration file. These options have Exim log what cipher
+         configuration file. (For Debian's configuration simply define
+         the MAIN_LOG_SELECTOR macro.)
+         This option makes Exim log what cipher
          your Exim and the peer's mailer have negotiated to use to
-         encrypt the transaction, and they have Exim log the
-         Distinguished Name of the peer's certificate.
+         encrypt the transaction.
        </para>
        <para>
          Exim can be configured to ask a client for a certificate and to
                  to no. <command>E4BCD_WATCH_PANICLOG=once</command> will
                  rotate a non-empty paniclog automatically after sending out
                  the warning e-mail.
+               </simpara>
+               <simpara>
+                 The <command>E4BCD_PANICLOG_LINES</command> setting can be
+                 used to limit the number of lines of paniclog quoted in
+                 warning email. It is set to 10 by default.
                </simpara>
              </listitem>
              <listitem>
@@ -1488,20 +1497,20 @@ smtp stream   tcp     nowait  Debian-exim     /usr/sbin/exim4 exim4 -bs
       </para>
       <para>
         Just in case that you need exceptions to the rule,
-       <filename>/etc/exim4/lowuid_aliases</filename> is an alias
+       <filename>/etc/exim4/lowuid-aliases</filename> is an alias
        file that is only honored for local accounts with UID lower
        than FIRST_USER_ACCOUNT_UID. If you define an alias for such an
        account here, incoming mail is processed according to the
        alias. If you alias the account to itself, messages are
        delivered to the account itself, which is an exception to the
        rule that messages for low-UID accounts are rejected. The
-       format of <filename>/etc/exim4/lowuid_aliases</filename> is
+       format of <filename>/etc/exim4/lowuid-aliases</filename> is
        just another alias file.
       </para>
     </section>
     <section> <title>How to bypass local routing specialities</title>
       <para>
-        Sometimes, it might be desireable to be able to bypass local
+        Sometimes, it might be desirable to be able to bypass local
        routing specialities like the alias file or a user-forward
        file. This is possible in the Debian Exim4 packages by
        prefixing the account name with "real-". For a local account
@@ -1772,7 +1781,7 @@ commands        rmail rnews rsmtp
   <section> <title>Misc Notes</title>
     <section> <title>PAM</title>
         <para>
-         PAM: On Debian systems the PAM modules run as the same user
+         On Debian systems the PAM modules run as the same user
          as the calling program, so they cannot do anything you
          could not do yourself, and in particular cannot access
          <filename>/etc/shadow</filename> unless the user is in group
@@ -1788,7 +1797,7 @@ commands        rmail rnews rsmtp
           In the default configuration, Exim cannot locally deliver
           mail to accounts which have capitals in their name. This is
           caused by the fact that Exim converts the local part of incoming
-          mail to lower case before the comparision done by the
+          mail to lower case before the comparison done by the
           check_local_user directive in routers is done.
         </para>
        <para>
@@ -1875,85 +1884,59 @@ paper</ulink>
     </section>
   </section>  
   <section> <title>Debian modifications to the Exim source</title>
-    <variablelist>
-      <varlistentry>
-       <term>
-         <ulink url="http://www.arise.demon.co.uk/exim-patches/">
-           Patches</ulink> by Steve Haslam:
-       </term>
-       <listitem>
-         <simpara>
-           boolean_redefine_protect
-           [src/mytypes.h]
-           Surround the definition of TRUE and FALSE macros with #ifndef
-           /#endif, in case some other header defines them (from mixing  No
-           Perl and Exim, istr)
-         </simpara>
-       </listitem>
-      </varlistentry>
-      <varlistentry>
-       <term>
-         Other stuff
-       </term>
-       <listitem>
-         <itemizedlist>
-           <listitem>
-             <simpara>
-               link exim dynamically against pcre.
-             </simpara>
-           </listitem>
-           <listitem>
-             <para>
-               The main binary is /usr/sbin/exim4:
-               <itemizedlist>
-                 <listitem>
-                   <simpara>
-                     src/globals.c was changed to use 'US
-                     BIN_DIRECTORY "/exim4"' as default for
-                     exim_path.
-                   </simpara>
-                 </listitem>
-                 <listitem>
-                   <simpara>
-                     changed default for $exim_path (modulo
-                     lower/upper case) from BIN_DIRECTORY/exim to
-                     BIN_DIRECTORY/exim4 in exicyclog.src,
-                     exim_checkaccess.src, eximon.src, exinext.src,
-                     exiqgrep.src, exiwhat.src.
-                   </simpara>
-                 </listitem>
-                 <listitem>
-                   <simpara>
-                     OS/Makefile-Linux:EXIWHAT_MULTIKILL_ARG=exim4
-                   </simpara>
-                 </listitem>
-               </itemizedlist>
-             </para>
-           </listitem>
-           <listitem>
-             <simpara>
-               <ulink
-url="http://marc.merlins.org/linux/exim/files/sa-exim-current/">localscan_dlopen
-.patch</ulink>:
-               Allow to use and switch between different local_scan
+    <itemizedlist>
+      <listitem>
+        <simpara>
+               Install the exim binary as /usr/sbin/exim4 instead of 
+               /usr/sbin/exim-&lt;version&gt; with a symlink /usr/sbin/exim. Also
+               adapt the documentation.
+        </simpara>
+      </listitem>
+      <listitem>
+        <simpara>
+               Make the build reproducible. Pull date/time from debian/changelog
+               and use it as build time instead of using __DATE__.
+        </simpara>
+      </listitem>
+      <listitem>
+        <simpara>
+               Documentation updates
+        </simpara>
+        <itemizedlist>
+         <listitem>
+            <simpara>
+                  Mention how to install the Debian packaged perl-modules needed
+                  for eximstats' graphs.
+            </simpara>
+         </listitem>
+         <listitem>
+            <simpara>
+                  Add a warning about convert4r4.
+            </simpara>
+         </listitem>
+         <listitem>
+           <simpara>
+                 Point to the <ulink
+                 url="mailto:pkg-exim4-users@lists.alioth.debian.org">
+                 Debian-specific mailing list</ulink> instead of
+                 the <ulink url="mailto:exim-users@exim.org">official
+                 exim-users list</ulink>.
+           </simpara>
+         </listitem>
+       </itemizedlist>
+      </listitem>
+      <listitem>
+        <simpara>
+          <ulink
+           url="http://marc.merlins.org/linux/exim/files/sa-exim-current/">localscan_dlopen.patch</ulink>:
+               This patch makes it possible to use and switch between
+               different local_scan
                functions without recompiling Exim. Use
                local_scan_path = /path/to/sharedobject to utilize
                local_scan() in <filename>/path/to/sharedobject</filename>.
-             </simpara>
-           </listitem>
-           <listitem>
-             <simpara>
-               changes to the documentation to have the <ulink
-               url="mailto:pkg-exim4-users@lists.alioth.debian.org">
-               Debian-specific mailing list</ulink> mentioned where
-               the <ulink url="mailto:exim-users@exim.org">official
-               exim-users list</ulink> is mentioned
-             </simpara>
-           </listitem>
-         </itemizedlist>
-       </listitem>
-      </varlistentry>
-    </variablelist>
+        </simpara>
+      </listitem>
+    </itemizedlist>
   </section>
 
   <section> <title>Credits</title>
index 8a55785..25a7022 100644 (file)
@@ -1,3 +1,725 @@
+exim4 (4.89-2+deb9u3~bpo8+1) jessie-backports; urgency=medium
+
+  * Rebuild for jessie-backports.
+  * b-d on libmysqlclient-dev | libmysqlclient15-dev instead of
+    default-libmysqlclient-dev.
+
+ -- Andreas Metzler <ametzler@debian.org>  Sun, 25 Feb 2018 15:26:27 +0100
+
+exim4 (4.89-2+deb9u3) stretch-security; urgency=high
+
+  * Non-maintainer upload by the Security Team.
+  * Fix base64d() buffer size (CVE-2018-6789) (Closes: #890000)
+
+ -- Salvatore Bonaccorso <carnil@debian.org>  Sat, 10 Feb 2018 09:26:05 +0100
+
+exim4 (4.89-2+deb9u2) stretch-security; urgency=high
+
+  * Non-maintainer upload by the Security Team.
+  * Avoid release of store if there have been later allocations
+    (CVE-2017-16943) (Closes: #882648)
+  * Chunking: do not treat the first lonely dot special (CVE-2017-16944)
+    (Closes: #882671)
+
+ -- Salvatore Bonaccorso <carnil@debian.org>  Tue, 28 Nov 2017 22:58:00 +0100
+
+exim4 (4.89-2+deb9u1) stretch-security; urgency=medium
+
+  * CVE-2017-100369
+
+ --  <jmm@debian.org>  Wed, 14 Jun 2017 07:03:07 +0200
+
+exim4 (4.89-2) unstable; urgency=medium
+
+  * Revert addition of header "# pidfile: /var/run/exim4/exim.pid" to
+    initscript (#844178). It breaks when the initscript does not start a
+    daemon but only runs update-exim4.conf. (inetd or QUEUERUNNER='nodaemon').
+    Closes: #860317
+  * When reporting bugs also attach /etc/default/exim4 by default.
+
+ -- Andreas Metzler <ametzler@debian.org>  Thu, 20 Apr 2017 17:14:04 +0200
+
+exim4 (4.89-1) unstable; urgency=medium
+
+  * Enable inbound (server-side) proxying for -heavy. Closes: #856712
+  * New upstream release, source identical to RC7.
+
+ -- Andreas Metzler <ametzler@debian.org>  Thu, 09 Mar 2017 17:49:47 +0100
+
+exim4 (4.89~RC7-1) unstable; urgency=medium
+
+  * New upstream version.
+
+ -- Andreas Metzler <ametzler@debian.org>  Wed, 01 Mar 2017 18:37:18 +0100
+
+exim4 (4.89~RC6-1) unstable; urgency=medium
+
+  * Document E4BCD_PANICLOG_LINES in README.Debian.
+  * New upstream version.
+
+ -- Andreas Metzler <ametzler@debian.org>  Thu, 23 Feb 2017 18:24:33 +0100
+
+exim4 (4.89~RC5-1) unstable; urgency=medium
+
+  * New upstream version.
+
+ -- Andreas Metzler <ametzler@debian.org>  Mon, 13 Feb 2017 19:04:46 +0100
+
+exim4 (4.89~RC4-1) unstable; urgency=medium
+
+  * New upstream version.
+    + Drop 92_CVE-2016-1238.diff.
+  * Use /run/exim4/ instead of legacy directory /var/run/exim4 for pidfile
+    while we are changing the init script.
+
+ -- Andreas Metzler <ametzler@debian.org>  Sun, 12 Feb 2017 15:28:09 +0100
+
+exim4 (4.89~RC3-1) unstable; urgency=medium
+
+  * New upstream version.
+    + Unfuzz 92_CVE-2016-1238.diff.
+  * init file:
+    + Source /etc/default/exim4 *before* defining the shell
+      variables holding the pidfilenames. Overriding these via
+      /etc/default/exim4 is not supported.
+    + Add missing support for reload when QUEUERUNNER='queueonly'.
+    + For QUEUERUNNER='queueonly' use $PIDFILE instead of $QRPIDFILE. This way
+      $PIDFILE is used for the main exim process for all available QUEUERUNNER
+      choices.
+    + Add header "# pidfile: /var/run/exim4/exim.pid" for improved systemd
+      interaction. systemd-sysv-generator uses this pseudoheader to set
+      PIDFile in the generated service file and it also sets
+      RemainAfterExit=no instead of yes if it is present. Thanks, Michael
+      Biebl for suggestion and explanation. Closes: #844178
+
+ -- Andreas Metzler <ametzler@debian.org>  Fri, 10 Feb 2017 19:08:52 +0100
+
+exim4 (4.89~RC2-1) unstable; urgency=medium
+
+  * New upstream version.
+    + Drop 75_add_bak_spec.txt.diff.
+
+ -- Andreas Metzler <ametzler@debian.org>  Sat, 04 Feb 2017 15:24:44 +0100
+
+exim4 (4.89~RC1-1) unstable; urgency=low
+
+  * Refresh debian/upstream/signing-key.asc.
+  * New upstream bugfix release.
+    + Drop superfluous patches.
+      75_00_DKIM-More-validation-of-DNS-key-record.-Bug-1926.patch
+      75_01_DKIM-Under-debug-when-signing-do-an-extra-check-on-t.patch
+      75_02_Do-not-call-ldap_start_tls_s-on-ldapi-connections.patch
+      75_03_PROXY-fix-v2-protocol-decode.-Bugs-2003-1747.patch
+      75_04_CHUNKING-fix-non-pipelined-synch-checks.-Bug-2004.patch
+    + Unfuzz 31_eximmanpage.dpatch and
+      78_Disable-chunking-BDAT-by-default.patch.
+    + Add 75_add_bak_spec.txt.diff - spec.txt and filter.txt missing in rc
+      tarball.
+    + Unfuzz debian/EDITME.exim4-*.
+    + Update debian/example.conf.md5. - Upstream typo fix.
+
+ -- Andreas Metzler <ametzler@debian.org>  Tue, 31 Jan 2017 19:52:50 +0100
+
+exim4 (4.88-5) unstable; urgency=medium
+
+  * 78_Disable-chunking-BDAT-by-default.patch: Change default value of main
+    option chunking_advertise_hosts and smtp transport option
+    hosts_try_chunking from "*" to empty.
+    This is a Debian specific change, we are right before the freeze and BDAT
+    needs a little time.
+
+ -- Andreas Metzler <ametzler@debian.org>  Thu, 19 Jan 2017 19:18:15 +0100
+
+exim4 (4.88-4) unstable; urgency=medium
+
+  * Upload to unstable.
+
+ -- Andreas Metzler <ametzler@debian.org>  Sat, 07 Jan 2017 14:38:00 +0100
+
+exim4 (4.88-3) experimental; urgency=medium
+
+  * Pull multiple patches from upstream GIT:
+   + 75_00_DKIM-More-validation-of-DNS-key-record.-Bug-1926.patch,
+     75_01_DKIM-Under-debug-when-signing-do-an-extra-check-on-t.patch
+   + 75_02_Do-not-call-ldap_start_tls_s-on-ldapi-connections.patch
+   + 75_03_PROXY-fix-v2-protocol-decode.-Bugs-2003-1747.patch
+   + 75_04_CHUNKING-fix-non-pipelined-synch-checks.-Bug-2004.patch
+     (Thanks, Bart Noordervliet for the pointer) Closes: #850175
+
+ -- Andreas Metzler <ametzler@debian.org>  Fri, 06 Jan 2017 17:32:20 +0100
+
+exim4 (4.88-2) unstable; urgency=medium
+
+  * Upload to unstable.
+
+ -- Andreas Metzler <ametzler@debian.org>  Tue, 27 Dec 2016 17:36:29 +0100
+
+exim4 (4.88-1) experimental; urgency=medium
+
+  * New upstream version.
+  * Upload to experimental, let (almost identical) 4.88~RC6-2 propagate to
+    testing.
+  * Drop 75_Fix-DKIM-information-leakage.patch.
+
+ -- Andreas Metzler <ametzler@debian.org>  Sun, 25 Dec 2016 18:07:12 +0100
+
+exim4 (4.88~RC6-2) unstable; urgency=high
+
+  * Add macro IGNORE_SMTP_LINE_LENGTH_LIMIT to allow disabling the SMTP DATA
+    physical line limit check for both for SMTP DATA ACL and remote_smtp*
+    transports. Closes: #828801
+    Also update corresponding NEWS entry.
+  * [lintian] debian/changelog: s/lenght/length/
+  * Pull 75_Fix-DKIM-information-leakage.patch from upstream GIT, fixing DKIM
+    information leakage issue CVE-2016-9963.
+
+ -- Andreas Metzler <ametzler@debian.org>  Thu, 22 Dec 2016 16:50:21 +0100
+
+exim4 (4.88~RC6-1) unstable; urgency=low
+
+  * New upstream version.
+
+ -- Andreas Metzler <ametzler@debian.org>  Thu, 08 Dec 2016 07:19:18 +0100
+
+exim4 (4.88~RC5-1) unstable; urgency=low
+
+  * New upstream version.
+    + Drop 75_01-Ensure-socket-is-nonblocking-before-draining.diff.
+
+ -- Andreas Metzler <ametzler@debian.org>  Sat, 19 Nov 2016 17:43:51 +0100
+
+exim4 (4.88~RC4-2) unstable; urgency=low
+
+  * Pull 75_01-Ensure-socket-is-nonblocking-before-draining.diff from upstream
+    GIT to fix exim bug 1914 (exim doesn't close connection after quit.
+  * Upload to unstable.
+
+ -- Andreas Metzler <ametzler@debian.org>  Sat, 12 Nov 2016 07:26:14 +0100
+
+exim4 (4.88~RC4-1) experimental; urgency=low
+
+  * New upstream version.
+
+ -- Andreas Metzler <ametzler@debian.org>  Mon, 07 Nov 2016 19:08:47 +0100
+
+exim4 (4.88~RC3-1) experimental; urgency=medium
+
+  * New upstream version.
+    Drop 75_01-Fix-check-for-commandline-macro-definition.patch
+    75_02_Fix-bug-with-aborted-server-TLS-connection-under-Gnu.patch.
+
+ -- Andreas Metzler <ametzler@debian.org>  Mon, 24 Oct 2016 19:25:31 +0200
+
+exim4 (4.88~RC2-3) experimental; urgency=medium
+
+  * Fix thinko in exim4-daemon-*.postinst. Do not regenerate gnutls params on
+    every upgrade.
+  * 75_02_Fix-bug-with-aborted-server-TLS-connection-under-Gnu.patch: Fix
+    longstanding bug with aborted TLS server connection handling. Under
+    GnuTLS, when a session startup failed (eg because the client
+    disconnected) Exim did stdio operations after fclose.  This was exposed by
+    a recent change which nulled out the file handle after the fclose.
+
+ -- Andreas Metzler <ametzler@debian.org>  Sun, 23 Oct 2016 16:39:13 +0200
+
+exim4 (4.88~RC2-2) experimental; urgency=medium
+
+  * 75_01-Fix-check-for-commandline-macro-definition.patch - Fix permission
+    problems on commandline mail submission. Closes: #840355
+
+ -- Andreas Metzler <ametzler@debian.org>  Thu, 13 Oct 2016 19:25:07 +0200
+
+exim4 (4.88~RC2-1) experimental; urgency=low
+
+  *  New upstream version.
+    + Changed default Diffie-Hellman parameters to be Exim-specific, created
+      by Phil Pennock. Added RFC7919 DH primes as an alternative.
+      Closes: #839978
+  * Set tls_dhparam = historic to use site-specific DH parameters.
+  * Again, ship /usr/share/exim4/exim4_refresh_gnutls-params, use it in
+    -daemon postinst.
+  * Initialize /var/spool/exim4/gnutls-params-2048 at daemon install, either
+    by running certtool or by installing
+    /usr/share/exim4/gnutls-params-2048. Do not try to use
+    openssl dhparam, it takes too long.
+
+ -- Andreas Metzler <ametzler@debian.org>  Sun, 09 Oct 2016 17:37:08 +0200
+
+exim4 (4.88~RC1-1) experimental; urgency=low
+
+  * Drop reference to removed (in 4.80-7) "what"-option in init script usage
+    message. (Thanks, Calum Mackay!) Closes: #823855
+  * 92_CVE-2016-1238.diff: eximstats: Remove . from @INC [CVE-2016-1238]
+    Closes: #832442
+  * [lintian] update-exim4.conf.8 - fix typo.
+  * [lintian] Drop unused override binaries-have-file-conflict.
+  * B-d on default-libmysqlclient-dev.
+  * New upstream version.
+    + Refresh patches: 31_eximmanpage.dpatch 32_exim4.dpatch 35_install.dpatch
+      50_localscan_dlopen.dpatch
+    + Drop superfluous patches.
+      71_01_configure.default-nice-message-for-overlong-lines-Bu.patch
+      71_02_Delivery-quieten-smtp-transport-conn-reuse-vs.-deliv.patch
+      71_03_Avoid-exposing-passwords-in-log-on-failing-ldap-look.patch
+      71_04_Avoid-exposing-passwords-in-log-on-failing-ldap-look.patch
+    + Fix crash in VRFY handling when handed an unqualified name
+      (lacking @domain).  Apply the same qualification processing as RCPT.
+      Closes: #834699
+    + Fix a possible security hole, wherein a process operating with the Exim
+      UID can gain a root shell.  Credit to http://www.halfdog.net/ for
+      discovery and writeup.  LP: #1580454
+  * [lintian] exim4-config_files.5 - fix typo.
+
+ -- Andreas Metzler <ametzler@debian.org>  Sun, 25 Sep 2016 15:44:00 +0200
+
+exim4 (4.87-3) unstable; urgency=medium
+
+  * Pull multiple patches from upstream GIT:
+    + 71_01_configure.default-nice-message-for-overlong-lines-Bu.patch
+      Improved message on overlong lines in example config.
+    + 71_02_Delivery-quieten-smtp-transport-conn-reuse-vs.-deliv.patch
+      Fix race condition related to connection reuse.
+      https://bugs.exim.org/show_bug.cgi?id=1810
+    + 71_03_Avoid-exposing-passwords-in-log-on-failing-ldap-look.patch
+      71_04_Avoid-exposing-passwords-in-log-on-failing-ldap-look.patch
+      Avoid exposing passwords in log on failing ldap lookup
+      expansion. https://bugs.exim.org/show_bug.cgi?id=165
+  * Copy information message on rejecting overlong lines in data ACL from
+    upstream example configuration. Closes: #823418
+  * Add NEWS entry on line-length-limit introduced in 4.87~RC1-1.
+    Closes: 821830
+
+ -- Andreas Metzler <ametzler@debian.org>  Sun, 08 May 2016 14:03:10 +0200
+
+exim4 (4.87-2) unstable; urgency=medium
+
+  * Fix reference to README.Debian in 01_exim4-config_listmacrosdefs.
+    (Thanks, L. Guruprasad!) Closes: #821416
+  * Add REMOTE_SMTP_SMARTHOST_HOSTS_REQUIRE_TLS macro to enforce TLS
+    connections (hosts_require_tls option) in remote_smtp_smarthost
+    transport. Closes: #822174
+  * exim4-daemon-heavy: Disable WITH_OLD_DEMIME ("demime" ACL condition). It
+    is deprecated and will be removed in 4.88.
+  * README.Debian*: Fix minor issues  found by lintian.
+  * Fix reference to spec.txt in 30_exim4-config_check_rcpt. Closes: #665399
+  * Drop exim4-base Recommends on perl-modules. This had been unnecessary
+    since 4.80~rc6-1 which dropped /usr/share/exim4/timeout.pl.
+
+ -- Andreas Metzler <ametzler@debian.org>  Sat, 30 Apr 2016 13:38:29 +0200
+
+exim4 (4.87-1) unstable; urgency=medium
+
+  * Fix comment in
+    conf.d/transport/30_exim4-config_remote_smtp_smarthost. (Thanks,
+    Jörg-Volker Peetz!) Closes: #819780
+  * New upstream release.
+
+ -- Andreas Metzler <ametzler@debian.org>  Thu, 07 Apr 2016 19:26:59 +0200
+
+exim4 (4.87~RC7-1) unstable; urgency=low
+
+  * Enable SOCKS support in both -light and -heavy. Closes: #818091
+  * Fix typos in configuration. (Thanks, Vincent Lefevre!) Closes: #819482
+  * New upstream version.
+    + Drop 74_Store-the-initial-working-directory.diff,
+      75_String-expansions-fix-extract.patch,
+      76_only_warn_on_nonempty_environment.diff.
+    + Update debian/example.conf.md5.
+
+ -- Andreas Metzler <ametzler@debian.org>  Fri, 01 Apr 2016 19:04:07 +0200
+
+exim4 (4.87~RC6-3) unstable; urgency=medium
+
+  * Merge changelog entries for 4.86.2-1 and -2.
+  * Upload to unstable.
+  * Add link to CVE details to latest NEWS entry and bump its version and date
+    to match this upload. Closes: #818349, #817244
+
+ -- Andreas Metzler <ametzler@debian.org>  Wed, 23 Mar 2016 18:44:22 +0100
+
+exim4 (4.87~RC6-2) experimental; urgency=medium
+
+  * 74_Store-the-initial-working-directory.diff,
+    76_only_warn_on_nonempty_environment.diff: Upstream followups on the
+    CVE fix (Thanks, Heiko Schlittermann!):
+    + Runtime warning is only generated if (and only if) keep_environment
+      is unset and environment is nonempty.
+    + Store the initial working directory and make it available in the new
+      expansion variable $initial_cwd.
+  * Merge all NEWS.Debian files into a single one, identical for all binary
+    packages. - Different NEWS files built from a single source package is not
+    and has not ever been supported by apt-listchanges which is the most
+    important frontend.
+  * Add a NEWS entry about the environment related runtime warning.
+
+ -- Andreas Metzler <ametzler@debian.org>  Sat, 19 Mar 2016 18:11:32 +0100
+
+exim4 (4.87~RC6-1) experimental; urgency=medium
+
+  * New upstream version.
+  * Add 75_String-expansions-fix-extract.patch from upstream GIT, fixing
+    ${extract } string expansion for the numeric/3-string case. (Bug was
+    introduced in 4.85.)
+  * Set keep_environment to empty value instead of setting a minimal PATH in
+    add_environment.
+
+ -- Andreas Metzler <ametzler@debian.org>  Fri, 11 Mar 2016 19:50:07 +0100
+
+exim4 (4.87~RC5-2) experimental; urgency=medium
+
+  * Update debian/upstream/signing-key.asc, using the keys listed in
+    ftp://ftp.exim.org/pub/exim/Exim-Maintainers-Keyring.asc. This adds
+    Heiko Schlittermann's key.
+  * Bump exim4-config Breaks to exim4-daemon-* (<< 4.87~RC5). Closes: #816790
+
+ -- Andreas Metzler <ametzler@debian.org>  Sat, 05 Mar 2016 13:17:01 +0100
+
+exim4 (4.87~RC5-1) experimental; urgency=medium
+
+  * exim4-config.postinst: Test for existence of /etc/inetd.conf before trying
+    to grep in it. Closes: #814998
+  * New upstream version, includes the patch for CVE-2016-1531. (Local root
+    exploit).
+  * Add macros MAIN_KEEP_ENVIRONMENT and MAIN_ADD_ENVIRONMENT to set the new
+    options. If neither is used we use add_environment to set a minimal
+    PATH=/bin:/usr/bin to avoid a runtime warning.
+
+ -- Andreas Metzler <ametzler@debian.org>  Wed, 02 Mar 2016 21:06:43 +0100
+
+exim4 (4.87~RC3-2) experimental; urgency=medium
+
+  * README.Debian: Refer to Exim specification by chapter name instead of
+    chapter number. Closes: #813351
+  * Fix some spelling errors found by lintian.
+  * Minor debian/rules cleanup:
+    + Restore originally intended behavior, upstream changelog is only
+      shipped in exim4-base, symlinks to it elsewhere.
+    + Drop workaround for #347577, fixed in debhelper 5.0.15.
+    + Use "dh binary-arch" and "dh binary-indep" and a bunch of override
+      targets instead of listing all dh-commands. While this is uglier and
+      slows things down a bit it shortens debian/rules by 40 lines and has the
+      huge benefit that we automatically use all suggested helpers in correct
+      order.
+    + Drop unused variables combinedidbgpackage/dhcombinedidbgpackage.
+    + Delete unused, commented code.
+    + Drop (exported) variable MTACONFLICTS, used only once.
+  * Bugfix: Stop build if generation of EDITME.exim4-heavy fails.
+  * Refresh debian/EDITME.*, -heavy was missing ldap and sql support.
+
+ -- Andreas Metzler <ametzler@debian.org>  Sat, 13 Feb 2016 20:10:53 +0100
+
+exim4 (4.87~RC3-1) experimental; urgency=medium
+
+  * Move Vcs-* from git/http to https.
+  * [lintian] README.Debian: s/desireable/desirable/.
+  * [lintian] README.Debian: Fix grammar error "allow + infinitive".
+  * [lintian] exim4-config.postinst: Use which foo > /dev/null
+    instead of [ -x /path/to/foo ].
+  * Update list of patches in debian/README.Debian.xml
+  * Drop 66_enlarge-dh-parameters-size.dpatch: It does not have any effect
+    with GnuTLS >= 2.12 and even stable has GnuTLS 3.x.
+  * New upstream version.
+    + Upstream's default rcpt ACL now requires that a HELO/EHLO was accepted,
+      merge this change and drop CHECK_MAIL_HELO_ISSUED macro.
+
+ -- Andreas Metzler <ametzler@debian.org>  Thu, 21 Jan 2016 17:44:00 +0100
+
+exim4 (4.87~RC2-1) experimental; urgency=medium
+
+  * New upstream version.
+
+ -- Andreas Metzler <ametzler@debian.org>  Sat, 19 Dec 2015 17:51:39 +0100
+
+exim4 (4.87~RC1-1) experimental; urgency=medium
+
+  * New upstream version.
+    + Refresh patches.
+    + Drop debian/patches/75_00xx*.patch from exim-4_86+fixes branch.
+    + Sync with upstream default configuration: Check maximum (physical, i.e.
+      before unfolding) line length in default spec file data ACL and smtp
+      transport. Bug 1684 Closes: #797919
+    + HS/02 Add the Exim version string to the process info.  This way exiwhat
+      gives some more detail about the running daemon. Closes: #240883
+  * Override upstream's new default of tls_advertise_hosts = * if
+    MAIN_TLS_ENABLE is not set.
+
+ -- Andreas Metzler <ametzler@debian.org>  Fri, 11 Dec 2015 20:15:30 +0100
+
+exim4 (4.86.2-2) unstable; urgency=high
+
+  * Bump exim4-config Breaks to exim4-daemon-* (<< 4.86.2). Closes: #816790
+
+ -- Andreas Metzler <ametzler@debian.org>  Sat, 05 Mar 2016 13:07:31 +0100
+
+exim4 (4.86.2-1) unstable; urgency=high
+
+  * Pull 75_0012_Cutthrough-Fix-bug-with-dot-only-line.patch from upstream
+    4.86+fixes branch.
+  * New upstream security release for CVE-2016-1531.
+    + New options keep_environment/add_environment which are empty by default,
+      i.e. any subprocesses start in a clean (empty) environment.
+    + -C requires an absolute path.
+    + Exim changes it's working directory to / right after startup.
+  * Add macros MAIN_KEEP_ENVIRONMENT and MAIN_ADD_ENVIRONMENT to set the new
+    options. If neither is used we use add_environment to set a minimal
+    PATH=/bin:/usr/bin to avoid a runtime warning.
+
+ -- Andreas Metzler <ametzler@debian.org>  Tue, 01 Mar 2016 19:34:39 +0100
+
+exim4 (4.86-7) unstable; urgency=medium
+
+  * Allow arch-indep build (dpkg-buildpackage -A). Closes: #806023
+  * 75_0011_MIME-fix-crash-on-filenames-having-null-charset.-Bug.patch from
+    exim-4_86+fixes branch fixes another MIME ACL related crash.
+    https://bugs.exim.org/show_bug.cgi?id=1730
+
+ -- Andreas Metzler <ametzler@debian.org>  Sat, 28 Nov 2015 18:45:31 +0100
+
+exim4 (4.86-6) unstable; urgency=medium
+
+  * Cleanup (actual patch is identical): Use
+    75_0009_Avoid-misaligned-access-in-cached-lookup.-Bug-1708.patch from
+    exim-4_86+fixes branch instad of
+    76_Avoid-misaligned-access-in-cached-lookup.-Bug-1708.patch.
+  * Pull 75_0010_DKIM-ignore-space-tab-embedded-in-base64-during-deco.patch,
+    DKIM: ignore space & tab embedded in base64 during decode.  Bug 1700
+
+ -- Andreas Metzler <ametzler@debian.org>  Sun, 08 Nov 2015 07:55:51 +0100
+
+exim4 (4.86-5) unstable; urgency=high
+
+  * Pull 76_Avoid-misaligned-access-in-cached-lookup.-Bug-1708.patch from GIT
+    head to avoid misaligned access in cached lookup. Closes: #803255
+
+ -- Andreas Metzler <ametzler@debian.org>  Tue, 03 Nov 2015 19:33:49 +0100
+
+exim4 (4.86-4) unstable; urgency=medium
+
+  * Fix documentation of lowuid_aliases router, exceptions are in
+    CONFDIR/lowuid-aliases not CONFDIR/lowuid_aliases. (Thanks, Tim Krah)
+    Closes: #799672
+  * fcron has been removed from Debian in 2011, stop listing it as an
+    alternative dependency of exim4-base (Thanks, Alexandre Detiste).
+    Closes: #798236
+  * Update to upstream exim-4_86+fixes branch:
+    + Drop 75_Fix-ESMTP-MAIL-command-option-processing.patch,
+      76_Fix-post-transport-crash.patch,
+      77_Fix-post-transport-crash-safeguard-for-missing-spool.patch,
+      78_Close-logs-after-daemon-process-exceptional-write.patch.
+    + Add 75_0001-Fix-post-transport-crash.patch
+      75_0002-Fix-post-transport-crash-safeguard-for-missing-spool.patch
+      75_0003-Fix-ESMTP-MAIL-command-option-processing.patch
+      75_0005-Close-logs-after-daemon-process-exceptional-write.-B.patch
+      75_0007-DNS-time-limit-cached-returns-using-TTL.-Bug-1395.patch
+      75_0008-Retry-always-use-interface-if-set-for-retry-DB-key.-.patch
+  * Use dh v9.
+
+ -- Andreas Metzler <ametzler@debian.org>  Sat, 17 Oct 2015 15:01:01 +0200
+
+exim4 (4.86-3) unstable; urgency=medium
+
+  * Pull three patches from upstream git:
+    + 75_Fix-ESMTP-MAIL-command-option-processing.patch:
+      Corrects handling of mail-addresses with whitespace.
+      <http://article.gmane.org/gmane.mail.exim.user/97069>
+    + 76_Fix-post-transport-crash.patch
+      77_Fix-post-transport-crash-safeguard-for-missing-spool.patch
+      <https://bugs.exim.org/show_bug.cgi?id=1671>
+  * Fix spelling error in copyright file. (Thanks, lintian)
+  * Pull 77_Fix-post-transport-crash-safeguard-for-missing-spool.patch from
+    upstream git, exim was keeping logfiles open after after a "too many
+    connections" event. Closes: #796524, #476958 (Thanks to Andreas Pflug for
+    chasing this.)
+  * When saving the berkeley DB version at build-time pass -P option to cpp,
+    to prevent linebreaks.
+
+ -- Andreas Metzler <ametzler@debian.org>  Tue, 25 Aug 2015 20:05:59 +0200
+
+exim4 (4.86-2) unstable; urgency=high
+
+  * Update exim4-config Breaks, PRDR support is was moved from being
+    Experimental into the mainline with 4.83.
+    Closes: #794320
+
+ -- Andreas Metzler <ametzler@debian.org>  Sun, 02 Aug 2015 07:40:24 +0200
+
+exim4 (4.86-1) unstable; urgency=medium
+
+  * New upstream version, identical to RC5 (except for the version string).
+
+ -- Andreas Metzler <ametzler@debian.org>  Sun, 26 Jul 2015 18:35:33 +0200
+
+exim4 (4.86~RC5-1) unstable; urgency=medium
+
+  * New upstream version.
+    + Drop 75_Bump-LOCAL_SCAN_ABI_VERSION.patch.
+
+ -- Andreas Metzler <ametzler@debian.org>  Sat, 18 Jul 2015 11:46:11 +0200
+
+exim4 (4.86~RC4-2) unstable; urgency=medium
+
+  * Drop libmysqlclient15-dev alternative build-dependency. Closes: #790463
+  * Update list of upstream gpg-keys (0x4D1E900E14C1CC04 Phil Pennock,
+    0x85AB833FDDC03262 Nigel Metheringham, 0xFFC0F14C84C71B6E Tony Finch,
+    0xC4F4F94804D29EBA Todd Lyons, 0xBCE58C8CE41F32DF Jeremy Harris,
+    0x63762CDA67E2F359 David Woodhouse, 0xAD5EDBB793EC57E4 Graeme Fowler),
+    transition from debian/upstream-signing-key.pgp to
+    debian/upstream/signing-key.asc.
+  * Pull 75_Bump-LOCAL_SCAN_ABI_VERSION.patch from upstream GIT and update
+    exim4-localscanapi-x.y provides to 2.0. A binNMU of sa-exim will then
+    properly fix the issue. Closes: #790616
+
+ -- Andreas Metzler <ametzler@debian.org>  Sun, 05 Jul 2015 11:47:47 +0200
+
+exim4 (4.86~RC4-1) unstable; urgency=medium
+
+  * unexport/undefine TZ in debian/rules for reproducible build. It would be
+    used as default value for TIMEZONE_DEFAULT.
+  * New upstream version.
+    + Unfuzz 31_eximmanpage.dpatch.
+
+ -- Andreas Metzler <ametzler@debian.org>  Mon, 29 Jun 2015 07:43:19 +0200
+
+exim4 (4.86~RC3-2) unstable; urgency=medium
+
+  * Upload to unstable.
+
+ -- Andreas Metzler <ametzler@debian.org>  Tue, 23 Jun 2015 19:11:19 +0200
+
+exim4 (4.86~RC3-1) experimental; urgency=medium
+
+  * Don't provide default-mta on Ubuntu and Ubuntu-derivatives. See LP-bug
+    1166671.
+  * New upstream version.
+
+ -- Andreas Metzler <ametzler@debian.org>  Mon, 22 Jun 2015 20:39:11 +0200
+
+exim4 (4.86~RC2-1) experimental; urgency=medium
+
+  * Drop nowadays unneeded XS-Testsuite: autopkgtest in debian/control
+    (Thanks, lintian).
+  * New upstream version:
+    +Drop included patches.
+     (-72_0001-Guard-routing-against-a-null-deref.-Bug-1639.patch,
+     72_0002-Spamd-add-missing-initialiser.-Rspamd-mode-was-incor.patch,
+     72_0003-DSN-fix-null-deref-when-bounce-is-due-to-conn-timeou.patch, 
+     72_0004-Content-scan-Use-ETIMEDOUT-not-ETIME-as-having-bette.patch)
+  * Sync Debian config with upstream default config:
+    + Set prdr_enable.
+    + Add +smtp_protocol_error +smtp_syntax_error +tls_certificate_verified to
+      log_selector option value.
+
+ -- Andreas Metzler <ametzler@debian.org>  Wed, 17 Jun 2015 19:49:58 +0200
+
+exim4 (4.86~RC1-3) experimental; urgency=medium
+
+  * Get time and date of latest debian/changelog entry and patch exim(on) to
+    use these instead of __DATE__ and __TIME__.
+  * Pull 72_0004-Content-scan-Use-ETIMEDOUT-not-ETIME-as-having-bette.patch
+    from GIT to fix FTBFS on kfreebsd.
+
+ -- Andreas Metzler <ametzler@debian.org>  Sat, 13 Jun 2015 15:22:47 +0200
+
+exim4 (4.86~RC1-2) experimental; urgency=medium
+
+  * Pull three post-release fixes from upstream GIT. (null pointer
+    derefencing, and spam scanning defaulting to rspam mode)
+    + 72_0001-Guard-routing-against-a-null-deref.-Bug-1639.patch
+    + 72_0002-Spamd-add-missing-initialiser.-Rspamd-mode-was-incor.patch
+    + 72_0003-DSN-fix-null-deref-when-bounce-is-due-to-conn-timeou.patch
+
+ -- Andreas Metzler <ametzler@debian.org>  Sun, 07 Jun 2015 07:26:13 +0200
+
+exim4 (4.86~RC1-1) experimental; urgency=medium
+
+  * New upstream release.
+    + Drop 84_Fix-truncation-of-items-in-headers_remove-lists-this.patch,
+      refresh patches.
+    + Update EDITME*, enable AUTH_TLS for -heavy.
+    + Sync Debian config with upstream default config, rfc1413 calls are now
+      disabled by default.
+    + Uses MIME format bounce messages (RFC 3461). Closes: #230284,#400741
+    + The spamd_address main option now supports an optional timeout value per
+      server (tmo=timespec), it defaults two 2 minutes. Closes:  #297915
+    + spamd_address also accepts hostnames and IPv6 addresses. Closes: #751687
+    + log reason for defer, on a hostlist dns-lookup temporary error.
+      Closes: #670035
+
+ -- Andreas Metzler <ametzler@debian.org>  Sat, 06 Jun 2015 15:41:33 +0200
+
+exim4 (4.85-3) unstable; urgency=medium
+
+  * Upload to unstable.
+
+ -- Andreas Metzler <ametzler@debian.org>  Tue, 28 Apr 2015 19:34:16 +0200
+
+exim4 (4.85-2) experimental; urgency=medium
+
+  * Merge from unstable 4.84-8.
+    + Tighten dependency of exim4 on exim4-base to (>= ${source:Version}) and
+      (<< ${source:Version}.1), at least source version, but not the next
+      sourceful upload. Closes: #777246
+    + Pull 84_Fix-truncation-of-items-in-headers_remove-lists-this.patch from
+      upstream GIT which fixes breakage of string-expansion in headers_remove
+      commands. (Thanks Gordon Dickens, for the pointer.) -
+      83_Remove-limit-on-remove_headers-item-size.-Bug-1533.patch not added
+      here since it already part of 4.85.
+
+ -- Andreas Metzler <ametzler@debian.org>  Sat, 21 Feb 2015 15:38:47 +0100
+
+exim4 (4.85-1) experimental; urgency=medium
+
+  * exim4-config_files.5: Escape dots in regex. (Thanks, ael)
+  * New upstream version.
+
+ -- Andreas Metzler <ametzler@debian.org>  Tue, 13 Jan 2015 18:48:45 +0100
+
+exim4 (4.85~RC4-1) experimental; urgency=medium
+
+  * update-exim4.conf:
+    + Drop unused variable UPEX4C_internal_tmp.
+    + Use tempfile(1) if the generated file will not be written to
+      /var/lib/exim4/.
+    + Add --check option.
+  * init-script: On restart use update-exim4.conf --check before stopping the
+    daemon. (This is a no-op with systemd since its sysv compat layer
+    translates "foo restart" into "foo stop" "foo start" instead of using the
+    init scripts restart target.)
+  * Handle _RC in watchfile with uversionmangle.
+  * New upstream version.
+    + Stop repacking source, rfcs have been dropped.
+
+ -- Andreas Metzler <ametzler@debian.org>  Wed, 31 Dec 2014 14:24:35 +0100
+
+exim4 (4.85~RC3+dfsg-1) experimental; urgency=medium
+
+  * New upstream version.
+
+ -- Andreas Metzler <ametzler@debian.org>  Thu, 18 Dec 2014 19:07:59 +0100
+
+exim4 (4.85~RC2+dfsg-1) experimental; urgency=medium
+
+  * New upstream version.
+  * Unfuzz patches: 50_localscan_dlopen.dpatch 67_unnecessaryCopt.diff
+    70_remove_exim-users_references.dpatch.
+
+ -- Andreas Metzler <ametzler@debian.org>  Mon, 01 Dec 2014 18:54:17 +0100
+
+exim4 (4.85~RC1+dfsg-1) experimental; 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
+  * New upstream version.
+    + Unfuzz 66_enlarge-dh-parameters-size.dpatch
+    + Drop 80_mime_empty_charset.diff.
+  * Remove rfc from upstream source and repack it.
+
+ -- Andreas Metzler <ametzler@debian.org>  Tue, 18 Nov 2014 19:28:20 +0100
+
 exim4 (4.84-8+hcoop4) unstable; urgency=medium
 
   * Missed another chown that needs skipping
@@ -3284,7 +4006,7 @@ exim4 (4.31-1) unstable; urgency=low
     - Supports CRL (Certificate Revocation List) (Closes: #229063)
     - exim_dbmbuild does not crash on _very_ long RHS values.
       (Closes: #231597)
-    - route_list does not use a fixed lenght buffer anymore. (Closes: #231979)
+    - route_list does not use a fixed length buffer anymore. (Closes: #231979)
     - An empty tls_verify_certificates file is correctly interpreted as empty
       list instead of breaking TLS. (Closes: #236478)
   * Korean translation of debconf templates by Changwoo Ryu (Closes: #241499)
index 7f8f011..ec63514 100644 (file)
@@ -1 +1 @@
-7
+9
index ead6280..4623715 100644 (file)
@@ -4,17 +4,14 @@ Priority: standard
 Maintainer: Exim4 Maintainers <pkg-exim4-maintainers@lists.alioth.debian.org>
 Uploaders: Andreas Metzler <ametzler@debian.org>,Marc Haber <mh+debian-packages@zugschlus.de>
 Homepage: http://www.exim.org/
-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
-Vcs-Browser: http://anonscm.debian.org/gitweb/?p=pkg-exim4/exim4.git
-Build-Depends: debhelper (>= 7.0.15), po-debconf, docbook-xsl, xsltproc,
+Standards-Version: 3.9.8
+Vcs-Git: https://anonscm.debian.org/git/pkg-exim4/exim4.git
+Vcs-Browser: https://anonscm.debian.org/git/pkg-exim4/exim4.git
+Build-Depends: debhelper (>= 9), po-debconf, docbook-xsl, xsltproc,
   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, libgnutls28-dev, libsasl2-dev
-XS-Testsuite: autopkgtest
 
 Package: exim4-base
 Architecture: any
@@ -24,10 +21,10 @@ Breaks: exim4-daemon-light (<<${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 | cron-daemon | anacron | fcron,
+ cron | cron-daemon | anacron,
  exim4-config (>=4.82) | exim4-config-2, adduser, netbase, lsb-base (>= 3.0-6)
 # psmisc just for exiwhat.
-Recommends: psmisc, mailx, perl-modules
+Recommends: psmisc, mailx
 Suggests: mail-reader, eximon4, exim4-doc-html|exim4-doc-info, 
  gnutls-bin | openssl, file, spf-tools-perl, swaks
 Description: support files for all Exim MTA (v4) packages
@@ -59,7 +56,7 @@ Description: support files for all Exim MTA (v4) packages
 
 Package: exim4-config
 Architecture: all
-Breaks: exim4-daemon-light (<<4.82~rc1), exim4-daemon-heavy (<<4.82~rc1)
+Breaks: exim4-daemon-light (<< 4.87~RC5), exim4-daemon-heavy (<< 4.87~RC5)
 Provides: exim4-config-2
 Conflicts: exim, exim-tls, exim4-config, exim4-config-2, ${MTA-Conflicts}
 Depends: ${shlibs:Depends}, ${misc:Depends}, adduser
@@ -93,7 +90,8 @@ Description: configuration for the Exim MTA (v4)
 
 Package: exim4-daemon-light
 Architecture: any
-Provides: mail-transport-agent, exim4-localscanapi-1.0, exim4-localscanapi-1.1, default-mta
+Provides: mail-transport-agent, exim4-localscanapi-2.0,
+ ${dist:Provides:exim4-daemon-light}
 Conflicts: mail-transport-agent
 Replaces: mail-transport-agent, exim4-base (<= 4.61-1)
 Depends: exim4-base (>= ${Upstream-Version}), ${shlibs:Depends}, ${misc:Depends}
@@ -149,7 +147,7 @@ Description: metapackage to ease Exim MTA (v4) installation
 Package: exim4-daemon-heavy
 Architecture: any
 Priority: optional
-Provides: mail-transport-agent, exim4-localscanapi-1.0, exim4-localscanapi-1.1
+Provides: mail-transport-agent, exim4-localscanapi-2.0
 Conflicts: mail-transport-agent
 Replaces: mail-transport-agent, exim4-base (<= 4.61-1)
 Depends: exim4-base (>= ${Upstream-Version}), ${shlibs:Depends},
@@ -183,7 +181,7 @@ Description: Exim MTA (v4) daemon with extended features, including exiscan-acl
 #Package: exim4-daemon-custom
 #Architecture: any
 #Priority: optional
-#Provides: mail-transport-agent, exim4-localscanapi-1.0, exim4-localscanapi-1.1
+#Provides: mail-transport-agent, exim4-localscanapi-2.0
 #Conflicts: mail-transport-agent
 #Replaces: mail-transport-agent, exim4-base (<= 4.61-1)
 #Depends: exim4-base (>= ${Upstream-Version}), ${shlibs:Depends}, ${misc:Depends}
index 453276d..cd123f2 100644 (file)
@@ -55,7 +55,7 @@ important feedback:
 
 
 -----------------------------------------------------------------
-exim is copyright (c) 1999 University of Cambridge.
+exim is copyright (c) 1995 - 2017 University of Cambridge.
 
 The original licence is as follows (from the file NOTICE in the upstream
 distribution); a copy of the GNU GPL version 2 is available in 
@@ -65,7 +65,7 @@ _________________________________________________________________________
 THE EXIM MAIL TRANSFER AGENT
 ----------------------------
 
-Copyright (c) 2002 University of Cambridge
+Copyright (c) 2004 University of Cambridge
 
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -83,7 +83,7 @@ GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
-Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
 
 UNSOLICITED EMAIL
@@ -101,14 +101,6 @@ INCORPORATED CODE
 
 A number of pieces of external code are included in the Exim distribution.
 
- .   Regular expressions are supported in the main Exim program and in the
-     Exim monitor using the freely-distributable PCRE library, copyright (c)
-     2003 University of Cambridge. The source is distributed in the directory
-     src/pcre. However, this is a cut-down version of PCRE. If you want to use
-     the PCRE library in other programs, you should obtain and install the
-     full version from ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre.
-
-
  .   Support for the cdb (Constant DataBase) lookup method is provided by code
      contributed by Nigel Metheringham of Planet Online Ltd. which contains
      the following statements:
@@ -222,34 +214,34 @@ A number of pieces of external code are included in the Exim distribution.
 
 --
 Philip Hazel            University of Cambridge Computing Service,
-ph10@cus.cam.ac.uk      Cambridge, England. Phone: +44 1223 334714.
------------------------------------------------------------------
-
-
-
 -----------------------------------------------------------------
 src/pdkim/*
 
 PDKIM - a RFC4871 (DKIM) implementation
 http://duncanthrax.net/pdkim/
-Copyright (C) 2009      Tom Kistner <tom@duncanthrax.net>
+Copyright (C) 2009 - 2016  Tom Kistner <tom@duncanthrax.net>
+Copyright (C) 2016 - 2017 Jeremy Harris <jgh@exim.org>
 
 Includes code from the PolarSSL project.
 http://polarssl.org
 Copyright (C) 2009      Paul Bakker <polarssl_maintainer@polarssl.org>
 Copyright (C) 2006-2008 Christophe Devine
+Copyright (C) 2006-2010, Brainspark B.V.
 
 This copy of PDKIM is included with Exim. For a standalone distribution,
 visit http://duncanthrax.net/pdkim/.
 
 License: Both the parts from PolarSSL and the original code are licensed
 under GPLv2+.
+
+Please note that the parts copied from PolarSSL are only used with ancient
+(< 2.10) GnuTLS.
 -----------------------------------------------------------------
 
 -----------------------------------------------------------------
 Generating a tarball from CVS snapshot.
 
-Upstream is keeping sourcecode and documention (including changelog) in
+Upstream is keeping sourcecode and documentation (including changelog) in
 separate CVS modules: exim-src and exim-doc. However the release tarball
 contains parts from both modules.
 
index 7a6a3e7..f8c53d6 100644 (file)
@@ -7,10 +7,5 @@
 # accepted or denied.
 #
 acl_check_mail:
-  .ifdef CHECK_MAIL_HELO_ISSUED
-  deny
-    message = no HELO given before MAIL command
-    condition = ${if def:sender_helo_name {no}{yes}}
-  .endif
 
   accept
index 4949587..d616720 100644 (file)
@@ -114,9 +114,10 @@ acl_check_rcpt:
   # to enable this feature.
   #
   # This feature does not work in smarthost and satellite setups as
-  # with these setups all domains pass verification. See spec.txt chapter
-  # 39.31 with the added information that a smarthost/satellite setup
-  # routes all non-local e-mail to the smarthost.
+  # with these setups all domains pass verification. See spec.txt section
+  # "Access control lists" subsection "Address verification" with the added
+  # information that a smarthost/satellite setup routes all non-local e-mail
+  # to the smarthost.
   .ifdef CHECK_RCPT_VERIFY_SENDER
   deny
     message = Sender verification failed
@@ -170,6 +171,10 @@ acl_check_rcpt:
     control = submission/sender_retain
     control = dkim_disable_verify
 
+  # Insist that a HELO/EHLO was accepted.
+
+  require message      = nice hosts say HELO first
+          condition    = ${if def:sender_helo_name}
 
   # Insist that any other recipient address that we accept is either in one of
   # our local domains, or is in a domain for which we explicitly allow
index 1b371d2..abfa164 100644 (file)
@@ -8,6 +8,15 @@
 
 acl_check_data:
 
+  # Deny if the message contains an overlong line.  Per the standards
+  # we should never receive one such via SMTP.
+  #
+  .ifndef IGNORE_SMTP_LINE_LENGTH_LIMIT
+  deny    message    = maximum allowed line length is 998 octets, \
+                       got $max_received_linelength
+          condition  = ${if > {$max_received_linelength}{998}}
+  .endif
+
   # Deny unless the address list headers are syntactically correct.
   #
   # If you enable this, you might reject legitimate mail.
index 8e51605..82b0d1f 100644 (file)
@@ -84,7 +84,8 @@ gecos_name = $1
 
 # These macros are documented in acl/30_exim4-config_check_rcpt,
 # can be changed here or overridden by a locally added configuration
-# file as described in README.Debian chapter 2.1.2
+# file as described in README.Debian section "Using Exim Macros to control
+# the configuration".
 
 .ifndef CHECK_RCPT_LOCAL_LOCALPARTS
 CHECK_RCPT_LOCAL_LOCALPARTS = ^[.] : ^.*[@%!/|`#&?]
@@ -96,5 +97,5 @@ CHECK_RCPT_REMOTE_LOCALPARTS = ^[./|] : ^.*[@%!`#&?] : ^.*/\\.\\./
 
 # always log tls_peerdn as we use TLS for outgoing connects by default
 .ifndef MAIN_LOG_SELECTOR
-MAIN_LOG_SELECTOR = +tls_peerdn
+MAIN_LOG_SELECTOR = +smtp_protocol_error +smtp_syntax_error +tls_certificate_verified +tls_peerdn
 .endif
index cae5e9b..bf00d03 100644 (file)
@@ -64,7 +64,7 @@ message_size_limit = MESSAGE_SIZE_LIMIT
 # Allow Exim to recognize addresses of the form "user@[10.11.12.13]",
 # where the domain part is a "domain literal" (an IP address) instead
 # of a named domain. The RFCs require this facility, but it is disabled
-# in the default config since it is seldomly used and frequently abused.
+# in the default config since it is rarely used and frequently abused.
 # Domain literal support also needs a special router, which is automatically
 # enabled if you use the enable macro MAIN_ALLOW_DOMAIN_LITERALS.
 # Additionally, you might want to make your local IP addresses (or @[])
@@ -91,18 +91,26 @@ host_lookup = MAIN_HOST_LOOKUP
 primary_hostname = MAIN_HARDCODE_PRIMARY_HOSTNAME
 .endif
 
-# The settings below, which are actually the same as the defaults in the
-# code, cause Exim to make RFC 1413 (ident) callbacks for all incoming SMTP
-# calls. You can limit the hosts to which these calls are made, and/or change
-# the timeout that is used. If you set the timeout to zero, all RFC 1413 calls
-# are disabled. RFC 1413 calls are cheap and can provide useful information
-# for tracing problem messages, but some hosts and firewalls are
-# misconfigured to drop the requests instead of either answering or
-# rejecting them. This can result in a timeout instead of an immediate refused
-# connection, leading to delays on starting up SMTP sessions. (The default was
-# reduced from 30s to 5s for release 4.61.)
-# rfc1413_hosts = *
-# rfc1413_query_timeout = 5s
+# The settings below cause Exim to make RFC 1413 (ident) callbacks
+# for all incoming SMTP calls. You can limit the hosts to which these
+# calls are made, and/or change the timeout that is used. If you set
+# the timeout to zero, all RFC 1413 calls are disabled. RFC 1413 calls
+# are cheap and can provide useful information for tracing problem
+# messages, but some hosts and firewalls have problems with them.
+# This can result in a timeout instead of an immediate refused
+# connection, leading to delays on starting up SMTP sessions.
+# (The default was reduced from 30s to 5s for release 4.61. and to
+# disabled for release 4.86)
+#
+#rfc1413_hosts = *
+#rfc1413_query_timeout = 5s
+
+
+# Enable an efficiency feature.  We advertise the feature; clients
+# may request to use it.  For multi-recipient mails we then can
+# reject or accept per-user after the message is received.
+#
+prdr_enable = true
 
 # When using an external relay tester (such as rt.njabl.org and/or the
 # currently defunct relay-test.mail-abuse.org, the test may be aborted
@@ -198,3 +206,13 @@ trusted_groups = MAIN_TRUSTED_GROUPS
 # SMTP Banner. The example includes the Debian version in the SMTP dialog
 # MAIN_SMTP_BANNER = "${primary_hostname} ESMTP Exim ${version_number} (Debian package MAIN_PACKAGE_VERSION) ${tod_full}"
 # smtp_banner = $smtp_active_hostname ESMTP Exim $version_number $tod_full
+
+.ifdef MAIN_KEEP_ENVIRONMENT
+keep_environment = MAIN_KEEP_ENVIRONMENT
+.else
+# set option to empty value to avoid warning.
+keep_environment =
+.endif
+.ifdef MAIN_ADD_ENVIRONMENT
+add_environment = MAIN_ADD_ENVIRONMENT
+.endif
index 3f40c59..86299e1 100644 (file)
@@ -75,4 +75,11 @@ tls_verify_hosts = MAIN_TLS_VERIFY_HOSTS
 tls_try_verify_hosts = MAIN_TLS_TRY_VERIFY_HOSTS
 .endif
 
+.ifdef _HAVE_GNUTLS
+tls_dhparam = historic
+.endif
+
+.else
+# Don't advertise TLS if MAIN_TLS_ENABLE is not set.
+tls_advertise_hosts =
 .endif
index b11b797..b7415b6 100644 (file)
@@ -2,7 +2,7 @@
 ### rewrite/31_exim4-config_rewriting
 #################################
 
-# This rewriting rule is particularily useful for dialup users who
+# This rewriting rule is particularly useful for dialup users who
 # don't have their own domain, but could be useful for anyone.
 # It looks up the real address of all local users in a file
 .ifndef NO_EAA_REWRITE_REWRITE
index 244b479..d37fea6 100644 (file)
@@ -4,7 +4,7 @@
 
 # This router handles e-mail addresses in "domain literal" form like
 # <user@[10.11.12.13]>. The RFCs require this facility, but it is disabled
-# in the default config since it is seldomly used and frequently abused.
+# in the default config since it is rarely used and frequently abused.
 # Domain literal support also needs to be enabled in the main config,
 # which is automatically done if you use the enable macro
 # MAIN_ALLOW_DOMAIN_LITERALS.
index 01a4c94..1884b21 100644 (file)
@@ -4,7 +4,7 @@
 
 .ifdef DCconfig_satellite
 # This router is only used for configtype=satellite.
-# It takes care to route all mail targetted to <somelocaluser@this.machine>
+# It takes care to route all mail targeted to <somelocaluser@this.machine>
 # to the host where we read our mail
 #
 hub_user:
index 11d72bb..42bd601 100644 (file)
@@ -2,10 +2,16 @@
 ### transport/30_exim4-config_remote_smtp
 #################################
 # This transport is used for delivering messages over SMTP connections.
+# Refuse to send any message with over-long lines, which could have
+# been received other than via SMTP. The use of message_size_limit to
+# enforce this is a red herring.
 
 remote_smtp:
   debug_print = "T: remote_smtp for $local_part@$domain"
   driver = smtp
+.ifndef IGNORE_SMTP_LINE_LENGTH_LIMIT
+  message_size_limit = ${if > {$max_received_linelength}{998} {1}{0}}
+.endif
 .ifdef REMOTE_SMTP_HOSTS_AVOID_TLS
   hosts_avoid_tls = REMOTE_SMTP_HOSTS_AVOID_TLS
 .endif
index b834249..9c18305 100644 (file)
@@ -5,10 +5,16 @@
 # This transport is used for delivering messages over SMTP connections
 # to a smarthost. The local host tries to authenticate.
 # This transport is used for smarthost and satellite configurations.
+# Refuse to send any messsage with over-long lines, which could have
+# been received other than via SMTP. The use of message_size_limit to
+# enforce this is a red herring.
 
 remote_smtp_smarthost:
   debug_print = "T: remote_smtp_smarthost for $local_part@$domain"
   driver = smtp
+.ifndef IGNORE_SMTP_LINE_LENGTH_LIMIT
+  message_size_limit = ${if > {$max_received_linelength}{998} {1}{0}}
+.endif
   hosts_try_auth = <; ${if exists{CONFDIR/passwd.client} \
         {\
         ${lookup{$host}nwildlsearch{CONFDIR/passwd.client}{$host_address}}\
@@ -18,6 +24,9 @@ remote_smtp_smarthost:
 .ifdef REMOTE_SMTP_SMARTHOST_HOSTS_AVOID_TLS
   hosts_avoid_tls = REMOTE_SMTP_SMARTHOST_HOSTS_AVOID_TLS
 .endif
+.ifdef REMOTE_SMTP_SMARTHOST_HOSTS_REQUIRE_TLS
+  hosts_require_tls = REMOTE_SMTP_SMARTHOST_HOSTS_REQUIRE_TLS
+.endif
 .ifdef REMOTE_SMTP_HEADERS_REWRITE
   headers_rewrite = REMOTE_SMTP_HEADERS_REWRITE
 .endif
index fac2d9c..59410db 100644 (file)
@@ -29,12 +29,13 @@ $0 - Generate exim4 configuration files
        --removecomments - Remove comment lines
        -o|--output file - write output to file instead of ${UPEX4C_outputfile}
        -d|--confdir directory - read input from given directory instead of ${UPEX4C_confdir}
+       --check - Test generated file for validity and remove it again.
 EOF
 }
 
 ## Parse commandline
 TEMP=$(getopt -n update-exim4.conf \
-       -l keepcomments,removecomments,output:,confdir:,help,verbose -- \
+       -l check,keepcomments,removecomments,output:,confdir:,help,verbose -- \
        +o:d:vh "$@")
 
 if test "$?" != 0; then
@@ -58,6 +59,9 @@ while test "$1" != "--"; do
                --removecomments)
                        UPEX4C_comments=no
                ;;
+               --check)
+                       UPEX4C_check=yes
+               ;;
                -o|--output)
                        shift
                        UPEX4C_outputfile="$1"
@@ -97,6 +101,14 @@ else
   exit 1
 fi
 
+
+UPEX4C_autoconfigfile=/var/lib/exim4/config.autogenerated
+if [ "$(dirname ${UPEX4C_outputfile})" = "/var/lib/exim4" ] ; then
+       UPEX4C_tmp="${UPEX4C_outputfile}.tmp"
+else
+       UPEX4C_tmp="$(tempfile -m600 -p ex4)"
+fi
+
 lowerpipe() {
   tr 'A-Z' 'a-z'
 }
@@ -152,9 +164,6 @@ dc_other_hostnames="$(lowercase $dc_other_hostnames | check_ascii_pipe)"
 local_domains="$(echo @:localhost:"${dc_other_hostnames}" | \
        sed -e 's/[;: ]*$//' -e 's/ *//' -e 's/;/:/g')"
 
-UPEX4C_internal_tmp="$(tempfile -m600 -p ex4)"
-
-trap "rm -f ${UPEX4C_internal_tmp}" EXIT INT TERM
 
 # run-parts emulation, stolen from Branden's /etc/X11/Xsession
 # Addition: Use file.rul instead if file if it exists.
@@ -213,19 +222,19 @@ cat_parts() {
 }
 
 gentmpconf() {
-       rm -f "${UPEX4C_outputfile}.tmp"
-       touch "${UPEX4C_outputfile}.tmp"
+       rm -f "${UPEX4C_tmp}"
+       touch "${UPEX4C_tmp}"
        # this can be removed by the end of 2007
        #chown --reference=${TEMPLATEFILE} \
-       #       ${UPEX4C_outputfile}.tmp ${UPEX4C_outputfile}
+       #       ${UPEX4C_tmp} ${UPEX4C_outputfile}
        #chmod --reference=${TEMPLATEFILE} \
-       #               ${UPEX4C_outputfile}.tmp ${UPEX4C_outputfile}
+       #               ${UPEX4C_tmp} ${UPEX4C_outputfile}
        if [ "$(id -u)" = "0" ]; then 
-               chown root:Debian-exim "${UPEX4C_outputfile}.tmp"
+               chown root:Debian-exim "${UPEX4C_tmp}"
                [ -e "${UPEX4C_outputfile}" ] && \
                chown root:Debian-exim "${UPEX4C_outputfile}"
        fi
-       chmod 640 "${UPEX4C_outputfile}.tmp"
+       chmod 640 "${UPEX4C_tmp}"
        if [ -e "${UPEX4C_outputfile}" ]; then
          chmod 640 "${UPEX4C_outputfile}"
        fi
@@ -241,7 +250,7 @@ removecomments(){
 
 gentmpconf
 
-cat << EOF >> "${UPEX4C_outputfile}.tmp"
+cat << EOF >> "${UPEX4C_tmp}"
 #########
 # WARNING WARNING WARNING
 # WARNING WARNING WARNING
@@ -252,17 +261,17 @@ cat << EOF >> "${UPEX4C_outputfile}.tmp"
 EOF
 
 if [ "${dc_use_split_config}" = "true" ] ; then
-cat << EOF >> "${UPEX4C_outputfile}.tmp"
+cat << EOF >> "${UPEX4C_tmp}"
 # split config files in the $UPEX4C_confd/ directory.
 EOF
 else
-cat << EOF >> "${UPEX4C_outputfile}.tmp"
+cat << EOF >> "${UPEX4C_tmp}"
 # non-split config ($UPEX4C_confdir/exim4.conf.localmacros
 # and $UPEX4C_confdir/exim4.conf.template).
 EOF
 fi
 
-cat << EOF >> "${UPEX4C_outputfile}.tmp"
+cat << EOF >> "${UPEX4C_tmp}"
 # The config files are supplemented with package installation/configuration
 # settings managed by debconf. This data is stored in
 # $UPEX4C_confdir/update-exim4.conf.conf
@@ -319,7 +328,7 @@ case "$dc_eximconfig_configtype" in
                                 cat_parts "${UPEX4C_confd}/$i"
                         done | \
                         removecomments \
-                       >> "${UPEX4C_outputfile}.tmp"
+                       >> "${UPEX4C_tmp}"
                 else
                        LOCALMACROS=""
                        if [ -e "/etc/exim4/exim4.conf.localmacros" ]; then
@@ -327,9 +336,9 @@ case "$dc_eximconfig_configtype" in
                        fi
                         cat "${LOCALMACROS:-/dev/null}" "${TEMPLATEFILE:-/dev/null}" | \
                         removecomments \
-                                >> "${UPEX4C_outputfile}.tmp"
+                                >> "${UPEX4C_tmp}"
                 fi
-               mv -f "${UPEX4C_outputfile}.tmp" "${UPEX4C_outputfile}"
+               mv -f "${UPEX4C_tmp}" "${UPEX4C_outputfile}"
                chmod "${CFILEMODE}" "${UPEX4C_outputfile}"
                [ "${UPEX4C_verbose}" = "yes" ] && \
                        echo "Not substituting variables since conftype is none (or other)"
@@ -409,7 +418,7 @@ true)
        done \
        | removecomments \
        | sed "s|^\(UPEX4CmacrosUPEX4C.*\)$|\1\n$UPEX4C_macros|" \
-       >> "${UPEX4C_outputfile}.tmp"
+       >> "${UPEX4C_tmp}"
        RELEVANTTEMPLATE="$UPEX4C_confd"
 ;;
 false)
@@ -424,12 +433,12 @@ false)
        cat "${LOCALMACROS:-/dev/null}" "${TEMPLATEFILE:-/dev/null}" \
        | removecomments \
        | sed "s|^\(UPEX4CmacrosUPEX4C.*\)$|\1\n$UPEX4C_macros|" \
-       >> "${UPEX4C_outputfile}.tmp"
+       >> "${UPEX4C_tmp}"
        RELEVANTTEMPLATE="$TEMPLATEFILE"
 ;;
 *)
        errormessage "Invalid value for dc_use_split_config: \"${dc_use_split_config}\", exiting."
-       rm -f "${UPEX4C_outputfile}.tmp"
+       rm -f "${UPEX4C_tmp}"
        exit 1
 ;;
 esac
@@ -451,19 +460,25 @@ if grep -qr '# UPEX4CmacrosUPEX4C' $RELEVANTTEMPLATE \
 fi
 
 
-# test validity if called without -o
-if [ "${UPEX4C_outputfile}" = "${UPEX4C_autoconfigfile}" ] && \
-       [ -x "${EXIM}" ] ; then
-       if ! "${EXIM}" -C "${UPEX4C_outputfile}.tmp" -bV > /dev/null ; then
-               # we have an error in the configuration file. Do not install
-               # and activate. However, errors in string expansions inside
-               # the configuration file are not detected by this check!
-               errormessage "Invalid new configfile ${UPEX4C_outputfile}.tmp, not installing ${UPEX4C_outputfile}.tmp to ${UPEX4C_outputfile}"
-               exit 1
+# test validity if called without -o or if --check was supplied
+if [ "${UPEX4C_outputfile}" = "${UPEX4C_autoconfigfile}" ] || \
+       [ "x${UPEX4C_check}" = "xyes" ]; then
+       if [ -x "${EXIM}" ] ; then
+               if ! "${EXIM}" -C "${UPEX4C_tmp}" -bV > /dev/null ; then
+                       # we have an error in the configuration file. Do not install
+                       # and activate. However, errors in string expansions inside
+                       # the configuration file are not detected by this check!
+                       errormessage "Invalid new configfile ${UPEX4C_tmp}, not installing ${UPEX4C_tmp} to ${UPEX4C_outputfile}"
+                       exit 1
+               fi
        fi
 fi
+if [ "x${UPEX4C_check}" = "xyes" ]; then
+       rm -f "${UPEX4C_tmp}"
+       exit 0
+fi
 
-mv -f "${UPEX4C_outputfile}.tmp" "${UPEX4C_outputfile}"
+mv -f "${UPEX4C_tmp}" "${UPEX4C_outputfile}"
 chmod "${CFILEMODE}" "${UPEX4C_outputfile}"
 
 # end of file
index ba51e3a..c16aa76 100644 (file)
@@ -1 +1 @@
-c181c27925094f50dbb2f1388602cf03  -
+855d721412eba13426a8781cc804157d  -
diff --git a/debian/exim4-base.NEWS b/debian/exim4-base.NEWS
deleted file mode 100644 (file)
index 4a37a68..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-exim4 (4.80~rc6-1) experimental; urgency=low
-
-  Upstream's handling of GnuTLS DH parameters has changed, hardcoded
-  parameters (from RFCs are used by default. See
-  /usr/share/doc/exim4-base/README.UPDATING* for details. Stop shipping
-  /usr/share/exim4/exim4_refresh_gnutls-params /usr/share/exim4/timeout.pl
-  and /var/spool/exim4/gnutls-params-2236.
-
- -- Andreas Metzler <ametzler@debian.org>  Sun, 27 May 2012 18:46:48 +0200
-
-exim4 (4.69-4) unstable; urgency=low
-
-  In reaction to #475194, the size of the Diffie-Hellman parameters
-  used by exim was increased to 2048, which is GnuTLS's default.
-  
-  Since periodically regenerating the Diffie-Hellman parameters
-  doesn't increase security that much (they're sent in clear text in the
-  TLS handshake, and some protocols even have hardcoded them in the
-  standard document), and automatically generating 2048 bits
-  Diffie-Hellman parameters can take a long time, this has been disabled
-  in the Exim4 packages starting with 4.69-4. All exim installations
-  will thus run with the Diffie-Hellman parameters shipped in the
-  package by default.
-  
-  Really, really paranoid people with sufficiently fast machines will
-  want to set up a cron job calling
-  /usr/share/exim4/exim4_refresh_gnutls-params manually - suggested
-  interval is weekly or monthly.
-
- -- Marc Haber <mh+debian-packages@zugschlus.de>  Sun, 27 Apr 2008 09:14:32 +0200
-
-exim4 (4.30-1) unstable; urgency=low
-
-  * Exim now runs under its own uid (Debian-exim) instead of using mail:mail.
-  
-    WARNING: You cannot downgrade this version to an older one without
-    manual chown|chrgrp all files owned by Debian-exim to mail.
-    
-    Securitywise this is a tradeoff:
-    - if exim is SUID root and runs without deliver_drop_privilege you win:
-      exim's internal data in /var/spool/exim4 is not open to attacks by
-      bugs in programs SGID mail (mail delivery agents like deliver or
-      procmail, or MUAs like pine) anymore. This is Debian's default setup.
-    - OTOH if you need to be able to make local deliveries to /var/mail and
-      want to run exim with reduced priviledge you have some additional work
-      to do:
-      * Use an SGID MDA for the actual delivery (I suggest maildrop.)
-      * Make changes to run exim4 under group mail:
-        - exim_group=mail.
-        - Hack: make Debian-exim a group with gid=8, i.e. an alias for
-          the mail group, _before_ you make the upgrade. (groupadd -o -g 8
-          Debian-exim)
-
- -- Andreas Metzler <ametzler@debian.org>  Sun,  7 Dec 2003 13:59:46 +0100
-
-exim4 (4.24-1) unstable; urgency=low
-
-  * This version of exim cannot run deliveries as root anymore, see change
-    5a for exim 4.23 in /usr/share/doc/exim4-base/changelog.gz. If you
-    don't redirect mail for root via /etc/aliases to a nonpriviledged
-    account the mail will be delivered to /var/mail/mail with permissions
-    0600 and owner mail:mail.
-
- -- Andreas Metzler <ametzler@debian.org>  Fri,  3 Oct 2003 18:11:17 +0200
-exim4 (4.22-1) unstable; urgency=low
-
-  * The way that the $h_ (and $header_) expansions work has been changed
-    by the addition of RFC 2047 decoding. See the main documentation (the
-    NewStuff file until release 4.30, then the manual) for full details.
-
-    Exim shipped with Debian defaults to HEADER_DECODE_TO="UTF-8"
-
- -- Andreas Metzler <ametzler@debian.org>  Mon, 18 Aug 2003 16:51:47 +0200
index 67a1059..8bc24e3 100644 (file)
@@ -4,7 +4,7 @@
 # Written by Miquel van Smoorenburg <miquels@drinkel.ow.org>.
 # Modified for Debian GNU/Linux by Ian Murdock <imurdock@gnu.ai.mit.edu>.
 # Modified for exim by Tim Cutts <timc@chiark.greenend.org.uk>
-# Modified for exim4 by Andreas Metzler <ametzler@downhill.at.eu.org>
+# Modified for exim4 by Andreas Metzler <ametzler@debian.org>
 #                   and Marc Haber <mh+debian-packages@zugschlus.de>
 
 ### BEGIN INIT INFO
@@ -37,9 +37,9 @@ export LANG
 QUEUERUNNER='combined'
 QUEUEINTERVAL='30m'
 UPEX4OPTS=''
-PIDFILE="/var/run/exim4/exim.pid"
-QRPIDFILE="/var/run/exim4/eximqr.pid"
 [ -f /etc/default/exim4 ] && . /etc/default/exim4
+PIDFILE="/run/exim4/exim.pid"
+QRPIDFILE="/run/exim4/eximqr.pid"
 
 upex4conf() {
   UPEX4CONF="update-exim4.conf"
@@ -48,7 +48,7 @@ upex4conf() {
   for p in $PATH; do
     if [ -x "$p/$UPEX4CONF" ]; then
       IFS="$OLDIFS"
-      $p/$UPEX4CONF $UPEX4OPTS
+      $p/$UPEX4CONF $UPEX4OPTS $1
       return 0
     fi
   done
@@ -81,8 +81,8 @@ log()
 
 start_exim()
 {
-  [ -e /var/run/exim4 ] || \
-    install -d -oDebian-exim -gDebian-exim -m750 /var/run/exim4
+  [ -e /run/exim4 ] || \
+    install -d -oDebian-exim -gDebian-exim -m750 /run/exim4
   case ${QUEUERUNNER} in
     combined)
       start_daemon -p "$PIDFILE" \
@@ -106,8 +106,8 @@ start_exim()
       log_progress_msg "exim4_queuerunner"
       ;;
     queueonly)
-      start_daemon -p "$QRPIDFILE" \
-        "$DAEMON" -oP $QRPIDFILE \
+      start_daemon -p "$PIDFILE" \
+        "$DAEMON" -oP $PIDFILE \
         "-q${QFLAGS}${QUEUEINTERVAL}" \
         ${COMMONOPTIONS} \
         ${QUEUERUNNEROPTIONS}
@@ -146,7 +146,7 @@ stop_exim()
 reload_exim()
 {
   case ${QUEUERUNNER} in
-    combined|no|ppp)
+    combined|no|ppp|queueonly)
       killproc -p "$PIDFILE" "$DAEMON" -HUP
       log_progress_msg "exim4"
       ;;
@@ -176,6 +176,8 @@ status()
   QRNAME="separate queue runner daemon"
   if [ "${QUEUERUNNER}" = "combined" ]; then
       SMTPNAME="combined SMTP listener and queue runner daemon"
+  elif [ "${QUEUERUNNER}" = "queueonly" ]; then
+    SMTPNAME="separate queue runner daemon"
   fi
   log_action_begin_msg "checking $QRNAME"
   if pidofproc -p "$QRPIDFILE" "$DAEMON" >/dev/null; then
@@ -238,11 +240,13 @@ case "$1" in
     warn_paniclog
     ;;
   restart)
+    # check whether newly generated config would work
+    upex4conf --check
     log_daemon_msg "Stopping MTA for restart"
+    stop_exim
     # regenerate exim4.conf
     upex4conf
     isconfigvalid
-    stop_exim
     log_end_msg 0
     sleep 2
     log_daemon_msg "Restarting MTA"
@@ -266,7 +270,7 @@ case "$1" in
     kill_all_exims $2
     ;;
   *)
-    echo "Usage: $0 {start|stop|restart|reload|status|what|force-stop}"
+    echo "Usage: $0 {start|stop|restart|reload|status|force-stop}"
     exit 1
     ;;
 esac
index 8e16f3e..f07dd6a 100644 (file)
@@ -1 +1,3 @@
 debian/script usr/share/bug/exim4-base
+debian/gnutls-params-2048 usr/share/exim4
+debian/exim4_refresh_gnutls-params usr/share/exim4
index 66e9a1a..4972855 100644 (file)
@@ -23,7 +23,7 @@ case "$1" in
        # Create directories for log etc
        # install also fixes permissions.
        install -d -oDebian-exim -gadm -m2750 /var/log/exim4
-       install -d -oDebian-exim -gDebian-exim -m750 /var/run/exim4
+       install -d -oDebian-exim -gDebian-exim -m750 /run/exim4
        install -d -oDebian-exim -gDebian-exim -m750 /var/spool/exim4
        install -d -oDebian-exim -gDebian-exim -m750 /var/spool/exim4/db \
                /var/spool/exim4/input /var/spool/exim4/msglog
index 331bafd..c875e52 100644 (file)
@@ -19,8 +19,8 @@ case "$1" in
        if [ -x /etc/init.d/exim4 ]; then
                 if [ -n "$EX4DEBUG" ]; then
                   netstat -tulpen
-                  ls -al /var/run/exim4/
-                  cat /var/run/exim4/exim.pid
+                  ls -al /run/exim4/
+                  cat /run/exim4/exim.pid
                   pidof exim4
                 fi
                if command -v invoke-rc.d >/dev/null 2>&1; then
@@ -30,8 +30,8 @@ case "$1" in
                fi
                 if [ -n "$EX4DEBUG" ]; then
                   netstat -tulpen
-                  ls -al /var/run/exim4/
-                  cat /var/run/exim4/exim.pid
+                  ls -al /run/exim4/
+                  cat /run/exim4/exim.pid
                   pidof exim4
                   if pidof exim4; then
                     echo >&2 "WARN: There are some exim4 processes still running after stopping exim"
@@ -59,7 +59,7 @@ case "$1" in
        fi
                                                        
        # remove logs and pid-dir.
-       rm -rf /var/run/exim4 /var/log/exim4 /var/spool/exim4/msglog \
+       rm -rf /run/exim4 /var/log/exim4 /var/spool/exim4/msglog \
                /var/spool/exim4/db /var/spool/exim4/exim-process.info \
                /var/spool/exim4/gnutls-params*
        rmdir /var/spool/exim4 /var/lib/exim4 2> /dev/null || true
index beaabf5..42d7b53 100644 (file)
@@ -183,7 +183,8 @@ fi
 #  Disable orphaned inetd-entries from exim (v3) caused by bugs #202670
 #  and #182206.
 if [ "$1" = "configure" ] &&\
-       [ -x /usr/sbin/update-inetd ] && [ ! -x /usr/sbin/exim ] && \
+       which update-inetd > /dev/null && which exim > /dev/null && \
+       [ -f /etc/inetd.conf ] && \
        grep -E -q '^smtp[[:space:]]*stream[[:space:]]*tcp[[:space:]]*nowait[[:space:]]*mail[[:space:]]*/usr/sbin/+exim exim -bs' /etc/inetd.conf
 then
    update-inetd --comment-chars \#disabled\# \
@@ -367,8 +368,8 @@ case "$1" in
                        db_stop
                        if [ -n "$EX4DEBUG" ]; then
                          netstat -tulpen
-                         ls -al /var/run/exim4/
-                         cat /var/run/exim4/exim.pid
+                         ls -al /run/exim4/
+                         cat /run/exim4/exim.pid
                          pidof exim4
                        fi
                        if [ "$dc_eximconfig_configtype" = "none" ]; then
@@ -380,8 +381,8 @@ case "$1" in
                        fi
                        if [ -n "$EX4DEBUG" ]; then
                          netstat -tulpen
-                         ls -al /var/run/exim4/
-                         cat /var/run/exim4/exim.pid
+                         ls -al /run/exim4/
+                         cat /run/exim4/exim.pid
                          pidof exim4
                          if pidof exim4; then
                            echo >&2 "WARN: There are some exim4 processes still running after stopping exim"
diff --git a/debian/exim4-daemon-heavy-dbg.links b/debian/exim4-daemon-heavy-dbg.links
new file mode 100644 (file)
index 0000000..a53f6ad
--- /dev/null
@@ -0,0 +1 @@
+usr/share/doc/exim4-base/changelog.gz usr/share/doc/exim4-daemon-heavy-dbg/changelog.gz
diff --git a/debian/exim4-daemon-heavy.NEWS b/debian/exim4-daemon-heavy.NEWS
deleted file mode 100644 (file)
index 9e38e08..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-exim4 (4.80~rc2-1) experimental; urgency=low
-
-  Ldap lookups returning multi-valued attributes now separate the attributes
-  with only a comma, not a comma-space sequence.
-
-  The GnuTLS support has been mostly rewritten. exim main configuration
-  options gnutls_require_kx, gnutls_require_mac and gnutls_require_protocols,
-  are no longer supported. (They are ignored if present now, but will trigger
-  an error in later releases.) Their functionality is entirely subsumed into
-  tls_require_ciphers.  In turn, tls_require_ciphers is no longer an Exim list
-  and is not parsed by Exim, but is instead given to gnutls_priority_init(3).
-
-  See /exim4-base/usr/share/doc/exim4-base/README.UPDATING.gz for details.
-
- -- Andreas Metzler <ametzler@debian.org>  Sat, 22 Oct 2011 19:16:58 +0200
-
-exim4 (4.77~rc4-1) experimental; urgency=low
-
-  Exim no longer performs string expansion on the second string of
-  the match_* expansion conditions: "match_address", "match_domain",
-  "match_ip" & "match_local_part". Named lists can still be used.
-
-  The previous behavior made it too easy to create (remotely) vulnerable
-  configurations. A more detailed rationale and explanation can be found on 
-  https://lists.exim.org/lurker/message/20111003.122326.fbcf32b7.en.html
-
- -- Andreas Metzler <ametzler@debian.org>  Thu, 05 Oct 2011 19:22:52 +0200
-
-exim4 (4.72-3) unstable; urgency=low
-
-  Exim versions up to and including 4.72 are vulnerable to CVE-2010-4345.
-  This is a privilege escalation issue that allows the exim user to gain
-  root privileges by specifying an alternate configuration file using the -C
-  option. The macro override facility (-D) might also be misused for this
-  purpose.
-
-  In reaction to this security vulnerability upstream has made a number of
-  user visible changes. This package includes these changes.
-  ---------------------------------------------------------
-  If exim is invoked with the -C or -D option the daemon will not regain
-  root privileges though re-execution. This is usually necessary for local
-  delivery, though. Therefore it is generally not possible anymore to run an
-  exim daemon with -D or -C options.
-
-  However this version of exim has been built with
-  TRUSTED_CONFIG_LIST=/etc/exim4/trusted_configs. TRUSTED_CONFIG_LIST
-  defines a list of configuration files which are trusted; if a config file
-  is owned by root and matches a pathname in the list, then it may be
-  invoked by the Exim build-time user without Exim relinquishing root
-  privileges.
-
-  As a hotfix to not break existing installations of mailscanner we have
-  also set WHITELIST_D_MACROS=OUTGOING. i.e. it is still possible to start
-  exim with -DOUTGOING while being able to do local deliveries.
-
-  If you previously were using -D switches you will need to change your
-  setup to use a separate configuration file. The ".include" mechanism
-  makes this easy.
-  ---------------------------------------------------------
-  The system filter is run as exim_user instead of root by default. If your
-  setup requies root privileges when running the system filter you will
-  need to set the system_filter_user exim main configuration option.
-  ---------------------------------------------------------
-
- -- Andreas Metzler <ametzler@debian.org>  Sat, 18 Dec 2010 18:57:16 +0100
-
-exim4 (4.60-2) unstable; urgency=low
-
-    The exim4 daemon packages now include a symlink from
-    /usr/sbin/exim4 to /usr/sbin/exim. This can break exim 3 cron and
-    init scripts if the last exim 3 you had installed was any earlier
-    than 3.36-5 and the conffiles from your exim 3 package are still
-    around. Be sure to have any exim 4 earlier than 3.36-5 _purged_
-    (not removed) before installing this package.
-
- -- Marc Haber <mh+debian-packages@zugschlus.de>  Wed, 24 Jan 2006 14:58:08 +0100
-
-exim4 (4.31-2) unstable; urgency=low
-
-    The local_scan perl-plugin has been removed because upstream
-    development has stopped. (am)
-
- -- Andreas Metzler <ametzler@debian.org>  Mon,  5 Apr 2004 15:55:12 +0200
-
-exim4 (4.22-2) unstable; urgency=low
-
-    Include exiscan-acl patch http://duncanthrax.net/exiscan-acl/ in
-    -heavy and -custom for easy integration of content-scanning and
-    invoking spamassassin at SMTP time.
-
- -- Andreas Metzler <ametzler@debian.org>  Wed, 27 Aug 2003 12:50:59 +0200
diff --git a/debian/exim4-daemon-light-dbg.links b/debian/exim4-daemon-light-dbg.links
new file mode 100644 (file)
index 0000000..a8e778e
--- /dev/null
@@ -0,0 +1 @@
+usr/share/doc/exim4-base/changelog.gz usr/share/doc/exim4-daemon-light-dbg/changelog.gz
diff --git a/debian/exim4-daemon-light.NEWS b/debian/exim4-daemon-light.NEWS
deleted file mode 100644 (file)
index 8deea5e..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-exim4 (4.80~rc2-1) experimental; urgency=low
-
-  Ldap lookups returning multi-valued attributes now separate the attributes
-  with only a comma, not a comma-space sequence.
-
-  The GnuTLS support has been mostly rewritten. exim main configuration
-  options gnutls_require_kx, gnutls_require_mac and gnutls_require_protocols,
-  are no longer supported. (They are ignored if present now, but will trigger
-  an error in later releases.) Their functionality is entirely subsumed into
-  tls_require_ciphers.  In turn, tls_require_ciphers is no longer an Exim list
-  and is not parsed by Exim, but is instead given to gnutls_priority_init(3).
-
-  See /exim4-base/usr/share/doc/exim4-base/README.UPDATING.gz for details.
-
- -- Andreas Metzler <ametzler@debian.org>  Sat, 22 Oct 2011 19:16:58 +0200
-
-exim4 (4.77~rc4-1) experimental; urgency=low
-
-  Exim no longer performs string expansion on the second string of
-  the match_* expansion conditions: "match_address", "match_domain",
-  "match_ip" & "match_local_part". Named lists can still be used.
-
-  The previous behavior made it too easy to create (remotely) vulnerable
-  configurations. A more detailed rationale and explanation can be found on 
-  https://lists.exim.org/lurker/message/20111003.122326.fbcf32b7.en.html
-
- -- Andreas Metzler <ametzler@debian.org>  Thu, 05 Oct 2011 19:22:52 +0200
-
-exim4 (4.72-3) unstable; urgency=low
-
-  Exim versions up to and including 4.72 are vulnerable to CVE-2010-4345.
-  This is a privilege escalation issue that allows the exim user to gain
-  root privileges by specifying an alternate configuration file using the -C
-  option. The macro override facility (-D) might also be misused for this
-  purpose.
-
-  In reaction to this security vulnerability upstream has made a number of
-  user visible changes. This package includes these changes.
-  ---------------------------------------------------------
-  If exim is invoked with the -C or -D option the daemon will not regain
-  root privileges though re-execution. This is usually necessary for local
-  delivery, though. Therefore it is generally not possible anymore to run an
-  exim daemon with -D or -C options.
-
-  However this version of exim has been built with
-  TRUSTED_CONFIG_LIST=/etc/exim4/trusted_configs. TRUSTED_CONFIG_LIST
-  defines a list of configuration files which are trusted; if a config file
-  is owned by root and matches a pathname in the list, then it may be
-  invoked by the Exim build-time user without Exim relinquishing root
-  privileges.
-
-  As a hotfix to not break existing installations of mailscanner we have
-  also set WHITELIST_D_MACROS=OUTGOING. i.e. it is still possible to start
-  exim with -DOUTGOING while being able to do local deliveries.
-
-  If you previously were using -D switches you will need to change your
-  setup to use a separate configuration file. The ".include" mechanism
-  makes this easy.
-  ---------------------------------------------------------
-  The system filter is run as exim_user instead of root by default. If your
-  setup requies root privileges when running the system filter you will
-  need to set the system_filter_user exim main configuration option.
-  ---------------------------------------------------------
-
- -- Andreas Metzler <ametzler@debian.org>  Sat, 18 Dec 2010 18:57:16 +0100
-
-exim4 (4.60-2) unstable; urgency=low
-
-    The exim4 daemon packages now include a symlink from
-    /usr/sbin/exim4 to /usr/sbin/exim. This can break exim 3 cron and
-    init scripts if the last exim 3 you had installed was any earlier
-    than 3.36-5 and the conffiles from your exim 3 package are still
-    around. Be sure to have any exim 4 earlier than 3.36-5 _purged_
-    (not removed) before installing this package.
-
- -- Marc Haber <mh+debian-packages@zugschlus.de>  Wed, 24 Jan 2006 14:58:08 +0100
-
index 95578d7..1096ac8 100644 (file)
@@ -29,6 +29,31 @@ case "$1" in
                        invoke-rc.d exim4 start
                fi
        fi
+
+       # set up DH-parameter file, update if older than 160 days
+       if test -e /var/spool/exim4/gnutls-params-2048 ; then
+               if [ `stat --format=%Y /var/spool/exim4/gnutls-params-2048` -le $(( `date +%s` - 13824000 )) ];
+                       then
+                       echo "Updating GnuTLS DH parameter file" 1>&2
+                       /usr/share/exim4/exim4_refresh_gnutls-params
+               fi
+       else
+               echo "Initializing GnuTLS DH parameter file" 1>&2
+               tempgnutls=$(tempfile --directory /var/spool/exim4 --mode 644 --prefix  "gnutp")
+               chown Debian-exim:Debian-exim $tempgnutls
+               if [ -x /usr/bin/certtool ] && \
+                       timeout --preserve-status --kill-after=15 120 \
+                       certtool --generate-dh-params --bits 2048 > $tempgnutls ; then
+                               mv $tempgnutls /var/spool/exim4/gnutls-params-2048
+               else
+                       rm -f $tempgnutls
+                       install -m 644 -o Debian-exim -g Debian-exim \
+                               /usr/share/exim4/gnutls-params-2048 \
+                               /var/spool/exim4/gnutls-params-2048
+               fi
+       fi
+
+
        ;;
 esac
 
index 8b7f855..ddda13c 100644 (file)
@@ -12,8 +12,8 @@ case "$1" in
        if [ -x /etc/init.d/exim4 ]; then
                if [ -n "$EX4DEBUG" ]; then
                  netstat -tulpen
-                 ls -al /var/run/exim4/
-                 cat /var/run/exim4/exim.pid
+                 ls -al /run/exim4/
+                 cat /run/exim4/exim.pid
                  pidof exim4
                fi
                if command -v invoke-rc.d >/dev/null 2>&1; then
@@ -23,8 +23,8 @@ case "$1" in
                fi
                if [ -n "$EX4DEBUG" ]; then
                  netstat -tulpen
-                 ls -al /var/run/exim4/
-                 cat /var/run/exim4/exim.pid
+                 ls -al /run/exim4/
+                 cat /run/exim4/exim.pid
                  pidof exim4
                  if pidof exim4; then
                    echo >&2 "WARN: There are some exim4 processes still running after stopping exim"
diff --git a/debian/exim4-dbg.links b/debian/exim4-dbg.links
new file mode 100644 (file)
index 0000000..de4f4be
--- /dev/null
@@ -0,0 +1 @@
+usr/share/doc/exim4-base/changelog.gz usr/share/doc/exim4-dbg/changelog.gz
diff --git a/debian/exim4_refresh_gnutls-params b/debian/exim4_refresh_gnutls-params
new file mode 100755 (executable)
index 0000000..c16d2e2
--- /dev/null
@@ -0,0 +1,52 @@
+#!/bin/sh
+set -e
+
+if [ -n "$EX4DEBUG" ]; then
+  echo "now debugging $0 $@"
+  set -x
+fi
+
+
+# regenerate $EXIM4_SPOOLDIR/gnutls-params-*
+# As this can take _very_ long on machines with little entropy, we limit
+# the maximum runtime to 1800 seconds and keep using the
+# old file otherwise.
+
+# Only do anything if exim4 is actually installed
+if [ ! -x /usr/lib/exim4/exim4 ]; then
+  exit 0
+fi
+
+# Only do anyting if TLS is enabled in exim
+if [ -z "$(/usr/lib/exim4/exim4 -bP tls_advertise_hosts | sed 's/.*=[[:space:]]\(.*\)/\1/')" ]; then 
+  # TLS disabled
+  exit 0
+fi
+
+TIMEOUT=${1:-1800}
+
+EXIM4_SPOOLDIR="${EXIM4_SPOOLDIR:-$(/usr/lib/exim4/exim4 -bP spool_directory | sed 's/.*=[[:space:]]\(.*\)/\1/')}"
+cd $EXIM4_SPOOLDIR
+
+# loop over gnutls-params-files
+for paramfile in `find -maxdepth 1 -regex '\./gnutls-params-[0-9][0-9][0-9]*'` ; do
+  bits=`echo ${paramfile} | sed -e 's:\./gnutls-params-::'`
+  tempgnutls=$(tempfile --directory $EXIM4_SPOOLDIR --mode 644 --prefix  "gnutp" )
+
+  if [ -x /usr/bin/certtool ] ; then
+    # GnuTLS
+    if timeout --preserve-status --kill-after=15 \
+        "$TIMEOUT" /usr/bin/certtool --generate-dh-params --bits ${bits} \
+        > "$tempgnutls" 2> /dev/null ; then
+      cat "$tempgnutls" > "${paramfile}" ; rm -f "$tempgnutls"
+    else
+      rm -f "$tempgnutls"
+      break
+    fi
+  else
+    # gnutls-bin not installed, let exim generate the DH params
+    rm -f "${paramfile}" "$tempgnutls"
+  fi
+done
+
+# vim:tabstop=2:expandtab:shiftwidth=2
diff --git a/debian/eximon4.links b/debian/eximon4.links
new file mode 100644 (file)
index 0000000..bdc19ec
--- /dev/null
@@ -0,0 +1 @@
+usr/share/doc/exim4-base/changelog.gz usr/share/doc/eximon4/changelog.gz
diff --git a/debian/gnutls-params-2048 b/debian/gnutls-params-2048
new file mode 100644 (file)
index 0000000..8716426
--- /dev/null
@@ -0,0 +1,31 @@
+generator:
+       02:
+
+prime:
+       b7:0b:a3:05:f1:f7:a1:65:11:e9:47:76:c3:58:f3:74
+       7e:3a:9e:ae:53:e2:5b:a3:0e:73:d3:32:c4:54:89:37
+       f9:ab:84:3a:a1:48:ba:9c:16:49:3a:6e:f7:83:44:52
+       27:2c:64:55:99:1b:ed:f1:cb:cd:67:4e:c0:f3:16:dc
+       fa:78:ab:1b:b0:2e:47:81:80:1f:a0:61:e2:4c:cf:7d
+       e8:05:5d:91:ee:4d:65:9b:39:17:60:f4:84:cd:91:96
+       f7:5a:e1:47:89:06:ab:48:54:60:44:43:c3:6a:10:d3
+       ba:67:58:16:0c:10:9b:ed:de:4c:b2:cc:14:1b:c6:29
+       79:f8:42:be:2a:f4:b8:98:16:7f:30:a2:08:22:0b:ec
+       a8:d0:a7:8c:32:ef:b3:5d:eb:c6:9e:3f:1f:78:0d:75
+       e9:bd:cf:a3:35:3c:e5:4b:05:f0:e2:c0:3d:2b:9c:ef
+       bc:cc:a3:66:1e:49:dd:1a:20:f0:f9:f2:cd:05:36:10
+       b3:11:58:a5:a1:9d:eb:a8:ad:87:18:ea:3c:41:62:78
+       c2:39:83:3e:60:f8:6a:5b:53:70:ad:07:f6:56:9e:f3
+       4f:53:74:00:01:13:ca:dc:7b:39:1f:bc:81:c3:a8:13
+       d7:26:57:05:28:1f:f9:b7:6e:02:99:38:a0:6f:92:03
+       
+
+
+-----BEGIN DH PARAMETERS-----
+MIIBCAKCAQEAtwujBfH3oWUR6Ud2w1jzdH46nq5T4lujDnPTMsRUiTf5q4Q6oUi6
+nBZJOm73g0RSJyxkVZkb7fHLzWdOwPMW3Pp4qxuwLkeBgB+gYeJMz33oBV2R7k1l
+mzkXYPSEzZGW91rhR4kGq0hUYERDw2oQ07pnWBYMEJvt3kyyzBQbxil5+EK+KvS4
+mBZ/MKIIIgvsqNCnjDLvs13rxp4/H3gNdem9z6M1POVLBfDiwD0rnO+8zKNmHknd
+GiDw+fLNBTYQsxFYpaGd66ithxjqPEFieMI5gz5g+GpbU3CtB/ZWnvNPU3QAARPK
+3Hs5H7yBw6gT1yZXBSgf+bduApk4oG+SAwIBAg==
+-----END DH PARAMETERS-----
index dc4a52c..b217377 100644 (file)
@@ -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-CONFIG_FILES 5 "Jan  5, 2014" EXIM4
+.TH EXIM4-CONFIG_FILES 5 "Jan  4, 2015" EXIM4
 .\" Please adjust this date whenever revising the manpage.
 .\"
 .\" Some roff macros, for reference:
@@ -291,7 +291,7 @@ mail.server.example:user:password
 .br
 # the regular expression
 .br
-^smtp[0\-9]*.mail.server.example:user:password
+^smtp[0\-9]*\\.mail\\.server\\.example:user:password
 .br
 
 .SH /etc/exim4/exim.crt
@@ -334,7 +334,7 @@ does not match the list. e.g. a local_host_blacklist consisting of
 notresolvable.example.com:rejectme.example.com
 
 is equivalent to an empty one. - Exim tries to match the IP-address of the
-conecting host to notresolvable.example.com, resolving this IP by DNS
+connecting host to notresolvable.example.com, resolving this IP by DNS
 fails, exim behaves as if the connecting host does not match the list. List
 processing stops at this point!
 
index 9d871b9..9a4efcb 100644 (file)
@@ -25,8 +25,14 @@ update\-exim4.conf \- Generate exim4 configuration files.
 
 .SH OPTIONS
 .TP
-.I \-v|\-\-verbose
-Enable verbose mode
+.I \-\-check
+Generate temporary configuration file, check its validity and exit with
+either success (exitcode 0) or an error (exitcode 1). On success the
+temporary file is deleted, otherwise the file is left for further
+debugging.
+.TP
+.I \-d|\-\-confdir directory
+Read input from directory instead of /etc/exim4.
 .TP
 .I \-h|\-\-help
 Show short help message and exit
@@ -34,14 +40,14 @@ Show short help message and exit
 .I \-\-keepcomments
 Do not remove comment lines from the output file.
 .TP
-.I \-\-removecomments
-Remove comment lines from the output file. [Default]
-.TP
 .I \-o|\-\-output file
 Write output to file instead of /var/lib/exim4/config.autogenerated.
 .TP
-.I \-d|\-\-confdir directory
-Read input from directory instead of /etc/exim4.
+.I \-\-removecomments
+Remove comment lines from the output file. [Default]
+.TP
+.I \-v|\-\-verbose
+Enable verbose mode
 
 .SH DESCRIPTION
 The script
@@ -145,7 +151,7 @@ the past, they used to be colon separated. This was changed to
 semicolon separation to make specification of IPv6 addresses easier.
 Backwards compatibility is preserved, so that old configurations using
 colons as separators do still work. Colons are deprecated and might
-stop working in a later release. If you need to specifiy a single IPv6
+stop working in a later release. If you need to specify a single IPv6
 address in a field that is defined as a list of host names or IP
 addresses, please prefix "<;" to explicitly specify the list separator
 as a semicolon. Otherwise, the code cannot tell an IP address from a
@@ -335,6 +341,6 @@ with debconf
 /usr/share/doc/exim4\-base/README.Debian.gz
 
 .SH AUTHOR
-Andreas Metzler <ametzler at downhill.at.eu.org>
+Andreas Metzler <ametzler at debian.org>
 .br
 Marc Haber <mh+debian-packages@zugschlus.de>
index 083f6a6..97f5ccb 100644 (file)
@@ -25,13 +25,13 @@ echo running minimal functionality test for binary $2 in directory $1
 top="$1/eximtest"
 
 rm -rf $1/eximtest/*
-mkdir -p $top/var/log $top/var/spool/db $top/var/spool/input $top/var/spool/msglog $top/var/run $top/var/mail
+mkdir -p $top/var/log $top/var/spool/db $top/var/spool/input $top/var/spool/msglog $top/run $top/var/mail
 cat <<EOF > $top/exim4.conf
 exim_user = `id -u`
 exim_group = `id -g`
 log_file_path = $top/var/log/%slog
 spool_directory = $top/var/spool
-pid_file_path = $top/var/run
+pid_file_path = $top/run
 primary_hostname = eximtest.example.com
 rfc1413_hosts =
 
index fc97b0c..af3ecd7 100755 (executable)
@@ -2,7 +2,7 @@ Description: We ship the binary as exim4 instead of exim, fix manpage
  accordingly.
 Author: Marc Haber <mh+debian-packages@zugschlus.de>, 
  Andreas Metzler <ametzler@bebt.de>
-Last-Update: 2014-05-29
+Last-Update: 2017-01-31
 Forwarded: not-needed (upstream uses the "exim" name)
 
 --- a/doc/exim.8
@@ -69,7 +69,7 @@ Forwarded: not-needed (upstream uses the "exim" name)
  .sp
  When an IPv6 address is given, it is converted into canonical form. In the case
  of the second example above, the value of \fI$sender_host_address\fP after
-@@ -411,7 +411,7 @@ main configuration options to be written
+@@ -412,7 +412,7 @@ main configuration options to be written
  of one or more specific options can be requested by giving their names as
  arguments, for example:
  .sp
@@ -78,7 +78,7 @@ Forwarded: not-needed (upstream uses the "exim" name)
  .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
-@@ -435,7 +435,7 @@ written directly into the spool director
+@@ -440,7 +440,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 @@ Forwarded: not-needed (upstream uses the "exim" name)
  .sp
  it searches for a matching named list of any type (domain, host, address, or
  local part) and outputs what it finds.
-@@ -444,7 +444,7 @@ If one of the words \fBrouter\fP, \fBtra
+@@ -449,7 +449,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 @@ Forwarded: not-needed (upstream uses the "exim" name)
  .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
-@@ -523,7 +523,7 @@ This option is for testing retry rules,
+@@ -532,7 +532,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 @@ Forwarded: not-needed (upstream uses the "exim" name)
    Retry rule: *.comp.mus.example  F,2h,15m; F,4d,30m;
  .sp
   The first
-@@ -536,7 +536,7 @@ rule is found that matches the host, one
+@@ -545,7 +545,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 @@ Forwarded: not-needed (upstream uses the "exim" name)
    Retry rule: *@haydn.comp.mus.example quota_3d  F,1h,15m
  .TP 10
  \fB\-brw\fP
-@@ -639,7 +639,7 @@ doing such tests.
+@@ -648,7 +648,7 @@ doing such tests.
  .TP 10
  \fB\-bV\fP
  This option causes Exim to write the current version number, compilation
@@ -123,7 +123,7 @@ Forwarded: not-needed (upstream uses the "exim" name)
  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.
-@@ -667,7 +667,7 @@ If no arguments are given, Exim runs in
+@@ -676,7 +676,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 @@ Forwarded: not-needed (upstream uses the "exim" name)
  security issues.
  .sp
  Verification differs from address testing (the \fB\-bt\fP option) in that routers
-@@ -780,14 +780,14 @@ command line item. \fB\-D\fP can be used
+@@ -789,14 +789,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
@@ -149,8 +149,8 @@ Forwarded: not-needed (upstream uses the "exim" name)
 +  exim4 '\-D ABC = something' ...
  .sp
  \fB\-D\fP may be repeated up to 10 times on a command line.
- .TP 10
-@@ -916,8 +916,8 @@ never provoke a bounce. An empty sender
+ Only macro names up to 22 letters long can be set.
+@@ -926,8 +926,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 @@ Forwarded: not-needed (upstream uses the "exim" name)
  .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
-@@ -1271,12 +1271,12 @@ other circumstances, they are ignored un
+@@ -1292,12 +1292,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,18 @@ Forwarded: not-needed (upstream uses the "exim" name)
  .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
-@@ -1474,13 +1474,13 @@ When scanning the queue, Exim can be mad
+@@ -1502,22 +1502,22 @@ If other commandline options specify an
+ will specify a queue to operate on.
+ For example:
+ .sp
+-  exim \-bp \-qGquarantine
++  exim4 \-bp \-qGquarantine
+   mailq \-qGquarantine
+-  exim \-qGoffpeak \-Rf @special.domain.example
++  exim4 \-qGoffpeak \-Rf @special.domain.example
+ .TP 10
+ \fB\-q\fP<\fIqflags\fP> <\fIstart id\fP> <\fIend id\fP>
+ When scanning the queue, Exim can be made to skip over messages whose ids are
  lexically less than a given value by following the \fB\-q\fP option with a
  starting message id. For example:
  .sp
@@ -192,7 +203,7 @@ Forwarded: not-needed (upstream uses the "exim" name)
  .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
-@@ -1496,7 +1496,7 @@ starting a queue runner process at inter
+@@ -1533,7 +1533,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 +212,7 @@ Forwarded: not-needed (upstream uses the "exim" name)
  .sp
  Such a daemon listens for incoming SMTP calls, and also starts a queue runner
  process every 30 minutes.
-@@ -1527,7 +1527,7 @@ regular expression; otherwise it is a li
+@@ -1564,7 +1564,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,7 +221,7 @@ Forwarded: not-needed (upstream uses the "exim" name)
  .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
-@@ -1637,6 +1637,26 @@ to the named file.  It is ignored by Exi
+@@ -1680,6 +1680,26 @@ under most shells.
  .sp
  .
  .SH "SEE ALSO"
index 84b39a1..5098991 100755 (executable)
@@ -4,8 +4,8 @@ Origin: vendor
 Forwarded: not-needed
 Last-Update: 2013-09-28
 
---- exim4-4.82~rc1.orig/OS/Makefile-Linux
-+++ exim4-4.82~rc1/OS/Makefile-Linux
+--- a/OS/Makefile-Linux
++++ b/OS/Makefile-Linux
 @@ -28,9 +28,9 @@ XLFLAGS=-L$(X11)/lib
  X11_LD_LIB=$(X11)/lib
  
@@ -18,8 +18,8 @@ Last-Update: 2013-09-28
  EXIWHAT_KILL_SIGNAL=-USR1
  
  # End
---- exim4-4.82~rc1.orig/src/exicyclog.src
-+++ exim4-4.82~rc1/src/exicyclog.src
+--- a/src/exicyclog.src
++++ b/src/exicyclog.src
 @@ -144,7 +144,7 @@ done
  
  st='   '
@@ -29,8 +29,8 @@ Last-Update: 2013-09-28
  
  spool_directory=`$exim_path -C $config -bP spool_directory | sed 's/.*=[  ]*//'`
  
---- exim4-4.82~rc1.orig/src/exim_checkaccess.src
-+++ exim4-4.82~rc1/src/exim_checkaccess.src
+--- a/src/exim_checkaccess.src
++++ b/src/exim_checkaccess.src
 @@ -52,7 +52,7 @@ done
  # a tab to keep the tab in one place.
  
@@ -40,8 +40,8 @@ Last-Update: 2013-09-28
  
  
  #########################################################################
---- exim4-4.82~rc1.orig/src/eximon.src
-+++ exim4-4.82~rc1/src/eximon.src
+--- a/src/eximon.src
++++ b/src/eximon.src
 @@ -72,7 +72,7 @@ config=${EXIMON_EXIM_CONFIG-$config}
  
  st='   '
@@ -51,8 +51,8 @@ Last-Update: 2013-09-28
  
  SPOOL_DIRECTORY=`$EXIM_PATH -C $config -bP spool_directory | sed 's/.*=[  ]*//'`
  LOG_FILE_PATH=`$EXIM_PATH -C $config -bP log_file_path | sed 's/.*=[  ]*//'`
---- exim4-4.82~rc1.orig/src/exinext.src
-+++ exim4-4.82~rc1/src/exinext.src
+--- a/src/exinext.src
++++ b/src/exinext.src
 @@ -90,7 +90,7 @@ if [ "$exim_path" = "" ]; then
    exim_path=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"`
  fi
@@ -71,8 +71,8 @@ Last-Update: 2013-09-28
      die "can't run exim_dumpdb";
  
    while (<DATA>)
---- exim4-4.82~rc1.orig/src/exiqgrep.src
-+++ exim4-4.82~rc1/src/exiqgrep.src
+--- a/src/exiqgrep.src
++++ b/src/exiqgrep.src
 @@ -21,7 +21,7 @@ use strict;
  use Getopt::Std;
  
@@ -82,8 +82,8 @@ Last-Update: 2013-09-28
  my $eargs = '-bpu';
  my %id;
  my %opt;
---- exim4-4.82~rc1.orig/src/exiwhat.src
-+++ exim4-4.82~rc1/src/exiwhat.src
+--- a/src/exiwhat.src
++++ b/src/exiwhat.src
 @@ -88,7 +88,7 @@ fi
  
  st='   '
@@ -93,9 +93,9 @@ Last-Update: 2013-09-28
  spool_directory=`$exim_path -C $config -bP spool_directory | sed "s/.*=[ ]*//"`
  process_log_path=`$exim_path -C $config -bP process_log_path | sed "s/.*=[ ]*//"`
  
---- exim4-4.82~rc1.orig/src/globals.c
-+++ exim4-4.82~rc1/src/globals.c
-@@ -633,7 +633,7 @@ int     errors_sender_rc       = EXIT_FA
+--- a/src/globals.c
++++ b/src/globals.c
+@@ -705,7 +705,7 @@ const uschar *event_name         = NULL;
  
  gid_t   exim_gid               = EXIM_GID;
  BOOL    exim_gid_set           = TRUE;          /* This gid is always set */
index b926110..d0ab602 100755 (executable)
@@ -3,23 +3,20 @@ Description: Exim's installation scripts install the binary as
 Author: Andreas Metzler <ametzler@debian.org>
 Origin: vendor
 Forwarded: not-needed
-Last-Update: 2013-09-28
+Last-Update: 2016-09-25
 
---- exim4-4.82~rc1.orig/scripts/exim_install
-+++ exim4-4.82~rc1/scripts/exim_install
-@@ -217,8 +217,9 @@ while [ $# -gt 0 ]; do
-   # The exim binary is handled specially
-   if [ $name = exim${EXE} ]; then
--    version=exim-`./exim -bV -C /dev/null | \
--      awk '/Exim version/ { OFS=""; print $3,"-",substr($4,2,length($4)-1) }'`${EXE}
-+    version=exim
-+#    version=exim-`./exim -bV -C /dev/null | \
-+#      awk '/Exim version/ { OFS=""; print $3,"-",substr($4,2,length($4)-1) }'`${EXE}
+--- a/scripts/exim_install
++++ b/scripts/exim_install
+@@ -221,6 +221,8 @@ while [ $# -gt 0 ]; do
+     version=exim-`$exim 2>/dev/null | \
+       awk '/Exim version/ { OFS=""; print $3,"-",substr($4,2,length($4)-1) }'`${EXE}
  
++    version=exim
++
      if [ "${version}" = "exim-${EXE}" ]; then
        echo $com ""
-@@ -368,10 +369,8 @@ done
+       echo $com "*** Could not run $exim to find version number ***"
+@@ -370,10 +372,8 @@ done
  
  
  
@@ -32,7 +29,7 @@ Last-Update: 2013-09-28
  
  # However, if CONFIGURE_FILE specifies a list of files, skip this code.
  
-@@ -394,7 +393,7 @@ elif [ ! -f ${CONFIGURE_FILE} ]; then
+@@ -396,7 +396,7 @@ elif [ ! -f ${CONFIGURE_FILE} ]; then
    ${real} ${MKDIR} -p `${DIRNAME} ${CONFIGURE_FILE}`
  
    echo sed -e '\\'
@@ -41,7 +38,7 @@ Last-Update: 2013-09-28
    echo "  ../src/configure.default > \${CONFIGURE_FILE}"
  
    # I can't find a way of writing this using the ${real} feature because
-@@ -403,7 +402,7 @@ elif [ ! -f ${CONFIGURE_FILE} ]; then
+@@ -405,7 +405,7 @@ elif [ ! -f ${CONFIGURE_FILE} ]; then
  
    if [ "$real" = "" ] ; then
      sed -e \
diff --git a/debian/patches/40_reproducible_build.diff b/debian/patches/40_reproducible_build.diff
new file mode 100644 (file)
index 0000000..818f0f3
--- /dev/null
@@ -0,0 +1,63 @@
+Description: Reproducible build fix.
+ Use REPBUILDDATE which is pulled from debian/changelog in debian/rules
+ instead of __DATE__ as compile date.
+Author: Andreas Metzler <ametzler@debian.org>
+
+--- a/exim_monitor/em_version.c
++++ b/exim_monitor/em_version.c
+@@ -10,6 +10,8 @@
+ #include <string.h>
+ #include <stdlib.h>
++#include "../src/repbuildtime.h"
++
+ extern uschar *version_string;
+ extern uschar *version_date;
+@@ -21,7 +23,7 @@ uschar today[20];
+ version_string = US"2.06";
+-Ustrcpy(today, __DATE__);
++Ustrcpy(today, REPBUILDDATE);
+ if (today[4] == ' ') i = 1;
+ today[3] = today[6] = '-';
+@@ -31,7 +33,7 @@ Ustrncat(version_date, today+4+i, 3-i);
+ Ustrncat(version_date, today, 4);
+ Ustrncat(version_date, today+7, 4);
+ Ustrcat(version_date, " ");
+-Ustrcat(version_date, __TIME__);
++Ustrcat(version_date, REPBUILDTIME);
+ }
+ /* End of em_version.c */
+--- a/src/version.c
++++ b/src/version.c
+@@ -11,6 +11,8 @@
+ #include "version.h"
++#include "../src/repbuildtime.h"
++
+ /* The header file cnumber.h contains a single line containing the
+ compilation number, making it easy to have it updated automatically.
+@@ -40,7 +42,7 @@ version_cnumber_format = US"%d\0<<eximcn
+ sprintf(CS version_cnumber, CS version_cnumber_format, cnumber);
+ version_string = US EXIM_VERSION_STR "\0<<eximversion>>";
+-Ustrcpy(today, __DATE__);
++Ustrcpy(today, REPBUILDDATE);
+ if (today[4] == ' ') today[4] = '0';
+ today[3] = today[6] = '-';
+@@ -50,7 +52,7 @@ Ustrncat(version_date, today+4, 3);
+ Ustrncat(version_date, today, 4);
+ Ustrncat(version_date, today+7, 4);
+ Ustrcat(version_date, " ");
+-Ustrcat(version_date, __TIME__);
++Ustrcat(version_date, REPBUILDTIME);
+ }
+ /* End of version.c */
index 67b48ae..1e83b92 100644 (file)
@@ -9,11 +9,11 @@ Description: Allow to use and switch between different local_scan functions
 Author: David Woodhouse, Derrick 'dman' Hudson, Marc MERLIN
 Origin: other, http://marc.merlins.org/linux/exim/files/sa-exim-current/
 Forwarded: no
-Last-Update: 2013-09-28
+Last-Update: 2014-12-01
 
 --- a/src/EDITME
 +++ b/src/EDITME
-@@ -783,6 +783,21 @@ HEADERS_CHARSET="ISO-8859-1"
+@@ -785,6 +785,21 @@ HEADERS_CHARSET="ISO-8859-1"
  
  
  #------------------------------------------------------------------------------
@@ -37,7 +37,7 @@ Last-Update: 2013-09-28
  # the documentation in "info" format, first fetch the Texinfo documentation
 --- a/src/config.h.defaults
 +++ b/src/config.h.defaults
-@@ -27,6 +27,8 @@ it's a default value. */
+@@ -28,6 +28,8 @@ it's a default value. */
  
  #define AUTH_VARS                     3
  
@@ -48,9 +48,9 @@ Last-Update: 2013-09-28
  #define CONFIGURE_FILE
 --- a/src/globals.c
 +++ b/src/globals.c
-@@ -134,6 +134,10 @@ BOOL    smtp_use_dsn           = FALSE;
+@@ -140,6 +140,10 @@ int     dsn_ret                = 0;
+ const pcre  *regex_DSN         = NULL;
  uschar *dsn_advertise_hosts    = NULL;
- #endif
  
 +#ifdef DLOPEN_LOCAL_SCAN
 +uschar *local_scan_path        = NULL;
@@ -61,9 +61,9 @@ Last-Update: 2013-09-28
  BOOL    gnutls_allow_auto_pkcs11 = FALSE;
 --- a/src/globals.h
 +++ b/src/globals.h
-@@ -134,6 +134,9 @@ extern BOOL     smtp_use_dsn;          /
+@@ -133,6 +133,9 @@ extern int      dsn_ret;               /
+ extern const pcre  *regex_DSN;         /* For recognizing DSN settings */
  extern uschar  *dsn_advertise_hosts;   /* host for which TLS is advertised */
- #endif
  
 +#ifdef DLOPEN_LOCAL_SCAN
 +extern uschar *local_scan_path;        /* Path to local_scan() library */
@@ -262,8 +262,8 @@ Last-Update: 2013-09-28
  #include "config.h"
  #include "mytypes.h"
  #include "store.h"
-@@ -194,4 +195,6 @@ extern uschar *string_copy(const uschar
- extern uschar *string_copyn(uschar *, int);
+@@ -192,4 +193,6 @@ extern uschar *string_copy(const uschar
+ extern uschar *string_copyn(const uschar *, int);
  extern uschar *string_sprintf(const char *, ...) ALMOST_PRINTF(1,2);
  
 +#pragma GCC visibility pop
@@ -271,7 +271,7 @@ Last-Update: 2013-09-28
  /* End of local_scan.h */
 --- a/src/readconf.c
 +++ b/src/readconf.c
-@@ -289,6 +289,9 @@ static optionlist optionlist_config[] =
+@@ -313,6 +313,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 --git a/debian/patches/66_enlarge-dh-parameters-size.dpatch b/debian/patches/66_enlarge-dh-parameters-size.dpatch
deleted file mode 100755 (executable)
index 8ffd66a..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-Description: Enlarge default server side size of DH parameters to 2048
- from 1024. This patch has no effect if building against gnutls >= 2.12,
- because exim is using gnutls_sec_param_to_pk_bits() to get correct number
- of dh_bits when built against newer gnutls-versions.
-Author: Marc Haber <mh+debian-packages@zugschlus.de>
-Origin: vendor
-Forwarded: no
-Last-Update: 2013-09-28
-
---- exim4-4.82~rc1.orig/src/tls-gnu.c
-+++ exim4-4.82~rc1/src/tls-gnu.c
-@@ -164,7 +164,7 @@ callbacks. */
- can ask for a bit-strength.  Without that, we stick to the constant we had
- before, for now. */
- #ifndef EXIM_SERVER_DH_BITS_PRE2_12
--#define EXIM_SERVER_DH_BITS_PRE2_12 1024
-+#define EXIM_SERVER_DH_BITS_PRE2_12 2048
- #endif
- #define exim_gnutls_err_check(Label) do { \
index 7561579..4a819ef 100644 (file)
@@ -2,11 +2,11 @@ Description: Stop using exim's -C option in utility scripts (exiwhat
   et al.) since this breaks with ALT_CONFIG_PREFIX.
 Author: Andreas Metzler <ametzler@downhill.at.eu.org>
 Forwarded: http://bugs.exim.org/show_bug.cgi?id=1045
-Last-Update: 2010-12-12
+Last-Update: 2014-12-01
 
---- exim4-4.72.orig/src/exicyclog.src
-+++ exim4-4.72/src/exicyclog.src
-@@ -147,10 +147,10 @@ st='      '
+--- a/src/exicyclog.src
++++ b/src/exicyclog.src
+@@ -146,10 +146,10 @@ st='      '
  exim_path=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"`
  if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim4; fi
  
@@ -19,9 +19,9 @@ Last-Update: 2010-12-12
  fi
  
  # If log_file_path contains only "syslog" then no Exim log files are in use.
---- exim4-4.72.orig/src/eximon.src
-+++ exim4-4.72/src/eximon.src
-@@ -68,8 +68,8 @@ st='  '
+--- a/src/eximon.src
++++ b/src/eximon.src
+@@ -74,8 +74,8 @@ st='  '
  EXIM_PATH=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"`
  if test "$EXIM_PATH" = ""; then EXIM_PATH=BIN_DIRECTORY/exim4; fi
  
@@ -32,9 +32,9 @@ Last-Update: 2010-12-12
  
  # If log_file_path is "syslog" then logging is only to syslog, and the monitor
  # is unable to display a log tail unless EXIMON_LOG_FILE_PATH is set to tell
---- exim4-4.72.orig/src/exinext.src
-+++ exim4-4.72/src/exinext.src
-@@ -92,8 +92,8 @@ if [ "$exim_path" = "" ]; then
+--- a/src/exinext.src
++++ b/src/exinext.src
+@@ -91,8 +91,8 @@ if [ "$exim_path" = "" ]; then
  fi
  
  if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim4; fi
@@ -45,7 +45,7 @@ Last-Update: 2010-12-12
  
  # Now do the job. Perl uses $ so frequently that we don't want to have to
  # escape them all from the shell, so pass in shell variable values as
-@@ -135,7 +135,7 @@ perl - $exim_path "$eximmacdef" $argone
+@@ -134,7 +134,7 @@ perl - $exim_path "$eximmacdef" $argone
    # Run Exim to get a list of hosts for the given domain; for
    # each one construct the appropriate retry key.
  
@@ -54,9 +54,9 @@ Last-Update: 2010-12-12
      die "can't run exim to route $address";
  
    while (<LIST>)
---- exim4-4.72.orig/src/exiwhat.src
-+++ exim4-4.72/src/exiwhat.src
-@@ -90,8 +90,8 @@ fi
+--- a/src/exiwhat.src
++++ b/src/exiwhat.src
+@@ -89,8 +89,8 @@ fi
  st='   '
  exim_path=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"`
  if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim4; fi
index abb6ea1..81e364f 100755 (executable)
@@ -3,11 +3,11 @@
 ##
 ## All lines beginning with `## DP:' are a description of the patch.
 ## DP: No description.
+Last-Update: 2014-12-01
 
-diff -NurbBp exim.orig/README exim/README
---- exim.orig/README   2005-08-30 12:07:58.000000000 +0200
-+++ exim/README        2009-11-15 12:17:48.000000000 +0100
-@@ -16,8 +16,16 @@ from Exim 3, though the basic structure 
+--- a/README
++++ b/README
+@@ -14,8 +14,16 @@ from Exim 3, though the basic structure
  older book may be helpful for the background, but a lot of the detail has
  changed, so it is likely to be confusing to newcomers.
  
@@ -26,10 +26,9 @@ diff -NurbBp exim.orig/README exim/README
  
  A copy of the Exim FAQ should be available from the same source that you used
  to obtain the Exim distribution. Additional formats for the documentation
-diff -NurbBp exim.orig/src/eximstats.src exim/src/eximstats.src
---- exim.orig/src/eximstats.src        2009-11-15 12:16:19.000000000 +0100
-+++ exim/src/eximstats.src     2009-11-15 12:17:48.000000000 +0100
-@@ -536,8 +536,7 @@ about how to create charts from the tabl
+--- a/src/eximstats.src
++++ b/src/eximstats.src
+@@ -537,8 +537,7 @@ about how to create charts from the tabl
  
  =head1 AUTHOR
  
diff --git a/debian/patches/78_Disable-chunking-BDAT-by-default.patch b/debian/patches/78_Disable-chunking-BDAT-by-default.patch
new file mode 100644 (file)
index 0000000..2d0b7f8
--- /dev/null
@@ -0,0 +1,58 @@
+Description: Disable chunking (BDAT) by default.
+  Change default value of main option chunking_advertise_hosts and smtp
+  transport option hosts_try_chunking from "*" to empty.
+Author: Andreas Metzler <ametzler@debian.org>
+Origin: vendor
+Forwarded: not-needed
+Last-Update: 2017-01-19
+
+--- a/doc/spec.txt
++++ b/doc/spec.txt
+@@ -13215,9 +13215,9 @@ There is a slight performance penalty fo
+ preceding 4.88 had these disabled by default; high-rate installations confident
+ they will never run out of resources may wish to deliberately disable them.
+-+--------------------------------------------------------------+
+-|chunking_advertise_hosts|Use: main|Type: host list*|Default: *|
+-+--------------------------------------------------------------+
+++------------------------------------------------------------------+
++|chunking_advertise_hosts|Use: main|Type: host list*|Default: unset|
+++------------------------------------------------------------------+
+ The CHUNKING extension (RFC3030) will be advertised in the EHLO message to
+ these hosts. Hosts may use the BDAT command as an alternate to DATA.
+@@ -22522,9 +22522,9 @@ connects. If authentication fails, Exim
+ unauthenticated. See also hosts_require_auth, and chapter 33 for details of
+ authentication.
+-+--------------------------------------------------------+
+-|hosts_try_chunking|Use: smtp|Type: host list*|Default: *|
+-+--------------------------------------------------------+
+++------------------------------------------------------------+
++|hosts_try_chunking|Use: smtp|Type: host list*|Default: unset|
+++------------------------------------------------------------+
+ This option provides a list of servers to which, provided they announce
+ CHUNKING support, Exim will attempt to use BDAT commands rather than DATA. BDAT
+--- a/src/globals.c
++++ b/src/globals.c
+@@ -498,7 +498,7 @@ BOOL    check_rfc2047_length   = TRUE;
+ int     check_spool_inodes     = 100;
+ int     check_spool_space      = 10*1024;     /* 10K Kbyte == 10MB */
+-uschar *chunking_advertise_hosts = US"*";
++uschar *chunking_advertise_hosts = NULL;
+ unsigned chunking_datasize     = 0;
+ unsigned chunking_data_left    = 0;
+ BOOL    chunking_offered       = FALSE;
+--- a/src/transports/smtp.c
++++ b/src/transports/smtp.c
+@@ -200,7 +200,7 @@ smtp_transport_options_block smtp_transp
+   NULL,                /* serialize_hosts */
+   NULL,                /* hosts_try_auth */
+   NULL,                /* hosts_require_auth */
+-  US"*",               /* hosts_try_chunking */
++  NULL,                /* hosts_try_chunking */
+ #ifdef EXPERIMENTAL_DANE
+   NULL,                /* hosts_try_dane */
+   NULL,                /* hosts_require_dane */
diff --git a/debian/patches/79_CVE-2017-1000369.patch b/debian/patches/79_CVE-2017-1000369.patch
new file mode 100644 (file)
index 0000000..87fb3b7
--- /dev/null
@@ -0,0 +1,43 @@
+commit 65e061b76867a9ea7aeeb535341b790b90ae6c21
+Author: Heiko Schlittermann (HS12-RIPE) <hs@schlittermann.de>
+Date:   Wed May 31 23:08:56 2017 +0200
+
+    Cleanup (prevent repeated use of -p/-oMr to avoid mem leak)
+
+diff --git a/src/exim.c b/src/exim.c
+index 67583e5..88e1197 100644
+--- a/src/exim.c
++++ b/src/exim.c
+@@ -3106,7 +3106,14 @@ for (i = 1; i < argc; i++)
+       /* -oMr: Received protocol */
+-      else if (Ustrcmp(argrest, "Mr") == 0) received_protocol = argv[++i];
++      else if (Ustrcmp(argrest, "Mr") == 0)
++
++        if (received_protocol)
++          {
++          fprintf(stderr, "received_protocol is set already\n");
++          exit(EXIT_FAILURE);
++          }
++        else received_protocol = argv[++i];
+       /* -oMs: Set sender host name */
+@@ -3202,7 +3209,15 @@ for (i = 1; i < argc; i++)
+     if (*argrest != 0)
+       {
+-      uschar *hn = Ustrchr(argrest, ':');
++      uschar *hn;
++
++      if (received_protocol)
++        {
++        fprintf(stderr, "received_protocol is set already\n");
++        exit(EXIT_FAILURE);
++        }
++
++      hn = Ustrchr(argrest, ':');
+       if (hn == NULL)
+         {
+         received_protocol = argrest;
diff --git a/debian/patches/80_Avoid-release-of-store-if-there-have-been-later-allo.patch b/debian/patches/80_Avoid-release-of-store-if-there-have-been-later-allo.patch
new file mode 100644 (file)
index 0000000..1b55f79
--- /dev/null
@@ -0,0 +1,40 @@
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Fri, 24 Nov 2017 20:22:33 +0000
+Subject: Avoid release of store if there have been later allocations.  Bug
+ 2199
+Origin: https://git.exim.org/exim.git/commit/4090d62a4b25782129cc1643596dc2f6e8f63bde
+Bug: https://bugs.exim.org/show_bug.cgi?id=2199
+Bug-Debian: https://bugs.debian.org/882648
+Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2017-16943
+
+---
+diff --git a/src/receive.c b/src/receive.c
+index 95cf13e1..20672dbe 100644
+--- a/src/receive.c
++++ b/src/receive.c
+@@ -1772,8 +1772,8 @@ for (;;)
+   (and sometimes lunatic messages can have ones that are 100s of K long) we
+   call store_release() for strings that have been copied - if the string is at
+   the start of a block (and therefore the only thing in it, because we aren't
+-  doing any other gets), the block gets freed. We can only do this because we
+-  know there are no other calls to store_get() going on. */
++  doing any other gets), the block gets freed. We can only do this release if
++  there were no allocations since the once that we want to free. */
+   if (ptr >= header_size - 4)
+     {
+@@ -1782,9 +1782,10 @@ for (;;)
+     header_size *= 2;
+     if (!store_extend(next->text, oldsize, header_size))
+       {
++      BOOL release_ok = store_last_get[store_pool] == next->text;
+       uschar *newtext = store_get(header_size);
+       memcpy(newtext, next->text, ptr);
+-      store_release(next->text);
++      if (release_ok) store_release(next->text);
+       next->text = newtext;
+       }
+     }
+-- 
+2.15.0
+
diff --git a/debian/patches/80_mime_empty_charset.diff b/debian/patches/80_mime_empty_charset.diff
deleted file mode 100644 (file)
index 99814fe..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-From 93cad488cb2c9a31aea345c8910a9f9c5815071c Mon Sep 17 00:00:00 2001
-From: Jeremy Harris <jgh146exb@wizmail.org>
-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, &param_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, &param_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 --git a/debian/patches/81_Chunking-do-not-treat-the-first-lonely-dot-special.-.patch b/debian/patches/81_Chunking-do-not-treat-the-first-lonely-dot-special.-.patch
new file mode 100644 (file)
index 0000000..62bfdce
--- /dev/null
@@ -0,0 +1,60 @@
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Mon, 27 Nov 2017 22:42:33 +0100
+Subject: Chunking: do not treat the first lonely dot special. CVE-2017-16944,
+ Bug 2201
+Origin: https://git.exim.org/exim.git/commit/4804c62909a62a3ac12ec4777ebd48c541028965
+Bug: https://bugs.exim.org/show_bug.cgi?id=2201
+Bug-Debian: https://bugs.debian.org/882671
+Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2017-16944
+
+(cherry picked from commit 178ecb70987f024f0e775d87c2f8b2cf587dd542)
+
+Change log update
+
+(cherry picked from commit b488395f4d99d44a950073a64b35ec8729102782)
+
+---
+diff --git a/src/receive.c b/src/receive.c
+index 20672dbe..2812ea2c 100644
+--- a/src/receive.c
++++ b/src/receive.c
+@@ -1827,7 +1827,7 @@ for (;;)
+   prevent further reading), and break out of the loop, having freed the
+   empty header, and set next = NULL to indicate no data line. */
+-  if (ptr == 0 && ch == '.' && (smtp_input || dot_ends))
++  if (ptr == 0 && ch == '.' && dot_ends)
+     {
+     ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
+     if (ch == '\r')
+diff --git a/src/smtp_in.c b/src/smtp_in.c
+index 1b45f84d..02075404 100644
+--- a/src/smtp_in.c
++++ b/src/smtp_in.c
+@@ -4955,16 +4955,23 @@ while (done <= 0)
+       DEBUG(D_receive) debug_printf("chunking state %d, %d bytes\n",
+                                   (int)chunking_state, chunking_data_left);
++      /* push the current receive_* function on the "stack", and
++      replace them by bdat_getc(), which in turn will use the lwr_receive_*
++      functions to do the dirty work. */
+       lwr_receive_getc = receive_getc;
+       lwr_receive_ungetc = receive_ungetc;
++
+       receive_getc = bdat_getc;
+       receive_ungetc = bdat_ungetc;
++      dot_ends = FALSE;
++
+       goto DATA_BDAT;
+       }
+     case DATA_CMD:
+     HAD(SCH_DATA);
++    dot_ends = TRUE;
+     DATA_BDAT:                /* Common code for DATA and BDAT */
+     if (!discarded && recipients_count <= 0)
+-- 
+2.15.0
+
diff --git a/debian/patches/81_buffer-overrun-in-spam-acl.diff b/debian/patches/81_buffer-overrun-in-spam-acl.diff
deleted file mode 100644 (file)
index 689c106..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-From e252eb8c71ea3bddb32bf73bddc8b22cfde2bc3a Mon Sep 17 00:00:00 2001
-From: Jeremy Harris <jgh146exb@wizmail.org>
-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 --git a/debian/patches/82_Fix-base64d-buffer-size-CVE-2018-6789.patch b/debian/patches/82_Fix-base64d-buffer-size-CVE-2018-6789.patch
new file mode 100644 (file)
index 0000000..146339c
--- /dev/null
@@ -0,0 +1,29 @@
+Description: Fix base64d() buffer size (CVE-2018-6789)
+ Credits for discovering this bug: Meh Chang <meh@devco.re>
+Origin: vendor
+Bug-Debian: https://bugs.debian.org/890000
+Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2018-6789
+Forwarded: not-needed
+Author: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Last-Update: 2018-02-10
+---
+
+--- a/src/base64.c
++++ b/src/base64.c
+@@ -152,10 +152,14 @@ static uschar dec64table[] = {
+ int
+ b64decode(uschar *code, uschar **ptr)
+ {
++
+ int x, y;
+-uschar *result = store_get(3*(Ustrlen(code)/4) + 1);
++uschar *result;
+-*ptr = result;
++{
++  int l = Ustrlen(code);
++  *ptr = result = store_get(1 + l/4 * 3 + l%4);
++}
+ /* Each cycle of the loop handles a quantum of 4 input bytes. For the last
+ quantum this may decode to 1, 2, or 3 output bytes. */
diff --git a/debian/patches/82_quoted-or-r-2047-encoded.diff b/debian/patches/82_quoted-or-r-2047-encoded.diff
deleted file mode 100644 (file)
index f807103..0000000
+++ /dev/null
@@ -1,194 +0,0 @@
-From 5c6cf6a0d5cb7da39e7fde01dca1ff862c1fa1c8 Mon Sep 17 00:00:00 2001
-From: Jeremy Harris <jgh146exb@wizmail.org>
-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, &param_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 --git a/debian/patches/83_Remove-limit-on-remove_headers-item-size.-Bug-1533.patch b/debian/patches/83_Remove-limit-on-remove_headers-item-size.-Bug-1533.patch
deleted file mode 100644 (file)
index d0ea3f6..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-From 8bc732e8b03ebb4309f4b42626917148d176db49 Mon Sep 17 00:00:00 2001
-From: Jeremy Harris <jgh146exb@wizmail.org>
-Date: Sun, 5 Oct 2014 21:31:20 +0100
-Subject: [PATCH] Remove limit on remove_headers item size. Bug 1533
-
----
- doc/doc-txt/ChangeLog | 5 +++++
- src/src/transport.c   | 3 +--
- 2 files changed, 6 insertions(+), 2 deletions(-)
-
-| diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog
-| index 76ecc20..0b03894 100644
-| --- a/doc/doc-txt/ChangeLog
-| +++ b/doc/doc-txt/ChangeLog
-| @@ -44,6 +44,11 @@ JH/05 Fix results-pipe from transport process.  Several recipients, combined
-|        to notice due to the introduction of conection certificate information,
-|        the item size being so much larger.  Found and fixed by Wolfgang Breyha.
-|  
-| +JH/06 Bug 1533: Fix truncation of items in headers_remove lists.  A fixed
-| +      size buffer was used, resulting in syntax errors when an expansion
-| +      exceeded it.
-| +
-| +
-|  Exim version 4.84
-|  -----------------
-|  TL/01 Bugzilla 1506: Re-add a 'return NULL' to silence complaints from static
-diff --git a/src/transport.c b/src/transport.c
-index 31437b1..15c30bf 100644
---- a/src/transport.c
-+++ b/src/transport.c
-@@ -643,8 +643,7 @@ for (h = header_list; h != NULL; h = h->next) if (h->type != htype_old)
-       {
-       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))))
-+      while ((s = string_nextinlist(&list, &sep, NULL, 0)))
-       {
-       int len;
--- 
-2.1.4
-
diff --git a/debian/patches/84_Fix-truncation-of-items-in-headers_remove-lists-this.patch b/debian/patches/84_Fix-truncation-of-items-in-headers_remove-lists-this.patch
deleted file mode 100644 (file)
index a61a06d..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-From 97f83c7a669a525a5f5964a5c0708c311673b87f Mon Sep 17 00:00:00 2001
-From: Jeremy Harris <jgh146exb@wizmail.org>
-Date: Thu, 29 Jan 2015 17:42:47 +0000
-Subject: [PATCH] Fix truncation of items in headers_remove lists, this time in
- routers.  Bug 1533
-
----
- src/src/routers/rf_get_munge_headers.c | 3 +--
- 1 file changed, 1 insertion(+), 2 deletions(-)
-
-diff --git a/src/routers/rf_get_munge_headers.c b/src/routers/rf_get_munge_headers.c
-index a4a13b0..3125f31 100644
---- a/src/routers/rf_get_munge_headers.c
-+++ b/src/routers/rf_get_munge_headers.c
-@@ -90,9 +90,8 @@ if (rblock->remove_headers)
-   uschar * list = rblock->remove_headers;
-   int sep = ':';
-   uschar * s;
--  uschar buffer[128];
--  while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
-+  while ((s = string_nextinlist(&list, &sep, NULL, 0)))
-     if (!(s = expand_string(s)))
-       {
-       if (!expand_string_forcedfail)
--- 
-2.1.4
-
index 8a882b2..3e0491e 100644 (file)
@@ -3,14 +3,14 @@
 33_eximon.binary.dpatch
 34_eximstatsmanpage.dpatch
 35_install.dpatch
+40_reproducible_build.diff
 50_localscan_dlopen.dpatch
 50-relax-appendfile-chown-openafs.patch
 60_convert4r4.dpatch
-66_enlarge-dh-parameters-size.dpatch
 67_unnecessaryCopt.diff
 70_remove_exim-users_references.dpatch
-80_mime_empty_charset.diff
-81_buffer-overrun-in-spam-acl.diff
-82_quoted-or-r-2047-encoded.diff
-83_Remove-limit-on-remove_headers-item-size.-Bug-1533.patch
-84_Fix-truncation-of-items-in-headers_remove-lists-this.patch
+78_Disable-chunking-BDAT-by-default.patch
+79_CVE-2017-1000369.patch
+80_Avoid-release-of-store-if-there-have-been-later-allo.patch
+81_Chunking-do-not-treat-the-first-lonely-dot-special.-.patch
+82_Fix-base64d-buffer-size-CVE-2018-6789.patch
index 582ed0f..8feb3cd 100755 (executable)
@@ -70,7 +70,6 @@ endif
 ifeq ($(buildbasepackages),yes)
 basedaemonpackages=exim4-daemon-light
 combinedadbgpackage=exim4-base eximon4
-combinedidbgpackage=exim4-config exim4
 exim4dbg=exim4-dbg
 dhstripparm=--dbg-package=$(exim4dbg)
 exim4dev=exim4-dev
@@ -78,14 +77,11 @@ extraadbgpackage=$(basedaemonpackages) $(extradaemonpackages)
 else
 basedaemonpackages=
 combinedadbgpackage=
-combinedidbgpackage=
 exim4dbg=
 dhstripparm=
 exim4dev=
 extraadbgpackage=$(extradaemonpackages)
 endif
-#DEBUGOUT:=$(shell echo >&2 buildbasepackages $(buildbasepackages))
-#DEBUGOUT:=$(shell echo >&2 extradaemonpackages $(extradaemonpackages))
 
 # If you want to build with OpenSSL instead of GnuTLS, uncomment this
 # OPENSSL:=1
@@ -98,7 +94,6 @@ buildpackages=$(combinedadbgpackage) $(extraadbgpackage) $(addsuffix -dbg,$(extr
 # generate -pexim4-base -peximon4 ... commandline for debhelper
 dhbuildpackages=$(addprefix -p,$(buildpackages))
 dhcombinedadbgpackage=$(addprefix -p,$(combinedadbgpackage))
-dhcombinedidbgpackage=$(addprefix -p,$(combinedidbgpackage))
 
 # exim4-daemon-heavy --> b-exim4-daemon-heavy/build-Linux-x86_64/exim
 daemonbinaries=$(addprefix b-,$(addsuffix /build-$(buildname)/exim,$(extradaemonpackages)))
@@ -109,7 +104,20 @@ BDIRS=$(addprefix b-,$(extradaemonpackages) $(basedaemonpackages))
 # get upstream-version from debian/changelog, i.e. anything until the first -
 DEBVERSION := $(shell dpkg-parsechangelog | sed -n '/^Version: /s/^Version: //p')
 UPSTREAMVERSION := $(shell echo $(DEBVERSION) | sed -n 's/\(.\+\)-[^-]\+/\1/p')
-MTACONFLICTS := $(shell cat $(DEBIAN)/mtalist)
+DEBTIME := $(shell dpkg-parsechangelog --show-field Date)
+REPBUILDDATE := \
+       $(shell env LC_ALL=C TZ=UTC date --date="$(DEBTIME)" '+%b %e %Y')
+REPBUILDTIME := \
+       $(shell env LC_ALL=C TZ=UTC date --date="$(DEBTIME)" '+%H:%M:%S')
+
+PROVIDE_DEFAULT_MTA := $(shell if dpkg-vendor --is Ubuntu || \
+       dpkg-vendor --derives-from Ubuntu ; then : ; else \
+       echo "default-mta" ; fi)
+# for reproducible build. If set exim would use $TZ as default value for
+# TIMEZONE_DEFAULT
+undefine TZ
+unexport TZ
+
 
 # set up build directory b-exim4-daemon-heavy/
 $(addsuffix /Makefile,$(BDIRS)): %/Makefile:
@@ -119,6 +127,10 @@ $(addsuffix /Makefile,$(BDIRS)): %/Makefile:
                -name 'b-*' -o -print0 | \
                xargs --no-run-if-empty --null \
                cp -a --target-directory=$*
+       printf '#define REPBUILDDATE "$(REPBUILDDATE)"\n' \
+               > $*/src/repbuildtime.h && \
+               printf '#define REPBUILDTIME "$(REPBUILDTIME)"\n' \
+               >> $*/src/repbuildtime.h
 
 
 unpack-configs: unpack-configs-stamp
@@ -130,11 +142,12 @@ unpack-configs-stamp: src/EDITME exim_monitor/EDITME
 ifdef OPENSSL
        patch EDITME.exim4-light $(DEBIAN)/EDITME.openssl.exim4-light.diff
 endif
-       -for editme in $(DEBIAN)/EDITME.exim4-*.diff; do \
+       for editme in $(DEBIAN)/EDITME.exim4-*.diff; do \
          if [ "$$editme" != "$(DEBIAN)/EDITME.exim4-light.diff" ]; then \
            TARGETNAME=`basename $$editme .diff`; \
            echo patch -o $$TARGETNAME EDITME.exim4-light $$editme; \
-           patch -o $$TARGETNAME EDITME.exim4-light $$editme; \
+           patch -o $$TARGETNAME EDITME.exim4-light $$editme || \
+           exit $$? ;\
          fi; \
        done
        touch unpack-configs-stamp
@@ -176,9 +189,6 @@ configure: configure-stamp
 configure-stamp: $(addsuffix /Makefile,$(BDIRS)) unpack-configs-stamp
        dh_testdir
        # Add here commands to configure the package.
-       # We currently do not want to build depend on debhelper 7.2.3 just to
-       # keep it from installing the wrong upstream changelog. 490937
-       rm -fv CHANGES
        touch $@ 
 
 # Build binaries for the base package, the eximon4 package, and the
@@ -212,15 +222,9 @@ b-exim4-daemon-custom/build-$(buildname)/exim: b-exim4-daemon-custom/Makefile co
        cd $(<D) && $(MAKE) FULLECHO=''
 
 build-indep: build-indep-stamp
-build-indep-stamp:
+build-indep-stamp: debian/README.Debian
        dh_testdir
-
-       # Add here command to compile/build the arch indep package.
-       # It's ok not to do anything here, if you don't need to build
-       #  anything for this package.
-       #/usr/bin/docbook-to-man $(DEBIAN)/exim.sgml > exim.1
-
-       touch build-indep-stamp
+       touch $@
 
 build-arch: build-arch-stamp test-stamp
 
@@ -232,7 +236,7 @@ endif
        dh_testdir
        # Which version of Berkeley DB are we building against?
        printf '#include <db.h>\ninstdbversionis DB_VERSION_MAJOR DB_VERSION_MINOR\n' | \
-               cpp | grep instdbversionis |\
+               cpp -P | grep instdbversionis |\
                sed -e 's/[[:space:]]*instdbversionis[[:space:]]//' \
                -e 's/[[:space:]][[:space:]]*/./' \
                -e 's_^_s/^BDBVERSION=.*/BDBVERSION=_' \
@@ -289,12 +293,9 @@ cleanfiles:
        #pwd
        chmod 755 $(DEBIAN)/exim-gencert \
                $(DEBIAN)/lynx-dump-postprocess $(DEBIAN)/script \
-               $(DEBIAN)/exim-adduser
+               $(DEBIAN)/exim-adduser $(DEBIAN)/exim4_refresh_gnutls-params
        dh_clean
        rm -rf $(BDIRS)
-       # fix broken (0600) permissions in original tarball
-       #find OS doc scripts exim_monitor src util -perm -044 -or -print0 |\
-       #       xargs -0r chmod -c og+r
 
 installbase-stamp: b-exim4-daemon-light/build-$(buildname)/exim debian/README.Debian debian/README.Debian.html
        dh_testdir
@@ -361,16 +362,13 @@ installbase-stamp: b-exim4-daemon-light/build-$(buildname)/exim debian/README.De
                { cd $(DEBIAN)/exim4-config/etc/exim4/conf.d/ && \
                tar xf - ; }
 
-       # ship a copy in examples
-       # install -m644 $(DEBIAN)/debconf/exim4.conf.template $(DEBIAN)/exim4-config/usr/share/doc/exim4-config/examples/exim4.conf.template.debconf
        install -m644 $(DEBIAN)/email-addresses $(DEBIAN)/exim4-config/etc/
        install -m640 -oroot -groot $(DEBIAN)/passwd.client \
                $(DEBIAN)/exim4-config/etc/exim4/
        chmod 755 $(DEBIAN)/debconf/update-exim4.conf.template
        env CONFDIR=$(DEBIAN)/debconf \
                $(DEBIAN)/debconf/update-exim4.conf.template --nobackup --run
-#      dh_movefiles
-       touch installbase-stamp
+       touch $@
 
 
 # This dependency expands to
@@ -382,57 +380,65 @@ $(debiandaemonbinaries): $(DEBIAN)/%/usr/sbin/exim4: b-%/build-$(buildname)/exim
        install -m4755 -oroot -groot $< $@
 
 
+ifeq ($(buildbasepackages),yes)
+install=installbase-stamp $(debiandaemonbinaries)
+else
+install=$(debiandaemonbinaries)
+endif
+
+override_dh_installchangelogs:
+       dh_installchangelogs -pexim4-base doc/ChangeLog
+       dh_installchangelogs --no-package=exim4-base \
+               -XCHANGES -Xdoc/ChangeLog
+
+override_dh_installppp:
+       dh_installppp --name=exim4
+
+override_dh_strip-arch:
+       dh_strip $(dhcombinedadbgpackage) $(dhstripparm)
+       for pkg in $(extraadbgpackage); do \
+         dh_strip -p$$pkg --dbg-package=$${pkg}-dbg; \
+       done
+
+override_dh_fixperms:
+       dh_fixperms -X/etc/exim4/passwd.client -Xusr/sbin/exim4
+
+override_dh_gencontrol:
+       dh_gencontrol -- \
+               -VUpstream-Version=$(UPSTREAMVERSION) \
+               -VMTA-Conflicts="$(shell cat $(DEBIAN)/mtalist)" \
+               -Vdist:Provides:exim4-daemon-light="$(PROVIDE_DEFAULT_MTA)"
+
+override_dh_installlogrotate:
+       dh_installlogrotate
+       dh_installlogrotate --name=exim4-paniclog
+
+override_dh_installinit:
+       dh_installinit --noscripts --name=exim4
+
+override_dh_install:
+       # install config.h from daemon package, but not from exim4-daemon-light
+       dh_install -p exim4-dev \
+               $(shell ls -1 b-exim4-daemon-*/build-$(buildname)/config.h | grep -v ^b-exim4-daemon-light/) \
+               usr/include/exim4
+       dh_install
+
+override_dh_link:
+       rm -rf debian/exim4/usr/share/doc/exim4
+       dh_link
+
+override_dh_auto_install:
+       # disabled
+
 # Build architecture-independent files here.
 # this is just exim4-config and exim4.
 binary-indep: build $(install)
 ifeq ($(buildbasepackages),yes)
-       dh_testdir -i
-       dh_testroot -i
-       # upstream changelog is only in exim4-base
-       dh_link -i
-       dh_installchangelogs -i -XCHANGES
-       dh_installdocs -i
-       dh_installexamples -i
-       #dh_installmenu -i
-       dh_installdebconf -i
-       dh_installlogrotate -i
-#      dh_installemacsen -i
-       #dh_installpam -i
-       #dh_installmime -i
-#      dh_installinit -i
-       dh_installcron -i
-#      dh_installinfo -i
-#      dh_undocumented -i
-       dh_installppp -i --name=exim4
-       dh_installman -i
-       dh_install -i
-       dh_lintian -i
-       dh_strip $(dhcombinedidbgpackage) $(dhstripparm)
-       #for pkg in $(extraidbgpackage); do \
-       #  dh_strip -p$$pkg --dbg-package=$${pkg}-dbg; \
-       #done
-       dh_compress -i
-       dh_fixperms -i -X/etc/exim4/passwd.client
-#      dh_makeshlibs -i
-       dh_installdeb -i
-#      dh_perl -i
-       dh_shlibdeps -i
-       dh_gencontrol -i -- -VUpstream-Version=$(UPSTREAMVERSION) \
-               -VMTA-Conflicts="$(MTACONFLICTS)"
-       dh_md5sums -i
-       dh_builddeb -i
+       dh binary-indep
 endif
 
 # Build architecture-dependent files here.
-ifeq ($(buildbasepackages),yes)
-install=installbase-stamp $(debiandaemonbinaries)
-else
-install=$(debiandaemonbinaries)
-endif
-
 binary-arch: build $(install)
-       dh_testdir $(dhbuildpackages)
-       dh_testroot $(dhbuildpackages)
        # symlink identical maintainerscripts
        @for i in $(extradaemonpackages) ; do \
                ln -sfv exim4-daemon-light.prerm \
@@ -440,50 +446,7 @@ binary-arch: build $(install)
                ln -sfv exim4-daemon-light.postinst \
                        "$(DEBIAN)/$$i.postinst" ; \
        done
-       # upstream changelog is only in exim4-base, the other packages include
-       # a symlink
-       dh_installchangelogs -pexim4-base doc/ChangeLog
-       # remove "-pexim4-base" from "-pexim4-base -pexim4-daemon-light ..."
-       dh_installchangelogs $(subst -pexim4-base ,,$(dhbuildpackages)) \
-               -XCHANGES
-       dh_installdocs $(dhbuildpackages)
-       dh_installexamples $(dhbuildpackages)
-       dh_installmenu $(dhbuildpackages)
-       dh_installdebconf $(dhbuildpackages)
-       dh_installlogrotate $(dhbuildpackages)
-       dh_installlogrotate $(dhbuildpackages) --name=exim4-paniclog
-#      dh_installemacsen $(dhbuildpackages)
-       dh_installpam $(dhbuildpackages)
-       dh_installmime $(dhbuildpackages)
-       #dh_installinit $(dhbuildpackages) --noscripts --name=exim4
-       # work around #347577 (fixed in debhelper 5.0.15)
-       dh_installinit $(dhbuildpackages) -n --name=exim4
-       dh_installcron $(dhbuildpackages)
-#      dh_installinfo $(dhbuildpackages)
-#      dh_undocumented $(dhbuildpackages)
-       dh_installman $(dhbuildpackages)
-       dh_install $(dhbuildpackages)
-       # install config.h from daemon package, but not from exim4-daemon-light
-       dh_install -p exim4-dev \
-               $(shell ls -1 b-exim4-daemon-*/build-$(buildname)/config.h | grep -v ^b-exim4-daemon-light/) \
-               usr/include/exim4
-       dh_lintian $(dhbuildpackages)
-       dh_strip $(dhcombinedadbgpackage) $(dhstripparm)
-       for pkg in $(extraadbgpackage); do \
-         dh_strip -p$$pkg --dbg-package=$${pkg}-dbg; \
-       done
-       dh_link $(dhbuildpackages)
-       dh_compress $(dhbuildpackages)
-       dh_fixperms $(dhbuildpackages) -Xusr/sbin/exim4
-#      dh_makeshlibs $(dhbuildpackages)
-       dh_installdeb $(dhbuildpackages)
-#      dh_perl $(dhbuildpackages)
-       dh_shlibdeps $(dhbuildpackages)
-       dh_gencontrol $(dhbuildpackages) -- \
-               -VUpstream-Version=$(UPSTREAMVERSION) \
-               -VMTA-Conflicts="$(MTACONFLICTS)"
-       dh_md5sums $(dhbuildpackages)
-       dh_builddeb $(dhbuildpackages)
+       dh binary-arch
 
 binary: binary-arch binary-indep
 .PHONY: build clean binary-indep binary-arch binary install
index 5fc09bd..bc0ef5c 100755 (executable)
@@ -23,3 +23,6 @@ if test -r /etc/mailname ; then
        eval echo -n 'mailname:' $REDIR
        eval cat /etc/mailname $REDIR
 fi
+if test -r  /etc/default/exim4 ; then
+       eval cat /etc/default/exim4 $REDIR
+fi
diff --git a/debian/source/include-binaries b/debian/source/include-binaries
deleted file mode 100644 (file)
index 95a390b..0000000
+++ /dev/null
@@ -1 +0,0 @@
-debian/upstream-signing-key.pgp
diff --git a/debian/source/lintian-overrides b/debian/source/lintian-overrides
deleted file mode 100644 (file)
index c7349e0..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-# A Conflict is unnecessary. *-daemon-dbg depends on the
-# respective -daemon, and the daemon-packages conflict with each other.
-exim4 source: binaries-have-file-conflict exim4-daemon-heavy-dbg exim4-daemon-light-dbg usr/lib/debug/usr/sbin/exim4
index 1725f53..06915e7 100644 (file)
@@ -71,7 +71,7 @@ QUEUERUNNEROPTIONS='${EX4DEF_QUEUERUNNEROPTIONS}'
 QFLAGS='${EX4DEF_QFLAGS}'
 # Options for the SMTP listener daemon. By default, it is listening on
 # port 25 only. To listen on more ports, it is recommended to use
-# -oX 25:587:10025 -oP /var/run/exim4/exim.pid
+# -oX 25:587:10025 -oP /run/exim4/exim.pid
 SMTPLISTENEROPTIONS='${EX4DEF_SMTPLISTENEROPTIONS}'
 EOF
        cat "${EX4DEF_TMP}" > "${defaultfile}"
diff --git a/debian/upstream-signing-key.pgp b/debian/upstream-signing-key.pgp
deleted file mode 100644 (file)
index 631e571..0000000
Binary files a/debian/upstream-signing-key.pgp and /dev/null differ
diff --git a/debian/upstream/signing-key.asc b/debian/upstream/signing-key.asc
new file mode 100644 (file)
index 0000000..29e2e56
--- /dev/null
@@ -0,0 +1,777 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQGiBEIV3d4RBADiY+ImtiuxCxe4ImIWZd6IetWIZaAjxLQliWrRHK7CdA6ANYAA
+OWwk6uMucPSjP2RUYXehDdVAb2i5AG3kGb/SNZ08x2eaeAtALAvRw3SxPW5/Ch4g
+bNB8VBCyyZlPsmS1epbaOags+1oD41FopdvfIQrtoD4I0d/ndG64wkDh2wCgiXdE
+QZzYknZgf4HA9DZHhizNnx0EAMBDVTpIq7xaYlK4dot4xNcWNJg4UX27a62lEKvV
+sDf1tH1qB4ujZy1ht83oXURpNk7uDf718kwaLGoSwW6qOx9iI46XoOtoxSH+6J8A
+oKtBNhCl03x10E8MK1fANe9WLdxARxgZxnPo9QOSTNO4PYR1yvrq0ThTKXvMweYT
+OJlIBADdTquCiM9fgoU3sBsnlmSMpFn27By0Yz4QjR8cLD0F1bZKmWPRAHDdwArS
+pOmKNv4tOaNp8WuuLEEJbPEcc6QdPEOH3lVQ/QZHdemYerwMN25i3MYeWAPRg4Sl
+dZ648IPWdHA/QYfp5JhlT/9UwwKPvIDTPg10FI5ecPYxcXUT2LQuTmlnZWwgTWV0
+aGVyaW5naGFtIChFeGltIGtleSkgPG5pZ2VsQGV4aW0ub3JnPohkBBMRCgAkAhsD
+BgsJCAcDAgMVAgMDFgIBAh4BAheABQJWzue5BQkVsOPbAAoJEIWrgz/dwDJiGmoA
+oIfRyEwpzL4v6JB4BzK3TqfH6mVRAJ90M8AfnhzW3KG7l3KYxscnVZdOlbkCDQRC
+Fd3nEAgAgeLGF7rot+0cc0hwGFK7h1aGP6r2p+o1arsR/zJystk99UBWqjmKzu+3
+6ve+H4J28Al4B7Sm75bvnKignppp0ZGP/WXlkGsk6Tt30c7tkK+1izrCFGlxf5j0
+LKrH/cCyZp7tgqRN0ewDoqK6OmEBmSqMgarSTatyYuZy5OKof8EcJEt6nTydPdts
+VgRziX71B1pd0t/bdWwLnuQ9gkSJNiwPGBrV53x9uh43ZcpqLl17yfXh/FaUcdlZ
+N1GPtXYMr208Hv8fGpPEQVr92OJAblrlGck+aWIoYgX3tqCZDqCYtxcBaXCyRZzu
+7usKJukY1Z6t0qF1U7aWTjeVVeWXhwADBQf/RYK2jTNLnhtCVWqWhFVd0/NTbXIs
+QDeZuZXp8xHB+YjxmcbrSTvKrkRqfCvPR5r5SBOwBtq+LHElwp1OcIt2xYIEmuS1
+Jod8+h+ohl9p11XtTp3Rd8selh7AHccFz6BYK1SsHO5ZdrFwlZf+oVxLrQzibFqZ
+Ob69T4HUp5Vh5Z9XO+YsVa5a3K1/pfpOJYMP3VgdsBlX/gUxkz9stfNUOIR5caQK
+UHfOaCQaQ02fAsmnThQkAmqACTapvqZV9wSHxgvUUbPcw2h3rty14u13J+cJDrE0
++x1tCDSsPLbq62A1d9GJor8s6GpyYXq1ArZJgBpdq74qOKU5jc1gvMmE8YhOBBgR
+AgAPBQJCFd3nAhsMBQkJZgGAAAoJEIWrgz/dwDJiqxIAnAm3NzfRaBtl5XpnCA6n
+W2MNAwIgAJds5g802u5CKZDLGE90hHNXgF2kuQENBE1Aj9kBCADfrgx9xrDHoYSU
+3aU8zST2GEoMZypO1fBi3AiInsKakMsVibZpEI8MVM24lZw9jxGfsX70Xr+mYiTI
+ZY9GJROG6fHFLKgUYFxYeUA1GtNNilFvBGlXJAYduyKYZMdEVVtUX4b6QpQqmTeY
+sgNCznb1HuVpj4Vl6CiirjWhnZ/WhR3L20AMK6422lCw9jZuAK5RbSRJwkgI55rl
+zZGpGbBmBIHSCccMB/jg2LRYsVs//D9Qrxtkt8W8fIHCj66L6eNw1gcndpEkyytZ
+bifE3khwlRWn/Llpw8NiQiJKUE01TWQusEvd5EHFThE/9bYpUGdMiR0UmpSLkEq3
+zurCcUK1ABEBAAGJAW4EGBECAA8FAk1Aj9kCGwIFCRKtsIABKQkQhauDP93AMmLA
+XSAEGQECAAYFAk1Aj9kACgkQA8m6p6iaqTb0Dwf/QiTT/Aj4XdoSVGR4yeXFpQNR
+l99dOtUwsP7wtSSeV5jQgEMpRwh8ib702retoWbHQva0FsDxotEatHKvdtkkCUqF
+D33jZ+aKkadcXjqnSepXY0m7sG605QN5hE1dXBhPPy5hUfXuAphSq+ma4Q4Vz+Zm
+al3etKXL2xIgAIkSX+srng3j09JfOaYdEDXOU5sNEMuDqcqPC/yt0giGFPDBd7xZ
+JQER08MyfDoFmwiVGi1Trbzjdnp1Y0q9UF2NpWUMB0q9/CaodwjU7SB4OU9FYst9
+uImVDwI3XqL45ULUCZGhUnuHz15ePb1W5cUUu55M0iuCrjhHqt0e8/c7BrdFuwee
+AJ41rUXzNNSj3w/o9T0O7mWd0rh+HQCfSNjhzVUditAzFdNneXLgs9KddFq5AQ0E
+TUCP7gEIALzLEYpmJLCDALPKv07Yd4bhyX/st+7Hz3Uj1BjIW/+pCEFf8e+ihZg/
+caWuSL695DddreiIhJlQiso8HsjehDccU51kep4vvTKu2p3zTSSZvIgsTTPAeyqa
+L12UCAm4SlkjhEH86Yf7Qyic5cZhkGBCtN/1RVxoEoonRGOJg2jkrvok3Dz1DQ5W
+UyS5gRASDnF58EW4HSMiRek2XgN/MEY9GLkXsoaSFWU9X3rW3Mgd4EMpTf+id2eS
+Ffp820Ati+1VB6Hte8JOWRhTopSB6FZfpZ322N2iCAX0TkZesfSwfZSTZ/Xc+29B
+3JHDrVbFmCLhJfzv6MqQ04VQZ1VWzUEAEQEAAYhPBBgRAgAPBQJNQI/uAhsMBQkS
+rbCAAAoJEIWrgz/dwDJiNIgAoIdWmf17rL5Zmf/EoPtmYngbadnaAJ45YtXrEDCV
+4fuUhLK6EdvHsGGtl5kBogRRHjTKEQQA7Nj/xLjtdH+34XBWzVRupKAEA27d5Ikn
+AVtyPK/4aiGZ2mQHPX7qaVOOHHFHVfj+38ENwZG2do87x5oJgaAf/WAqQRp0m81r
+7YZ3DGWZxeDuCYESwZxEkJ9SfOwmQ66NrHuXjjabOoQEoxtQdxcyaGDBWbvpDaXS
+4fG1oKyx1T8AoOGl+25xKVwA5GKU/DLqbBOoyOi7A/914vhUW1bd8TcKk5owI7/q
+FoSIjk1/lxxDFX600giri1FrENN+ERg0jaIBFFnkJF4dx6G5xIuEAHLJ0Y2BdXCF
+mJPJw7ZzgtTmWSKW0kDhbRx+Ozvpwa1spxyjgQAg3B1fVUBkGlV6+bDZOHmMDK8b
+7RoRdW44+ygbE+WHS5/oiQQAiZtFY14WcSi4bqhpTDK5YFZh2lyhQ2snYfOiQWB/
+gLLfKDTDJ6pVygtayPKlx4jXuapyNE62QhU5zgCKr9DpsM7v7UnPfTgPYse5HqUW
+IPOiOE+ga0TpZT4egqzW6mPGRYQ/ZjViL+JGMa2ATvrSoR1BJCd8BFmmplDs2it2
+Nme0LlRvZGQgTHlvbnMgKEV4aW0gTWFpbnRhaW5lcikgPHRseW9uc0BleGltLm9y
+Zz6IZgQTEQIAJgUCUR40ygIbAwUJCWYBgAYLCQgHAwIEFQIIAwQWAgMBAh4BAheA
+AAoJEMT0+UgE0p66MDwAnRW1VWjfUD5yGhedcxiHsEg1A8vnAJ9NxfoOwPP50sWT
+f2vycK0mGECYcrkCDQRRHjTREAgAlhjQZt1+uSQ3puq7p9o/AqRrVsZxxbi/C0cS
+eAvr/iN4tkKk/4esSMevwLIMPw0ByuwCDdZusdLAI6TdDe3nwDBQVRbMlmmQM1fx
+1wsJHbiEO+WDENULU0SxqU7lwq3YCqL7oKVtZsJ0MkmEAbZlWuzBE1RzNTgdoMSB
+GmSeDu5f5q1a+BMH1gcZWQkW7Y1e1kgHDgnz6vh+cBulWCwEzrwGaEvmJJ+w2HPE
+cD9q4IvTjXxZbli7WHrSctqCdgF433iWOa+NjUCfl98z4D7KjKMqvXKqD88NYbqG
+wrvupQZMOeNjybWMnkouAXHJdA8fiTy5hV9P7nat1OMq6h+YRwAEDQf9Gl43A+H4
+xJJ34RrCp9il8/Ef7VHEn9ZnaoMNuwCjYU9OaTHAjd7V5N23ZF15+XMvO0Szx/to
+qQ14ev385VgBD/FWGy1r+UBK1/gA3pArQhpd4mtzRsjg8e2yl0D5v3v4K1EjEtDn
+37IBwAmWjwbMU12SP0NM+KQXtO0WCQF+ggRhD8hhUPV20ejYqnismX5b7LYX+8NB
+OCleryW4pz4ZQT6MTolyjeojyCyaHE9G554ECKX+fKG/WMQmjjwjngkrPk0s3HN/
+uU8UvQv+uucP62iHcPRKwIk6jrlR7KODR00IzSXaRNYtJoDC8oFS0xyhrG1vMiGv
+OQTBfKpgyxoIBIhPBBgRAgAPBQJRHjTRAhsMBQkJZgGAAAoJEMT0+UgE0p66lx4A
+n2JHiU9h4ElPNbDSfqjQoshYKIb3AJ9RjvMg0AdlIPi6k2PWTTBAKsoB+JkCDQRU
+rvZBARAA4jmen1cqxMnj2SIOPBV5igqnsSljlCADmC8MlW1OzozaxFJo/GMMfZjE
+AAiST3IFIzk8YBotDfUwSaVpRQ8QFz0XT6+BrDwKvMId7lZ3AuaqWkXT4+uv52Yr
+PVN87kbn52MLoUEtxgWxa1dvNmg8+wzsBVI63Oep3yo9eot95SIHeqDQj+4Rzd2Z
+Ejh/m3AHcoZl+Y71b9zsaherqvBgB6QpBNaYhEXXAFZGXzynX+6WxNKQ9gRxnsKD
+ZkbnJvBOyOLz+fsVI/lbGnSXycQ/hVw3xg30bXHuOkYhIe1SRz78YAaAlBp76o3P
++M9oJA9SxP8j6XWj3vlBtbLRNl1eUXl1ED8S95jGVzmou0I08HGJRmOGAmEYQjDJ
+JB8UR6RHn4m0yCQZZgocXCGERgSRNmPMUOaIskMnBqoCfqEifGS1ATqZgYuEik9M
+o8wfHCAeMOGsjr6ew1NGPfjvzUQGRUPRgvuE5c3m6WcsDJkgTH7YW9P8T4QeboeV
+Y7xpwkAp9Sd/eoQvpXGXjEAkC5dJhaHXKbtxrxlLHaV7cTp17+Vajuf4s3zzXhjQ
+rh9ojiNyEVBDetsowzN+UxgWybGeFtXeeqUmUgLpoV8iOjaqKI/n24+dl+JY51tH
+8cR8DG93N9xYL/CDersmvxgIZEVDrpvc3/YMhCWVHDZ0ZqmnQrsAEQEAAbQzSGVp
+a28gU2NobGl0dGVybWFubiAoRHJlc2RlbikgPGhzQHNjaGxpdHRlcm1hbm4uZGU+
+iQJABBMBCgAqAhsBAh4BAheAAhkBBQsJCAcDBRUKCQgLBRYCAwEABQJYVnXoBQkF
+vsCnAAoJECYQG2L2k3bOhKkP/2zWhq0BlT7AAAuefaZPl9b52uT7PbY4owcMWXJz
+i7FTLWFo6KJOCBH9UTX0TXmf9S3AMMfoewblU6zOy+H1Q/ZVdzth5iJaXSbTgLlZ
+7yc7k3P+qUdBGdCHwpUJmBScdGaKCkbdcOPIxTi02sPFTBJx45ogr3/n0S8PFNOY
+Vv0fl4Nnr2bOpoSKSka08lk4HJKsMMA/BRfaSffez1QYdRJKhKTkljlJjA682Fuf
+NBaZIQ8GHUjyyOIUwQUit2yAGChbBCh9wq5Z//xzBwdqGx64QLHHF+wCg2r9Ba3D
+QMNllPidfPBPUPQ+xGXmHz0R0FzlaTYnFYKqpJSX8j/5IhaijZRxvtJljXa0fOg3
+D1A7ZuagCpNcXWVM66FeOx2hYMlBNn/eLejBc244ydlI5lqyocGRL3qjHufp6JVi
+uwJpNMnWyLvxqrwgC6mcCDx7jJL7eI7rdAFLwfoTYnBb0zNPStf9pWngLmxsD9G0
+U3nJnVzhsZfa9s7F13wxkfZYio/HkKW1IGZHTkJzWswXx0Ba2UK9oLDCy8dByesA
+5KmtA09dk0M/GuMFcb+ZZ3x3USa36Cw7vbJYcmDw6O4XgNf1aja5cdltENLsVKIW
+I1VguZGfIwkLC3iXN2PzO3yOW5GXQ1wPHTc/SPMuBeT6UAqPBjO6vRQRFf4ghslG
+//T2tDVIZWlrbyBTY2hsaXR0ZXJtYW5uIChIUzEyLVJJUEUpIDxoc0BzY2hsaXR0
+ZXJtYW5uLmRlPokCPwQTAQoAKQIbAQcLCQgHAwIBBhUIAgkKCwQWAgMBAh4BAheA
+BQJYVnXuBQkFvsCnAAoJECYQG2L2k3bO3YYP/0vbSNKAD68r2EN8//yRGgH1xyUe
+uRARgxJnhw7tBsO3k1YIkIEG7vKzLcRhi3vcM12ttY9R425Kl1c5ug6f4jt22bnO
+ONrJ++0Or6hRucJ3L5IHRK0b2niPqvXBbg9PMp/9p0jKCHqme7mdD6jBOHBAQIZe
+MuGLyzNKx6Dk52DZeLYRznoloYtUEurckrysL1/C9Qsah3JKlURSihVFibnIF1Wa
+GfphxKsgLDDi8FUyNWrt99MhxYwwlAbBNQ99ifX3ZLFR9Q2B2ntL4Vfvom9QBYWG
+5e3rzlfQtw4pGWpFZFDSi0LdP8FfM9wKhtnbHVEav9Te7syYgMBDx5q6irqwTh58
+gKLicWkD22rtVGYPv+En54thAq6MXMQuzJ3s4MW/5GTZcbtsBBAj4OtHvtyKzI08
+/TlS09bk9mlaI8PYGUU8JKZj39alL7bI7hZVn5HkGMn1Z/lojdW8Is35uKmMZnF+
+im0vonw1n52OTv+4nOpBcidckeDr0PsiAScJBnaJNVF6v+jL5hrUxs4hD4UgTgSL
+obUzHi1g4/UP/eC1cEZH7aC2FiG2jTUqo84qTZ9Cik07fmUf95jCfsWFvijzVCPB
+oIg4W5SDfkccvoermqS2KE9b9DXdZDiaWTLO3U98nwkO6ps24lbX6mjJ+QjsSokA
+msGdN5BhOaltRYBZ0dHq0egBEAABAQAAAAAAAAAAAAAAAP/Y/+AAEEpGSUYAAQEB
+AEgASAAA/9sAQwADAgIDAgIDAwMDBAMDBAUIBQUEBAUKBwcGCAwKDAwLCgsLDQ4S
+EA0OEQ4LCxAWEBETFBUVFQwPFxgWFBgSFBUU/9sAQwEDBAQFBAUJBQUJFA0LDRQU
+FBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU
+/8IAEQgAYQBQAwERAAIRAQMRAf/EABwAAAIDAQEBAQAAAAAAAAAAAAQFAwYHCAIB
+AP/EABsBAAIDAQEBAAAAAAAAAAAAAAIDAQQFAAYH/9oADAMBAAIQAxAAAAG9fPO/
+GubSEtBlGa96w7AivEFcmWBP87YnPpNAfdeJLIrVB5bw3TDYD3w4bu2LTZXYacWO
+gVnc64Pf8Ec9x669KlsTzp6t1iYG/tSFU0r7naLuwuQl12uih5tA9jqX6uq2jvAz
+pGF6McSNYyGJiilQ83NYWhp/p61ZWTds7d5z0VM584dN0L7eKFnVdTHT5L9NlFkL
+Jva35/0DrrVIVyK3lLNTL2PPsXHG0OOvT44MFAwd0xttYpwTK1Q38ZNWPTkM3fF0
+OIvQZknQrAnUwPMrh54UPTFkg+osm3wFrVWJQNEvXACUD12FzzDoYjPXeS7gnWSR
+0nGJtlalBxLL5HGT12juo6HcKX+skxbGBn/EvUTEoWDLWe08e6Foxw5fK4kD5o5m
+sxQloUADLA+1dXbvUH//xAAmEAACAgICAgICAgMAAAAAAAACAwEEAAUREgYTFCEi
+MyM1FTEy/9oACAEBAAEFAh54gp4AvUZvJ8dYrKN0sFQz7ztyx4H0EEdgn+AVsX2K
+5xi+MsfZSAxnq9LIzmSBbDrKZvr20zWWNhGTtuhVrgymkEXBTUUEP16HjdozXaov
+Sqyv7Wdy7mp019t/4ZaUE1EkNSPWK3fUv4zdhDteu4LYesShtWHx48tCNjsdwe12
+IVFoxOzQbWEa8m1j7PtqjS7NtA34pW18MuyE6hX+QsDaHjY0G2XFto1jW2l2AM5s
+hS9iWprwqCszeskE962zirt1Vu5q2ddQPrfOlzY1Qa7cTYzSMVegV+qKZBraztqE
+4yxFsNXuRva2xHOTswprhrbFx4EkvFtlM7guCwbISTYggUz0un4y2PsEWBYbJV7n
+5fuqeLdGb0W85DjwH/U/nIjETZniFxAQMxirnTNeInc7CEBH0AdmNqlVeEfTmd2/
+Z53xGePqk7HP8g5o6/v2vkyYXduNJeTONiKtYOWEOePK4R2EWDni/wDeeTf2lz98
+f9bP9dfA/wBaX9LM/8QAJBEAAgIBBAEFAQEAAAAAAAAAAAECESEDEBIxBBMgIkFR
+YZH/2gAIAQMBAT8BSxkqhfFnPNGIrByOxd2cnJUL9stfZJR7TLbwjH2Pb+oqTF/d
+mv0wjPaOV9kYOZSXRQ/hh7TXHZtbR8SXpx1ZfZGCiqJad9Honkaa4EbWSXyjndIe
+Uor62STOBrriNcskouqW1EVy1IxIZPTjfIbjWTng8jWt0iMs0KHE620MT5M0v0q+
+xxU0eRqLRVEu7NFf6Kx5KONM0pYJan6S8hRXZqOWq7EQbjIvOCtmKbHJsorbojK1
+XsYt62hWPavatn0IXuW0tl7frb//xAAoEQACAQQCAQMEAwEAAAAAAAAAAQIDERIx
+ECEEEyJBMlFhgQUUIDP/2gAIAQIBAT8BpSxlhq5DFv03olRg+oK37MZItdWaMLqy
+KVTF+74HVb7HJkYRlG6J+NWqRTUbiozo1FCcdkqcaZOLqO8dHtTJSVzrQrJWIuKj
+7iNWUFaJSpQpRxpxsjBS+on4cZEv4+pD/mz+nSppZO7I04LSJUKc9oq+P6c7LR38
+opoSb0KDH+CfkpSdNbRlkyLMir7omSnsjSim5JlhuxFfY+XL7iaL20ZmSkmiFKVN
+JD/BcZf06U6n6LnbVhNofZHxHVpvuwlOldNGbkrF78eXb08I6MS6RdrR41F15dkX
+ZWPM3f4HjcXSMhTyjYls2QoTqOyKWFBYkvuVkpwdy3Xei/C2OCexRSLsTPgqfSyV
+7/ji/CXZLm5LRPLvlbESf+HolzFEnx8cyHwti0S3w9LmQz//xAA0EAABAwMBBgMH
+AgcAAAAAAAABAAIRAxIhMRMiQVFhcQQQMjNCUnKRobEjgRRDc4PC0fH/2gAIAQEA
+Bj8C1VvWUZG8RhmpQZZ6viUTp7sLdAlundSLbQbiOql2GgnXK9mY9Wn3QqNvtHqJ
+z9EHkWNd7xVxfAHpRxCE7x+KFy6KMCcYVwJiMoAjXM81YSZ4f9Vt4z6uqB2NXkKj
+dP3TA6kwt4mlWbP0KqsqU3B7W3Y94KnX2l1MiRjVNe+6HZDV7Nv0UFlvVuFsHP3T
+o5fq6nqsZ5ZwrfD0Kj/lHBUm1m7Jky5u0Ex2TfFube2qbalB+lv+097GWUb5ZQ4A
+LOvm951p7yve1k94V7XXcZHBbO51Dw4/l0d2e5VgY2nSbmOyaGez9DJ/KnLg3QLZ
+k2nqVLTLVJT2YBdjKaTdS4YCDKTczo1EXFsc0/ZH1cUQ/dB3TH3+35UGAOSa9jJc
+NHoUaxEwtrScOyLA8Nd1WyfLGTndmED6qg4kJjKb3Y1eOATWTLCcTwXhWM3aIJbn
+jPFbd+eYTphpbp1VV7ocXHIKLGkbaoIps5K5zpfqZWTFenHHgjNS+dJQm7avy6Gz
++yItdBzK2o3Xg7w6qnvfrgb0c1Lt1HfuJ91uq/iK+vM8Ft2Cze+kqjve0ZaUZkSt
+LD1XMoEenQjmENk2uK54Mfa09UTUe6uebiY/ZQHW/LhC+Z+JVGE3GJXhqjn2U272
+vRNHCOC1u7rGDy4KVf75bHZAKSroXrjpqgwzZxtQtGQPJoGpMKx2qzlFABQcqeCc
+/sPutbR5eFZzeqQbrZJ+qY1pha5KpPHrqE/QeXRMd8dQKXCR5UOzvwv7bfId14T+
+n/kfPwnzD8o91//EACUQAQACAgEEAgIDAQAAAAAAAAEAESExQVFhcYGhsZHBENHw
+4f/aAAgBAQABPyE9Z0rcCdB2bi2gtW4HmMjIVWHuYcUCU5eYwoKFP2hBbZQu19sF
+WDFwPZOZgcDQquaH+Yxs5203VdHuFqNXouUu6EZ+Z1hTDzTyxnM2aDHT1EbUVngs
+BSjk5PuFkJ5DWp0M5q09Mths3DHf6UTfwkPHrMbbXRvQOEmjDiBef7oFSfCo7F8n
+SLaqvShWNQC6XDUeXtNSX2i4ae6lqNVvt8EfJrA7V0fcxG1jFyP/ACV9cKBqOiUZ
+C2wXUN1ApsETZqunJIbCqcbC1m/dd4h6PcoosA95waBemcygowzxV8n1CAldAx+G
+OkInmlb5kcXOqMULtdvdZzsfDvL+5eXLDgwcETK42ClB5gsmWhGyDjvAo+hw1rIV
+9xwyiycHKe4fkg6SxlMs8Vz6mlZG7809IE71q2QhtMSziU80qOcS/wCBscTUFAae
+5nJNf12vZAGlQczs+e8aURa6ew/GYQRAvyXqXlqPNGre4DAcqcwxTNC4oUqEbJcl
+biDq9rKPMLLuULL4ivJldr7qiB6YFVL8gr8TptMS6eyMT0lxwSMNwp9qE2vLaYjv
+XMHtP6jsaTjgGggcJu1d8kFcDC9Vv9TEDAt1EJs+D+J9kefEfIF+uQ4kBP2GO5Ag
+Tyx8JjS81gCjw0bY3cFQ+JV5iroWcvcomXk7O8cl2dMyCY6tvK8RFTCsu+0teB/b
+O9OZeCphaTSkz8OWFmWqmDiurNXKDVUxz5gmEbUh+U18t/mMXgnTEypbiqsw6syz
+AGjrHFnPuRKNbJhRgUl/lgr4M/qH0ZfZhCE1tqWF5RdHUDxi+X6m59xaDSY6z8JC
+aFK2E1n+p1z/AGeZ9H8d8/DV/m13h//aAAwDAQACAAMAAAAQHHAkhuHnIM0XSVXk
+2hYx4dFqqkUj87TBa9n+afigH7l5i9yqnkoqIre5SNPbUU7e8//EACARAQACAgMA
+AwEBAAAAAAAAAAEAESExEEFRYXGB8PH/2gAIAQMBAT8QJY8fHUttqO9u11G1pn57
+i0lUztYjpmDC1RtJ/vvzGVqhZWAwTm+S5AAjvGuag4qpoYZK6xBsVVKvHc9FxssZ
+sNUdOxiNrVDNURCUkbmtMAKzN6jP9+Ro3K2JlQluitTtDF/XnsqCG7gTuE0ySzYz
+EEDMv2NsuS+6IktRiSqxBqcMtqULuW7FV5GEJTD3KR9t+iFGI4DdTIKIxu558TCD
+Q/sMDuUCEb2bj6lkow7UclYgA7llruXDRg3PeBUHccjqV17gVDq4UPuZFMIV7U6H
+mWIXeZpANE2LcB5GDCQbXBEcJEoiomU1KiGY3H93LkuOYY8umbnDpRNQezUMqcum
+IHPGsORthO4bQ1P/xAAnEQEAAwACAQMDBAMAAAAAAAABABEhMUFhUYGxEHGRocHh
+8CDR8f/aAAgBAgEBPxAGWhXj2uOieu61TMeZb0hy/Dc4lXFTKOT+PxEWfb489ylj
+hXxEJxbivXj++s0HnzzAbWfEqE5y9e3x94q5OgUteh4ilDGel+rLo7eeePHvAuvb
+fz79TB7ZCbx8fDXEVlJXiLjXOZV9/wB6ll8lS3giEAPzH0tr+9wAQTrd8k1EHO5s
+BzPacX+2fEwD1X5/3KVlH7SzUr9fecGj0uD16QLMNehi7WZ7GkFi9bELz+1n/ZyJ
+OuiIqp0Yi15foXLB+sOdyK0snlFN7GGXfVXn7QKdQlYrbIFfmqfdgTmLY8RtouM0
+Tm4eP5ii1nv/ABFhc/M1yV5yWddv3fWJvSX6qDVUUuD94YDgjwlsbXiWYuu1ikmq
+qXS5Iwp+Jq2ARzt6lANr8zKjIZdC/wATNpBjVTGJoAgthUUy5XyRbRFIC/NINcQg
+1lyZkqEuuoMiVVLHE2XOJmTJeoQ2XCGNXKgbKFY5QR3mJQfX6Gv0X2o/4lfpP3nc
+OPo7T//EACUQAQACAgMAAgICAwEAAAAAAAERIQAxQVFhcZGBoRCx0fDxwf/aAAgB
+AQABPxCUBCWAk8m945oGlsQalN+ZGw2ECd9zQQNzsxJ8GXOmKEjFkyxgQcQ+ZQEQ
+mLyRUgMBGQ/Dn2MR5mTXYGgZibmeIMVlINayjJgqHH6y6LMN0kG5tGGYwsioXfaV
+yxhhBW4OFS/b8YuvJGyFPBN4CBa0SKESkjAu8aYmuwMtOwz/AJwXCcxJ7Adx9Y7K
+AGg3C8G2zcc4rIY2hYH5fqNzlwVTAoA2l+O0MkAZCKJIUiSYoVvBBCEBrgiwsKSR
+zlDgqv2M9tJw5VZocibdDM6JVvKs9CCIpCXTNZ2Y04tyRLZunfVYPlAK2zwXHfxg
+9FANFHUsucm6Lgjin4cTkQ+W1rpAJJ3gaMdwQ1KFcoyNlZ7cMTOjbVXxjZuKMbQn
+R2rK5BiSzwTKntV65yVaC1QEKRAQCNFRluTDWhmuSWkQ0hkgJEp2v+MCcPT3ixFe
+hj5gnJwAPiGY8weAkvaIdiRp5EZrxTqkYigJE6U+cCnlwd5kDwjARSogJarKFCsf
+jCWJRVIKg0LfgA4x+WIHpD9ivK41E0QCzpOMC2OmSs1iJxz/AMwoOFCWttD81ktc
+ZIkOARg5XPZjp5lYkI2JgV6kGItFG19eXHN3YbWUh2nZ7hD5WqQU1sSOeMM0RBHg
+E86x8bDCjOWpv2cnq4dB8v8AOCFKDbbqsZ4yFlzz4dTxvWb7UBTTcLl8ljgTKiRi
+2RwmHdMVgr3HejpRnhMPWE8lASSO5wG1MRdMelBXBBh8yYFRGx5iNe+YgikAEiRZ
+0jWDKpGjgBPMWQ3pGTXGhzKusGAB5oeNER5lONEQOShhBE/IY96ZaMNTFp/WUBix
+85oB5tcYKAEYSW4eZDKB2cgJV0xJ0icGD2ROsJD8WJ/OPY7JpXsnXxORpkBfIJo+
+0e5CKdoDhLtCNEr+cC0aAzIAQVNY4lH63ojxD+cnNBQlGe+OoxrKsBE+J0n0MFwD
+cGD5D+sv6PTba95HhBwyPgtCSKOtgYkbDJMkS0upbPX6ylD0FfZf7wO7B0mrmZwT
+YyVY2COq/vGeuYqZxJqGFc4ERwS5iE+LwNUiIiD4xTQrSDlTZg2kqyz78aNUdB9L
+3iQgNwcBo+8OaC3vJwOaFA+ZwGgwILT1VE+uVpWRTr0SFXihhAmpW2N/qM72dvOR
+3ok+Qf8AuHSQRRJzA8cRsxtVv85SwUvgcYiANqODFJ5dSVhk3wA/AwDaSYf+MON/
+JWK0kneU2jzC8arwrxZtC5JIfQ/eSsO8/onzHUJkK79xsC+Coko5lcNjYWVitI6O
+XFemlMcCcPuGgEBLvizNH8FNf+152Z/Sf2fxc/f/AIf990z99/bn/9mJAj8EEwEK
+ACkCGwEHCwkIBwMCAQYVCAIJCgsEFgIDAQIeAQIXgAUCWFZ19gUJBb7ApwAKCRAm
+EBti9pN2znUgD/0X0ppBbZ8LZBYkyCtCtIqK+CzQTI6sWU6NDAjHrHoDsmy2RcQS
+K0Ihb4g3w0VWhU/xbwDsOyHCEj5KnhfLQTUAD/8LIKUha0lJFnpT0/WDUV9EBRMT
+xJYENuE+Cn6VhjJLrsXNTawcifU3RFUOnxYDHI/0UwEJ52b+9l1D4c+HxkJZGjqQ
+DSQh8skqos2Lrhm4m41B7/dY2BfpzA/ZVUpMtWOwLHumBjtu2n97h6Jhx6duTSif
++qghW9ViLAK0u86ZXyQKnhZSbTpeHdfU5tJUpCVb3hFNqzaS0HSfRTxeanQ09zyV
+92eoRuOVqfcj2/uYq6PerLgoPPhmP90PpSg8WVHSo/nsqV7+oteFkEvPxU2Pq21k
+B4iqD0TNann6h9qu40ZkrwX/oe1y7DVRBmBhcRHYiClmQQHO19OvD/gGt5KHKXZR
+jEvMD1EhW2d8sDlr3tvOiplim+k2EdjMBa/edhmtoRVV0NAuStlgiWNuzehFay9g
+7AjA2qurNoGvLlr/016hDy8KcP+0Uhg7bdhuELzU4RDqGRPGD49cH5QFYn4FaGre
+LrYksk/zNt8Hj1nko9seOMX36gSXqA+dyl/095Mtl+8E3rwhWtQbx4AzWlhFmQ1m
+f1sKZxdPbIa2MuSmzWBnctUCIus/4i8AOi4w4J/gAQ6txiAVaytzMUxf8bQ6SGVp
+a28gU2NobGl0dGVybWFubiAoRXhpbSBNVEEgTWFpbnRhaW5lcikgPGhlaWtvQGV4
+aW0ub3JnPokCPQQTAQoAJwIbAQULCQgHAwUVCgkICwUWAgMBAAIeAQIXgAUCWFZ1
+/AUJBb7ApwAKCRAmEBti9pN2zo33D/4xSI5qfxOJMVdwmcK03uWQoaAkda4n5/AV
+yZb2lEfwR+CfuwmXFKwTc4ogZhE06lkDoW6bfQbsbA81Vnmjzn6YUIR0/7Te5YDQ
+l30MeBR7dD5yXArOw1yNT+/jDU9BM2wisJyAzdGuYUm9AEH2EDn8iRehSKYIhDwK
+eqhSWGr0Epl6qQLB2nTQb3yCB6dXxYKVOr1OFcZI7sOn2yc9LxbHdajWXcf+xvWP
+khvnGdsx2ZDjCKUvEa9JmKkF9WszqIdHl0oNceJSa5qf1PXKL2EcNGd6KMx5Pjwu
+LKBxQtWx3SD6tGs33jHBh99keQ06zZwpS4DsrQWR3g/ks8YvjIY2DJJEtMbka0dk
+cnZGbUl114/UYFEsmLK6r5/TB5WTAL4ucl/chrr5+CZ3yZChhv6+1HUyEtIDQ5CL
+zjtheVb6PTzWbSYZTaqXv9Rkq9611LpUeb+61PaHDKw00hFur+e4ITKM0ouaMQBo
+XKLhTYt4HsiRKuoTjiaTlMm2yLPQDpc+Fcmnrq1YaNIAq1qVzapRb7pL06ZwJm28
+6sixlfrC4K/p4TZ5H91uorI+8zaIiKH1knbg1y1iW1J1JgJ+4qkG23TFYPeFevsU
+dY5KitWUEIGZUYbvi7IfP4FKUfobT2Ed/4nWvm67lDUXT1dU+KkII2Zp3fnYTBKa
+dWmOwHP62LkBDQRUrwUqAQgAoloa9GF0nWdO/3DrH4XvOdcupSk6oFoZMQdoQfx8
+7NoxjR4epy1iZtYrZNgexs6S7a3lOyaAmH0zSBw8iJ5CydKpY7pVFd2lFbUvS2qe
+Hz/XVVOnCXcDShHfYULBpt9geuJc9NGmoSlF8Jjp0h3HxrSTDneatYlrwJaxMCmz
+4AfC2QIwmt8FfX7WvNm5qqEc/7qDLgAVhbFBNPLRUpyhLn2JfMaXM0aPFaPvqwSw
+0reLIpe+L4TXdv68jRq8FPjBzcXBgsW9uV3qJnncE3yHVVv5pIF3ls8V24jl7k+W
+wf0vdbHFomPbFRWosabwlG00O23X1TCdqytDNal7iHCzPQARAQABiQIfBBgBAgAJ
+BQJUrwUqAhsMAAoJECYQG2L2k3bOL/4QAJHiGmiO+h7e7G9AUMmZUmiLdcZ0QJhz
+webKbsebI5qGF6x4sqsT5FuVEFs4HYEaXCP/Mk92xBpt/5/9h1uKqrxToiIsL7EY
+dDtTQM9dlLACPTinbz/JRXG13aH19IAQcpc2mVwKNSR4qPnPLUJmBIrGdUGNh7dm
+zmnTrziM8U35DcnEf6Dj1GzIK3wfj+p4DFp0YWXr5dNGmxU63e/RJXOA6fZet4ZU
+ON6BhooEEGiZHxQ3sL43VLEKUaGbOkFBHq4+I1zec7VM++SkW+7zjNWvspvk3Tab
+tPDAf4OEtl84jHQpC863AzehOcXT+60THTC+K/1/u7C2B3yPUO1gIArHFkBrWIu6
+ePUj5YsqxXDhM3u3EYG4vqUB3b3zbg+1vLx8w+j0/Y0b6UX5GkbfYAVi27SGxg7o
+FaLR+ceFuzybw1xhUVWp795gHf6pX0XZOFRoBUlsSGczCJK+BhJzDm6swEtbSBcT
+eZsfnH1GmBM4X0+730tGs0Z6Va/+rn7KgST+JzztiO6/D3uBeUVC/wOHuMNcI3AP
+e0lSZp3iX57nxedd24TioFyOhXGjExl5Rb7PtntGT2cFrn4hZcxUMaobKZDsGVi9
+pGaT/LvWPauIzY06f+kS/iCdDUHQhrtzEj+vuZF4xY2YYKxbTpicC76LrdW0iVBF
+CS4Bdra3PzODuQENBFSvBtkBCACz7w7u9QK+K1Sbtr5wree+76DNF79X8a3+I9hL
+w+mRJXV3CIn666fzaqI666nQFeUXK5C6x/utoGfqPn9Ki3nXOg5NibHRcwC6yRi1
+vxoFLhsPYZGtHUuReToGpBqRxa6VtwKbiojRIr7EXS+JAwhrEsEpYIO0CymXHFmb
+2p4EPWQB16ukWOO3MRn/Z1ucuF+9LJCwWVEGI0oKyEFQ9QNFRCnqP9gSjU8q0HVZ
+XQWUr7+hNmfkK8ODVnnNW1EHpEZAO2AfBObngSjfT9ETzNzLTsWsgvhDx33o79SZ
+Iim47U6JYjTsfavRjEkXhaJNkTKGC/1RXAjBI3NaISmQFjeBABEBAAGJAkcEKAEK
+ADEFAlTSdNYqHQFrZXkgZGVzdHJveWVkLCByZXBsYWNlZCB3aXRoIG5ldyB2ZXJz
+aW9uAAoJECYQG2L2k3bO58wQAKYDrOJAhpamwad8AcgA98Ary2AWPMLeSKqiV7uv
+3c0JN19owZcsSR5lmknaXH5fCAVaJg4x2RlO1iFGwRBekS2gX781er/evNktWBvA
+EHX9dZjbuc/78k6Pl9XpbBCljbGtClLi/gM7k/tgGEwyqr+Pg+dXBFhGbgknumjh
+0XJ+cc+1Hiq/pgzx+/m1blQPACxruh2Dmt9QE/SfkvxseGNcCVVppWM2JvZAQI6B
+YVGUiKDOcO1bgdaISzp47/2ShJJ2RNQzKMQ2pAPjtTUbTfq3VxkJCi3pzkkoKVkZ
+hgduh/tKA6RMqPYCXuRimB1QEixfRWwBGlAPbgCmXtaFR8FcFWtSMFs2w2zibxe0
+cWRLAAUfqkMEUPJA8aUZzsBaM0o4Qlz7+ZX6Vp8/av4nfjfgZVQyrwmedGcgCj3X
+uYTdiGLLhjYA7XyH8uiKyVjCXRc2j8GcTtKfa0DTFMvdMwPtt39IEv9Fs4m2xlIq
+hg9rIUydgIv1+iiJOUF5iqoF8tMUko2moqEoCe3cc8+w8BsTncjKiN6nbng77vIk
+zRO101YJN6Kw1bPvGeFu8MapXNq3/fKM1CGJBx7G/dI545CHsc7Cd4YWX5LF7+6Q
+Fc2jTAceFG81OEoYD6O1YHXDcwEcTQYrLO3iPSHBLW7qAeCkhVH7BmHjXyQYuyZH
+sH0fiQM+BBgBAgAJBQJUrwbZAhsCASkJECYQG2L2k3bOwF0gBBkBAgAGBQJUrwbZ
+AAoJEJG05d4bZCmnqnoH/24cH0moIvRY+KPhEkSEn/9BTTd0ugm6wxNi2MyS9bWS
+wGaUkk31OG6I4unGauca7qMbbhHqn0G+ibWT4IHyU7En8ROyXbLXs4ySzk9Tja48
+g3qaFWeqTZVpMzhqewM8R3cZxvucYPxriDFdZjWHmdi/qCTd+s8RPCOQ8fW04VH/
+U/Eeoon9soQE+8s/MeA9fyyrBMI/AXIiiEHP3dpAiWLJsMKZoHSmAvIonolan8BW
+4NRH4SqO7jvoj05Ac8snkHVTO/BxHanZ0kEUsytABs0L4XEI30w5ctC+XAVyTFoR
+UjPp9UY8lGRIN2E8cn51klNAaQIrNje71Db6PqLos4ExURAAqtjFVU+Cr2vUwVfk
+Fp58c136MDmxv1sjNczDQ6ujyOV9cwMI5t0ibAw7T/JxkqfLltX8uZc6hPaBFQNW
+aJNgHNjKooTYSkrrBJS/nkv9zt9ORhjzEOETa0pMCEaKW+WtNCWcomOxJkhq1PTn
+V+17ZLLZ4iF4w4ApWW9lzEtVjr3bUibHGuSjB4gchHj0maMIbmVuOtNWqgWi3lVS
+wgD6Wh9ZEPvgdl+H3Ue1TmuI+ZIoy+2PMHntrJAy7Q6OOu9KbsLl3aDslxKNxNGO
+yv550QclwIhabZhMnXMzwvMC5RBNF5Yb05+RK6ZI1aATdTISCHfs1MKuS1gNSBGP
+Sr9TnT3TxmLkLb9g5+ytu58BmzQ5M2lalc75ii4WE5vDD241cGCflPFsFY+ODZBR
+9u0fqaqyUSopELgNFYXn/5dqWtpC/lANuLgLai93ATPcY5K8mB8pe9yXut9lO59W
+EPLwHnPt7BEpzTlm6vTfWzICn3sLDX814DRGqlxi02LSTq4TuLSRfDeGQWPJ8xEu
+gSTjinhyilCcTSBjkZVPzHpfNgrRbMZ6XRKItHk5+2m1XQuqFRChw9k/zuksrw2E
+BeD+8hExpr2k4H8kzD9iIDX7+JgafRi2zYwWHtGpkelerPQv/K3aEYxopWPzj9zJ
+wcu1OS+DX6R3v4p6iiF3vtKudJe5AQ0EVK8HqAEIAJTaC3AINpl8qDPK9qSq5zV+
+lfeVA9D0O3BqCA+iqZneW3c7mi7T7A2da+KpRGanywOJtibB2TF/jWrNrbltpbhO
+JAvsou0/edeZQ0xpTAYRt/gURgRLGvRveaY/EE/zyWAmLqz1FYJUoYcyAvGRl3Yi
+AgbeDBMsrCUpJF5S77sxg03/QEjpO6jicfFdSC7HvYwfC/KLOU3nckWKkElFJG1G
+/X0+cww3H2yl7smZ/a/rs4nolcPOl9pvtZPqSuyzW3Z3JBktaeVZPGMrxqtCOgQ4
+HCXhWSNdtuilO3r5Ojwt1mJLf1VAFm8oOB8/AZUeKDGNFJJl9VjIX6UAOhdYkEUA
+EQEAAYkCHwQYAQIACQUCVK8HqAIbIAAKCRAmEBti9pN2znd7EACmlHur1eB5p7Tm
+sOn8cHN7/3vbXqaGJab4q3i0Yg+0ZTmq3AmvjFnT9tsE1FxkSHM7cvtg9jSIZ4J2
+aqQu50x+heypV12VSMpSVMoI58YoX6IIj2vAxBjbNsUvpXemOzisYPdpCd4z9h+0
+C6b6vd3r1cWnE4SQoD0+QDJh0eXPSmESdF7DJPmKz/BvRJzJQW+XdV0+w+6+Dxex
+W3gFkqM5mix6BTDs4NoVqWgXHNDuoM/26RODm9FaI3tueFfszRxGq8X6DHFTWr0Z
+dHvZoDudz/LNNOXU/jsajcB0dBmbB3f2P3EjOlxsoau8bq145iltr97RmnHDqdPK
+du7uNcelXn6Qct63dyizFzvZh7LejXHslikupKe4pXccCCpc8HtQ6OoUNGXdVyWO
+0WgMKJ53NGLKxtiRpQrr+7D9YAXEi7KsfwDxcH1AIupVKgHAfs9NF06KOr5tYYi7
+JhaCAxlGZ5uz0AX/h0caLdrCoLQZ9deV8dRhXe1d1pVzuMc9e40RI0y+z/B/q+DJ
+23I23Q5kE6zuBfhJrgCUUj76cEU3PugDBlDkjAyjfgEkKGsyz0QohGYCwQq/aKEX
+eAJ+NrfkD9Jv1jWOafk0UEX7KyWLsCbnlfSkVY7QIYDPNgwwKC5dQD9EIYWyQb6u
+QnWuUai52+ANTEFuDj8tmeiwvTienIkCRwQoAQoAMQUCVNJ07iodAWtleSBkZXN0
+cm95ZWQsIHJlcGxhY2VkIHdpdGggbmV3IHZlcnNpb24ACgkQJhAbYvaTds66/w/7
+BcpolgxUGKvdObzd1bfM7uCXgahvwIOY6PAi3b2yFElRlkWNnUUSRq4ZcZnqcMF+
+eOWkKkomsTHD5z64vH0jBZxTVis6vMSAuWgmjOcWZzfDU9lecPtj/72cXOf912vZ
+0Jarlwfb+e48wCFtSyZWKr1OyC2yWZctu7K9r9SToKIKs4BM+DQMQksFKDTOjmT1
+5yORHoCDboliqSI7hrSEKCnlJmtWATitVmm8X3th87tf0vpZgMGbaoOxwl9/DcD7
+gBcRJQAur8d0AFfOfitU1oz56AR7O8G8b/B2RFHsKs0oo7S2Gv8i4sjFVK9AJt9c
+obIBYCi0F8IcZyv4N8U8lOf5/Y4GTBMIOJtxSHqFxerQ8mL14+0SubgRki77eUeN
+JFjYlJPKZdS/iLZq01Mp4/+oNcLi62FpBD0z0pcioGaI08erLAIgzDlR48aVsVZ4
+ZwJFzpSzLnHEz8aFxEIvbFzvAcq20e6ZlUtPrFzQerV27ZZQbDwaGD0/snTihi6k
+of9URScnbN0D7PLM8KLK9sKOUzKwjHCIl6WJ/+J+ITOtToTy1dDo2JkKMRxNHLYv
+KZ7RaQ3liTLw2HjdXwLtmWYomBP/uAghnvnJmLztlylmTEB8C72nbPKAhqk6XonZ
++sCKbDbFTYOpYnyhXEarlYfest+hj1vibh3nkxrjeO+5AQ0EVNJ1IgEIAJwynfBE
+7wL03nQdEmO/D3ZaPnOT8jFORIXrjXsxuCxScYoIsSLqPWVuU5ddXTtBKZ8g95Cr
+CciHP/haERbkp52XhfKycB3AfNfm0CJH3pOa3PmWv6OsCfOMjM3asFOTqHNTK1XZ
+P9031Ostbhmj0np71FJKNO0rlVDizgbrHif6Hc/BNpUbdoidRy3G0V4vqUf/AyyJ
+uFPjy2CCmCq8QzQZZ9ppQe8FiCzes+3InGhNx82afdtLKnkhn5dLXV+c+8CONhGX
+H6hEVpqzXctP5s15kV/6qIU7suyNOm8K7+2rBojS7wH7z+sJ7EZy24aNNxZauBHn
+db3nXK9GT7cmgcUAEQEAAYkDPgQYAQoACQUCVNJ1IgIbAgEpCRAmEBti9pN2zsBd
+IAQZAQoABgUCVNJ1IgAKCRBqF2OKoEUM9e+4CACKtQ+EJkf2auqHlbGMx/+fq9EN
+CTOX/iSg9WvTrTzZFeGdweslgQOr1SBgVtRgekK1ffXX8VwM6mL7A7g2j7TXLFzW
+yu4kCrd+ZZVqvhvT5H4/cK0axKPq738FgyTJ6eQtjPYbnDwnN2iwlBOVF9rizi7T
+zqA/RrfZr+/pzoHRWXgDZ43x9bFM7IGDnJilV+yjnFeO/Z5DU9TV3qiJnpF5pExR
+ZliBNP80PTISkmnvhdH1eQIL+lIr0XOdTH7P6PWs1mpexwf+bttBQT1fonmV87Ep
+xtOZL15JnXBjmkqzD+fmdFOx36NWLZDYTHltm+HSJmS3wmVG+tkOyuCwqFvntQIP
+/2AG8xgVX5ZE77BAIsC9LW42qqRjHAFjFoOopTZ6htkb3eBkxsuujzGNJ2Dlcu9+
+KO58skhcuCF21B/elXqWtBuicw5IokUVYXd1T3xBSvKjWUWF3NlvKIUFfLEFP8EV
+qThD+5Mw+a5usIXNId6jXi2143Ig30u/OZgIx8FVjzs2Lj5cWixNBmkHTDGD55+t
+op3AIHnYyfcF3p2LoKLX22KH1+uSJdNcAlIb/m9Qrknd1pcBEJ4mu8ZP6PxVXUaA
+vsehhR3haY8s7EfUCVXZlA3Q3S8r7VTg/pDB67FhaJcc6rVXlKHdPtW8rzKI010J
+625omSYA7N+HlTGDL+E0DzYapkLleDHcwkvppl52yY8S/GNpwEVIeInw3iR+jPKh
+EKlhhx05HIDwBRBDOZDURZMmBRZZTXx0Ykp0QerjDAi17YJk8mpm6KNkZt0dWODg
+qNsK8haBoiKK3pEMeGub8QsONSwxx65vlxlCBWYtZ+gJh3aBnB6tDovZ6ytfZ1Mi
+bvZqOcOBFNzrPBNldVfdsiMfZzTtGbqUQV4qiqdYmg95xkFq0upinBvr5sBI8qln
+q+4vdZosivEt8hp6uMzaFKBbX2ktrIk1jUIMwhI6ZjBHBIlaz8HxSOTgNta3r0QO
+7UelMLWZ9w1LJWsaLWNhPXQxIA70WbLb8geMVq7VyuE+uQENBFTSdbQBCACE132Q
+pR7pocJTL+LrdLkXj9Em0fs2yXv1tRS5eW7tVIzc1XITsqjXThn5hzfJ5f33ONqv
+esqeaBakMMaW39I3SZKGHFoLwqaczGfBk4ihnsSmiGoyeMD2F9gTUCGxdT23tlmZ
+SlwDH6rAnXV1JFk3QEh/QmFwjAdDfkzpt8roWOiZRWYHKwC7I1eVC5OEadK+287/
+/RWS1mfieMaOiGIZTZqTDtGaokN3rLB62LygOUQjW20J9j4ZGIaHBvmf6dQ3LwBB
+xumeSsLxGq17VCZID9EPCAoTVPkuKs8ZfrKiLjAbuyZqgTm3oxHqStmJhGlKVn0Q
+a9IRfztb+NF0yqdNABEBAAGJAh8EGAEKAAkFAlTSdbQCGyAACgkQJhAbYvaTds4e
+fw/9Hdd/bHOfZACu0BrGS7dX+/2QmVZ6SP+yxegCQTeu4w0iZ+ohXVx4NUNzoBsg
+JqmnlY9+ulWUKMKQjTHJuC1W/4Md2rYLVMDvDl5xXY1fwkiGwAdjAVVQyJmQCjXL
+tKD50Bm1txiHARKScIuNoFj96c19pA+MUvZoLWXL52PNEKCHdi7mq6Vtu3ae3W4S
+QhFpXAlcm3CrKK52OxMFKTqMkk0r4/P+U5U9tdooElDJVoUIYoLfSr/rqPf7UrUA
+JNyk9AhajaYYgJ+Spw7FrnLoUJXgrQzRCSyDiWK6StHiCrzBej+4Co+m/N3ajqWY
+kZeFtvARPSNDxjFELxT3Jaj855WoR7DV/biAgvu3TwYcav4GYykYuq/hdFFy0Z0P
+QxSAL2Hu2s8f8T8rGjqED4++BeqTabDynKCT5dmRQ/fDw0LTTHeoxfveFKfegc8O
+R/nzYteGj71DBPpdaGTCZGDIYdSy3wb9a+9ezg2vEmP3JKMn1Z7DxP4LNOoL/ySu
+mIQIcrZWxWZSuPsiOm8FUWuvQ4iwzu+ZUC8kzNwQp7MFWPwh+DYHkp8K7m2AdjeJ
+EGPaIlhqTKIUrUEVUxkuTHGMExd/+gp3CIT5v3X08msnKrN4/HRU4x4wyUJqWVIB
+43Pv6Xfqz/1LayeY/PvMbHSSXOeXjl20iDCVxKt5qii8sfCZAg0EUmYFigEQAOeF
+OFMWA6lDAGSAlUU6g/pRDegFlNxFJhPHcDilxCLjLOIhJU6D0T1+HZh4bB4BkA9E
+qt6/FDzaW/mQO/xS+UI6cSH28fiWl8NqCKuIQCRxNzvJSYIkDJHzKDkqbtXTV+9s
+tNYhmKx/kSrADBV2Qhp6fkINjHF9rLu/iMEZfE3B1C7ieww4a5g3dOXQUGVaJ/Qz
+KEZPKGXqsxPaWXIqeUlodsKgCyle83VFda2qj9satyibcV82Z/dsP/wrELnwOYEu
+eGcN5q7q2iFI/yHfGvzoLF1hvVfPwTkhFWZmij80szRsbWEeSJREeImqjfpGgxqs
+USEJ/KgfC/3wfO55ZXVXDxlZxkcy4ciyRP/94jadxSfcHNPei7d5LHotmhLg10q1
+QqpTJPzYcNdj1xSAu50MD93ZhSLkHLZi+AZcVE6YqO2o5ONSq7mTQFMA6N9fn8hU
+ED7PbpdgmAjTVtaK8Pk8ji2G0l3zydfbx6+7pLA3R6/93VNPv6sazRYyKh9Yuel7
+4rXbzsm5D5alWF/39R9xxFsvmthflNCnFh0zMm/LVPEeKfMT6MRwSRjQdUGE62v9
+xrnolWI6UBCL0CDjtJuwMrUKDwHaE7gygRW6mQEX3ZEdERDX5GGcLxwdfki8T0Jv
+i1g/cNvJ39lRZC61tusKhos/DO7qfrzIjgm9AKOdABEBAAG0G1BoaWwgUGVubm9j
+ayA8cGRwQGV4aW0ub3JnPokCWAQTAQIAQgUCUmYKXAIbAwQLCQgHBRUKCQgLBBYD
+AgECHgECF4AiGGhrcDovL2hhLnBvb2wuc2tzLWtleXNlcnZlcnMubmV0LwAKCRBN
+HpAOFMHMBL2BD/4kqg1vkxbZmlIVCjPS/YYhsAzd445elkpvx56S66HOJwEK3h5g
+tJvuSBuIXQgfvfeqwWf4w1tFja5GiBTpRd0SSq3ZT2OOXOYpNrAnFDyRy13B7Pmd
+Cz1ibZtM/7W75SXWVL0bkuSzxTYO7v2VJ4XjEsZmBhj6i3JKidmR31a5gf1WBtky
+Eun9WV+KaQSKjaxbPlK+wTvWdXpClVNOR6izFGbxATowWQmZR1do8yLh64WPf0Ia
+/yg88cM7ZnnGKa6X9Tgr8vgJ4LyUgNmCPIX4eQKQ4PVTGB9M7hEobutQicvBceHB
+AMJI79GXzker9n17E7Fyo2uJzjIdWoKyCYqp1ASu4oBuk+LxnEW6nv2A48YnZSr5
+kF/6SRM9PVykWoEKIrj/GEHzo9dpgeg8EBrjQpJ76GyTqy/KJwRUxRw1M8wrSeGX
+X1tEJbRgbXih2k1zLjQVCq9rrNTf2nX30PEcMEoLiO9mbLYkDqIvhGAfcwjoB302
+oPuPlLfCnI//3HnhbBs1lZryLjjoWzMBbHK8E3HLruN6uvYxtnKY7rF7hsFJLB6j
+6kgeC8Li9ZjmID40/0vvyamUs6jsvIiS+1mDvCCYhOX/7G/19bl8gcOCCbDh9tC5
+bGSf0KpHu1EqaV7I+ny25g7TFX8AaPtuu2AmUi4P4JC1crBDESuigUBv07QcUGhp
+bCBQZW5ub2NrIDxwZHBAZ251cGcubmV0PokCWAQTAQIAQgUCUv0tJQIbAwQLCQgH
+BRUKCQgLBBYDAgECHgECF4AiGGhrcDovL2hhLnBvb2wuc2tzLWtleXNlcnZlcnMu
+bmV0LwAKCRBNHpAOFMHMBLNyEAConhqhQTA1q0tQ0b5NEAellt7aae2m1rtLC74T
+PArVMU5SZqcFdbhiRKo0s1QlI4V+SNZShkNH79pk7ltjx4B7Qy2H0WTjygNNULM3
+X2AalDxs0j3vPdi3TCm0ebLO04WNUbyPr1972mHjqaCE2JgTrEr5ZUebg7/7CsYV
+dtO3T1i3KAy5J0ODg65wqcf++TJs5YGJhQD6Xu5T6glndBxK+5ChHJ39Mz4GlCLr
+Wa87YKgQfyupZRNx3H7TMp9jbjFcpIXar8wFPvX+3K8eLmr03tMbCA62biuULrl0
+k54ZE9R/E0faqMAXydPSPc95B6BxxSeONoicFuwocESyJUKLo60FR42F671OBud2
+WqEGhjxO2/tIymPLUJEdESq1pKCqaq+dIZwVf/H99wPKFEvBhzzFvtdgkGIKsvAE
+LFK8dmRSaLzU51CLmjwzVodiKNLxAV4ma6Kp6V0lCGcqKXGZwkqO0+DUJ0//ZTRN
+ARcS5MQqV7rPVkS5ejqZTBU0xPOiWCkpvfzgmVZaw/9B+eb7uR9OBLxUiHo/rtDY
+uwhRkX+JtwvWBkZur0zpHwIeJn/nkvV47PdUgPuwIn1ZhlQWwAj5ryhNUaAsQYlV
+POUELHjsJSy/MpHUKbs3Zz/MoYHEQgB2TEf97/lS6H4LDGFOi11t49d7Xi7F5DMc
+L+fqd7QfUGhpbCBQZW5ub2NrIDxwZHBAc3BvZGh1aXMub3JnPokCWAQTAQIAQgUC
+UmYJ5AIbAwQLCQgHBRUKCQgLBBYDAgECHgECF4AiGGhrcDovL2hhLnBvb2wuc2tz
+LWtleXNlcnZlcnMubmV0LwAKCRBNHpAOFMHMBIaED/4v+2yqYRS87QasQ945CE5H
+eeTU2oKbqnZBgeK5FlPmHC0fWFBA8/iJsLB+TwfZ5pNlnYbowX01ixa9usW9qGDh
+nHAxnHeI8lRheZ36rNnbXMiHXE9fEzrWcTkgIy4iB5vlV1KBQ5UrQFcxGlexdLqq
+CENaSPxHYohusrBPBbk6V0KxNVonCACOdXL2ECPZcjA2TIFDjn9bAFO/DFh0pJuZ
+TVqzBlazqDxzL/YTwMGimKiy1SeQFoIZGbQNdYoXyG2TRCuQYX/qGCXAbbvym0eU
+TqQfzHQ4f0zXxeu5ZVZaspRUTSZiydXG+/4HEDeSICMtRXWl4aPXRG19u4A4lLob
+g6Ty2+Hez2RsvAtCwmgt0DQfqKDKnLdubFtM0LtmfPPQ/4vx9dfcO8jzcG1ZGWHL
+DjJoOscUBY/kheaA6Vi+68GZVfQPh8/qLDPU0PZ6/6PLtvm/XFsJjuBIwG2fy8QD
+UaE0O5zKGbcEnKQPTEF4cjLuusz5Kp3iu25VwmUEQNcFLKhUEI2bQ3r43wADJNHw
+GPY5olkHSflyhv5fWsZCL24H2WuQMmlEq/a+53hYD+WFu0w9sVE01wSZInNdCen0
+K5dhP9StLShPHFDlFxazGssV1LDRX0FGlyfw7LcW8vPSBFmq2/csH455QXqzFgJ3
+waeojCbZQn5zX9AI+XRsMrQnUGhpbCBQZW5ub2NrIDxwaGlsLnBlbm5vY2tAZ2xv
+Ym5peC5vcmc+iQJYBBMBAgBCBQJSZgnCAhsDBAsJCAcFFQoJCAsEFgMCAQIeAQIX
+gCIYaGtwOi8vaGEucG9vbC5za3Mta2V5c2VydmVycy5uZXQvAAoJEE0ekA4UwcwE
+nQ4P/jB+mcHiWC4qEhIfXln15ydho9j1BNAGCx3u/axC8Lu1Ykzq5MfMyTYbpiiL
+I4Wq2w1eXp6N2e9cif25nVo9yVISTxdd1wzZzehedbjz85rjtCUMRgYsQh4N52PH
+nYYlkk5ctjdvrENUJ17J7v92hogDY0qXhGply0pI9LeH6g//OyrcysHAVqbIgr/B
+yjYKgaOHRvzxdYB34Djw253NQkyqA7kio6SPegHhSVlfJceNFDuf+lJ4wXyB0wlU
+TIGFnJfE4Gl5bqOhKMLOqGr9BhUoGMj/wEKjh2Mcb9aHQy1p97IiODgj+J/mloqg
+9VDfC3+I/dh3E842rApu5aLrFn8nPjyz9LRcpBwPHPIjOibGeNMlLDW3VeEPNo4+
+/e/TU9O1fJJxioqKyytSnOs2ACwzVMH2EobfkhaSBe9VhmX2SB8TFErGc2JhQteC
+G6ueXCVqGPIcFsD1IQvUVFgxkS2IMld8vEXGZTK2jLWjJ+WH81Thij6MEoqGmtjz
+Siddr1uKNsxKp7XOioIG8r4ZEVDPvTiUiSp7dbQqVEXtI4NOIKheIqtURJ21t4Ww
+vMrIpJT1aZBrMhCIdn2xTl5NZyD7mfKnZfbdCsQxo501D6R4Flq3il0fPxsCPy6G
+T04rpaMFlE0VY4B35bGwikKy+tHIqouYFtyp+kHbDDW8nDE3tChQaGlsIFBlbm5v
+Y2sgPHBoaWwucGVubm9ja0BzcG9kaHVpcy5vcmc+iQJbBBMBAgBFAhsDBAsJCAcF
+FQoJCAsEFgMCAQIeAQIXgCIYaGtwOi8vaGEucG9vbC5za3Mta2V5c2VydmVycy5u
+ZXQvBQJSZgrxAhkBAAoJEE0ekA4UwcwEWhgP/1JmfyfHoIsCJEBXhSKb2YxcEuzu
+z6R/KhBvqyCFByjjmqh5P7SWsoTRUN1ntetQVRUGe8fK1vPcmnTjI5UVwYchNwVR
+Pr7WS66zD0Vie2UQROQB+XE3V0jgewojoSkw+fEXkLJi3q1AbHnFg0AtlxhfMl8P
+KXYzjgJJ/ZwHh+cAiRMNjy9MOK/bQlyDY6iTG9DUP0/Zny7FAq6+oyiuP1TT163L
+knFbVaEH/UdhbewQLs5GXufJ0R8TGP3VaSCiSk33kqOe4qvwFxkDN+7ioXR2A60y
+RAZNOsDd4KOxdwhUm8mNIWHne6WjFxGznrPv/VKRxUwwDV0clf7DZYvPJ0xCFLTx
+xC/9x1oKwpDB6fmqkA7DJ1GHJuKXM4O7EjVQ3SJPacU01tr2qC2BodYJG6PvzE2+
+FzGndtwQfb+eBYrEQ12Apd6rADrFnbAyd+FH6uwRxWCPweMCyUZpCF9ZQhjd20O1
+fDOSUhaHQUDa7NLcZA3Pzka0S4Rjkj4NPJd7r3ckXIwSgp3vwPBe9yUt/PZ09WbI
+YGYFy1z9kml2uycdsaY4WMQiA0unkpbkQN/WaraZltNTrfs5a47b/LWYeBe97n8P
+dczXAC3jSrj/wOJNb4as+bUVJ8U34BeUlJo0UCJPBINdRcKSiakjfGa8WAYEgbZl
+P9rRR5hZQvUaS4zytCxQaGlsIFBlbm5vY2sgPHBoaWwucGVubm9ja0BncnVtcHkt
+dHJvbGwub3JnPokCWAQTAQIAQgUCUmYKUAIbAwQLCQgHBRUKCQgLBBYDAgECHgEC
+F4AiGGhrcDovL2hhLnBvb2wuc2tzLWtleXNlcnZlcnMubmV0LwAKCRBNHpAOFMHM
+BFksD/4k7P55N/ZHdHuMU59DfQSvk4r6DNrGzZNvjiwpDa9GUdvFw2vXhFsxASFI
+A4i7fmkxVUzfy508+hkP3rZivqltnaie0HRSDhilruiJF8mwSWvJ1yGvmouJvT82
+lUyUqtw79lnEADw3NypRXIRP+oz3N3jZ0s3Wmil+Lj5A2tn7QLIqTcLLtX2YmmSt
+fjc8Kk+tt6gaT+r8pov2JDjU/gG5xtKG0LfPbO12y7+qY7dJFd4gNaXAub1O0qrt
+IWsUyNqxvG98DHD0ub/+NqQdzrzhBfW7QG8hzrSafkc5qvxBR2PwJW2F5RPRwURj
++JkT1GPHZWFlUK8t6EG9w6kzL7i1xOYkxTjYK+1VFXXMQQRIy6d5a3+7ac2OtS3X
+qvhEBH8XBLdHi/0i3GQ8EAkNC3nB+p8RUrbJQbq7mzeZ5FuHOUbf2Uo9I8FCm0aK
+trVYFiLYh5joYYlXoE2Yo8rB9uMtttyvCcdIm+ewZCIQCF8MuA8PshXaOVwq/k60
+JIIJlo+r9vX0Zgq4hEQUHA3hYkxoXWGAn0TMVZ9TekZSIdhxAIo4VsJzll2Bc51L
+IgH3zJ0FxFBTcGdKU6mDUVhrIiDe29PPQkla3wCbuH9l7W0dgebTqVZX92hbQYgm
+E3h0UcX+vnCFFPm5qpdYs4puNrSgJF8Cn9LDUGFPvUN696wmE7QkUGhpbCBQZW5u
+b2NrIDxwaGlsQHBlbm5vY2stdGVjaC5jb20+iQJYBBMBCABCBQJXqC8TAhsDBAsJ
+CAcFFQoJCAsEFgMCAQIeAQIXgCIYaGtwOi8vaGEucG9vbC5za3Mta2V5c2VydmVy
+cy5uZXQvAAoJEE0ekA4UwcwE6b4P/jUOwdtIiNmAwYNWRJvlGoq7/l+gu8CIo18e
+i35j/r6LFFuwC0+vgEowZHCqLGIBpK6yliIX1S2voguGCpoxkalPdNEb2mcBODNz
+FUVscRqzjPMOD5VY7pipP9JFJJR1FNLKCdy2OCD+lTpQkmaBmKXaGanhJ/wkDZep
+TURn75WhgpDzzdISR9tPygZvKWeE8/Ov+RzL0caOcoR4yuI+dbld1bwz0hem4rBe
+XiT8+ZSW5F8OE6MirBMyHU3fHQjHvQ3Iy/UUsMyPxE0iIMmirkKwB//U6vCJTRf/
+2M2/k9h2DZYhsMpDkiFSI1q7jo9/zrEQEeQCX20atAPZJeaO+OLQPdBy8sghA2HD
+8UY+wZ+bfWTfQpUHvWVuPmfLUtFzulcBvE3rwJpNsgu499XZg9GJ2O8ulhOgJRHH
+z/ddaNYvSQXQvJt3woF6ElkJI9kF9MKdvlt3Rm4Lp9dfb7wmpnv5isYBte4MlVJp
+nHCg+ABZJdzm86HP6/LdfWNpZnCX5IHFgtZwdT30aUM55fVMpqCDyfM3zcnBPE5L
+pLabNyWoSYXllDy9J4FkuRkGr55bTijNK6WU47EF5U2+5BeLVbizuJ20DHcwEl5f
+p5IyZBdBmSrlltwtX6Qp1qRfaIeyiTHzx2Bz8LzONEL29swcsQxuRyf4Xovt6EIz
+4VNGqRc3uQINBFJmBYoBEAC6C/l0gjpwGcO+6BV0YP/eYSF8XxQ7BcEj+ooSs18j
+Zeg+9ih1yJyMWqrzXrREpPoIvxSTXgYN9cvc1hXSu0OxqCLjJ9R+wfIpUJyFoaQw
+AvuFfrnbwqUDoa/bSFXoUFxv/M9d9o8brO3ilgBouys3QTDTZuVttK6GQUZDYcgt
+gQsaKGQKvylwqmoldProvcetvNG2nTAnXYtNetF2r58jn/cVXS8t2Wn0wTs+b3WV
+kgnChnODJT9qaaoCFHhygNH6ERp+XlaqW81sNTkjyd+Wq+vXMvFzyk7i6ezplnYG
+vhEE1hPxYRDZEc9dEROgI95k0RzYXQvSahqoCyMS3DybqEPJJh2mxJim6UYHD5gc
+cVhTb7j3WfWoyMRZeEzb5bSesnkzrb4kRz6ZYvYF0EyvcWC7mSOtnIkQDjO/FfMo
+fRgtolBchHc1AOjBGVjRn39YhCDijo5cB5z+nhyK5BNSOQAtyrGOU+8mNSVDw4TW
+WjH2ZDJRnbE4NwSpDzVRbBEEPGILjIoPaPQ11IObjYHY8WQ+dxb+9e45WGjv2KlD
+S3UF3ABeLkjSYyPTTuH83gykU12gr60MrUQExdbG46mGjfTs/GOgzlItkEuQc4xZ
+kjk53jl1s1RjjFo+LxLpAYu1D0KpiclhNqWPbp6I9amEF5AeIWgDDOI46yC2Rtkb
+0wARAQABiQIfBBgBAgAJBQJSZgWKAhsMAAoJEE0ekA4UwcwEfrgP/1E7HYaMcyDT
+RbEy8GJt+grN+m07wLO4bnES2VzVN9X1ymWP407upZt7vy8LUN7d7AEXCMJ8KffH
+IhTN+tMbx/+xMqNhSVG5AYTlPfdaumL8jR7WvZXh6nRXZNbeGqofH36zlAbV1NiT
+SWBMxQZ6MbkW3z6QXvad/MTQFlcFouGlFHmvGdtSIBdg0e25Y+mrwXnyN1OgLJLg
+L1CzmSae944LSA8fi1EA/R+vwgJNkQPTWbuiFNKvH/UwOUXJ+JxKG/CamPT3Lgzw
+VoW6bKqDPsgWz2gSGBmN1Umb86n+xV7fu39BfWaEfpoY5g2dq+CLFYgxzymKOxj8
+oIBfy/2VZuX2Aj8Gzh8q/Q2b0iqlrLzfXViHLD7LTzHn0G/xOks2qkwvm90wM32m
+2qkniAGimeYD0MFpbL9cD0fRAhLkMsF4t1EUTIzSdZKouKF7DMI9eJe9RbqCcOiw
+6V9h456hwqFd7Z5fi3/SbHNS8weP004DUcVhSwNsAbMDCxSDlv/QNOmGc2QDRGiP
+QrWIhq1fVj1YfWq6dfkOvwI1qvgg8b9GybIasL5YuC0xW/GHPwo54xiFcGBoWkjh
+QwxzJFDCAlO80ugGRpgEqPis6Q7gAWYjQxHuvEtgCtUcWOmlIZDyxDbcvlP2VPt6
+mUjOkYtwOLN6xiYi7OGBxwcJU02OmG+NiQIlBBgBAgAPAhsMBQJXwTGaBQkF0dMQ
+AAoJEE0ekA4UwcwEg9AP/2QqpW7xqcuU4RQzCEJQhg+iiSx1AhFZyP/+rMMuPDvk
+CGXtEepUz67AcdWEHLKXOnQJjiFJ70jgBNtD1A+EU+kUMuWZt8QjFXyx5g0ua+nz
+oTXGjCx95uDeVz8UmmjtEf3WqwgdOeB5ezcLCbNpcotYHj7lx3eHnIbC+Tv/GQf6
+YFS+OWS1SmNkJ8XlYoDSQwx3rjcyx5Oa07fS3a7+nsYCyHjepVRt7BPI+567+bEp
+FIcmx+BEYp3XSHUsXzp31o2aVgndLaJdbi79TN8cL+v7QwKTGdrE0PQx4moT3ww8
+jR4gAPB+xKYHg3ArE5LeRTxvq+UAj8CrC35gtaiLSnAoYtfqS3hsqtGfRm8SigPy
+5qObH+9VrHW7f3EZFgPXHGJHig1xl2egq6AbJquG1Hg6+5AmaPIBlea7nDspfPoX
++c83Vagc/70WN0EKn6dKx+EJ43XQsEqJhUzNE1mgOEwPWz39/I/Emu2ROVD5W0nb
+ZJq/TiIQI/cmKGmxkyjk5iF0y2se58tqMclXLyUfdmZyfLeDT7tcl3FoGghosiby
+Nze5rGPjl7qpEOAqK14HxpTCTtenrmPhebYTAL4qOByQB6DdbZOST8MRPBq84Vo8
+c1O8Rq74o2KIbAaotUHe0XlxFY7d4zwiWiWxIoOfbeD0JaQcYK/TBwBx8btmDFhm
+uDMEV8EwcRYJKwYBBAHaRw8BAQdAJf5CtBXUXVqiGpt1xQ4NlzBtqamtSgshdXad
+LIuJLHaJAn8EGAEIAAkFAlfBMHECGwIAagkQTR6QDhTBzARfIAQZFggABgUCV8Ew
+cQAKCRBREE5mjdBEgfmhAQCkv6N3THjDjvp6VDcXQzTTY1d3sUqi7L1qB4Ez6gdL
+iQD/bygVdVyoOtrP1/lsWfBfbjTsIMsprPUyneOKO9Gnogep/RAAkZflNkOvoJ0R
+Fjlw5MKzvTLTaraxU43p3GJwT5QDE4vWeql5/YWI6hu7h744AZhmeCbyg1AE01cD
+oRNz5SD7NRU/mnczCSkUALnYZYX3Ko6M5pm5DHVBmhbD9aFtraLH6tlJKLXM9rGs
+vyJCl7Tgy3cgXCYuXFiFPZn24MX+Wi1E5Nbk8hxaa3bIdht0vRdisan3n0OYo0aW
+muBMFtZN67BpBTD9I6Tw6Lzeq/7xh1k3K5rEvPeqHRVLHH29CcYxuyUOmLb6Fc65
+Mm4xWztS0+2wWBk85AhZ00Lf2i2WkdATrPx7NGWw5fssV/7UIU+Q+NuquzQPh84S
+v7KKWjQOP3mLGcJ7WU4PKR4STBAThd2WsgaMs52LTtD+IwtMZAMvTc9Ws1e3VqTy
+lMkjtJlGxC6Uvf5OpvnYfKcENu6LBLKOp0IYBn+hEKFatbq12Dduiz1iKK8+AizA
+J+vLS1zdYanbKAJtYW+AdmbFTyfC6ytONyIiHpvXHAb/B5vH+UE8yIrJEL4XXAup
+0kEOjts26jcbPxbvfe8FHD4NZIM8F5tbuST+TckfSNfUwJ2/7M7nC66vwN2oMsuV
+faZk5NFlUlwaDiNDgQnb1qQm/ltLBrVsVFc3Qra41IZu18etxsOaDRpTmyW/i+2z
+F5QWE4RzEccLAJ+BPLdIQX5xKVZYhsy4OARXwTClEgorBgEEAZdVAQUBAQdAt3Vx
+YCdOrV+5P3o39foPJbUE97JkCZsH/SLX7d4WK3UDAQgHiQIlBBgBCAAPBQJXwTCl
+AhsMBQkFo5qAAAoJEE0ekA4UwcwE5q4QAN/5x/N/8gldfGwarLCLrtHywZy0JMwJ
+ZcZjT0z5mBBTwNsP1Ib2k9tGqAeqR92IYuAEJI7UYNJ8aEMbDDbfOtuhecQupfXH
+yAahLrKSaCXj49m/nwBQGDESDSbaOU/j9YSwwrG2vFZESwhTUdhJha+Uple3vtj3
+H7JH+CvdCucjOTWSpdl0nf/64wPHbos+SfeS862UjLJnS6kq4GA+T8Wyh5ttYzho
+bdNZSRh1aU5clQNFLhe+O8GWTY81AI/t3wT0WLsavhUa3CqVPJM6vzHBT46weyim
+P0qoHRpo1sfJ2A4/YGc/+r8cwDimHpG9mIr2G3nx1Z8FhXxjQN4k1QFpMJ8LyHrO
+LS1oIpmNmwWzVDXuRQoerXXqOO61qEaorQi0buR6Y3uT0+DhnGmbXYvy07IaLlyk
+fAE6/CCUkIo5BNBBm0spChAud3Hhr95uLmey0JvEGXd4kjU+6QCnY0pI//2Px3Bm
+2V9d1o1+31dr8cbh5RkhbT9qrg+5QIOeWG3SsOQqKhOaK1l5VpdlpSMoE1OJxUOT
+TsA5RcDWlbSC6hQR+8AnUpGnB9eZDtTsAVzzcJfiphoQhCb0tyjgyeioSSPyA2SW
+/Xe8lk9swoG5eK2PI5rKQ+Av/f7vZgG3qMX9F7Ywl/Cewje6cXFaeMbOWzkrNGRN
+6Y7KlqQSpY4luQINBFfBMcwBEAC6AUNasY9Ibw9B064L2U4uflZq3N41ZUKEcrhA
+JZbDhPlKYqLPN0xwJrbUGFCkkTZF/5jsy/2YKir1ywjNPuvrIgqRwuyouTeJOLQX
+dAlbZHajVR8ljbQehdVxMJ2nPYHyuwRAQTjtYceMC2DFe/YrdWKa9p2x7z9hD7sx
+g3HrzXXtAj0Cp/F5fokQg5xnqGqUyEjV7AHajljsap0bOZpJLkXDhDYsgMtObWgt
+yZHKfmpKv3XdFgHUpxu+XX8hw6Q9FIS28DEOVRFCzGsY6tqRUMfBSnUvj+x3pF/g
+XNMH1HJF7u4nQ5nulhlyyDupXQ5BNIF0o1bEKMOLHc0TZ7wgJnPvZXFB27dE/U5j
+W839cu0e5eVGrJz21AwnwIUSDkVG+qIGjRIVys4ce65kgjCOJkL7yRJD5YCWW7hj
+T3/JU3l3lAQVwS6bR4Qi6Qsl0eKmhsjqkT7N3KDXbCSqmAp9b994bxYOeNyotFtQ
+APCmUVFykQYDMJTUdm0iZX40q8p7T+OhkWtmuL5xGpP9KU6IOWHRxgcRN7cVNOoo
+igE9mqwiTl236kxY7NQLilF5dzNbExtFSKf2h2aXyU+CpYE2aa6FTetMUxZnI2+o
+B1hGmVGoGBzdlhdXjrn9uZdiLOumSaWZ1mt/EMNJVT6aPIPSASkXx1Se7g8pCCW1
+K2OtgQARAQABiQIlBBgBCAAPBQJXwTHMAhsMBQkFo5qAAAoJEE0ekA4UwcwEBqUP
+/jLOHwYJeaJsgsi5I2v4HPUR0kZZNrAeraW66O+zkHyPehiU2dc4KeuWAP1YZ04c
+jUyusCN9QLSx4zbaZHPJjqStLQqsanYZ9qdeAVLgxp3A3ZFAt/19DlSMIf0cWmg2
+VY9md4Ex0rTfv50cGnaB8CLpEaKwwJJVkSG3YfqgBb/1rjWj7KubM4k3QNQ2UwG4
+ABs+mZ0f7VYpd9hQkRza8IcOQ/xpGIoNlweWqOrMm9Xk1XN3LQ2SUG5pPs56FHGW
+/Q4jN0zqCGVPv68T+ij50w//JOfrL98x+QOkTYHhFBUDsqmysBO7deBeQ772Rr3w
+qLVqmVyVovhSWn+r6QLX+OD80o2I7bJKAgxK4A2blBcjIA6zK7rKlh5zitACRs3B
+vmGYenCxF55n5BTY4osVE/8iG6c3i7NLZFRtsYwaA9TCYZxOUIW8cfZi2PyfMw0A
+mndldZ5SauaACfEdcIe7LE4Qu6vUz1qEr0DCG1+VetK0NKePSXMg/VV3RTi4mUOX
+mv/pSrJxDSQMPA3y6QdXqUtQl25nM6KzgcOpGah8UOdyDZccK6B3zlq1ngKE9U19
+e/FgZamUn49hqKNu6QhQ+2pgiehgRJ1Nmo6iyf+0O12kwTHWhXsb7ZbfA86OHlPu
+EjFDDW4kXkOSE3SRTP9GY5w7HVY1qUWy+2/FeYyW4syGuQINBFfD2hsBEACqLMDp
+uA+/9VWscimKTs7+k0BiuxfPwNJAYYznAVNFt+GE464v6YJNXpKt07BRzDpuivaD
+PobqtFXc2nvBHcCUOP6QTUP89rOC/bw039B+KRaPlQJTGbPKL/kqIXiK5ihjgSXd
+HDCmzNFHuec07pWgBMI+LYfZpKIHGsFVynIL53mmhxavGTCSzJrBd6pyhoeCzMsI
+ZAq6pZ0HKjfVWP7B3yBJfazCr2V/HkOmKV/vPJT+oflE4f+PP5tTuvEWE5UXM8VX
+nROMcxaNHLB43Pbh3A5neGgFm74Ha0tfWZHrZYnNCFRGbxp7PnfbKL+tZ8xtyQr1
+pQ+x1y8Bkxj1MgiOj55MmRmjxlVJ+L6zyB5Tw7kqsaBHiSDBWUz6SJz3pFD3X3GP
+D/nkNqhBhSzFM2qxHME3CkK+hU4jOEkcZpHhsjL+pXVudGNHIByDNj9lqP7vswg7
+cnGN7QIPdpBdvgcFg4qZS93LsLJlqhNDtCwd/Ut+QNT6xE51HflZ+3/su9FEjUFK
+ZMEtAu0TDoaf7iV9VyD84wjLWAm1GVXpDh1/WuSUBifMfTyHXyLN2y2Ja5D1mws1
+g2ywzHBW/2e3gUzYSd4JQEWLYld0kZhQ5V/Y9Y19jDpDUUgxkZmb5dnHRaGwmyx2
+7zReKqN5NF2tdeWsUMibZkEQdib0n+WnzuJMYwARAQABiQQ+BBgBCAAJBQJXw9ob
+AhsCAikJEE0ekA4UwcwEwV0gBBkBCAAGBQJXw9obAAoJEBPa2Zx+QVGcxvwP/2aI
+UD60sKExN2fLXj7mMZ/wWlDnCdqvTGD7lrk6r/fAQcaOAgajCMEXOPZXlPBhdQ4j
+xD3FLs52CNZkcwzXMbspz1lfIOk2U1UGhmnAyriY4Uf5cRu2RPR0HYwOBB0xr69S
+IrsmlX4pf1AnulE7CIY/oPBjB2XQRQ7ls8sMqmm+0TxRysaosHGu7Vbez5iKBm3p
+0rEh8TcVkgMivdUPue/ip+mCaDCfGeAiXLXWtiEiwaS3Pq+QzHhZtBvShWlc3k2m
+CFlrGQwovPxY5SqGs6QwifrmnGSSlyaAorDZcQEkZe/HP2/qXKb7uBD3/r8t2OE+
+BZKwJxW2fIpaO+u8k5EXSDzuxRqSNj3wYUI2+WNQzBmAyOZ6XBX4Pz0xZyahtXCz
+J+5deqCnEtJI1HdPSvM7STE6s6BmkhUl8weSAD+7v/HNPWvQXYFoeGFeqvoVOCqB
+7jJZUj+n/eUh9PxsOtwdJlvdoODuQIYyzuSapm6OPnBKg+v7Bp39Ym8j5Nfe3xqg
++O6CQVH/qx3NoFrKfAaLKGsV++jnf894b23Y/fgu84Myt+Kn8uOrO6jbBwiWLkgn
+0uzmO57bi/6F7aMQwSxcMcAY3DhCoeXkeYq0QRZZd2raPbA5r278wPXWg/U5bHen
+GYX1COWlRehWqXkqR9ZJYY1hTT0/WSAK2ZLCGTK5tDYP/iiHbpeWlZhwgx9Jkfmg
+L+N5XoAW6oJna3tozS+xVM5pxTaTNO24vnQw+XQxkiCFwtf81chd/oXhjWpLg/K1
+vF0AWGomN9yS5dtKtlWZ0H/3KeEGkKf9iRp8j1bVNF6mBhb8Xl+nKLWiqE/uezx6
+OYBFJuj6WpCgbmaRUbmKpX7P++JuOosg0n+BzzJYAIKP4+/FLL35qSpLW+DuWZaX
+bvgS/OgjJUL8AQj8Nwk7ViRyhBRwSAvwpdcwvlAH1VfTHfpQ8a0jjN1Nzf8Tr9Ij
+o8NQnsa+5y6Pmf6l40j4C8HPsMB7SX8ptFig8lnBRPtzEWj54/WtXJwGRG10XW4r
+dQU5hR9Tufc+WFuRfwdLgrhPTnKGyVG9zOkTd9Cl4j58tEsju+m4HNkUN5goouvd
+xHSe/dmA6cQAWf6/nhJ/uSM3aJPSUOtZwPZO7/NzsMgkwZTLXbehm+9xWMkPRt1Q
+T7V5MgfxnxhVoIeoPAEYBo8t0P2GXVMNZdZkJPoViWGOei4iPE3rj6NBynIIoEZN
+DEJ0OQOUe6Naq5AaG/a6wPa9+ITzKY8VR5KMf3XgcKLBlntyyxTgnHY7j5VrhxU3
++mUrnwg8LIN9Sx4oWDks/SEB7KN3KGjSgczn1k3GIJRF8BhYin5Cuw/+aD16w5gS
+HxUhIgwH2BbM5X8eopbp/csAmQENBFWABsQBCADTFfb9EHGGiDel/iFzU0ag1Ruo
+HfL/09z1y7iQlLynOAQTRRNwCWezmqpDp6zDFOf1Ldp0EdEQtUXva5g2lm3o56o+
+mnXrEQr11uZIcsfGIck7yV/y/17I7ApgXMPg/mcjifOTM9C7+Ptghf3jUhj4ErYM
+FQLelBGEZZifnnAoHLOEAH70DENCI08PfYRRG6lZDB09nPW7vVG8RbRUWjQyxQUW
+wXuq4gQohSFDqF4NE8zDHE/DgPJ/yFy+wFr2ab90DsE7vOYb42y95keKtTBp98/Y
+7/2xbzi8EYrXC+291dwZELMHnYLF5sO/fDcrDdwrde2cbZ+wtpJwtSYPNvVxABEB
+AAG0HkplcmVteSBIYXJyaXMgPGpnaEByZWRoYXQuY29tPokBOAQTAQIAIgUCVYAW
+BgIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQvOWMjOQfMt/0Bwf6Ah3U
+WuUL1L2wChjHXktv0j8oQmL8CD1AUYkg+4NRTkZTm5ngZlNk4ZSJB7sonaEmzs30
+fw9zex8LMtCMnEHQYtFNb6r1M2QfMS8ZUdeaUNmlGHu8UnHqr+aTkQbQsvhs/UaL
+knWlOWqdsM29Z311yGA3BdlGxw/2wej+AtRSazT4dEISP8K8xfnoQmhIVUZ33aMV
+DF70iinmAfWfqUKhgRctrVMLgXxKtYiOeTGXDtm2dnvXTHOO3u0N2skwc6YwOxLj
+1XXwWL6KQJx77/2SqVHDJVeEkMEb9Wr/e/l1PggU+fYxLZ5/HWbGatNmoRFoYNuC
+GlpBL9XOuQK98PcIyrQmSmVyZW15IEhhcnJpcyAobm9uZSkgPGpnaEB3aXptYWls
+Lm9yZz6JATsEEwECACUCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheABQJVgBgE
+AhkBAAoJELzljIzkHzLfiIUH/3CjMhhGiA11jVp6MUWLFvr77LhvWuLKMy9YRQt4
+TMOVDSUxPpnc/FeFkgsPjLho/srPHSjNdrmporLjUQA8pCg+KmdEAfThDK0lsgRG
+/PxOi38t4JUpRzQb0NXE48EPTdzNOCqDPgSNXaq+csX6tNTRgF6+s0KW4qiwZJ37
+dG8tZW7SEGGf2kQsp9ck1JBdlv5OkcOFINn+AKuCUEQ6EDphZsNv/iDP7lMUp2T4
+H76IBBlIe/vMhJpuM34E9iAjjsD4xTgnJyhdoBScxzSXltrp8Y1oivOu4ThoBmuU
+/mj7uaVT0ybRAmp2pjFg8CKmUkatm/5hcfV+nm54QreX40a5AQ0EVYAGxAEIAOmE
+sdopOhG5H8TtMd6sGIKMNq3AJoRM4o5NjbNEFClpDfan8XZcgYtLwJzbv6CtlIpD
+plfRk3js74AXIUcXwMf3QhdkWklHdFvzOBdPyOctfTwMzfV4QJkedHMWEaU6arpY
+BSWoHcYoI9QJjZzh5NFfKhcu15PGtcJiiPjnL9ia+VmuWicE2M8EDIeI78s3P5Xt
+9m02w3s39caucttx018135IPUQ2ZssnxG/LKbGC5PIH+Rr0l2MccihAQnovXroHe
+GF8Iem3yILQY9mS2L0gyXQ2gnTb2MmbcmrWoRx4QGfkflAwafoWrriJfBOw7VMw1
+TClbHymO9XvBUjGMjxkAEQEAAYkBHwQYAQIACQUCVYAGxAIbDAAKCRC85YyM5B8y
+303oB/wJLYJOsxAV2GQYS0FeYviJ8PxQcWQFEEaYzxkvZ9ZQFNldPyat1Ew4rq1w
++cpZoK9a8qvSSe33vSP8PICAWYfyGA6LfJy2KAV5xUOOOKUB4IkyrfyzW1gpiIsN
+sF0da12QD24dnCreV93dDFwQQ7dBqZAX507uHyAA5eUb6mjzseb4TTDPizAgHz5L
+fsnOvH267QtIUN8kJMr5MgoZrlSfwvE/HKr1aec0OHvbMrsGJGJ2T+zjQpw2h3zc
+0zgef+xsZ/ItryxLQXcwTRL6hxIw6K79kcc6LCktg1vBMnuy1nEayuC5Z5P0/5qb
+FsD9iUr3kt52y3C835Zwdnt374CumQGiBDzS0/URBACREmlUnPeSzfnC0m2oQV4e
+SzgYjskiLfwZ++Ql3zErPw0AphH7m95dZwAscTm3CQRHDDd/RYxkJMAYA+jmw8cV
+X1rXtQ2URRmzy2/I+qBU1NCPrqBjKRqrav9uhLCLGvEwdqWg2dqn8TMwNdlETbH+
+R0QQ/1lK8XtW0NiHC8I+NwCgj/8Av8ifdpVSnFp1QesTAVwdTbMD/icRYOZ5I94D
+SRk5GGnmD+lyhfj+ejYbuVEgg2igV9HuXJMnBKTnuwriuskTreeNQBvBCTltHrRe
+1LujAtlsbixooTgUU5jkzY+J/PeNfLd1J9uoqTGQ7GjT4SMfKuetSRBhcRZYvm9F
+M+54vsumKcXGK+qBfPVBHo1bk8goJxgBA/9tnrAoLIUPvs4d4ce9h5BGA2yG9Syn
+z3w1l8Zr+4coomUjbJFV86ZWKPM6nyb2RhDb20ESkZnCoDxZY+p5t9c3aiQJKQQV
+8Gj0tj3c7/OKoyMePgabH9752Q6upiZ5Ml3mfse/Kja4THRoPEjkQzAn77jxfves
+KiEh+fu6gsJ3cLQZVG9ueSBGaW5jaCA8ZG90QGRvdGF0LmF0PohiBBMRAgAaBQsH
+CgMEAxUDAgMWAgECF4ACGQEFAjzS3ywAEgkQ/8DxTITHG24HZUdQRwABASeAAJ99
+oc3W8UA0Peqdc5cX4Lbis7hI5QCgg7U7yZqSbW1bRDP8kufk/86S5g+0GlRvbnkg
+RmluY2ggPGZhbmZAZXhpbS5vcmc+iGAEExECACAFAkRka8wCGwMGCwkIBwMCBBUC
+CAMEFgIDAQIeAQIXgAAKCRD/wPFMhMcbblBiAJ9ggPC4h2/eyMlfUlypfFzLqQki
+LwCfd83Ub3FN2C01OLRovTWsmXWBaWC0HFRvbnkgRmluY2ggPGZhbmYyQGNhbS5h
+Yy51az6IZAQTEQIAHAUCPRc64wIbAwQLBwMCAxUCAwMWAgECHgECF4AAEgkQ/8Dx
+TITHG24HZUdQRwABAbmqAJ48Zhf7b9JQWWEiVO0m35yrUG4/7gCfc5OE/gBTg9P/
+1C/5UFC6wzPXtdy0HFRvbnkgRmluY2ggPGZhbmZAYXBhY2hlLm9yZz6IXwQTEQIA
+FwUCPNLYtgULBwoDBAMVAwIDFgIBAheAABIJEP/A8UyExxtuB2VHUEcAAQHATwCf
+QaJHzDZcMzhOrYjhobphXayiTboAnifEwKJ1DDVZxPxxWvxNoTvaPwm2tB1Ub255
+IEZpbmNoIDxmYW5mQEZyZWVCU0Qub3JnPohfBBMRAgAXBQI80tiTBQsHCgMEAxUD
+AgMWAgECF4AAEgkQ/8DxTITHG24HZUdQRwABAfCfAJ4santm5g2yaXD29CKE/OJ5
+4Sd5LwCfbDiwEI1mLyu0nScjBddGF9AiHx65Ag0EPNLUFRAIAJtkhGBrUaEVP2fO
+4wQpmujYfPc7+GT+Q0naKCXrMQ1vDK5ppsghiSr9TdVB3kdkev2oGxgsCfy2uPC/
+JuewQByYBmtKJuU6GDaRVXgMhpVwhcRraaDeYZm0GIDQEX3fWSlL07xxbzSZnewl
+SqUEAznHjLGN1pq9mvPBczq2hrAsd9TPHo/IB9JsVmHV9GYasHUSbVWx1S6ntU2k
+V2TyKpBS4luF1Z7y6yIWS9pwiZjTlWdUGSfUkkTu6sM59dBAxv9S5Q8TY44TUQfh
+HQhcLTz84UurU96i6cb99ZmN5uq6IP6NPIumhOJAqPvHSqly+Ez/oSzSyUoyZ0Sa
+j35E1C8AAwUH/0tkQh1bn/BhIyBO4S9z5wQfI+ZpR7npeKZ1aYQUjFzbULb27Y20
+HRujvXljFPoWB1oJO+oXULkCaNWI+72TYXzKRDqYWMaubwrYe5dHJ4hEDpmpqeG7
+W425rItDfhz2wKORc9vk+eHMHGZZhKamurmeH7hrVpe33BRfts5yvYWofYonWGF+
+KydBcrMp3AMbKGQMSOwcBiSpIJVn0HYJFIOWmthtKIMqfVmLWS2sqFKITbBKHBem
+P+97FVAc82dXxj6irB7/jBjdPX5/5B8HHOXWeEvuHSjZ+6efXFrTVbeh2u1alB0a
+X5kz4cb8Fl9Oziqc2Lx5HLgfkKiWgDAu4YOITgQYEQIABgUCPNLUFQASCRD/wPFM
+hMcbbgdlR1BHAAEBh+4AniTeOAdNc4fOd+lc1EMiNmo8+MkQAJ9cCqXvdHcqeQ6p
+c1DsXNhc4g8rvpkCDQROjXEBARAAzeS7Rq/35b643de5gjparUQdurY+huIwHOVV
+EWG3o0Bm22Mz+S/nwi3w6NNTGCyOo335JX6XA0R4dq/wArwPjQU01az/l1/PrPPm
+OPSnv9/a7eDVFgv7fVGiJFftID9wz2EANhrHjhsGhfFe79wV6ula8KMldipQ+LwG
+FGoSedlcbGRvvyIa72Z9jI5gMm9X482WK/+xl+evAinUWOVWlRaiyl3Qu2c0WTm4
+M0fN82mt3KAu5d3BUbZhkZrbQ4FCfEdzqqdl/aHvnspc6Zp3RGZMxj2YiPdFZmXI
+b7dV1Cf1UaUcD8Zib68/jSVlZLcw1NZKGrsjposgdnDuvkXEjGqECF/k6cqiWfeq
+3eirBwsk6HRd/d8bO99FduKUSV0m6iacgTUzo3dk/OejCPQiENEkb01CRrKeMfNo
+/t6yb0ihkwpT8BTiZCdCmkMjzCGrnT9D3bKlC0qB14gZN5Pso+rYPQmvOE67Eqy8
+dX7zOLAGaaqOaS64g25e44urVGaL6ltOjEU+6xQjIyVtAZPIz6dq/+QEnY799y48
+b6/vcHmByef6zSfTFFcN615sg21Ie/rgJv9ntuM9usROi7MSQfCc3UakUjKl3X/C
+bIrkC1qSmQcGKISw/hCivm36ar0wBx9/Vyz8/h8dT8oN/p5HECSB7GToh+bp3kMn
++aCHdDEAEQEAAbQgRGF2aWQgV29vZGhvdXNlIDxkd213MkBleGltLm9yZz6JAjgE
+EwECACICGwMCHgECF4AFAk7PxLgGCwkIBwMCBhUIAgkKCwQWAgMBAAoJEGN2LNpn
+4vNZhm4QALEBYT7YFCeywswA3PH88h951uia3Cc5Gn4XBKbQxQQ4QRWHkrRhmINR
+qc7SMBUfxUtYnT+T2/Ei07OtRzKX1AjKN74mF+p7s8i7JCM2t7Kc+/xSIZIhpwgb
+f4OOjtUQ3RJoYjlL+ke8YomX6geMZV/IXN2nqj4a8CYkmzXCi2dg7uWf8v/p/hyk
+/DLYlD+HwxpRG6ANUkQ6zxTxgnzwihrnhaNsu2PAnWJo9G/Tfk8o5JuTRBn5qGr7
+SyQ0PUG5s8D2IPgMaABHhpoT9mYvVOundroC2RyusS9xzrTJC+BEvLZ+J3idAvT7
+/TfjJuOrPpkr2BUIZYr4MF+acG0QQUstsJdp7V27iINNN0jmlybbCl7RiIO8nCSf
+VRssgKbfJMnThvMGjYSSFPUz25gIgH95t8a/2rGR5nnBJQYbd+1Toj0vqc4PIuSA
+Lk8bF/fr0s1DwKUJGgbiUYA4moIY165he7/RVGVwm5qM49YgSaJWwintDCGox7kD
+JMBfOz1n0FVi5LLGCHmWosLt/CRpb+F+r0ix2g4d5kIU/JedT1kU8dOugLVb5bLu
+isK28h5J06k48VfTkzkSjOb8Nn4w7q78RUZ2zx8Ny5Y5+BFEKtmu7Bs9Pzs6698D
+HSaeZzqIuSTgn8ddu8iBjHZF/sw7wrZO1z2cKj6FW6bMen/bX+HbtCJEYXZpZCBX
+b29kaG91c2UgPGRhdmlkQHdvb2Rob3Uuc2U+iQI4BBMBAgAiAhsDAh4BAheABQJO
+z8S4BgsJCAcDAgYVCAIJCgsEFgIDAQAKCRBjdizaZ+LzWbR7D/4hKUfh04TLD2ZF
+sIWxrgEE/661lHaYZNi/rJAkhX73+bpPP5aVuWiqvFkYbcIvA4+PzSi8KXuKiLSb
+xtUDgqBKPWI9Zh2cOj2Ykl/+Qqp+TAPnjTde5+lc++MUm7K0QU2CJQZwvRwnLtwM
+vqsj7dlF37N46oSOqcPb6JRsDmJmoJUn1ylZhjys0qAw9A+3VVxXIIacsf7Oxr+5
+VDMTJmyclfGwbsAAEyYYEgopQ2R8Z+bEOVTdDYSC051oO0KUHidbRGU8/un7yM8R
+FtZSoPp88O4wdWyr9xbahSr4LYImoNUGpJLQQKf+EtMI4pKITDs5Nkl8S6q/Gkh8
+nhqleuVQ/jT35Uk0T1qzhX+8EaUAs4Bp/kUJ50K+V6C4wBMECoMDHXyvmgkKCkb1
+8g7tMgv1ea3gZXcOU7MUvhgSzcndLKZi+taGTgmO+bNNdOnA1MAxMJpoU45cWpVy
+Pp5nUg1E5/joQGW9VDJFLkoIArO50e2Ccx+beDPtD20zBO4Yga+hfrztlAP9aGUA
+r5Zxu49MpeClqTyTnCoFyAMbAJXSjaBEcnpIWghaUZinyvnneB8JpK4I4zjwBgwa
+N2+D6K0MTDJjyYw/bkRa4U6vv8L91NTH6avCpMJMdo9SeokLVuPGXxAH85JfzeK/
+q1bnjrGBca/HJSksT+3wtj7XCAKIKLQiRGF2aWQgV29vZGhvdXNlIDxkd213MkBr
+ZXJuZWwub3JnPokCOAQTAQIAIgIbAwIeAQIXgAUCTs/EuAYLCQgHAwIGFQgCCQoL
+BBYCAwEACgkQY3Ys2mfi81lOqg/9Ev3xFwdEWPZdknj63f4DruELPC7GYb5aY4mA
+NzmsLkl5qlbr6+JtTZOyvM5wmR/0zD6me3e7YvMWC3bQJplMExcRJVlTBrk9hdie
+P/0CGaY5iXFLLqSVbKyNNQ3BoES6vJBX4OAgnD5J5NmCy7pnplHF7hRiasK0YyCG
+2QcDtMdgq2AKkqRjaQ3r0kBblbNQbU1KMhVfww890wYIJ/1H51ep3IkCw9L1i/0C
+8Z9mBQbUBGW8k6Vd4wtvnPYs6LNBXHuDX9qZumClEALfdIx/WIQZZ5OIhB94FSC2
+06gP4pgMFJb+dgOrQU6Q46y8rRsArEJRkQBS0m1Nd5hTxYi+O5V2igbi2vvMw3ij
+emA+nEURCJku7/qb7vXhtfUYCK+9XUUIHkW6IadW5hRqt0+O24tnOsoj5yZWdbbS
+2tpH44F/lFO5VRhKkKVy+j9D5+WXsR2NLnujMpqLVezIZY+5H8QsNp9+nPXKaLy6
+kfg86Ou4C0gdOXY3M9h+j6METzPOehhPcU4Oep6uwdogFEP85cQH/YubpX/xrTmV
+VcXJPfYsDoR/SEvCN0ZW6HBRbXs5fJrCZeFwvAG+ytXJ6CY56vp9n9fHp1n1+WuE
+f8eMBJvWn9IaZYa4fUKNPp2FGj5eCRS95onmKngom8YL4nzEN2qRQ8edF1Sz9H78
+Osshh5+0JURhdmlkIFdvb2Rob3VzZSA8ZHdtdzJAaW5mcmFkZWFkLm9yZz6JAjsE
+EwECACUCGwMCHgECF4ACGQEFAk7PxK8GCwkIBwMCBhUIAgkKCwQWAgMBAAoJEGN2
+LNpn4vNZjlAP/0QmueyzFVNlUC3855fh5yDLpnucSwCrrxBZzudRu6bMbd3eTNaf
+2WLnIstHqQS+PWDnDq3tf2k4btROqkJizSPDvajME+slM0mTyuTTT9HbhE5VfgGN
+vW0FR0sS4id72VLsycjaho1NP2/JNXTs4tz9qisq/eHIjp2vJbjcgNUBdAGoUvsf
+6I/O3SZJM6j64LBjUbmm6yZZSUtQCTzcB96cEkKCPoXRatzFj0xHEGmCCEFWrTuH
+KczbC6VTgQfGOK3N9UeaSplrR1mEBij+M51T4rXqQvb52ko/L/UoAPNuk0TiRQs6
+YvQTy16cQEszkJvxBZUTS3ifSmEVfaWt8f9sbVfeWPm5USIG/HwsiNNy977wbbao
+BO25C+3rC4W7rFKqzYsRXnKKBWiTVtDs7gvQBdGqWRwJMj1crTDFgI09Gn+N/Xth
+IcC/STvJdDgaomxuv9oUqM9QMm1x8jVD+4nnEYPWpV4mtxjoA+gIW+Vv1PGJS+39
++dlA2TEtDzJfGPE3YF0jjy8ycqw+y9ar6+nnspyrLafCUybXCafQ121+F+zIVfR6
+KKr+Xy5bdHUVuRWeP+EfWnaYuevRoMsY+29eURO6hh1S1ZYukpANJP7Nu2onAPOO
+P2e8X8TF23ZcYcON0/sneMnnCuWLQ/Z91ZjTDu8BjbCYPWqgUuD6f8Q2uQINBE6N
+cQEBEADahf5YXCjYAsBznLgpRFL47H0ThjvxJ7LX/bCPTo81X8T3u+kd82AFr6qN
+yc/da3mVBJ0HUMqOSGXTnT6ncvlxe56HaHX09ZWc9yONa+LLhWMvHh8cfS9Z6fH5
+I1WP0DrtLRofO99K+gGE8GflaETIoqGVCcKbHwcmBmyfJM7OcYbBNq1vMj7vsF6I
+VyYGsGCmLoAwjuZX3gO/mZSwiJGY4XHQQx4wiRLmhxl/HvcCiqNOZy3FaD8s+KBZ
+hXoOeAtj5g0vQleRcoLp6fWEXBz/eSaAC3y2P9egj7CWjsQ/8ky4dEq+96VD+Xr9
+GE0cKVFfAPDSCbC2cHBfFbLBDXlnizLgqBWEjJJ1jPAcG5pcdk4YlL0Nh73Zkp9E
+uB9nLs5bLsWsmcNBCsHXkgq/GuDKzkWzmVhgQ6YpdIM0PJ+ycmys5mErZjkU942R
+JID9xpO2tIsBoWQT5w0nvAOejjjoSFMVGIWKRwMpNyXo/MQ8IovahZwn1B/1CQgb
+aTP5unmAgyYgQ5bKvf7QVoFB30tu2SX9c8Inx2ma0tpI82GZXmEA4Crgok1q3LQR
+NO7TVEmE4I5c37HkWW7z9oyO59KZLI6jCQKcIZnTuNu3viKf1GC2fy26QgdnTZFI
+5VOlfRYbVzu/V7MrAG56i3lZjEJ7uXhBPNugxMXtxoegvbXj8wARAQABiQIfBBgB
+AgAJBQJOjXEBAhsMAAoJEGN2LNpn4vNZDq0QAMeOdXlaM4pLO6spFxElkUK7YwSD
+j2oaI3DKDfsMt5Y1cM1pn6DPJWFE0+I3HG7KuNj1ldcxDQJ75LQclgS0SJUkn3kM
+AFkZRcpB2rbXYpUoN/9dZyiPFj689EgqooiQVVv0mbyrnDMJIlQ3oj0DUGUfAY3K
+XBVSameDnIadMKsPauwWIuqaT6BookoVYajEG7meUs4fCIG8Kwi+Yz98dFScQbkv
+YSUGC34i9g+35KnQ6ZyY2n7hYQHRizfkuYOPk9iF8YLMaefw+SDGu62EH+eS5Ip5
+crNwNAzjdETHRs3fNVzWxHt4+8KYI+nRBBwSeQes+gx5IYPaBJ9u16Bb6ygK84GA
+pgxdYBT6d9O8GST5VSFFVa6bZDW2Gr7MUAW6O4jFLrflZx6qqef86AG/2y36pZgr
+pfTg4CJszLSncTxuLQS6fKxSuaoB+H4Xn3U0nWDkpmG8tHCEWZkPktBcDebp3K+i
+BGH/00oy24sTQPzj/m3z4STzBmMFyIo7/9Md1H0PahgTYbVDquDP2+uEIh0Bh4sx
+bw5VudBxre4VpVxMLfun2alD019JN1nTsCLsvknb6A2zyEb/bK/YArPkgC0eK/uG
+x+tSCVNJzJwdmtyx6lISsuRWgamakejZAQJ1RsYey4uxchVoIBosEr96HGAXC6QG
+qUGpmp4gd45ZOhyRmQINBEvFfPcBEADU5bFOcbVCBDsTGq3D+8AnA933S4iYvxPw
+Z2eRaT415jXQs91wNTpVwgY3xRzkjThXyt6FV7Cd+BdS4YvGHMvqKZCOgVnrzpxe
+Oqy7GsC10yfU+dA7GvZiY8hm4tw/bdkjTsQQeWePgWoCgmqpadKAn4iGh0u3fBZC
+7p4Z8Jsf02yqUFIy48OxKf/aeV8W5sTRr1m3HDmbjtUw/UbUW761GIgR11BxcPIa
+MaN7WAwKHNpIUqjIZyZ6z6tGGQ3fRwTAJwi1SROEU74DgRtRHn+pu2kp0pOsD3wz
+8yT95gL92CC2hkitTZB4+3uglmMvuQ9nwQjadPt8as3fB2mfGK7c9tLXMIq8bmDZ
+w+mWU/XRbU4vrCdQVyWH6LUI28/Wnj6LUEC6hZOnmiLSll3bapo0eca3LV9NVp0G
+aNy8QX7Op2KwE3mWkN4F7c2ZyGQ8MaL6qLlmE63Qe4QFy5wJ4H16NCvXHqGxm60b
+nJy51NmQJGd7YQeDa5AFbgFJ8V4Bg+f1LfIeh9rpHKiuZRPZDs8yxs0kFOQOjZ4u
+5HNn2AyuYvibT1CXj0X8OHzXpQ3SylBy+LE4xEnt6JPkQjXahwEwTotUxTJbu0fK
+9UvBB4mqzITs270HSowzEofhc/5YJSxlxQ8a3dPBBRjzUeTfNN6gBrEV/7YalfDm
+xHKEyFHepwARAQABtDtHcmFlbWUgRm93bGVyIChLZXkgY3JlYXRlZCAyMDEwLTA0
+LTE0KSA8Z3JhZW1lQGdyYWVtZWYubmV0PokCPAQTAQIAJgIbAwYLCQgHAwIEFQII
+AwQWAgMBAh4BAheABQJWrk32BQkSbZ7/AAoJEK1e27eT7FfklXUP/2XzrCgNOhRr
+DkonKQddWqzgz9DXPttmA9mNeXAM0gBDcnNUhFXiL5yjRyCmtmrjdiv2GE3yE5Xg
+arCoyb8tTve1Ps9ouzkbHQomDOdoTv8maL+p7MovSXr0jcJow9rVIdSP03BdFExZ
+u+H7nXrKoSKcBFIMdDVznMOGBO49Z4dXFeDQgZ8xafDpB0KqCHKMf5PNQ7iQ77uv
+3lIVFvX4svnPP5t0FEmFmwZ0YaKbRoa/I4fy0jHCpfCtEEtmrUuKIuQt8uzpYpo/
+BnH/yqXp/ajJ7x/P4n4IbLV+HSkX8Pxfh6ABeilHKvwmmNKYrNl2vCnYzUyWOyDF
+SEySxHAEN6hLdrExZNXDaLh73QKN/y8bl4sh3Ehml/DBhbktGteUBHt5M09OHvu+
+AYLpYx0iZUGzUd6hxkWlkJ2kPeToMcKfRLNGT+237VAvbGaNrXwFZXELwaptL22X
+uu2Tfn403/aD9ssk4L2v7GwJPeTQ1U9xH0742eWJf7OX7UMUoCG2HJf7Sg2nRtIg
+hK531cE1cv3i35EOHeVWClTeX2kc/9hwgzXAMWKrEe/3OMGPHQNNRsnPCsJHjhUY
+PRvS1pa6k1nuid8IZEeWskDT2PmSNWJQay9oA2ojcFua+UC/29upvBqO0O+/CtLH
+YvinLANDShtEZJ42rwbYEorT6OO8YIwbuQINBEvFfPcBEACtCHNuOn+pZjBOWmW0
+rqCnN9Oywq0h0Twk/UsqkhAijImgXrZLMoeylGA+UIsnuNl6e+x76Ke2z6H0Jytw
+IZEi9EqqZa3UhpN7JQ0ddNzkzE8tvYdicPdcXkZ99KBcHoPd25/N3fNJWJmbBv/b
+CQMW2J/zRo5QPokOjEl770xNS9wcXmA3ptTKbyzfQ4Wh8LALrJ3F9vZw8GsZFAmF
+NeMLCJ4Qhxk3MjCoQdzzRSTYEu4c7eYbE+biU/ZUgBMJH4Ed4urhOO81d9dDvf2C
+CdcJdftAYy/ACtTeq8tc3YzG+E4J+uplxxyD+IFP8U8Q5TyWdb5AU/rAWa1UdpwZ
+IQiaJfy4E1x+ac9BHAD1BZaCMv0fTcPxYm8m67GYUfFqRaI5Yd9sPvuzF8IDs2bo
+Nl5L60ce8ROOBtwRGp9daOHmhIlRKGoG7FPc1dTGjrVd5lWgzet+CHnWZ+HKYsNg
+W7cDo52Dwa8BjenK9OUxvzTNzwmz97cCioufv+ysUS9DY9tl7P0eHR1tehTM7HSA
+n/lCEU90j5/f/ozwBR8cDF8lLSMXlKybudjHteLFA/2/HbzWIEWVLpckmu3Xxpw9
+EF9xoiQmbJTmkWEIBBSLAALYtuygBbiGUdwPBeJQQUYliNpdgrwKXp9OIB8NFK99
+DvG7xB61569hUaekwnmB5uEU0wARAQABiQIlBBgBAgAPBQJLxXz3AhsMBQkSzAMA
+AAoJEK1e27eT7Ffkop4P/R97j+X8zfPt9gsABnU5zHtGS6jQ9Ahax+q0Dx0Vm7Wv
+qH8DC7RsSGp51YflfS4S3xNVGtQTUSV+z7H4cFUSD8f22RnubLUKOplVup6m3Dqz
+/Nosht8sU5Yo2mFmRNMFGo/gJF6vtqX15rPpPh0gHsEi3Toa7qzegnIVfuU14ZND
+tRnn5OmuJfFP9xO1PxIwqi+GaY06zkKbcmSw12xOwOCEt8kv4FGCx1FiSrFdHK4G
+fszvtzOG6VPbAROnERG2AbGQPXO0+m3bfYxSv8rxepSjo4f/1sXbVAX5AgH8wtcs
+iZLq7D+UrTdRe27dB/PmN+L4xz9UEmU//1rLzxOBfyWLfITF+65e4QZkrxIwVI2M
+4AVB9pAb+fSMmWMb4IU1tuSkeJT3k/bKeWdGVbXrDiSgd6XChBeui+Td/KR0Hbl+
+i3jeDm/6tYeCob4XQ4nhJ3dI9gI1S4YXJgGizhZmiWqipA41b12rQAB6ieU6aE/N
+OX7rwcNGaCwbyCgDOQfR7fiqxVF2xA8som/asBwAUWFZIMEhfijsn7fpK/7uGoN9
+2Eqfxvcwr7Rkp+bhCS6Wg0q+bgBn/02MB+9Uk9Yi9O7/DI8CqpwsiMzZ72ZMm4pT
+Lp8WFmqbxePWSxr4JA6XmApmC7sSlH75melFC6MCwaxpoRMbeH2mDhAfwbjXSPMO
+=XUWN
+-----END PGP PUBLIC KEY BLOCK-----
index eac4c0a..e0d3fce 100644 (file)
@@ -1,3 +1,3 @@
 version=3
-opts=pgpsigurlmangle=s/$/.asc/ \
+opts=pgpsigurlmangle=s/$/.asc/,uversionmangle=s/_/~/ \
 http://ftp.exim.org/pub/exim/exim4/exim-(\d.*)\.(?:tgz|tar\.(?:gz|bz2|xz))
index bea6b1a..5641694 100644 (file)
@@ -1,5 +1,678 @@
 Change log file for Exim from version 4.21
--------------------------------------------
+------------------------------------------
+This document describes *changes* to previous versions, that might
+affect Exim's operation, with an unchanged configuration file.  For new
+options, and new features, see the NewStuff file next to this ChangeLog.
+
+
+Exim version 4.89
+-----------------
+
+JH/01 Bug 1922: Support IDNA2008.  This has slightly different conversion rules
+      than -2003 did; needs libidn2 in addition to libidn.
+
+JH/02 The path option on a pipe transport is now expanded before use.
+
+PP/01 GitHub PR 50: Do not call ldap_start_tls_s on ldapi:// connections.
+      Patch provided by "Björn", documentation fix added too.
+
+JH/03 Bug 2003: fix Proxy Protocol v2 handling: the address size field was
+      missing a wire-to-host endian conversion.
+
+JH/04 Bug 2004: fix CHUNKING in non-PIPELINEING mode.  Chunk data following
+      close after a BDAT command line could be taken as a following command,
+      giving a synch failure.  Fix by only checking for synch immediately
+      before acknowledging the chunk.
+
+PP/02 GitHub PR 52: many spelling fixes, which include fixing parsing of
+      no_require_dnssec option and creation of _HAVE_TRANSPORT_APPEND_MAILDIR
+      macro.  Patches provided by Josh Soref.
+
+JH/05 Have the EHLO response advertise VRFY, if there is a vrfy ACL defined.
+      Previously we did not; the RFC seems ambiguous and VRFY is not listed
+      by IANA as a service extension.  However, John Klensin suggests that we
+      should.
+
+JH/06 Bug 2017: Fix DKIM verification in -bh test mode.  The data feed into
+      the dkim code may be unix-mode line endings rather than smtp wire-format
+      CRLF, so prepend a CR to any bare LF.
+
+JH/07 Rationalise the coding for callout smtp conversations and transport ones.
+      As a side-benfit, callouts can now use PIPELINING hence fewer round-trips.
+
+JH/08 Bug 2016: Fix DKIM verification vs. CHUNKING.  Any BDAT commands after
+      the first were themselves being wrongly included in the feed into dkim
+      processing; with most chunk sizes in use this resulted in an incorrect
+      body hash calculated value.
+
+JH/09 Bug 2014: permit inclusion of a DKIM-Signature header in a received
+      DKIM signature block, for verification.  Although advised against by
+      standards it is specifically not ruled illegal.
+
+JH/10 Bug 2025: Fix reception of (quoted) local-parts with embedded spaces.
+
+JH/11 Bug 2029: Fix crash in DKIM verification when a message signature block is
+      missing a body hash (the bh= tag).
+
+JH/12 Bug 2018: Re-order Proxy Protocol startup versus TLS-on-connect startup.
+      It seems that HAProxy sends the Proxy Protocol information in clear and
+      only then does a TLS startup, so do the same.
+
+JH/13 Bug 2027: Avoid attempting to use TCP Fast Open for non-transport client
+      TCP connections (such as for Spamd) unless the daemon successfully set
+      Fast Open mode on its listening sockets.  This fixes breakage seen on
+      too-old kernels or those not configured for Fast Open, at the cost of
+      requiring both directions being enabled for TFO, and TFO never being used
+      by non-daemon-related Exim processes.
+
+JH/14 Bug 2000: Reject messages recieved with CHUNKING but with malformed line
+      endings, at least on the first header line.  Try to canonify any that get
+      past that check, despite the cost.
+
+JH/15 Angle-bracket nesting (an error inserted by broken sendmails) levels are
+      now limited to an arbitrary five deep, while parsing addresses with the
+      strip_excess_angle_brackets option enabled.
+
+PP/03 Bug 2018: For Proxy Protocol and TLS-on-connect, do not over-read and
+      instead leave the unprompted TLS handshake in socket buffer for the
+      TLS library to consume.
+
+PP/04 Bug 2018: Also handle Proxy Protocol v2 safely.
+
+PP/05 FreeBSD compat: handle that Ports no longer create /usr/bin/perl
+
+JH/16 Drop variables when they go out of scope.  Memory management drops a whole
+      region in one operation, for speed, and this leaves assigned pointers
+      dangling.  Add checks run only under the testsuite which checks all
+      variables at a store-reset and panics on a dangling pointer; add code
+      explicitly nulling out all the variables discovered.  Fixes one known
+      bug: a transport crash, where a dangling pointer for $sending_ip_address
+      originally assigned in a verify callout, is re-used.
+
+PP/06 Drop '.' from @INC in various Perl scripts.
+
+PP/07 Switch FreeBSD iconv to always use the base-system libc functions.
+
+PP/08 Reduce a number of compilation warnings under clang; building with
+      CC=clang CFLAGS+=-Wno-dangling-else -Wno-logical-op-parentheses
+      should be warning-free.
+
+JH/17 Fix inbound CHUNKING when DKIM disabled at runtime.
+
+HS/01 Fix portability problems introduced by PP/08 for platforms where
+      realloc(NULL) is not equivalent to malloc() [SunOS et al].
+
+HS/02 Bug 1974: Fix missing line terminator on the last received BDAT
+      chunk. This allows us to accept broken chunked messages. We need a more
+      general solution here.
+
+PP/09 Wrote util/chunking_fixqueue_finalnewlines.pl to help recover
+      already-broken messages in the queue.
+
+JH/18 Bug 2061: Fix ${extract } corrupting an enclosing ${reduce }  $value.
+
+JH/19 Fix reference counting bug in routing-generated-address tracking.
+
+
+Exim version 4.88
+-----------------
+
+JH/01 Use SIZE on MAIL FROM in a cutthrough connection, if the destination
+      supports it and a size is available (ie. the sending peer gave us one).
+
+JH/02 The obsolete acl condition "demime" is removed (finally, after ten
+      years of being deprecated). The replacements are the ACLs
+      acl_smtp_mime and acl_not_smtp_mime.
+
+JH/03 Upgrade security requirements imposed for hosts_try_dane: previously
+      a downgraded non-dane trust-anchor for the TLS connection (CA-style)
+      or even an in-clear connection were permitted.  Now, if the host lookup
+      was dnssec and dane was requested then the host is only used if the
+      TLSA lookup succeeds and is dnssec.  Further hosts (eg. lower priority
+      MXs) will be tried (for hosts_try_dane though not for hosts_require_dane)
+      if one fails this test.
+      This means that a poorly-configured remote DNS will make it incommunicado;
+      but it protects against a DNS-interception attack on it.
+
+JH/04 Bug 1810: make continued-use of an open smtp transport connection
+      non-noisy when a race steals the message being considered.
+
+JH/05 If main configuration option tls_certificate is unset, generate a
+      self-signed certificate for inbound TLS connections.
+
+JH/06 Bug 165: hide more cases of password exposure - this time in expansions
+      in rewrites and routers.
+
+JH/07 Retire gnutls_require_mac et.al.  These were nonfunctional since 4.80
+      and logged a warning sing 4.83; now they are a configuration file error.
+
+JH/08 Bug 1836: Fix crash in VRFY handling when handed an unqualified name
+      (lacking @domain).  Apply the same qualification processing as RCPT.
+
+JH/09 Bug 1804: Avoid writing msglog files when in -bh or -bhc mode.
+
+JH/10 Support ${sha256:} applied to a string (as well as the previous
+      certificate).
+
+JH/11 Cutthrough: avoid using the callout hints db on a verify callout when
+      a cutthrough deliver is pending, as we always want to make a connection.
+      This also avoids re-routing the message when later placing the cutthrough
+      connection after a verify cache hit.
+      Do not update it with the verify result either.
+
+JH/12 Cutthrough: disable when verify option success_on_redirect is used, and
+      when routing results in more than one destination address.
+
+JH/13 Cutthrough: expand transport dkim_domain option when testing for dkim
+      signing (which inhibits the cutthrough capability).  Previously only
+      the presence of an option was tested; now an expansion evaluating as
+      empty is permissible (obviously it should depend only on data available
+      when the cutthrough connection is made).
+
+JH/14 Fix logging of errors under PIPELINING.  Previously the log line giving
+      the relevant preceding SMTP command did not note the pipelining mode.
+
+JH/15 Fix counting of empty lines in $body_linecount and $message_linecount.
+      Previously they were not counted.
+
+JH/16 DANE: treat a TLSA lookup response having all non-TLSA RRs, the same
+      as one having no matching records.  Previously we deferred the message
+      that needed the lookup.
+
+JH/17 Fakereject: previously logged as a normal message arrival "<="; now
+      distinguished as "(=".
+
+JH/18 Bug 1867: make the fail_defer_domains option on a dnslookup router work
+      for missing MX records.  Previously it only worked for missing A records.
+
+JH/19 Bug 1850: support Radius libraries that return REJECT_RC.
+
+JH/20 Bug 1872: Ensure that acl_smtp_notquit is run when the connection drops
+      after the data-go-ahead and data-ack.  Patch from Jason Betts.
+
+JH/21 Bug 1846: Send DMARC forensic reports for reject and quarantine results,
+      even for a "none" policy.  Patch from Tony Meyer.
+
+JH/22 Fix continued use of a connection for further deliveries. If a port was
+      specified by a router, it must also match for the delivery to be
+      compatible.
+
+JH/23 Bug 1874: fix continued use of a connection for further deliveries.
+      When one of the recipients of a message was unsuitable for the connection
+      (has no matching addresses), we lost track of needing to mark it
+      deferred.  As a result mail would be lost.
+
+JH/24 Bug 1832: Log EHLO response on getting conn-close response for HELO.
+
+JH/25 Decoding ACL controls is now done using a binary search; the source code
+      takes up less space and should be simpler to maintain.  Merge the ACL
+      condition decode tables also, with similar effect.
+
+JH/26 Fix problem with one_time used on a redirect router which returned the
+      parent address unchanged.  A retry would see the parent address marked as
+      delivered, so not attempt the (identical) child.  As a result mail would
+      be lost.
+
+JH/27 Fix a possible security hole, wherein a process operating with the Exim
+      UID can gain a root shell.  Credit to http://www.halfdog.net/ for
+      discovery and writeup.  Ubuntu bug 1580454; no bug raised against Exim
+      itself :(
+
+JH/28 Enable {spool,log} filesystem space and inode checks as default.
+      Main config options check_{log,spool}_{inodes,space} are now
+      100 inodes, 10MB unless set otherwise in the configuration.
+
+JH/29 Fix the connection_reject log selector to apply to the connect ACL.
+      Previously it only applied to the main-section connection policy
+      options.
+
+JH/30 Bug 1897: fix callouts connection fallback from TLS to cleartext.
+
+PP/01 Changed default Diffie-Hellman parameters to be Exim-specific, created
+      by me.  Added RFC7919 DH primes as an alternative.
+
+PP/02 Unbreak build via pkg-config with new hash support when crypto headers
+      are not in the system include path.
+
+JH/31 Fix longstanding bug with aborted TLS server connection handling.  Under
+      GnuTLS, when a session startup failed (eg because the client disconnected)
+      Exim did stdio operations after fclose.  This was exposed by a recent
+      change which nulled out the file handle after the fclose.
+
+JH/32 Bug 1909: Fix OCSP proof verification for cases where the proof is
+      signed directly by the cert-signing cert, rather than an intermediate
+      OCSP-signing cert.  This is the model used by LetsEncrypt.
+
+JH/33 Bug 1914: Ensure socket is nonblocking before draining after SMTP QUIT.
+
+HS/01 Fix leak in verify callout under GnuTLS, about 3MB per recipient on
+      an incoming connection.
+
+HS/02 Bug 1802: Do not half-close the connection after sending a request
+      to rspamd.
+
+HS/03 Use "auto" as the default EC curve parameter. For OpenSSL < 1.0.2
+      fallback to "prime256v1".
+
+JH/34 SECURITY: Use proper copy of DATA command in error message.
+      Could leak key material.  Remotely exploitable.  CVE-2016-9963.
+
+
+Exim version 4.87
+-----------------
+
+JH/01 Bug 1664: Disable OCSP for GnuTLS library versions at/before 3.3.16
+      and 3.4.4 - once the server is enabled to respond to an OCSP request
+      it does even when not requested, resulting in a stapling non-aware
+      client dropping the TLS connection.
+
+TF/01 Code cleanup: Overhaul the debug_selector and log_selector machinery to
+      support variable-length bit vectors. No functional change.
+
+TF/02 Improve the consistency of logging incoming and outgoing interfaces.
+      The I= interface field on outgoing lines is now after the H= remote
+      host field, same as incoming lines. There is a separate
+      outgoing_interface log selector which allows you to disable the
+      outgoing I= field.
+
+JH/02 Bug 728: Close logfiles after a daemon-process "exceptional" log write.
+      If not running log_selector +smtp_connection the mainlog would be held
+      open indefinitely after a "too many connections" event, including to a
+      deleted file after a log rotate. Leave the per net connection logging
+      leaving it open for efficiency as that will be quickly detected by the
+      check on the next write.
+
+HS/01 Bug 1671: Fix post transport crash.
+      Processing the wait-<transport> messages could crash the delivery
+      process if the message IDs didn't exist for some reason. When
+      using 'split_spool_directory=yes' the construction of the spool
+      file name failed already, exposing the same netto behaviour.
+
+JH/03 Bug 425: Capture substrings in $regex1, $regex2 etc from regex &
+      mime_regex ACL conditions.
+
+JH/04 Bug 1686: When compiled with EXPERIMENTAL_DSN_INFO: Add extra information
+      to DSN fail messages (bounces): remote IP, remote greeting, remote response
+      to HELO, local diagnostic string.
+
+JH/05 Downgrade message for a TLS-certificate-based authentication fail from
+      log line to debug.  Even when configured with a tls authenticator many
+      client connections are expected to not authenticate in this way, so
+      an authenticate fail is not an error.
+
+HS/02 Add the Exim version string to the process info.  This way exiwhat
+      gives some more detail about the running daemon.
+
+JH/06 Bug 1395: time-limit caching of DNS lookups, to the TTL value.  This may
+      matter for fast-change records such as DNSBLs.
+
+JH/07 Bug 1678: Always record an interface option value, if set,  as part of a
+      retry record, even if constant.  There may be multiple transports with
+      different interface settings and the retry behaviour needs to be kept
+      distinct.
+
+JH/08 Bug 1586: exiqgrep now refuses to run if there are unexpected arguments.
+
+JH/09 Bug 1700: ignore space & tab embedded in base64 during decode.
+
+JH/10 Bug 840: fix log_defer_output option of pipe transport
+
+JH/11 Bug 830: use same host for all RCPTS of a message, even under
+      hosts_randomize.  This matters a lot when combined with mua_wrapper.
+
+JH/12 Bug 1706: percent and underbar characters are no longer escaped by the
+      ${quote_pgsql:<string>} operator.
+
+JH/13 Bug 1708: avoid misaligned access in cached lookup.
+
+JH/14 Change header file name for freeradius-client.  Relevant if compiling
+      with Radius support; from the Gentoo tree and checked under Fedora.
+
+JH/15 Bug 1712: Introduce $prdr_requested flag variable
+
+JH/16 Bug 1714: Permit an empty string as expansion result for transport
+      option transport_filter, meaning no filtering.
+
+JH/17 Bug 1713: Fix non-PDKIM_DEBUG build.  Patch from Jasen Betts.
+
+JH/18 Bug 1709: When built with TLS support, the tls_advertise_hosts option now
+      defaults to "*" (all hosts).  The variable is now available when not built
+      with TLS, default unset, mainly to enable keeping the testsuite sane.
+      If a server certificate is not supplied (via tls_certificate) an error is
+      logged, and clients will find TLS connections fail on startup.  Presumably
+      they will retry in-clear.
+      Packagers of Exim are strongly encouraged to create a server certificate
+      at installation time.
+
+HS/03 Add -bP config_file as a synonym for -bP configure_file, for consistency
+      with the $config_file variable.
+
+JH/19 Two additional event types: msg:rcpt:defer and msg:rcpt:host:defer. Both
+      in transport context, after the attempt, and per-recipient. The latter type
+      is per host attempted.  The event data is the error message, and the errno
+      information encodes the lookup type (A vs. MX) used for the (first) host,
+      and the trailing two digits of the smtp 4xx response.
+
+GF/01 Bug 1715: Fix for race condition in exicyclog, where exim could attempt
+      to write to mainlog (or rejectlog, paniclog) in the window between file
+      creation and permissions/ownership being changed. Particularly affects
+      installations where exicyclog is run as root, rather than exim user;
+      result is that the running daemon panics and dies.
+
+JH/20 Bug 1701: For MySQL lookups, support MySQL config file option group names.
+
+JH/21 Bug 1720: Add support for priority groups and weighted-random proxy
+      selection for the EXPERIMENTAL_SOCKS feature, via new per-proxy options
+      "pri" and "weight".  Note that the previous implicit priority given by the
+      list order is no longer honoured.
+
+JH/22 Bugs 963, 1721: Fix some corner cases in message body canonicalization
+      for DKIM processing.
+
+JH/23 Move SOCKS5 support from Experimental to mainline, enabled for a build
+      by defining SUPPORT_SOCKS.
+
+JH/26 Move PROXY support from Experimental to mainline, enabled for a build
+      by defining SUPPORT_PROXY.  Note that the proxy_required_hosts option
+      is renamed to hosts_proxy, and the proxy_{host,target}_{address,port}.
+      variables are renamed to proxy_{local,external}_{address,port}.
+
+JH/27 Move Internationalisation support from Experimental to mainline, enabled
+      for a build by defining SUPPORT_I18N
+
+JH/28 Bug 1745: Fix redis lookups to handle (quoted) spaces embedded in parts
+      of the query string, and make ${quote_redis:} do that quoting.
+
+JH/29 Move Events support from Experimental to mainline, enabled by default
+      and removable for a build by defining DISABLE_EVENT.
+
+JH/30 Updated DANE implementation code to current from Viktor Dukhovni.
+
+JH/31 Fix bug with hosts_connection_nolog and named-lists which were wrongly
+      cached by the daemon.
+
+JH/32 Move Redis support from Experimental to mainline, enabled for a build
+      by defining LOOKUP_REDIS. The libhiredis library is required.
+
+JH/33 Bug 1748: Permit ACL dnslists= condition in non-smtp ACLs if explicit
+      keys are given for lookup.
+
+JH/34 Bug 1192: replace the embedded copy of PolarSSL RSA routines in the DKIM
+      support, by using OpenSSL or GnuTLS library ones.  This means DKIM is
+      only supported when built with TLS support.  The PolarSSL SHA routines
+      are still used when the TLS library is too old for convenient support.
+
+JH/35 Require SINGLE_DH_USE by default in OpenSSL (main config option
+      openssl_options), for security.  OpenSSL forces this from version 1.1.0
+      server-side so match that on older versions.
+
+JH/36 Bug 1778: longstanding bug in memory use by the ${run } expansion: A fresh
+      allocation for $value could be released as the expansion processing
+      concluded, but leaving the global pointer active for it.
+
+JH/37 Bug 1769: Permit a VRFY ACL to override the default 252 response,
+      and to use the domains and local_parts ACL conditions.
+
+JH/38 Fix cutthrough bug with body lines having a single dot. The dot was
+      incorrectly not doubled on cutthrough transmission, hence seen as a
+      body-termination at the receiving system - resulting in truncated mails.
+      Commonly the sender saw a TCP-level error, and retransmitted the message
+      via the normal store-and-forward channel. This could result in duplicates
+      received - but deduplicating mailstores were liable to retain only the
+      initial truncated version.
+
+JH/39 Bug 1781: Fix use of DKIM private-keys having trailing '=' in the base-64.
+
+JH/40 Fix crash in queryprogram router when compiled with EXPERIMENTAL_SRS.
+
+JH/41 Bug 1792: Fix selection of headers to sign for DKIM: bottom-up.  While
+      we're in there, support oversigning also; bug 1309.
+
+JH/42 Bug 1796: Fix error logged on a malware scanner connection failure.
+
+HS/04 Add support for keep_environment and add_environment options.
+
+JH/43 Tidy coding issues detected by gcc --fsanitize=undefined.  Some remain;
+      either intentional arithmetic overflow during PRNG, or testing config-
+      induced overflows.
+
+JH/44 Bug 1800: The combination of a -bhc commandline option and cutthrough
+      delivery resulted in actual delivery.  Cancel cutthrough before DATA
+      stage.
+
+JH/45 Fix cutthrough, when connection not opened by verify and target hard-
+      rejects a recipient: pass the reject to the originator.
+
+JH/46 Multiple issues raised by Coverity. Some were obvious or plausible bugs.
+      Many were false-positives and ignorable, but it's worth fixing the
+      former class.
+
+JH/47 Fix build on HP-UX and older Solaris, which need (un)setenv now also
+      for the new environment-manipulation done at startup.  Move the routines
+      from being local to tls.c to being global via the os.c file.
+
+JH/48 Bug 1807: Fix ${extract } for the numeric/3-string case. While preparsing
+      an extract embedded as result-arg for a map, the first arg for extract
+      is unavailable so we cannot tell if this is a numbered or keyed
+      extraction.  Accept either.
+
+
+Exim version 4.86
+-----------------
+
+JH/01 Bug 1545: The smtp transport option "retry_include_ip_address" is now
+      expanded.
+
+JH/02 The smtp transport option "multi_domain" is now expanded.
+
+JH/03 The smtp transport now requests PRDR by default, if the server offers
+      it.
+
+JH/04 Certificate name checking on server certificates, when exim is a client,
+      is now done by default.  The transport option tls_verify_cert_hostnames
+      can be used to disable this per-host.  The build option
+      EXPERIMENTAL_CERTNAMES is withdrawn.
+
+JH/05 The value of the tls_verify_certificates smtp transport and main options
+      default to the word "system" to access the system default CA bundle.
+      For GnuTLS, only version 3.0.20 or later.
+
+JH/06 Verification of the server certificate for a TLS connection is now tried
+      (but not required) by default.  The verification status is now logged by
+      default, for both outbound TLS and client-certificate supplying inbound
+      TLS connections
+
+JH/07 Changed the default rfc1413 lookup settings to disable calls.  Few
+      sites use this now.
+
+JH/08 The EXPERIMENTAL_DSN compile option is no longer needed; all Delivery
+      Status Notification (bounce) messages are now MIME format per RFC 3464.
+      Support for RFC 3461 DSN options NOTIFY,ENVID,RET,ORCPT can be advertised
+      under the control of the dsn_advertise_hosts option, and routers may
+      have a dsn_lasthop option.
+
+JH/09 A timeout of 2 minutes is now applied to all malware scanner types by
+      default, modifiable by a malware= option.  The list separator for
+      the options can now be changed in the usual way.  Bug 68.
+
+JH/10 The smtp_receive_timeout main option is now expanded before use.
+
+JH/11 The incoming_interface log option now also enables logging of the
+      local interface on delivery outgoing connections.
+
+JH/12 The cutthrough-routing facility now supports multi-recipient mails,
+      if the interface and destination host and port all match.
+
+JH/13 Bug 344: The verify = reverse_host_lookup ACL condition now accepts a
+      /defer_ok option.
+
+JH/14 Bug 1573: The spam= ACL condition now additionally supports Rspamd.
+      Patch from Andrew Lewis.
+
+JH/15 Bug 670: The spamd_address main option (for the spam= ACL condition)
+      now supports optional time-restrictions, weighting, and priority
+      modifiers per server.  Patch originally by <rommer@active.by>.
+
+JH/16 The spamd_address main option now supports a mixed list of local
+      and remote servers.  Remote servers can be IPv6 addresses, and
+      specify a port-range.
+
+JH/17 Bug 68: The spamd_address main option now supports an optional
+      timeout value per server.
+
+JH/18 Bug 1581: Router and transport options headers_add/remove can
+      now have the list separator specified.
+
+JH/19 Bug 392: spamd_address, and clamd av_scanner, now support retry
+      option values.
+
+JH/20 Bug 1571: Ensure that $tls_in_peerdn is set, when verification fails
+      under OpenSSL.
+
+JH/21 Support for the A6 type of dns record is withdrawn.
+
+JH/22 Bug 608: The result of a QUIT or not-QUIT toplevel ACL now matters
+      rather than the verbs used.
+
+JH/23 Bug 1572: Increase limit on SMTP confirmation message copy size
+      from 255 to 1024 chars.
+
+JH/24 Verification callouts now attempt to use TLS by default.
+
+HS/01 DNSSEC options (dnssec_require_domains, dnssec_request_domains)
+      are generic router options now. The defaults didn't change.
+
+JH/25 Bug 466: Add RFC2322 support for MIME attachment filenames.
+      Original patch from Alexander Shikoff, worked over by JH.
+
+HS/02 Bug 1575: exigrep falls back to autodetection of compressed
+      files if ZCAT_COMMAND is not executable.
+
+JH/26 Bug 1539: Add timeout/retry options on dnsdb lookups.
+
+JH/27 Bug 286: Support SOA lookup in dnsdb lookups.
+
+JH/28 Bug 1588: Do not use the A lookup following an AAAA for setting the FQDN.
+      Normally benign, it bites when the pair was led to by a CNAME;
+      modern usage is to not canonicalize the domain to a CNAME target
+      (and we were inconsistent anyway for A-only vs AAAA+A).
+
+JH/29 Bug 1632: Removed the word "rejected" from line logged for ACL discards.
+
+JH/30 Check the forward DNS lookup for DNSSEC, in addition to the reverse,
+      when evaluating $sender_host_dnssec.
+
+JH/31 Check the HELO verification lookup for DNSSEC, adding new
+      $sender_helo_dnssec variable.
+
+JH/32 Bug 1397: Enable ECDHE on OpenSSL, just the NIST P-256 curve.
+
+JH/33 Bug 1346: Note MAIL cmd seen in -bS batch, to avoid smtp_no_mail log.
+
+JH/34 Bug 1648: Fix a memory leak seen with "mailq" and large queues.
+
+JH/35 Bug 1642: Fix support of $spam_ variables at delivery time.  Was
+      documented as working, but never had.  Support all but $spam_report.
+
+JH/36 Bug 1659: Guard checking of input smtp commands again pseudo-command
+      added for tls authenticator.
+
+HS/03 Add perl_taintmode main config option
+
+
+Exim version 4.85
+-----------------
+
+TL/01 When running the test suite, the README says that variables such as
+      no_msglog_check are global and can be placed anywhere in a specific
+      test's script, however it was observed that placement needed to be near
+      the beginning for it to behave that way. Changed the runtest perl
+      script to read through the entire script once to detect and set these
+      variables, reset to the beginning of the script, and then run through
+      the script parsing/test process like normal.
+
+TL/02 The BSD's have an arc4random API. One of the functions to induce
+      adding randomness was arc4random_stir(), but it has been removed in
+      OpenBSD 5.5. Detect this OpenBSD version and skip calling this
+      function when detected.
+
+JH/01 Expand the EXPERIMENTAL_TPDA feature.  Several different events now
+      cause callback expansion.
+
+TL/03 Bugzilla 1518: Clarify "condition" processing in routers; that
+      syntax errors in an expansion can be treated as a string instead of
+      logging or causing an error, due to the internal use of bool_lax
+      instead of bool when processing it.
+
+JH/02 Add EXPERIMENTAL_DANE, allowing for using the DNS as trust-anchor for
+      server certificates when making smtp deliveries.
+
+JH/03 Support secondary-separator specifier for MX, SRV, TLSA lookups.
+
+JH/04 Add ${sort {list}{condition}{extractor}} expansion item.
+
+TL/04 Bugzilla 1216: Add -M (related messages) option to exigrep.
+
+TL/05 GitHub Issue 18: Adjust logic testing for true/false in redis lookups.
+      Merged patch from Sebastian Wiedenroth.
+
+JH/05 Fix results-pipe from transport process.  Several recipients, combined
+      with certificate use, exposed issues where response data items split
+      over buffer boundaries were not parsed properly.  This eventually
+      resulted in duplicates being sent.  This issue only became common enough
+      to notice due to the introduction of connection certificate information,
+      the item size being so much larger.  Found and fixed by Wolfgang Breyha.
+
+JH/06 Bug 1533: Fix truncation of items in headers_remove lists.  A fixed
+      size buffer was used, resulting in syntax errors when an expansion
+      exceeded it.
+
+JH/07 Add support for directories of certificates when compiled with a GnuTLS
+      version 3.3.6 or later.
+
+JH/08 Rename the TPDA experimental facility to Event Actions.  The #ifdef
+      is EXPERIMENTAL_EVENT, the main-configuration and transport options
+      both become "event_action", the variables become $event_name, $event_data
+      and $event_defer_errno.  There is a new variable $verify_mode, usable in
+      routers, transports and related events.  The tls:cert event is now also
+      raised for inbound connections, if the main configuration event_action
+      option is defined.
+
+TL/06 In test suite, disable OCSP for old versions of openssl which contained
+      early OCSP support, but no stapling (appears to be less than 1.0.0).
+
+JH/09 When compiled with OpenSSL and EXPERIMENTAL_CERTNAMES, the checks on
+      server certificate names available under the smtp transport option
+      "tls_verify_cert_hostname" now do not permit multi-component wildcard
+      matches.
+
+JH/10 Time-related extraction expansions from certificates now use the main
+      option "timezone" setting for output formatting, and are consistent
+      between OpenSSL and GnuTLS compilations.  Bug 1541.
+
+JH/11 Fix a crash in mime ACL when meeting a zero-length, quoted or RFC2047-
+      encoded parameter in the incoming message.  Bug 1558.
+
+JH/12 Bug 1527: Autogrow buffer used in reading spool files.  Since they now
+      include certificate info, eximon was claiming there were spoolfile
+      syntax errors.
+
+JH/13 Bug 1521: Fix ldap lookup for single-attr request, multiple-attr return.
+
+JH/14 Log delivery-related information more consistently, using the sequence
+      "H=<name> [<ip>]" wherever possible.
+
+TL/07 Bug 1547: Omit RFCs from release. Draft and RFCs have licenses which
+      are problematic for Debian distribution, omit them from the release
+      tarball.
+
+JH/15 Updates and fixes to the EXPERIMENTAL_DSN feature.
+
+JH/16 Fix string representation of time values on 64bit time_t architectures.
+      Bug 1561.
+
+JH/17 Fix a null-indirection in certextract expansions when a nondefault
+      output list separator was used.
 
 
 Exim version 4.84
@@ -9,12 +682,12 @@ TL/01 Bugzilla 1506: Re-add a 'return NULL' to silence complaints from static
       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.
+      This was a regression introduced 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.
+      EXPERIMENTAL_DSN is enabled.  Fix from Wolfgang Breyha.
 
 
 Exim version 4.83
@@ -340,7 +1013,7 @@ JH/14 SMTP PRDR (http://www.eric-a-hall.com/specs/draft-hall-prdr-00.txt).
       advertises the facility.  If the client requests PRDR a new
       acl_data_smtp_prdr ACL is called once for each recipient, after
       the body content is received and before the acl_smtp_data ACL.
-      The client is controlled by bolth of: a hosts_try_prdr option
+      The client is controlled by both of: a hosts_try_prdr option
       on the smtp transport, and the server advertisement.
       Default client logging of deliveries and rejections involving
       PRDR are flagged with the string "PRDR".
@@ -368,7 +1041,7 @@ PP/20 Added force_command boolean option to pipe transport.
 JH/15 AUTH support on callouts (and hence cutthrough-deliveries).
       Bugzilla 321, 823.
 
-TF/04 Added udpsend ACL modifer and hexquote expansion operator
+TF/04 Added udpsend ACL modifier and hexquote expansion operator
 
 PP/21 Fix eximon continuous updating with timestamped log-files.
       Broken in a format-string cleanup in 4.80, missed when I repaired the
@@ -487,7 +1160,7 @@ PP/12 MAIL args handles TAB as well as SP, for better interop with
       Analysis and variant patch by Todd Lyons.
 
 NM/04 Bugzilla 1237 - fix cases where printf format usage not indicated
-      Bug report from Lars Müller <lars@samba.org> (via SUSE), 
+      Bug report from Lars Müller <lars@samba.org> (via SUSE),
       Patch from Dirk Mueller <dmueller@suse.com>
 
 PP/13 tls_peerdn now print-escaped for spool files.
@@ -512,7 +1185,7 @@ PP/15 LDAP: Check for errors of TLS initialisation, to give correct
       diagnostics.
       Report and patch from Dmitry Banschikov.
 
-PP/16 Removed "dont_insert_empty_fragments" fron "openssl_options".
+PP/16 Removed "dont_insert_empty_fragments" from "openssl_options".
       Removed SSL_clear() after SSL_new() which led to protocol negotiation
       failures.  We appear to now support TLS1.1+ with Exim.
 
@@ -642,7 +1315,7 @@ TF/04 Improved ratelimit ACL condition.
       has clearer semantics. The /leaky, /strict, and /readonly update modes
       are mutually exclusive. The update mode is no longer included in the
       database key; it just determines when the database is updated. (This
-      means that when you upgrde Exim will forget old rate measurements.)
+      means that when you upgrade Exim will forget old rate measurements.)
 
       Exim now checks that the per_* options are used with an update mode that
       makes sense for the current ACL. For example, when Exim is processing a
@@ -777,7 +1450,7 @@ PP/09 Fix another SIGFPE (x86) in ${eval:...} expansion, this time related to
 Exim version 4.75
 -----------------
 
-NM/01 Workround for PCRE version dependency in version reporting
+NM/01 Workaround for PCRE version dependency in version reporting
       Bugzilla 1073
 
 TF/01 Update valgrind.h and memcheck.h to copies from valgrind-3.6.0.
@@ -847,7 +1520,7 @@ NM/05 Fix to spam.c to accommodate older gcc versions which dislike
       variable declaration deep within a block.  Bug and patch from
       Dennis Davis.
 
-PP/15 lookups-Makefile IRIX compatibilty coercion.
+PP/15 lookups-Makefile IRIX compatibility coercion.
 
 PP/16 Make DISABLE_DKIM build knob functional.
 
@@ -1272,7 +1945,7 @@ PH/02 When an IPv6 address is converted to a string for single-key lookup
       colons if the lookup type is iplsearch. This is not incompatible, because
       previously such lookups could never work.
 
-      The situation is now rather anomolous, since one *can* have colons in
+      The situation is now rather anomalous, since one *can* have colons in
       ordinary lsearch keys. However, making the change in all cases is
       incompatible and would probably break a number of configurations.
 
@@ -1442,7 +2115,7 @@ PH/19 Change 4.64/PH/36 introduced a bug: when address_retry_include_sender
 PH/20 Added hosts_avoid_pipelining to the smtp transport.
 
 PH/21 Long custom messages for fakedefer and fakereject are now split up
-      into multiline reponses in the same way that messages for "deny" and
+      into multiline responses in the same way that messages for "deny" and
       other ACL rejections are.
 
 PH/22 Applied Jori Hamalainen's speed-up changes and typo fixes to exigrep,
@@ -1849,7 +2522,7 @@ PH/36 After a 4xx response to a RCPT error, that address was delayed (in queue
       runs only) independently of the message's sender address. This meant
       that, if the 4xx error was in fact related to the sender, a different
       message to the same recipient with a different sender could confuse
-      things. In particualar, this can happen when sending to a greylisting
+      things. In particular, this can happen when sending to a greylisting
       server, but other circumstances could also provoke similar problems.
       I have changed the default so that the retry time for these errors is now
       based a combination of the sender and recipient addresses. This change
@@ -1918,7 +2591,7 @@ SC/08 Eximstats V1.50
 
 JJ/03 exipick.20061117.2, made header handling as similar to exim as possible
       (added [br]h_ prefixes, implemented RFC2047 decoding.  Fixed
-      whitesspace changes from 4.64-PH/27
+      whitespace changes from 4.64-PH/27
 
 JJ/04 exipick.20061117.2, fixed format and added $message_headers_raw to
       match 4.64-PH/13
@@ -2240,7 +2913,7 @@ PH/14 When a uid/gid is specified for the queryprogram router, it cannot be
 
       (a) Failures to set uid/gid, the current directory, or a process leader
           in a subprocess such as that created by queryprogram now generate
-          suitable debugging ouput when -d is set.
+          suitable debugging output when -d is set.
 
       (b) The queryprogram router detects when it is not running as root,
           outputs suitable debugging information if -d is set, and then runs
@@ -2600,7 +3273,7 @@ PH/09 Applied a patch from the Sieve maintainer which:
       and most important:
 
       o  fixes a bug in processing the envelope test (when testing
-         multiple envelope elements, the last element determinted the
+         multiple envelope elements, the last element determined the
          result)
 
 PH/10 Exim was violating RFC 3834 ("Recommendations for Automatic Responses to
@@ -2609,7 +3282,7 @@ PH/10 Exim was violating RFC 3834 ("Recommendations for Automatic Responses to
         Auto-submitted: auto-generated
 
       in the messages that it generates (bounce messages and others, such as
-      warnings). In the case of bounce messages for non-SMTP mesages, there was
+      warnings). In the case of bounce messages for non-SMTP messages, there was
       also a typo: it was using "Auto_submitted" (underscore instead of
       hyphen). Since every message generated by Exim is necessarily in response
       to another message, thes have all been changed to:
@@ -2974,7 +3647,7 @@ TK/03 Merged latest SRS patch from Miles Wilton.
 PH/05 There's a shambles in IRIX6 - it defines EX_OK in unistd.h which conflicts
       with the definition in sysexits.h (which is #included earlier).
       Fortunately, Exim does not actually use EX_OK. The code used to try to
-      preserve the sysexits.h value, by assumimg that macro definitions were
+      preserve the sysexits.h value, by assuming that macro definitions were
       scanned for macro replacements. I have been disabused of this notion,
       so now the code just undefines EX_OK before #including unistd.h.
 
@@ -2994,7 +3667,7 @@ PH/07 Added "fullpostmaster" verify option, which does a check to <postmaster>
 SC/01 Eximstats: added -xls and the ability to specify output files
      (patch written by Frank Heydlauf).
 
-SC/02 Eximstats: use FileHandles for outputing results.
+SC/02 Eximstats: use FileHandles for outputting results.
 
 SC/03 Eximstats: allow any combination of xls, txt, and html output.
 
@@ -5019,7 +5692,7 @@ Exim version 4.31
 58. When a "warn" ACL statement has a log_message modifier, the message is
     remembered, and not repeated. This is to avoid a lot of repetition when a
     message has many recipients that cause the same warning to be written.
-    Howewer, Exim was preserving the list of already written lines for an
+    However, Exim was preserving the list of already written lines for an
     entire SMTP session, which doesn't seem right. The memory is now reset if a
     new message is started.
 
@@ -5109,7 +5782,7 @@ Exim version 4.31
     the list was checked. (An example that provoked this was putting <; in the
     middle of a list instead of at the start.) If this happened during a DATA
     ACL check, a -D file could be left lying around. This kind of configuration
-    error no longer causes Exim to die; instead it causes a defer errror. The
+    error no longer causes Exim to die; instead it causes a defer error. The
     incident is still logged to the main and panic logs.
 
 74. Buglet left over from Exim 3 conversion. The message "too many messages
@@ -5173,7 +5846,7 @@ Exim version 4.30
     systems (e.g. Solaris), it also passes back the IP address string as the
     "host name". However, on others (e.g. Linux), it passes back an empty
     string. Exim wasn't checking for this, and was changing the host name to an
-    empty string, assuming it had been canonicized.
+    empty string, assuming it had been canonicalized.
 
  5. Although rare, it is permitted to have more than one PTR record for a given
     IP address. I thought that gethostbyaddr() or getipnodebyaddr() always gave
@@ -5225,7 +5898,7 @@ Exim version 4.30
 
 13. The install script calls Exim with "-C /dev/null" in order to find the
     version number. If ALT_CONFIG_PREFIX was set, this caused an error message
-    to be output. Howeve, since Exim outputs its version number before the
+    to be output. However, since Exim outputs its version number before the
     error, it didn't break the script. It just looked ugly. I fixed this by
     always allowing "-C /dev/null" if the caller is root.
 
@@ -5306,7 +5979,7 @@ Exim version 4.30
 
 34. Testing for a connection timeout using "timeout_connect" in the retry rules
     did not work. The code looks as if it has *never* worked, though it appears
-    to have been documented since at least releast 1.62. I have made it work.
+    to have been documented since at least release 1.62. I have made it work.
 
 35. The "timeout_DNS" error in retry rules, also documented since at least
     1.62, also never worked. As it isn't clear exactly what this means, and
@@ -5751,7 +6424,7 @@ Exim version 4.21
 16. Check for letters, digits, hyphens, and dots in the names of dnslist
     domains, and warn by logging if others are found.
 
-17. At least on BSD, alignment is not guarenteed for the array of ifreq's
+17. At least on BSD, alignment is not guaranteed for the array of ifreq's
     returned from GIFCONF when Exim is trying to find the list of interfaces on
     a host. The code in os.c has been modified to copy each ifreq to an aligned
     structure in all cases.
@@ -5785,7 +6458,7 @@ Exim version 4.21
 24. Ignore Sendmail's -Ooption=value command line item.
 
 25. When execve() failed while trying to run a command in a pipe transport,
-    Exim was returning EX_UNAVAILBLE (69) from the subprocess. However, this
+    Exim was returning EX_UNAVAILABLE (69) from the subprocess. However, this
     could be confused with a return value of 69 from the command itself. This
     has been changed to 127, the value the shell returns if it is asked to run
     a non-existent command. The wording for the related log line suggests a
@@ -5898,7 +6571,7 @@ Exim version 4.21
 
 47. Change 50 for 4.20 was a heap of junk. I don't know what I was thinking
     when I implemented it. It didn't allow for the fact that some option values
-    may legitimatetly be negative (e.g. size_addition), and it didn't even do
+    may legitimately be negative (e.g. size_addition), and it didn't even do
     the right test for positive values.
 
 48. Domain names in DNS records are case-independent. Exim always looks them up
diff --git a/doc/DANE-draft-notes b/doc/DANE-draft-notes
new file mode 100644 (file)
index 0000000..21b3992
--- /dev/null
@@ -0,0 +1,11 @@
+
+draft 11
+
+3.1.2 - Para 4 (records with Sel Full(0) are discouraged)
+==> There's a matching type Full but not such a Selector type.
+    Should this be "Cert(0), or Matching Type Full(0)" ?
+    Suspect the latter.
+
+3.1.2 Needs a para added regarding certificate date verification,
+    to contrast with the requirement to NOT check for
+    DANE-EE defined in 3.1.1
index 5c5024a..4ab94c4 100644 (file)
@@ -115,7 +115,7 @@ always been set up specifically, as described in the manual.
 
 5. The way in which Exim scans its queue when split_spool_directory is set has
 changed, but this shouldn't make any noticeable difference. See doc/NewStuff
-for defails.
+for details.
 
 
 Upgrading from release 3.03
index a97d41f..528d94d 100644 (file)
@@ -802,7 +802,7 @@ The smtp transport
 . The authenticate_hosts option has been renamed as hosts_try_auth. A new
   option called hosts_require_auth has been added; if authentication fails for
   one of these hosts, Exim does _not_ try to send unauthenticated. It defers
-  instead. The deferal error is detectable in the retry rules, so this can be
+  instead. The deferral error is detectable in the retry rules, so this can be
   turned into a hard failure if required.
 
 
@@ -1206,7 +1206,7 @@ and the bounce.
 
 The logging options that have been abolished are: log_all_parents,
 log_arguments, log_incoming_port, log_interface, log_ip_options,
-log_level, log_queue_run_level, log_received_sender, log_received_rceipients,
+log_level, log_queue_run_level, log_received_sender, log_received_recipients,
 log_rewrites, log_sender_on_delivery, log_smtp_confirmation,
 log_smtp_connections, log_smtp_syntax_errors, log_subject, tls_log_cipher,
 tls_log_peerdn.
@@ -1323,7 +1323,7 @@ String Expansion
 
 . There's a new expansion feature for running commands:
 
-    ${run{comand args}{yes}{no}}
+    ${run{command args}{yes}{no}}
 
   Like all the other conditional items, the {yes} and {no} strings are
   optional. Omitting both is equivalent to {$value}. The standard output of the
index 1b7ad35..9d9c817 100644 (file)
@@ -3,9 +3,169 @@ New Features in Exim
 
 This file contains descriptions of new features that have been added to Exim.
 Before a formal release, there may be quite a lot of detail so that people can
-test from the snapshots or the CVS before the documentation is updated. Once
+test from the snapshots or the Git before the documentation is updated. Once
 the documentation is updated, this file is reduced to a short list.
 
+Version 4.89
+------------
+
+ 1. Allow relative config file names for ".include"
+
+ 2. A main-section config option "debug_store" to control the checks on
+    variable locations during store-reset.  Normally false but can be enabled
+    when a memory corrution issue is suspected on a production system.
+
+
+Version 4.88
+------------
+
+ 1. The new perl_taintmode option allows to run the embedded perl
+    interpreter in taint mode.
+
+ 2. New log_selector: dnssec, adds a "DS" tag to acceptance and delivery lines.
+
+ 3. Speculative debugging, via a "kill" option to the "control=debug" ACL
+    modifier.
+
+ 4. New expansion item ${sha3:<string>} / ${sha3_<N>:<string>}.
+    N can be 224, 256 (default), 384, 512.
+    With GnuTLS 3.5.0 or later, only.
+
+ 5. Facility for named queues:  A command-line argument can specify
+    the queue name for a queue operation, and an ACL modifier can set
+    the queue to be used for a message.  A $queue_name variable gives
+    visibility.
+
+ 6. New expansion operators base32/base32d.
+
+ 7. The CHUNKING ESMTP extension from RFC 3030.  May give some slight
+    performance increase and network load decrease.  Main config option
+    chunking_advertise_hosts, and smtp transport option hosts_try_chunking
+    for control.
+
+ 8. LMDB lookup support, as Experimental. Patch supplied by Andrew Colin Kissa.
+
+ 9. Expansion operator escape8bit, like escape but not touching newline etc..
+
+10. Feature macros, generated from compile options.  All start with "_HAVE_"
+    and go on with some roughly recognisable name.  Driver macros, for
+    router, transport and authentication drivers; names starting with "_DRIVER_".
+    Option macros, for each configuration-file option; all start with "_OPT_".
+    Use the "-bP macros" command-line option to see what is present.
+
+11. Integer values for options can take a "G" multiplier.
+
+12. defer=pass option for the ACL control cutthrough_delivery, to reflect 4xx
+    returns from the target back to the initiator, rather than spooling the
+    message.
+
+13. New built-in constants available for tls_dhparam and default changed.
+
+14. If built with EXPERIMENTAL_QUEUEFILE, a queuefile transport, for writing
+    out copies of the message spool files for use by 3rd-party scanners.
+
+15. A new option on the smtp transport, hosts_try_fastopen.  If the system
+    supports it (on Linux it must be enabled in the kernel by the sysadmin)
+    try to use RFC 7413 "TCP Fast Open".  No data is sent on the SYN segment
+    but it permits a peer that also supports the facility to send its SMTP
+    banner immediately after the SYN,ACK segment rather then waiting for
+    another ACK - so saving up to one roundtrip time.  Because it requires
+    previous communication with the peer (we save a cookie from it) this
+    will only become active on frequently-contacted destinations.
+
+16. A new syslog_pid option to suppress PID duplication in syslog lines.
+
+
+Version 4.87
+------------
+
+ 1. The ACL conditions regex and mime_regex now capture substrings
+    into numeric variables $regex1 to 9, like the "match" expansion condition.
+
+ 2. New $callout_address variable records the address used for a spam=,
+    malware= or verify= callout.
+
+ 3. Transports now take a "max_parallel" option, to limit concurrency.
+
+ 4. Expansion operators ${ipv6norm:<string>} and ${ipv6denorm:<string>}.
+    The latter expands to a 8-element colon-sep set of hex digits including
+    leading zeroes. A trailing ipv4-style dotted-decimal set is converted
+    to hex.  Pure ipv4 addresses are converted to IPv4-mapped IPv6.
+    The former operator strips leading zeroes and collapses the longest
+    set of 0-groups to a double-colon.
+
+ 5. New "-bP config" support, to dump the effective configuration.
+
+ 6. New $dkim_key_length variable.
+
+ 7. New base64d and base64 expansion items (the existing str2b64 being a
+    synonym of the latter).  Add support in base64 for certificates.
+
+ 8. New main configuration option "bounce_return_linesize_limit" to
+    avoid oversize bodies in bounces. The default value matches RFC
+    limits.
+
+ 9. New $initial_cwd expansion variable.
+
+
+Version 4.86
+------------
+
+ 1. Support for using the system standard CA bundle.
+
+ 2. New expansion items $config_file, $config_dir, containing the file
+    and directory name of the main configuration file. Also $exim_version.
+
+ 3. New "malware=" support for Avast.
+
+ 4. New "spam=" variant option for Rspamd.
+
+ 5. Assorted options on malware= and spam= scanners.
+
+ 6. A command-line option to write a comment into the logfile.
+
+ 7. If built with EXPERIMENTAL_SOCKS feature enabled, the smtp transport can
+    be configured to make connections via socks5 proxies.
+
+ 8. If built with EXPERIMENTAL_INTERNATIONAL, support is included for
+    the transmission of UTF-8 envelope addresses.
+
+ 9. If built with EXPERIMENTAL_INTERNATIONAL, an expansion item for a commonly
+    used encoding of Maildir folder names.
+
+10. A logging option for slow DNS lookups.
+
+11. New ${env {<variable>}} expansion.
+
+12. A non-SMTP authenticator using information from TLS client certificates.
+
+13. Main option "tls_eccurve" for selecting an Elliptic Curve for TLS.
+    Patch originally by Wolfgang Breyha.
+
+14. Main option "dns_trust_aa" for trusting your local nameserver at the
+    same level as DNSSEC.
+
+
+Version 4.85
+------------
+
+ 1. If built with EXPERIMENTAL_DANE feature enabled, Exim will follow the
+    DANE SMTP draft to assess a secure chain of trust of the certificate
+    used to establish the TLS connection based on a TLSA record in the
+    domain of the sender.
+
+ 2. The EXPERIMENTAL_TPDA feature has been renamed to EXPERIMENTAL_EVENT
+    and several new events have been created. The reason is because it has
+    been expanded beyond just firing events during the transport phase. Any
+    existing TPDA transport options will have to be rewritten to use a new
+    $event_name expansion variable in a condition. Refer to the
+    experimental-spec.txt for details and examples.
+
+ 3. The EXPERIMENTAL_CERTNAMES features is an enhancement to verify that
+    server certs used for TLS match the result of the MX lookup. It does
+    not use the same mechanism as DANE.
+
+
 Version 4.84
 ------------
 
@@ -28,7 +188,7 @@ Version 4.83
 
  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
+    and a second regex to extract malware_name.  The mail spoolfile name can
     be included in the command line.
 
  5. The smtp transport now supports options "tls_verify_hosts" and
@@ -57,7 +217,7 @@ Version 4.83
 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
+    Delivery Status Notification messages in MIME format, and negotiate
     DSN features per RFC 3461.
 
 
@@ -112,20 +272,20 @@ Version 4.82
     ignored.
 
  7. New cutthrough routing feature.  Requested by a "control = cutthrough_delivery"
-    ACL modifier; works for single-recipient mails which are recieved on and
+    ACL modifier; works for single-recipient mails which are received on and
     deliverable via SMTP.  Using the connection made for a recipient verify,
     if requested before the verify, or a new one made for the purpose while
     the inbound connection is still active.  The bulk of the mail item is copied
     direct from the inbound socket to the outbound (as well as the spool file).
     When the source notifies the end of data, the data acceptance by the destination
-    is negociated before the acceptance is sent to the source.  If the destination
+    is negotiated before the acceptance is sent to the source.  If the destination
     does not accept the mail item, for example due to content-scanning, the item
     is not accepted from the source and therefore there is no need to generate
     a bounce mail.  This is of benefit when providing a secondary-MX service.
     The downside is that delays are under the control of the ultimate destination
     system not your own.
 
-    The Recieved-by: header on items delivered by cutthrough is generated
+    The Received-by: header on items delivered by cutthrough is generated
     early in reception rather than at the end; this will affect any timestamp
     included.  The log line showing delivery is recorded before that showing
     reception; it uses a new ">>" tag instead of "=>".
@@ -179,14 +339,14 @@ Version 4.82
     "aaaa" and "a" lookups is done and the full set of results returned.
 
 14. New expansion variable $headers_added with content from ACL add_header
-    modifier (but not yet added to messsage).
+    modifier (but not yet added to message).
 
 15. New 8bitmime status logging option for received messages.  Log field "M8S".
 
 16. New authenticated_sender logging option, adding to log field "A".
 
 17. New expansion variables $router_name and $transport_name.  Useful
-    particularly for debug_print as -bt commandline option does not
+    particularly for debug_print as -bt command-line option does not
     require privilege whereas -d does.
 
 18. If built with EXPERIMENTAL_PRDR, per-recipient data responses per a
@@ -210,7 +370,7 @@ Version 4.82
     provided to the authentication method which failed.  It is available
     for use in subsequent ACL processing (typically quit or notquit ACLs).
 
-23. New ACL modifer "udpsend" can construct a UDP packet to send to a given
+23. New ACL modifier "udpsend" can construct a UDP packet to send to a given
     UDP host and port.
 
 24. New ${hexquote:..string..} expansion operator converts non-printable
index ef61956..696b5f3 100644 (file)
@@ -54,7 +54,7 @@ acl_not_smtp_mime                    string*         unset         main
 acl_smtp_auth                        string*         unset         main              4.00
 acl_smtp_connect                     string*         unset         main              4.11
 acl_smtp_data                        string*         unset         main              4.00
-acl_smtp_data_prdr                   string*         unset         main              4.82 with expreimental_prdr
+acl_smtp_data_prdr                   string*         unset         main              4.82 with experimental_prdr
 acl_smtp_dkim                        string*         unset         main              4.70 unless disable_dkim
 acl_smtp_etrn                        string*         unset         main              4.00
 acl_smtp_expn                        string*         unset         main              4.00
@@ -152,6 +152,7 @@ data_timeout                         time            5m            smtp
 debug_print                          string*         unset         authenticators    4.00
                                                      unset         routers           4.00
                                                      unset         transports        2.00
+debug_store                          boolean         false         main                     4.90
 delay_after_cutoff                   boolean         true          smtp
 delay_warning                        time list       24h           main
 delay_warning_condition              string*         +             main              1.73
@@ -181,6 +182,7 @@ dns_check_names_pattern              string          +             main
 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_dane_ok                          integer         -1            main              4.83
 dns_ipv4_lookup                      boolean         false         main              3.20
 dns_qualify_single                   boolean         true          smtp
 dns_retrans                          time            0s            main              1.60
@@ -299,6 +301,7 @@ hosts_require_ocsp                   host list       unset         smtp
 hosts_require_tls                    host list       unset         smtp              3.20
 hosts_treat_as_local                 domain list     unset         main              1.95
 hosts_try_auth                       host list       unset         smtp              4.00
+hosts_try_fastopen                   host list       unset         smtp              4.88
 hosts_try_prdr                       host list       unset         smtp              4.82 if experimental_prdr
 ibase_servers                        string          unset         main              4.23
 ignore_bounce_errors_after           time            0s            main              4.00
@@ -868,7 +871,7 @@ EXIM_MONITOR                 optional     set to eximon.bin to compile
 EXIM_PERL                    optional
 EXIM_USER                    mandatory    user to use for Exim
 EXIWHAT_EGREP_ARG            system**     to find Exim processes from ps
-EXIWHAT_KILL_SIGNAL          system**     -SIGUSER1 or numerical equivalent
+EXIWHAT_KILL_SIGNAL          system**     -SIGUSR1 or numerical equivalent
 EXIWHAT_MULTIKILL_CMD        system**
 EXIWHAT_MULTIKILL_ARG        system**
 EXIWHAT_PS_ARG               system**     to list all processes
@@ -967,7 +970,7 @@ TCP_WRAPPERS_DAEMON_NAME     system*      daemon name used by tcpwrappers librar
 TIMEZONE_DEFAULT             optional     default for timezone option
 TLS_INCLUDE                  optional     path to include files for TLS
 TLS_LIBS                     optional     additional libraries for TLS
-TMPDIR                       system       value for TMPDIR environment variable
+EXIM_TMPDIR                  system       value for TMPDIR environment variable
 TRANSPORT_APPENDFILE         driver       include appendfile transport
 TRANSPORT_AUTOREPLY          driver       include autoreply transport
 TRANSPORT_LMTP               driver       include lmtp transport
index 9b22745..d36998f 100644 (file)
@@ -274,7 +274,7 @@ The draft does not specify how strings using MIME entities are used
 to compose messages.  As a result, different implementations generate
 different mails.  The Exim Sieve implementation splits the reason into
 header and body.  It adds the header to the mail header and uses the body
-as mail body.  Be aware, that other imlementations compose a multipart
+as mail body.  Be aware, that other implementations compose a multipart
 structure with the reason as only part.  Both conform to the specification
 (or lack thereof).
 
diff --git a/doc/cve-2016-9663 b/doc/cve-2016-9663
new file mode 100644 (file)
index 0000000..ffff3db
--- /dev/null
@@ -0,0 +1,95 @@
+CVE ID:     CVE-2016-9963
+Date:       2016-12-15
+Credits:    Bjoern Jacke <bjoern@j3e.de>
+Version(s): 4.69 -> 4.87
+Issue:      If several conditions are met, Exim leaks private information
+            to a remote attacker.
+
+Conditions
+==========
+
+If *all* of the following conditions are met
+
+    Build options
+    -------------
+
+    * Exim is built with DKIM enabled (default for newer versions)
+      exim -bV | grep 'Support.*DKIM'
+
+    Runtime options
+    ---------------
+
+    * Exim uses DKIM signing (transport options dkim_private_key,
+      dkim_domain, and other)
+
+    * The dkim_private_key option names a file containing the key.
+
+      exim -bP transports | grep 'dkim_private_key = .'
+
+    * Exim uses PRDR (transport option hosts_try_prdr) (default
+      since 4.86)
+
+      exim -bP transports | grep 'hosts_try_prdr = .'
+
+      *OR*
+
+      Exim uses the LMTP protocol variant for SMTP transport.
+
+      exim -bP transports | grep 'protocol = lmtp'
+
+    Operation
+    ---------
+
+    * Exim transports a multi-recipient message
+
+    * The destination host supports PRDR
+      OR
+      the message transport uses LMTP
+
+    * One or more recipients are rejected after the DATA phase
+
+Impact
+======
+
+Exim leaks the private DKIM signing key to the log files.  Additionally,
+if the build option EXPERIMENTAL_DSN_INFO=yes is used, the key material
+is included in the bounce message.
+
+Fix
+===
+
+Install a fixed Exim version:
+
+    4.88
+    4.87.1
+
+If you can't install one of the above versions, ask your package
+maintainer for a version containing the backported fix. On request and
+depending on our resources we will support you in backporting the fix.
+(Please note, that Exim project officially doesn't support versions
+prior the current stable version.)
+
+If you think that you MIGHT be affected, we HIGHLY recommend to create
+a new set of DKIM keys and fade out the previous DKIM key soon to make
+sure that a possibly leaked DKIM key can not be misused in the future.
+
+
+Workaround
+==========
+
+Disable PRDR in your outgoing transport(s): set hosts_try_prdr to an
+empty string.
+
+AND do not use the LMTP protocol variant of the SMTP driver.
+
+Indication
+==========
+
+You can check if you where affected already. The mainlog entries look like this:
+
+2016-12-17 09:44:33 10HmaX-0005vi-00 ** baduser@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4]: PRDR error after -----BEGIN RSA PRIVATE KEY-----\nMIICXQIBAAKBgQDXRFf+VhT+lCgFhhSkinZKcFNeRzjYdW8vT29Rbb3NadvTFwAd\n+cVLPFwZL8H5tUD/7JbUPqNTCPxmpgIL+V5T4tEZMorHatvvUM2qfcpQ45IfsZ+Y\ndhbIiAslHCpy4xNxIR3zylgqRUF4+Dtsaqy3a5LhwMiKCLrnzhXk1F1hxwIDAQAB\nAoGAZPokJKQQmRK6a0zn5f8lWemy0airG66KhzDF0Pafb/nWKgDCB02gpJgdw5rJ\nbO7/HI3IeqsfRdYTP7tjfmZtPiPo1mnF7D1rSRspZjOF2yXY/ky7t7c5xChRcSxf\n+69CknwjrfteY9Aj0j6o7N+2w2uvHO+AAq8BHDgXKmPo0SECQQDzQ/glyhNH9tlO\nx+3TTMwwyZUf2mYYosN3Q9NIl3Umz/3+13K5b6Ed6fZvS/XwU55Qf5IBUVj2Fujk\nRv2lbGPpAkEA4okpnzYz5nm1X5WjpJPQPyo8nGEU1A5QfoDbkAvWYvVoYrpWPOx5\nHFpOAHkvSk1Y1vhCUa+zHwiQRBC8OMp6LwJBAOAUK/AjQ792UpWO9DM++pe2F/dP\nZdwrkYG6qFSlrvQhgwXLz5GgkfjMGoRKpDDL1XixCfzMwfVtBPnBqsNGJIECQGYX\nSIGu7L7edMXJ60C9OKluwHf9LGTQuqf4LHsDSq+4Rz3PGhREwePsMqD1/EDxEKt4\noHKtyvyeYF28aQbzARMCQQCRtJlR6vlKhxYL8+xoPrCu3MijKgVruRUcNstXkDZK\nfKQax6vhiMq+0qIiEwLA1wavyLVKZ7Mfag+/4NTcDUVC\n-----END RSA PRIVATE KEY-----\n: 550 PRDR R=<baduser@test.ex> refusal
+
+Even if there is no evidence in the existing log files, that a DKIM key
+leakage happened this might have happened in the past, log files might
+have been deleted already but a key leak could have ended up via mail
+bounce in a user mail box
index e82987b..4de5773 100644 (file)
@@ -157,7 +157,7 @@ This dbm library can be called by Exim in one of two ways: via the ndbm
 compatibility interface, or via its own native interface. There are two
 advantages to doing the latter: (1) you don't run the risk of Exim's seeing the
 "wrong" version of the ndbm.h header, as described above, and (2) the
-performace is better. It is therefore recommended that you set USE_DB=yes in an
+performance is better. It is therefore recommended that you set USE_DB=yes in an
 appropriate Local/Makefile-xxx file. (If you are compiling for just one OS, it
 can go in Local/Makefile itself.)
 
index 0fccbc7..11f8e6b 100644 (file)
@@ -163,7 +163,7 @@ continuations. As in Exim's run time configuration, white space at the start of
 continuation lines is ignored. Each argument or data line is passed through the
 string expansion mechanism, and the result is output. Variable values from the
 configuration file (for example, \fI$qualify_domain\fP) are available, but no
-message\-specific values (such as \fI$sender_domain\fP) are set, because no message
+message\-specific values (such as \fI$message_exim_id\fP) are set, because no message
 is being processed (but see \fB\-bem\fP and \fB\-Mset\fP).
 .sp
 \fBNote\fP: If you use this mechanism to test lookups, and you change the data
@@ -370,7 +370,8 @@ preference to the address taken from the message. The caller of Exim must be a
 trusted user for the sender of a message to be set in this way.
 .TP 10
 \fB\-bmalware\fP <\fIfilename\fP>
-This debugging option causes Exim to scan the given file,
+This debugging option causes Exim to scan the given file or directory
+(depending on the used scanner interface),
 using the malware scanning framework.  The option of \fBav_scanner\fP influences
 this option, so if \fBav_scanner\fP's value is dependent upon an expansion then
 the expansion should have defaults which apply to this invocation.  ACLs are
@@ -419,8 +420,12 @@ users, the output is as in this example:
 .sp
   mysql_servers = <value not displayable>
 .sp
-If \fBconfigure_file\fP is given as an argument, the name of the run time
-configuration file is output.
+If \fBconfig\fP is given as an argument, the config is
+output, as it was parsed, any include file resolved, any comment removed.
+.sp
+If \fBconfig_file\fP is given as an argument, the name of the run time
+configuration file is output. (\fBconfigure_file\fP works too, for
+backward compatibility.)
 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
@@ -453,6 +458,10 @@ using one of the words \fBrouter_list\fP, \fBtransport_list\fP, or
 settings can be obtained by using \fBrouters\fP, \fBtransports\fP, or
 \fBauthenticators\fP.
 .sp
+If \fBenvironment\fP is given as an argument, the set of environment
+variables is output, line by line. Using the \fB\-n\fP flag suppresses the value of the
+variables.
+.sp
 If invoked by an admin user, then \fBmacro\fP, \fBmacro_list\fP and \fBmacros\fP
 are available, similarly to the drivers.  Because macros are sometimes used
 for storing passwords, this option is restricted.
@@ -790,6 +799,7 @@ example:
   exim '\-D ABC = something' ...
 .sp
 \fB\-D\fP may be repeated up to 10 times on a command line.
+Only macro names up to 22 letters long can be set.
 .TP 10
 \fB\-d\fP<\fIdebug options\fP>
 This option causes debugging information to be written to the standard
@@ -1003,6 +1013,16 @@ This option is not intended for use by external callers. It is used internally
 by Exim in conjunction with the \fB\-MC\fP option. It signifies that the
 connection to the remote host has been authenticated.
 .TP 10
+\fB\-MCD\fP
+This option is not intended for use by external callers. It is used internally
+by Exim in conjunction with the \fB\-MC\fP option. It signifies that the
+remote host supports the ESMTP DSN extension.
+.TP 10
+\fB\-MCG\fP
+This option is not intended for use by external callers. It is used internally
+by Exim in conjunction with the \fB\-MC\fP option. It signifies that an
+alternate queue is used, named by the following option.
+.TP 10
 \fB\-MCP\fP
 This option is not intended for use by external callers. It is used internally
 by Exim in conjunction with the \fB\-MC\fP option. It signifies that the server to
@@ -1141,7 +1161,8 @@ for that message.
 \fB\-n\fP
 This option is interpreted by Sendmail to mean "no aliasing".
 For normal modes of operation, it is ignored by Exim.
-When combined with \fB\-bP\fP it suppresses the name of an option from being output.
+When combined with \fB\-bP\fP it makes the output more terse (suppresses
+option names, environment values and config pretty printing).
 .TP 10
 \fB\-O\fP <\fIdata\fP>
 This option is interpreted by Sendmail to mean set option. It is ignored by
@@ -1404,7 +1425,8 @@ configuration option called \fBprod_requires_admin\fP which can be set false to
 relax this restriction (and also the same requirement for the \fB\-M\fP, \fB\-R\fP,
 and \fB\-S\fP options).
 .sp
-The \fB\-q\fP option starts one queue runner process. This scans the queue of
+If other commandline options do not specify an action,
+the \fB\-q\fP option starts one queue runner process. This scans the queue of
 waiting messages, and runs a delivery process for each one in turn. It waits
 for each delivery process to finish before starting the next one. A delivery
 process may not actually do any deliveries if the retry times for the addresses
@@ -1469,6 +1491,21 @@ The \fIl\fP (the letter "ell") flag specifies that only local deliveries are to
 be done. If a message requires any remote deliveries, it remains on the queue
 for later delivery.
 .TP 10
+\fB\-q[q][i][f[f]][l][G<name>[/<time>]]]\fP
+If the \fIG\fP flag and a name is present, the queue runner operates on the
+queue with the given name rather than the default queue.
+The name should not contain a \fI/\fP character.
+For a periodic queue run (see below)
+append to the name a slash and a time value.
+.sp
+If other commandline options specify an action, a \fI\-qG<name>\fP option
+will specify a queue to operate on.
+For example:
+.sp
+  exim \-bp \-qGquarantine
+  mailq \-qGquarantine
+  exim \-qGoffpeak \-Rf @special.domain.example
+.TP 10
 \fB\-q\fP<\fIqflags\fP> <\fIstart id\fP> <\fIend id\fP>
 When scanning the queue, Exim can be made to skip over messages whose ids are
 lexically less than a given value by following the \fB\-q\fP option with a
@@ -1634,6 +1671,12 @@ this option.
 \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.
+.TP 10
+\fB\-z\fP <\fIlog\-line\fP>
+This option writes its argument to Exim's logfile.
+Use is restricted to administrators; the intent is for operational notes.
+Quotes should be used to maintain a multi\-word item as a single argument,
+under most shells.
 .sp
 .
 .SH "SEE ALSO"
index 6657f63..0b1afb2 100644 (file)
@@ -6,7 +6,7 @@ about experimental  features, all  of which  are unstable and
 liable to incompatible change.
 
 
-Brightmail AntiSpam (BMI) suppport
+Brightmail AntiSpam (BMI) support
 --------------------------------------------------------------
 
 Brightmail  AntiSpam  is  a  commercial  package.  Please  see
@@ -42,7 +42,7 @@ These four steps are explained in more details below.
 1) Adding support for BMI at compile time
 
   To compile with BMI support,  you need to link Exim  against
-  the   Brighmail  client   SDK,  consisting   of  a   library
+  the  Brightmail  client   SDK,  consisting   of  a   library
   (libbmiclient_single.so)  and  a  header  file  (bmi_api.h).
   You'll also need to explicitly set a flag in the Makefile to
   include BMI support in the Exim binary. Both can be achieved
@@ -448,16 +448,29 @@ spf_guess = v=spf1 a/16 mx/16 ptr ?all
 would relax host matching rules to a broader network range.
 
 
+A lookup expansion is also available. It takes an email
+address as the key and an IP address as the database:
+
+  ${lookup {username@domain} spf {ip.ip.ip.ip}}
+
+The lookup will return the same result strings as they can appear in
+$spf_result (pass,fail,softfail,neutral,none,err_perm,err_temp).
+Currently, only IPv4 addresses are supported.
+
+
+
 SRS (Sender Rewriting Scheme) Support
 --------------------------------------------------------------
 
 Exiscan  currently  includes SRS  support  via Miles  Wilton's
 libsrs_alt library. The current version of the supported
-library is 0.5.
+library is 0.5, there are reports of 1.0 working.
 
 In order to  use SRS, you  must get a  copy of libsrs_alt from
 
-http://srs.mirtol.com/
+https://opsec.eu/src/srs/
+
+(not the original source, which has disappeared.)
 
 Unpack the tarball, then refer to MTAs/README.EXIM
 to proceed. You need to set
@@ -467,8 +480,10 @@ EXPERIMENTAL_SRS=yes
 in your Local/Makefile.
 
 
+
 DCC Support
 --------------------------------------------------------------
+Distributed Checksum Clearinghouse; http://www.rhyolite.com/dcc/
 
 *) Building exim
 
@@ -538,7 +553,7 @@ Then set something like
 mout-xforward.gmx.net           82.165.159.12
 mout.gmx.net                    212.227.15.16
 
-Use a reasonable IP. eg. one the sending cluster acutally uses.
+Use a reasonable IP. eg. one the sending cluster actually uses.
 
 DMARC Support
 --------------------------------------------------------------
@@ -623,10 +638,10 @@ exim will send these forensic emails.  It's also advised that you
 configure a dmarc_forensic_sender because the default sender address
 construction might be inadequate.
 
-  control = dmarc_forensic_enable
+  control = dmarc_enable_forensic
 
 (AGAIN: You can choose not to send these forensic reports by simply
-not putting the dmarc_forensic_enable control line at any point in
+not putting the dmarc_enable_forensic control line at any point in
 your exim config.  If you don't tell it to send them, it will not
 send them.)
 
@@ -755,398 +770,307 @@ b. Configure, somewhere before the DATA ACL, the control option to
 
   deny    dmarc_status   = reject
           !authenticated = *
-          message        = Message from $domain_used_domain failed sender's DMARC policy, REJECT
-
-
-
-Transport post-delivery actions
---------------------------------------------------------------
-
-An arbitrary per-transport string can be expanded on successful delivery,
-and (for SMTP transports) a second string on deferrals caused by a host error.
-This feature may be used, for example, to write exim internal log information
-(not available otherwise) into a database.
-
-In order to use the feature, you must set
-
-EXPERIMENTAL_TPDA=yes
-
-in your Local/Makefile
-
-and define the expandable strings in the runtime config file, to
-be executed at end of delivery.
-
-Additionally, there are 6 more variables, available at end of
-delivery:
-
-tpda_delivery_ip             IP of host, which has accepted delivery
-tpda_delivery_port           Port of remote host which has accepted delivery
-tpda_delivery_fqdn           FQDN of host, which has accepted delivery
-tpda_delivery_local_part     local part of address being delivered
-tpda_delivery_domain         domain part of address being delivered
-tpda_delivery_confirmation   SMTP confirmation message
-
-In case of a deferral caused by a host-error:
-tpda_defer_errno             Error number
-tpda_defer_errstr            Error string possibly containing more details
-
-The $router_name and $transport_name variables are also usable.
-
-
-To take action after successful deliveries, set the following option
-on any transport of interest.
-
-tpda_delivery_action
-
-An example might look like:
-
-tpda_delivery_action = \
-${lookup pgsql {SELECT * FROM record_Delivery( \
-    '${quote_pgsql:$sender_address_domain}',\
-    '${quote_pgsql:${lc:$sender_address_local_part}}', \
-    '${quote_pgsql:$tpda_delivery_domain}', \
-    '${quote_pgsql:${lc:$tpda_delivery_local_part}}', \
-    '${quote_pgsql:$tpda_delivery_ip}', \
-    '${quote_pgsql:${lc:$tpda_delivery_fqdn}}', \
-    '${quote_pgsql:$message_exim_id}')}}
-
-The string is expanded after the delivery completes and any
-side-effects will happen.  The result is then discarded.
-Note that for complex operations an ACL expansion can be used.
-
+          message        = Message from $dmarc_used_domain failed sender's DMARC policy, REJECT
+
+
+
+DANE
+------------------------------------------------------------
+DNS-based Authentication of Named Entities, as applied
+to SMTP over TLS, provides assurance to a client that
+it is actually talking to the server it wants to rather
+than some attacker operating a Man In The Middle (MITM)
+operation.  The latter can terminate the TLS connection
+you make, and make another one to the server (so both
+you and the server still think you have an encrypted
+connection) and, if one of the "well known" set of
+Certificate Authorities has been suborned - something
+which *has* been seen already (2014), a verifiable
+certificate (if you're using normal root CAs, eg. the
+Mozilla set, as your trust anchors).
+
+What DANE does is replace the CAs with the DNS as the
+trust anchor.  The assurance is limited to a) the possibility
+that the DNS has been suborned, b) mistakes made by the
+admins of the target server.   The attack surface presented
+by (a) is thought to be smaller than that of the set
+of root CAs.
+
+It also allows the server to declare (implicitly) that
+connections to it should use TLS.  An MITM could simply
+fail to pass on a server's STARTTLS.
+
+DANE scales better than having to maintain (and
+side-channel communicate) copies of server certificates
+for every possible target server.  It also scales
+(slightly) better than having to maintain on an SMTP
+client a copy of the standard CAs bundle.  It also
+means not having to pay a CA for certificates.
+
+DANE requires a server operator to do three things:
+1) run DNSSEC.  This provides assurance to clients
+that DNS lookups they do for the server have not
+been tampered with.  The domain MX record applying
+to this server, its A record, its TLSA record and
+any associated CNAME records must all be covered by
+DNSSEC.
+2) add TLSA DNS records.  These say what the server
+certificate for a TLS connection should be.
+3) offer a server certificate, or certificate chain,
+in TLS connections which is traceable to the one
+defined by (one of?) the TSLA records
+
+There are no changes to Exim specific to server-side
+operation of DANE.
+
+The TLSA record for the server may have "certificate
+usage" of DANE-TA(2) or DANE-EE(3).  The latter specifies
+the End Entity directly, i.e. the certificate involved
+is that of the server (and should be the sole one transmitted
+during the TLS handshake); this is appropriate for a
+single system, using a self-signed certificate.
+  DANE-TA usage is effectively declaring a specific CA
+to be used; this might be a private CA or a public,
+well-known one.  A private CA at simplest is just
+a self-signed certificate which is used to sign
+cerver certificates, but running one securely does
+require careful arrangement.  If a private CA is used
+then either all clients must be primed with it, or
+(probably simpler) the server TLS handshake must transmit
+the entire certificate chain from CA to server-certificate.
+If a public CA is used then all clients must be primed with it
+(losing one advantage of DANE) - but the attack surface is
+reduced from all public CAs to that single CA.
+DANE-TA is commonly used for several services and/or
+servers, each having a TLSA query-domain CNAME record,
+all of which point to a single TLSA record.
+
+The TLSA record should have a Selector field of SPKI(1)
+and a Matching Type field of SHA2-512(2).
+
+At the time of writing, https://www.huque.com/bin/gen_tlsa
+is useful for quickly generating TLSA records; and commands like
+
+  openssl x509 -in -pubkey -noout <certificate.pem \
+  | openssl rsa -outform der -pubin 2>/dev/null \
+  | openssl sha512 \
+  | awk '{print $2}'
+
+are workable for 4th-field hashes.
+
+For use with the DANE-TA model, server certificates
+must have a correct name (SubjectName or SubjectAltName).
+
+The use of OCSP-stapling should be considered, allowing
+for fast revocation of certificates (which would otherwise
+be limited by the DNS TTL on the TLSA records).  However,
+this is likely to only be usable with DANE-TA.  NOTE: the
+default of requesting OCSP for all hosts is modified iff
+DANE is in use, to:
+
+  hosts_request_ocsp = ${if or { {= {0}{$tls_out_tlsa_usage}} \
+                                {= {4}{$tls_out_tlsa_usage}} } \
+                         {*}{}}
+
+The (new) variable $tls_out_tlsa_usage is a bitfield with
+numbered bits set for TLSA record usage codes.
+The zero above means DANE was not in use,
+the four means that only DANE-TA usage TLSA records were
+found. If the definition of hosts_request_ocsp includes the
+string "tls_out_tlsa_usage", they are re-expanded in time to
+control the OCSP request.
+
+This modification of hosts_request_ocsp is only done if
+it has the default value of "*".  Admins who change it, and
+those who use hosts_require_ocsp, should consider the interaction
+with DANE in their OCSP settings.
+
+
+For client-side DANE there are two new smtp transport options,
+hosts_try_dane and hosts_require_dane.
+[ should they be domain-based rather than host-based? ]
+
+Hosts_require_dane will result in failure if the target host
+is not DNSSEC-secured.
+
+DANE will only be usable if the target host has DNSSEC-secured
+MX, A and TLSA records.
+
+A TLSA lookup will be done if either of the above options match
+and the host-lookup succeeded using dnssec.
+If a TLSA lookup is done and succeeds, a DANE-verified TLS connection
+will be required for the host.  If it does not, the host will not
+be used; there is no fallback to non-DANE or non-TLS.
+
+If DANE is requested and useable (see above) the following transport
+options are ignored:
+  hosts_require_tls
+  tls_verify_hosts
+  tls_try_verify_hosts
+  tls_verify_certificates
+  tls_crl
+  tls_verify_cert_hostnames
+
+If DANE is not usable, whether requested or not, and CA-anchored
+verification evaluation is wanted, the above variables should be set
+appropriately.
+
+Currently dnssec_request_domains must be active (need to think about that)
+and dnssec_require_domains is ignored.
+
+If verification was successful using DANE then the "CV" item
+in the delivery log line will show as "CV=dane".
+
+There is a new variable $tls_out_dane which will have "yes" if
+verification succeeded using DANE and "no" otherwise (only useful
+in combination with EXPERIMENTAL_EVENT), and a new variable
+$tls_out_tlsa_usage (detailed above).
+
+
+
+DSN extra information
+---------------------
+If compiled with EXPERIMENTAL_DSN_INFO extra information will be added
+to DSN fail messages ("bounces"), when available.  The intent is to aid
+tracing of specific failing messages, when presented with a "bounce"
+complaint and needing to search logs.
+
+
+The remote MTA IP address, with port number if nonstandard.
+Example:
+  Remote-MTA: X-ip; [127.0.0.1]:587
+Rationale:
+  Several addresses may correspond to the (already available)
+  dns name for the remote MTA.
 
-In order to log host deferrals, add the following option to an SMTP
-transport:
+The remote MTA connect-time greeting.
+Example:
+  X-Remote-MTA-smtp-greeting: X-str; 220 the.local.host.name ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+Rationale:
+  This string sometimes presents the remote MTA's idea of its
+  own name, and sometimes identifies the MTA software.
 
-tpda_host_defer_action
+The remote MTA response to HELO or EHLO.
+Example:
+  X-Remote-MTA-helo-response: X-str; 250-the.local.host.name Hello localhost [127.0.0.1]
+Limitations:
+  Only the first line of a multiline response is recorded.
+Rationale:
+  This string sometimes presents the remote MTA's view of
+  the peer IP connecting to it.
+
+The reporting MTA detailed diagnostic.
+Example:
+  X-Exim-Diagnostic: X-str; SMTP error from remote mail server after RCPT TO:<d3@myhost.test.ex>: 550 hard error
+Rationale:
+  This string sometimes give extra information over the
+  existing (already available) Diagnostic-Code field.
 
-This is a private option of the SMTP transport. It is intended to
-log failures of remote hosts. It is executed only when exim has
-attempted to deliver a message to a remote host and failed due to
-an error which doesn't seem to be related to the individual
-message, sender, or recipient address.
-See section 47.2 of the exim documentation for more details on how
-this is determined.
 
-Example:
+Note that non-RFC-documented field names and data types are used.
 
-tpda_host_defer_action = \
-${lookup mysql {insert into delivlog set \
-    msgid = '${quote_mysql:$message_exim_id}', \
-    senderlp = '${quote_mysql:${lc:$sender_address_local_part}}', \
-    senderdom = '${quote_mysql:$sender_address_domain}', \
-    delivlp = '${quote_mysql:${lc:$tpda_delivery_local_part}}', \
-    delivdom = '${quote_mysql:$tpda_delivery_domain}', \
-    delivip = '${quote_mysql:$tpda_delivery_ip}', \
-    delivport = '${quote_mysql:$tpda_delivery_port}', \
-    delivfqdn = '${quote_mysql:$tpda_delivery_fqdn}', \
-    deliverrno = '${quote_mysql:$tpda_defer_errno}', \
-    deliverrstr = '${quote_mysql:$tpda_defer_errstr}' \
-    }}
-
-
-Redis Lookup
---------------------------------------------------------------
 
-Redis is open source advanced key-value data store. This document
-does not explain the fundamentals, you should read and understand how
-it works by visiting the website at http://www.redis.io/.
+LMDB Lookup support
+-------------------
+LMDB is an ultra-fast, ultra-compact, crash-proof key-value embedded data store.
+It is modeled loosely on the BerkeleyDB API. You should read about the feature
+set as well as operation modes at https://symas.com/products/lightning-memory-mapped-database/
 
-Redis lookup support is added via the hiredis library.  Visit:
+LMDB single key lookup support is provided by linking to the LMDB C library.
+The current implementation does not support writing to the LMDB database.
 
-  https://github.com/redis/hiredis
+Visit https://github.com/LMDB/lmdb to download the library or find it in your
+operating systems package repository.
 
-to obtain a copy, or find it in your operating systems package repository.
 If building from source, this description assumes that headers will be in
 /usr/local/include, and that the libraries are in /usr/local/lib.
 
-1. In order to build exim with Redis lookup support add
+1. In order to build exim with LMDB lookup support add or uncomment
 
-EXPERIMENTAL_REDIS=yes
+EXPERIMENTAL_LMDB=yes
 
 to your Local/Makefile. (Re-)build/install exim. exim -d should show
-Experimental_Redis in the line "Support for:".
+Experimental_LMDB in the line "Support for:".
 
-EXPERIMENTAL_REDIS=yes
-LDFLAGS += -lhiredis
+EXPERIMENTAL_LMDB=yes
+LDFLAGS += -llmdb
 # CFLAGS += -I/usr/local/include
 # LDFLAGS += -L/usr/local/lib
 
 The first line sets the feature to include the correct code, and
-the second line says to link the hiredis libraries into the
+the second line says to link the LMDB libraries into the
 exim binary.  The commented out lines should be uncommented if you
-built hiredis from source and installed in the default location.
+built LMDB from source and installed in the default location.
 Adjust the paths if you installed them elsewhere, but you do not
 need to uncomment them if an rpm (or you) installed them in the
 package controlled locations (/usr/include and /usr/lib).
 
+2. Create your LMDB files, you can use the mdb_load utility which is
+part of the LMDB distribution our your favourite language bindings.
 
-2. Use the following global settings to configure Redis lookup support:
+3. Add the single key lookups to your exim.conf file, example lookups
+are below.
 
-Required:
-redis_servers       This option provides a list of Redis servers
-                    and associated connection data, to be used in
-                    conjunction with redis lookups. The option is
-                    only available if Exim is configured with Redis
-                    support.
-
-For example:
+${lookup{$sender_address_domain}lmdb{/var/lib/baruwa/data/db/relaydomains.mdb}{$value}}
+${lookup{$sender_address_domain}lmdb{/var/lib/baruwa/data/db/relaydomains.mdb}{$value}fail}
+${lookup{$sender_address_domain}lmdb{/var/lib/baruwa/data/db/relaydomains.mdb}}
 
-redis_servers = 127.0.0.1/10/ - using database 10 with no password
-redis_servers = 127.0.0.1//password - to make use of the default database of 0 with a password
-redis_servers = 127.0.0.1// - for default database of 0 with no password
 
-3. Once you have the Redis servers defined you can then make use of the
-experimental Redis lookup by specifying ${lookup redis{}} in a lookup query.
+Queuefile transport
+-------------------
+Queuefile is a pseudo transport which does not perform final delivery.
+It simply copies the exim spool files out of the spool directory into
+an external directory retaining the exim spool format.
 
-4. Example usage:
+The spool files can then be processed by external processes and then
+requeued into exim spool directories for final delivery.
 
-(Host List)
-hostlist relay_from_ips = <\n ${lookup redis{SMEMBERS relay_from_ips}}
+The motivation/inspiration for the transport is to allow external
+processes to access email queued by exim and have access to all the
+information which would not be available if the messages were delivered
+to the process in the standard email formats.
 
-Where relay_from_ips is a Redis set which contains entries such as "192.168.0.0/24" "10.0.0.0/8" and so on.
-The result set is returned as
-192.168.0.0/24
-10.0.0.0/8
-..
-.
+The mailscanner package is one of the processes that can take advantage
+of this transport to filter email.
 
-(Domain list)
-domainlist virtual_domains = ${lookup redis {HGET $domain domain}}
+The transport can be used in the same way as the other existing transports,
+i.e by configuring a router to route mail to a transport configured with
+the queuefile driver.
 
-Where $domain is a hash which includes the key 'domain' and the value '$domain'.
+The transport only takes one option:
 
-(Adding or updating an existing key)
-set acl_c_spammer = ${if eq{${lookup redis{SPAMMER_SET}}}{OK}}
+* directory - This is used to specify the directory messages should be
+copied to
 
-Where SPAMMER_SET is a macro and it is defined as
+The generic transport options (body_only, current_directory, disable_logging,
+debug_print, delivery_date_add, envelope_to_add, event_action, group,
+headers_add, headers_only, headers_remove, headers_rewrite, home_directory,
+initgroups, max_parallel, message_size_limit, rcpt_include_affixes,
+retry_use_local_part, return_path, return_path_add, shadow_condition,
+shadow_transport, transport_filter, transport_filter_timeout, user) are
+ignored.
 
-"SET SPAMMER <some_value>"
+Sample configuration:
 
-(Getting a value from Redis)
+(Router)
 
-set acl_c_spam_host = ${lookup redis{GET...}}
+scan:
+   driver = accept
+   transport = scan
 
+(Transport)
 
-Proxy Protocol Support
---------------------------------------------------------------
+scan:
+  driver = queuefile
+  directory = /var/spool/baruwa-scanner/input
 
-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.
+In order to build exim with Queuefile transport support add or uncomment
 
-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).
+EXPERIMENTAL_QUEUEFILE=yes
 
+to your Local/Makefile. (Re-)build/install exim. exim -d should show
+Experimental_QUEUEFILE in the line "Support for:".
 
 
 --------------------------------------------------------------
index 6d8544e..a8947e0 100644 (file)
@@ -4,10 +4,8 @@ Philip Hazel
 
 Copyright (c) 2014 University of Cambridge
 
-+-----------------------------------------------------------------------------+
-+-------------------------------------+--------------------------------+------+
-|Revision 4.84                        |11 Aug 2014                     |PH    |
-+-------------------------------------+--------------------------------+------+
+Revision 4.89  07 Mar 2017 PH
+
 -------------------------------------------------------------------------------
 
 TABLE OF CONTENTS
@@ -79,7 +77,7 @@ TABLE OF CONTENTS
 
 This document describes the user interfaces to Exim's in-built mail filtering
 facilities, and is copyright (c) University of Cambridge 2014. It corresponds
-to Exim version 4.84.
+to Exim version 4.89.
 
 
 1.1 Introduction
diff --git a/doc/openssl.txt b/doc/openssl.txt
new file mode 100644 (file)
index 0000000..5d3da04
--- /dev/null
@@ -0,0 +1,117 @@
+OpenSSL
+=======
+
+The OpenSSL Project documents their supported releases at
+<https://www.openssl.org/policies/releasestrat.html>.  The Exim
+Maintainers are unwilling to try to support Exim built with a
+version of a critical security library which is unmaintained.
+
+Thus as versions of OpenSSL become unsupported by OpenSSL, they become
+unsupported by Exim.  Exim might build with older releases of OpenSSL,
+but that's risky behaviour.
+
+If your operating system vendor continues to ship an older version of
+OpenSSL and is diligently backporting security fixes, and they support
+Exim, then they will be backporting fixes to their packages of Exim too.
+If you wish to stick purely to packages of OpenSSL, then stick to
+packages of Exim too.
+
+If someone maintains "backports", that is worth exploring too.
+
+Note that a number of OSes use Exim with GnuTLS, not OpenSSL.
+
+Otherwise, assuming that your operating system has old OpenSSL, and you
+wish to use current Exim with OpenSSL, then you need to build and
+install your own, without interfering with the system libraries.
+Fortunately, this is easy.
+
+So this only applies if you build Exim yourself.
+
+
+Build
+-----
+
+Extract the current source of OpenSSL.  Change into that directory.
+
+This assumes that `/opt/openssl` is not in use.  If it is, pick
+something else.  `/opt/exim/openssl` perhaps.
+
+    ./config --prefix=/opt/openssl --openssldir=/etc/ssl  \
+        -L/opt/openssl/lib -Wl,-R/opt/openssl/lib         \
+        enable-ssl-trace
+    make
+    make install
+
+You now have an installed OpenSSL under /opt/openssl which will not be
+used by any system programs.
+
+When you copy `src/EDITME` to `Local/Makefile` to make your build edits,
+choose the pkg-config approach in that file, but also tell Exim to add
+the relevant directory into the rpath stamped into the binary:
+
+    SUPPORT_TLS=yes
+    USE_OPENSSL_PC=openssl
+    EXTRALIBS_EXIM=-ldl -Wl,-rpath,/opt/openssl/lib
+
+The -ldl is needed by OpenSSL 1.1+ on Linux and is not needed on most
+other platforms.
+
+Then tell pkg-config how to find the configuration files for your new
+OpenSSL install, and build Exim:
+
+    export PKG_CONFIG_PATH=/opt/openssl/lib/pkgconfig
+    make
+    sudo make install
+
+(From Exim 4.89, you can put that `PKG_CONFIG_PATH` directly into
+ your `Local/Makefile` file.)
+
+
+Confirming
+----------
+
+Run:
+
+    exim -d-all+expand --version
+
+and look for the `Library version: OpenSSL:` lines.
+
+To look at the libraries _probably_ found by the linker, use:
+
+    ldd $(which exim)          # most platforms
+    otool -L $(which exim)     # MacOS
+
+although that does not correctly handle restrictions imposed upon
+executables which are setuid.
+
+If the `chrpath` package is installed, then:
+
+    chrpath -l $(which exim)
+
+will show the DT_RPATH stamped into the binary.
+
+Your `binutils` package should come with `readelf`, so an alternative
+is to run:
+
+    readelf -d $(which exim) | grep RPATH
+
+
+Very Advanced
+-------------
+
+You can not use $ORIGIN for portably packing OpenSSL in with Exim with
+normal Exim builds, because Exim is installed setuid which causes the
+runtime linker to ignore $ORIGIN in DT_RPATH.
+
+_If_ following the steps for a non-setuid Exim, _then_ you can use:
+
+    EXTRALIBS_EXIM=-ldl '-Wl,-rpath,$$ORIGIN/../lib'
+
+The doubled `$$` is needed for the make(1) layer and the quotes needed
+for the shell invoked by make(1) for calling the linker.
+
+Note that this is sufficiently far outside normal that the build-system
+doesn't support it by default; you'll want to drop a symlink to the lib
+directory into the Exim release top-level directory, so that lib exists
+as a sibling to the build-$platform directory.
+
index e400286..0592640 100644 (file)
@@ -2,12 +2,10 @@ Specification of the Exim Mail Transfer Agent
 
 Exim Maintainers
 
-Copyright (c) 2014 University of Cambridge
+Copyright (c) 2017 University of Cambridge
+
+Revision 4.89  07 Mar 2017 EM
 
-+-----------------------------------------------------------------------------+
-+-------------------------------------+--------------------------------+------+
-|Revision 4.84                        |11 Aug 2014                     |EM    |
-+-------------------------------------+--------------------------------+------+
 -------------------------------------------------------------------------------
 
 TABLE OF CONTENTS
@@ -87,20 +85,21 @@ TABLE OF CONTENTS
     6.6. Redefining macros
     6.7. Overriding macro values
     6.8. Example of macro usage
-    6.9. Conditional skips in the configuration file
-    6.10. Common option syntax
-    6.11. Boolean options
-    6.12. Integer values
-    6.13. Octal integer values
-    6.14. Fixed point numbers
-    6.15. Time intervals
-    6.16. String values
-    6.17. Expanded strings
-    6.18. User and group names
-    6.19. List construction
-    6.20. Changing list separators
-    6.21. Empty items in lists
-    6.22. Format of driver configurations
+    6.9. Builtin macros
+    6.10. Conditional skips in the configuration file
+    6.11. Common option syntax
+    6.12. Boolean options
+    6.13. Integer values
+    6.14. Octal integer values
+    6.15. Fixed point numbers
+    6.16. Time intervals
+    6.17. String values
+    6.18. Expanded strings
+    6.19. User and group names
+    6.20. List construction
+    6.21. Changing list separators
+    6.22. Empty items in lists
+    6.23. Format of driver configurations
 
 7. The default configuration file
 
@@ -125,21 +124,23 @@ TABLE OF CONTENTS
     9.8. Lookup caching
     9.9. Quoting lookup data
     9.10. More about dnsdb
-    9.11. Pseudo dnsdb record types
-    9.12. Multiple dnsdb lookups
-    9.13. More about LDAP
-    9.14. Format of LDAP queries
-    9.15. LDAP quoting
-    9.16. LDAP connections
-    9.17. LDAP authentication and control information
-    9.18. Format of data returned by LDAP
-    9.19. More about NIS+
-    9.20. SQL lookups
-    9.21. More about MySQL, PostgreSQL, Oracle, and InterBase
-    9.22. Specifying the server in the query
-    9.23. Special MySQL features
-    9.24. Special PostgreSQL features
-    9.25. More about SQLite
+    9.11. Dnsdb lookup modifiers
+    9.12. Pseudo dnsdb record types
+    9.13. Multiple dnsdb lookups
+    9.14. More about LDAP
+    9.15. Format of LDAP queries
+    9.16. LDAP quoting
+    9.17. LDAP connections
+    9.18. LDAP authentication and control information
+    9.19. Format of data returned by LDAP
+    9.20. More about NIS+
+    9.21. SQL lookups
+    9.22. More about MySQL, PostgreSQL, Oracle, InterBase, and Redis
+    9.23. Specifying the server in the query
+    9.24. Special MySQL features
+    9.25. Special PostgreSQL features
+    9.26. More about SQLite
+    9.27. More about Redis
 
 10. Domain, host, address, and local part lists
 
@@ -369,246 +370,258 @@ TABLE OF CONTENTS
     40.1. Using spa as a server
     40.2. Using spa as a client
 
-41. Encrypted SMTP connections using TLS/SSL
-
-    41.1. Support for the legacy "ssmtp" (aka "smtps") protocol
-    41.2. OpenSSL vs GnuTLS
-    41.3. GnuTLS parameter computation
-    41.4. Requiring specific ciphers in OpenSSL
-    41.5. Requiring specific ciphers or other parameters in GnuTLS
-    41.6. Configuring an Exim server to use TLS
-    41.7. Requesting and verifying client certificates
-    41.8. Revoked certificates
-    41.9. Configuring an Exim client to use TLS
-    41.10. Use of TLS Server Name Indication
-    41.11. Multiple messages on the same encrypted TCP/IP connection
-    41.12. Certificates and all that
-    41.13. Certificate chains
-    41.14. Self-signed certificates
-
-42. Access control lists
-
-    42.1. Testing ACLs
-    42.2. Specifying when ACLs are used
-    42.3. The non-SMTP ACLs
-    42.4. The SMTP connect ACL
-    42.5. The EHLO/HELO ACL
-    42.6. The DATA ACLs
-    42.7. The SMTP DKIM ACL
-    42.8. The SMTP MIME ACL
-    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
-
-    43.1. Scanning for viruses
-    43.2. Scanning with SpamAssassin
-    43.3. Calling SpamAssassin from an Exim ACL
-    43.4. Scanning MIME parts
-    43.5. Scanning with regular expressions
-    43.6. The demime condition
-
-44. Adding a local scan function to Exim
-
-    44.1. Building Exim to use a local scan function
-    44.2. API for local_scan()
-    44.3. Configuration options for local_scan()
-    44.4. Available Exim variables
-    44.5. Structure of header lines
-    44.6. Structure of recipient items
-    44.7. Available Exim functions
-    44.8. More about Exim's memory handling
-
-45. System-wide message filtering
-
-    45.1. Specifying a system filter
-    45.2. Testing a system filter
-    45.3. Contents of a system filter
-    45.4. Additional variable for system filters
-    45.5. Defer, freeze, and fail commands for system filters
-    45.6. Adding and removing headers in a system filter
-    45.7. Setting an errors address in a system filter
-    45.8. Per-address filtering
-
-46. Message processing
-
-    46.1. Submission mode for non-local messages
-    46.2. Line endings
-    46.3. Unqualified addresses
-    46.4. The UUCP From line
-    46.5. Resent- header lines
-    46.6. The Auto-Submitted: header line
-    46.7. The Bcc: header line
-    46.8. The Date: header line
-    46.9. The Delivery-date: header line
-    46.10. The Envelope-to: header line
-    46.11. The From: header line
-    46.12. The Message-ID: header line
-    46.13. The Received: header line
-    46.14. The References: header line
-    46.15. The Return-path: header line
-    46.16. The Sender: header line
-    46.17. Adding and removing header lines in routers and transports
-    46.18. Constructed addresses
-    46.19. Case of local parts
-    46.20. Dots in local parts
-    46.21. Rewriting addresses
-
-47. SMTP processing
-
-    47.1. Outgoing SMTP and LMTP over TCP/IP
-    47.2. Errors in outgoing SMTP
-    47.3. Incoming SMTP messages over TCP/IP
-    47.4. Unrecognized SMTP commands
-    47.5. Syntax and protocol errors in SMTP commands
-    47.6. Use of non-mail SMTP commands
-    47.7. The VRFY and EXPN commands
-    47.8. The ETRN command
-    47.9. Incoming local SMTP
-    47.10. Outgoing batched SMTP
-    47.11. Incoming batched SMTP
-
-48. Customizing bounce and warning messages
-
-    48.1. Customizing bounce messages
-    48.2. Customizing warning messages
-
-49. Some common configuration settings
-
-    49.1. Sending mail to a smart host
-    49.2. Using Exim to handle mailing lists
-    49.3. Syntax errors in mailing lists
-    49.4. Re-expansion of mailing lists
-    49.5. Closed mailing lists
-    49.6. Variable Envelope Return Paths (VERP)
-    49.7. Virtual domains
-    49.8. Multiple user mailboxes
-    49.9. Simplified vacation processing
-    49.10. Taking copies of mail
-    49.11. Intermittently connected hosts
-    49.12. Exim on the upstream server host
-    49.13. Exim on the intermittently connected client host
-
-50. Using Exim as a non-queueing client
-51. Log files
-
-    51.1. Where the logs are written
-    51.2. Logging to local files that are periodically "cycled"
-    51.3. Datestamped log files
-    51.4. Logging to syslog
-    51.5. Log line flags
-    51.6. Logging message reception
-    51.7. Logging deliveries
-    51.8. Discarded deliveries
-    51.9. Deferred deliveries
-    51.10. Delivery failures
-    51.11. Fake deliveries
-    51.12. Completion
-    51.13. Summary of Fields in Log Lines
-    51.14. Other log entries
-    51.15. Reducing or increasing what is logged
-    51.16. Message log
-
-52. Exim utilities
-
-    52.1. Finding out what Exim processes are doing (exiwhat)
-    52.2. Selective queue listing (exiqgrep)
-    52.3. Summarizing the queue (exiqsumm)
-    52.4. Extracting specific information from the log (exigrep)
-    52.5. Selecting messages by various criteria (exipick)
-    52.6. Cycling log files (exicyclog)
-    52.7. Mail statistics (eximstats)
-    52.8. Checking access policy (exim_checkaccess)
-    52.9. Making DBM files (exim_dbmbuild)
-    52.10. Finding individual retry times (exinext)
-    52.11. Hints database maintenance
-    52.12. exim_dumpdb
-    52.13. exim_tidydb
-    52.14. exim_fixdb
-    52.15. Mailbox maintenance (exim_lock)
-
-53. The Exim monitor
-
-    53.1. Running the monitor
-    53.2. The stripcharts
-    53.3. Main action buttons
-    53.4. The log display
-    53.5. The queue display
-    53.6. The queue menu
-
-54. Security considerations
-
-    54.1. Building a more "hardened" Exim
-    54.2. Root privilege
-    54.3. Running Exim without privilege
-    54.4. Delivering to local files
-    54.5. Running local commands
-    54.6. Trust in configuration data
-    54.7. IPv4 source routing
-    54.8. The VRFY, EXPN, and ETRN commands in SMTP
-    54.9. Privileged users
-    54.10. Spool files
-    54.11. Use of argv[0]
-    54.12. Use of %f formatting
-    54.13. Embedded Exim path
-    54.14. Dynamic module directory
-    54.15. Use of sprintf()
-    54.16. Use of debug_printf() and log_write()
-    54.17. Use of strcat() and strcpy()
-
-55. Format of spool files
-
-    55.1. Format of the -H file
-
-56. Support for DKIM (DomainKeys Identified Mail)
-
-    56.1. Signing outgoing messages
-    56.2. Verifying DKIM signatures in incoming mail
-
-57. Adding new drivers or lookup types
+41. The tls authenticator
+42. Encrypted SMTP connections using TLS/SSL
+
+    42.1. Support for the legacy "ssmtp" (aka "smtps") protocol
+    42.2. OpenSSL vs GnuTLS
+    42.3. GnuTLS parameter computation
+    42.4. Requiring specific ciphers in OpenSSL
+    42.5. Requiring specific ciphers or other parameters in GnuTLS
+    42.6. Configuring an Exim server to use TLS
+    42.7. Requesting and verifying client certificates
+    42.8. Revoked certificates
+    42.9. Configuring an Exim client to use TLS
+    42.10. Use of TLS Server Name Indication
+    42.11. Multiple messages on the same encrypted TCP/IP connection
+    42.12. Certificates and all that
+    42.13. Certificate chains
+    42.14. Self-signed certificates
+
+43. Access control lists
+
+    43.1. Testing ACLs
+    43.2. Specifying when ACLs are used
+    43.3. The non-SMTP ACLs
+    43.4. The SMTP connect ACL
+    43.5. The EHLO/HELO ACL
+    43.6. The DATA ACLs
+    43.7. The SMTP DKIM ACL
+    43.8. The SMTP MIME ACL
+    43.9. The SMTP PRDR ACL
+    43.10. The QUIT ACL
+    43.11. The not-QUIT ACL
+    43.12. Finding an ACL to use
+    43.13. ACL return codes
+    43.14. Unset ACL options
+    43.15. Data for message ACLs
+    43.16. Data for non-message ACLs
+    43.17. Format of an ACL
+    43.18. ACL verbs
+    43.19. ACL variables
+    43.20. Condition and modifier processing
+    43.21. ACL modifiers
+    43.22. Use of the control modifier
+    43.23. Summary of message fixup control
+    43.24. Adding header lines in ACLs
+    43.25. Removing header lines in ACLs
+    43.26. ACL conditions
+    43.27. Using DNS lists
+    43.28. Specifying the IP address for a DNS list lookup
+    43.29. DNS lists keyed on domain names
+    43.30. Multiple explicit keys for a DNS list
+    43.31. Data returned by DNS lists
+    43.32. Variables set from DNS lists
+    43.33. Additional matching conditions for DNS lists
+    43.34. Negated DNS matching conditions
+    43.35. Handling multiple DNS records from a DNS list
+    43.36. Detailed information from merged DNS lists
+    43.37. DNS lists and IPv6
+    43.38. Rate limiting incoming messages
+    43.39. Ratelimit options for what is being measured
+    43.40. Ratelimit update modes
+    43.41. Ratelimit options for handling fast clients
+    43.42. Limiting the rate of different events
+    43.43. Using rate limiting
+    43.44. Address verification
+    43.45. Callout verification
+    43.46. Additional parameters for callouts
+    43.47. Callout caching
+    43.48. Sender address verification reporting
+    43.49. Redirection while verifying
+    43.50. Client SMTP authorization (CSA)
+    43.51. Bounce address tag validation
+    43.52. Using an ACL to control relaying
+    43.53. Checking a relay configuration
+
+44. Content scanning at ACL time
+
+    44.1. Scanning for viruses
+    44.2. Scanning with SpamAssassin and Rspamd
+    44.3. Calling SpamAssassin from an Exim ACL
+    44.4. Scanning MIME parts
+    44.5. Scanning with regular expressions
+
+45. Adding a local scan function to Exim
+
+    45.1. Building Exim to use a local scan function
+    45.2. API for local_scan()
+    45.3. Configuration options for local_scan()
+    45.4. Available Exim variables
+    45.5. Structure of header lines
+    45.6. Structure of recipient items
+    45.7. Available Exim functions
+    45.8. More about Exim's memory handling
+
+46. System-wide message filtering
+
+    46.1. Specifying a system filter
+    46.2. Testing a system filter
+    46.3. Contents of a system filter
+    46.4. Additional variable for system filters
+    46.5. Defer, freeze, and fail commands for system filters
+    46.6. Adding and removing headers in a system filter
+    46.7. Setting an errors address in a system filter
+    46.8. Per-address filtering
+
+47. Message processing
+
+    47.1. Submission mode for non-local messages
+    47.2. Line endings
+    47.3. Unqualified addresses
+    47.4. The UUCP From line
+    47.5. Resent- header lines
+    47.6. The Auto-Submitted: header line
+    47.7. The Bcc: header line
+    47.8. The Date: header line
+    47.9. The Delivery-date: header line
+    47.10. The Envelope-to: header line
+    47.11. The From: header line
+    47.12. The Message-ID: header line
+    47.13. The Received: header line
+    47.14. The References: header line
+    47.15. The Return-path: header line
+    47.16. The Sender: header line
+    47.17. Adding and removing header lines in routers and transports
+    47.18. Constructed addresses
+    47.19. Case of local parts
+    47.20. Dots in local parts
+    47.21. Rewriting addresses
+
+48. SMTP processing
+
+    48.1. Outgoing SMTP and LMTP over TCP/IP
+    48.2. Errors in outgoing SMTP
+    48.3. Incoming SMTP messages over TCP/IP
+    48.4. Unrecognized SMTP commands
+    48.5. Syntax and protocol errors in SMTP commands
+    48.6. Use of non-mail SMTP commands
+    48.7. The VRFY and EXPN commands
+    48.8. The ETRN command
+    48.9. Incoming local SMTP
+    48.10. Outgoing batched SMTP
+    48.11. Incoming batched SMTP
+
+49. Customizing bounce and warning messages
+
+    49.1. Customizing bounce messages
+    49.2. Customizing warning messages
+
+50. Some common configuration settings
+
+    50.1. Sending mail to a smart host
+    50.2. Using Exim to handle mailing lists
+    50.3. Syntax errors in mailing lists
+    50.4. Re-expansion of mailing lists
+    50.5. Closed mailing lists
+    50.6. Variable Envelope Return Paths (VERP)
+    50.7. Virtual domains
+    50.8. Multiple user mailboxes
+    50.9. Simplified vacation processing
+    50.10. Taking copies of mail
+    50.11. Intermittently connected hosts
+    50.12. Exim on the upstream server host
+    50.13. Exim on the intermittently connected client host
+
+51. Using Exim as a non-queueing client
+52. Log files
+
+    52.1. Where the logs are written
+    52.2. Logging to local files that are periodically "cycled"
+    52.3. Datestamped log files
+    52.4. Logging to syslog
+    52.5. Log line flags
+    52.6. Logging message reception
+    52.7. Logging deliveries
+    52.8. Discarded deliveries
+    52.9. Deferred deliveries
+    52.10. Delivery failures
+    52.11. Fake deliveries
+    52.12. Completion
+    52.13. Summary of Fields in Log Lines
+    52.14. Other log entries
+    52.15. Reducing or increasing what is logged
+    52.16. Message log
+
+53. Exim utilities
+
+    53.1. Finding out what Exim processes are doing (exiwhat)
+    53.2. Selective queue listing (exiqgrep)
+    53.3. Summarizing the queue (exiqsumm)
+    53.4. Extracting specific information from the log (exigrep)
+    53.5. Selecting messages by various criteria (exipick)
+    53.6. Cycling log files (exicyclog)
+    53.7. Mail statistics (eximstats)
+    53.8. Checking access policy (exim_checkaccess)
+    53.9. Making DBM files (exim_dbmbuild)
+    53.10. Finding individual retry times (exinext)
+    53.11. Hints database maintenance
+    53.12. exim_dumpdb
+    53.13. exim_tidydb
+    53.14. exim_fixdb
+    53.15. Mailbox maintenance (exim_lock)
+
+54. The Exim monitor
+
+    54.1. Running the monitor
+    54.2. The stripcharts
+    54.3. Main action buttons
+    54.4. The log display
+    54.5. The queue display
+    54.6. The queue menu
+
+55. Security considerations
+
+    55.1. Building a more "hardened" Exim
+    55.2. Root privilege
+    55.3. Running Exim without privilege
+    55.4. Delivering to local files
+    55.5. Running local commands
+    55.6. Trust in configuration data
+    55.7. IPv4 source routing
+    55.8. The VRFY, EXPN, and ETRN commands in SMTP
+    55.9. Privileged users
+    55.10. Spool files
+    55.11. Use of argv[0]
+    55.12. Use of %f formatting
+    55.13. Embedded Exim path
+    55.14. Dynamic module directory
+    55.15. Use of sprintf()
+    55.16. Use of debug_printf() and log_write()
+    55.17. Use of strcat() and strcpy()
+
+56. Format of spool files
+
+    56.1. Format of the -H file
+
+57. Support for DKIM (DomainKeys Identified Mail)
+
+    57.1. Signing outgoing messages
+    57.2. Verifying DKIM signatures in incoming mail
+
+58. Proxies
+
+    58.1. Inbound proxies
+    58.2. Outbound proxies
+    58.3. Logging
+
+59. Internationalisation
+
+    59.1. MTA operations
+    59.2. MDA operations
+
+60. Events
+61. Adding new drivers or lookup types
 
 
 
@@ -657,8 +670,8 @@ ACKNOWLEDGMENTS, in which I have started recording the names of contributors.
 1.1 Exim documentation
 ----------------------
 
-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
+This edition of the Exim specification applies to version 4.89 of Exim.
+Substantive changes from the 4.88 edition are marked in some renditions of the
 document; this paragraph is so marked if the rendition is capable of showing a
 change indicator.
 
@@ -711,6 +724,7 @@ experimental.txt documentation of experimental features
 filter.txt       specification of the filter language
 Exim3.upgrade    upgrade notes from release 2 to release 3
 Exim4.upgrade    upgrade notes from release 3 to release 4
+openssl.txt      installing a current OpenSSL release
 
 The main specification and the specification of the filtering language are also
 available in other formats (HTML, PostScript, PDF, and Texinfo). Section 1.6
@@ -1133,7 +1147,7 @@ specifying policy controls on incoming mail:
     remote host. However, the most common places are after each RCPT command,
     and at the very end of the message. The sysadmin can specify conditions for
     accepting or rejecting individual recipients or the entire message,
-    respectively, at these two points (see chapter 42). Denial of access
+    respectively, at these two points (see chapter 43). Denial of access
     results in an SMTP error code.
 
   * An ACL is also available for locally generated, non-SMTP messages. In this
@@ -1147,7 +1161,7 @@ specifying policy controls on incoming mail:
   * When a message has been received, either from a remote host or from the
     local host, but before the final acknowledgment has been sent, a locally
     supplied C function called local_scan() can be run to inspect the message
-    and decide whether to accept it or not (see chapter 44). If the message is
+    and decide whether to accept it or not (see chapter 45). If the message is
     accepted, the list of recipients can be modified by the function.
 
   * Using the local_scan() mechanism is another way of calling external scanner
@@ -1155,7 +1169,7 @@ specifying policy controls on incoming mail:
     Exim to be compiled with the content-scanning extension.
 
   * After a message has been accepted, a further checking mechanism is
-    available in the form of the system filter (see chapter 45). This runs at
+    available in the form of the system filter (see chapter 46). This runs at
     the start of every delivery process.
 
 
@@ -1269,7 +1283,7 @@ checking by the non-SMTP ACL, if one is defined. Messages received using SMTP
 (either over TCP/IP, or interacting with a local process) can be checked by a
 number of ACLs that operate at different times during the SMTP session. Either
 individual recipients, or the entire message, can be rejected if local policy
-requirements are not met. The local_scan() function (see chapter 44) is run for
+requirements are not met. The local_scan() function (see chapter 45) is run for
 all incoming messages.
 
 Exim can be configured not to start a delivery process when a message is
@@ -1303,7 +1317,7 @@ The envelope information consists of the address of the message's sender and
 the addresses of the recipients. This information is entirely separate from any
 addresses contained in the header lines. The status of the message includes a
 list of recipients who have already received the message. The format of the
-first spool file is described in chapter 55.
+first spool file is described in chapter 56.
 
 Address rewriting that is specified in the rewrite section of the configuration
 (see chapter 31) is done once and for all on incoming addresses, both in the
@@ -1337,7 +1351,7 @@ frozen bounces, the second to any frozen messages.
 
 While Exim is working on a message, it writes information about each delivery
 attempt to its main log file. This includes successful, unsuccessful, and
-delayed deliveries for each recipient (see chapter 51). The log lines are also
+delayed deliveries for each recipient (see chapter 52). The log lines are also
 written to a separate message log file for each message. These logs are solely
 for the benefit of the administrator, and are normally deleted along with the
 spool files when processing of a message is complete. The use of individual
@@ -1607,7 +1621,7 @@ When a message is to be delivered, the sequence of events is as follows:
     interfaces to mail filtering. (Note: Sieve cannot be used for system filter
     files.)
 
-    Some additional features are available in system filters - see chapter 45
+    Some additional features are available in system filters - see chapter 46
     for details. Note that a message is passed to the system filter only once
     per delivery attempt, however many recipients it has. However, if there are
     several delivery attempts because one or more addresses could not be
@@ -1732,7 +1746,7 @@ delivery attempt are listed in a single message. If the original message has
 many recipients, it is possible for some addresses to fail in one delivery
 attempt and others to fail subsequently, giving rise to more than one bounce
 message. The wording of bounce messages can be customized by the administrator.
-See chapter 48 for details.
+See chapter 49 for details.
 
 Bounce messages contain an X-Failed-Recipients: header line that lists the
 failed addresses, for the benefit of programs that try to analyse such messages
@@ -1743,7 +1757,7 @@ obtained from the message's envelope. For incoming SMTP messages, this is the
 address given in the MAIL command. However, when an address is expanded via a
 forward or alias file, an alternative address can be specified for delivery
 failures of the generated addresses. For a mailing list expansion (see section
-49.2) it is common to direct bounce messages to the manager of the list.
+50.2) it is common to direct bounce messages to the manager of the list.
 
 
 3.17 Failures to deliver bounce messages
@@ -1766,7 +1780,7 @@ only a short time (see timeout_frozen_after and ignore_bounce_errors_after).
 
 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.84) into which the following files are placed:
+exim-4.89) into which the following files are placed:
 
     ACKNOWLEDGMENTS contains some acknowledgments
     CHANGES         contains a reference to where changes are documented
@@ -1940,7 +1954,7 @@ facilities, you need to set
 WITH_CONTENT_SCAN=yes
 
 in your Local/Makefile. For details of the facilities themselves, see chapter
-43.
+44.
 
 If you are going to build the Exim monitor, a similar configuration process is
 required. The file exim_monitor/EDITME must be edited appropriately for your
@@ -1963,8 +1977,8 @@ described RFC 2047. This makes it possible to transmit characters that are not
 in the ASCII character set, and to label them as being in a particular
 character set. When Exim is inspecting header lines by means of the $h_
 mechanism, it decodes them, and translates them into a specified character set
-(default ISO-8859-1). The translation is possible only if the operating system
-supports the iconv() function.
+(default is set at build time). The translation is possible only if the
+operating system supports the iconv() function.
 
 However, some of the operating systems that supply iconv() do not support very
 many conversions. The GNU libiconv library (available from http://www.gnu.org/
@@ -2029,7 +2043,7 @@ USE_GNUTLS_PC=gnutls
 
 You do not need to set TLS_INCLUDE if the relevant directory is already
 specified in INCLUDE. Details of how to configure Exim to make use of TLS are
-given in chapter 41.
+given in chapter 42.
 
 
 4.8 Use of tcpwrappers
@@ -2073,10 +2087,8 @@ Two different types of DNS record for handling IPv6 addresses have been
 defined. AAAA records (analogous to A records for IPv4) are in use, and are
 currently seen as the mainstream. Another record type called A6 was proposed as
 better than AAAA because it had more flexibility. However, it was felt to be
-over-complex, and its status was reduced to "experimental". It is not known if
-anyone is actually using A6 records. Exim has support for A6 records, but this
-is included only if you set "SUPPORT_A6=YES" in Local/Makefile. The support has
-not been tested for some time.
+over-complex, and its status was reduced to "experimental". Exim used to have a
+compile option for including A6 record support but this has now been withdrawn.
 
 
 4.10 Dynamically loaded lookup module support
@@ -2112,9 +2124,6 @@ types, and creates a build directory if one does not exist. For example, on a
 Sun system running Solaris 8, the directory build-SunOS5-5.8-sparc is created. 
 Symbolic links to relevant source files are installed in the build directory.
 
-Warning: The -j (parallel) flag must not be used with make; the building
-process fails if it is set.
-
 If this is the first time make has been run, it calls a script that builds a
 make file inside the build directory, using the configuration files from the
 Local directory. The new make file is then passed to another instance of make.
@@ -2327,7 +2336,7 @@ The Exim binary is required to be owned by root and have the setuid bit set,
 for normal configurations. Therefore, you must run "make install" as root so
 that it can set up the Exim binary in this way. However, in some special
 situations (for example, if a host is doing no local deliveries) it may be
-possible to run Exim without making the binary setuid root (see chapter 54 for
+possible to run Exim without making the binary setuid root (see chapter 55 for
 details).
 
 Exim's run time configuration file is named by the CONFIGURE_FILE setting in
@@ -2376,7 +2385,7 @@ when you have set INFO_DIRECTORY, as described in section 4.17 below.
 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.84-1. The script then arranges for a symbolic link called
+for example exim-4.89-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).
@@ -2768,8 +2777,8 @@ brief message about itself and exits.
     at the start of continuation lines is ignored. Each argument or data line
     is passed through the string expansion mechanism, and the result is output.
     Variable values from the configuration file (for example, $qualify_domain)
-    are available, but no message-specific values (such as $sender_domain) are
-    set, because no message is being processed (but see -bem and -Mset).
+    are available, but no message-specific values (such as $message_exim_id)
+    are set, because no message is being processed (but see -bem and -Mset).
 
     Note: If you use this mechanism to test lookups, and you change the data
     files or databases you are using, you must exit and restart Exim before
@@ -2885,7 +2894,7 @@ brief message about itself and exits.
     actually perform an ident callout when testing using -bh because there is
     no incoming SMTP connection.
 
-    Warning 2: Address verification callouts (see section 42.45) are also
+    Warning 2: Address verification callouts (see section 43.45) are also
     skipped when testing using -bh. If you want these callouts to occur, use 
     -bhc instead.
 
@@ -2898,7 +2907,7 @@ brief message about itself and exits.
 
     The exim_checkaccess utility is a "packaged" version of -bh whose output
     just states whether a given recipient address from a given host is
-    acceptable or not. See section 52.8.
+    acceptable or not. See section 53.8.
 
     Features such as authentication and encryption, where the client input is
     not plain text, cannot easily be tested with -bh. Instead, you should use a
@@ -2963,7 +2972,7 @@ brief message about itself and exits.
     this for special cases.
 
     Policy checks on the contents of local messages can be enforced by means of
-    the non-SMTP ACL. See chapter 42 for details.
+    the non-SMTP ACL. See chapter 43 for details.
 
     The return code is zero if the message is successfully accepted. Otherwise,
     the action is controlled by the -oex option setting - see below.
@@ -2987,8 +2996,9 @@ brief message about itself and exits.
 
 -bmalware <filename>
 
-    This debugging option causes Exim to scan the given file, using the malware
-    scanning framework. The option of av_scanner influences this option, so if 
+    This debugging option causes Exim to scan the given file or directory
+    (depending on the used scanner interface), using the malware scanning
+    framework. The option of av_scanner influences this option, so if 
     av_scanner's value is dependent upon an expansion then the expansion should
     have defaults which apply to this invocation. ACLs are not invoked, so if 
     av_scanner references an ACL variable then that variable will never be
@@ -3038,10 +3048,13 @@ brief message about itself and exits.
 
     mysql_servers = <value not displayable>
 
-    If configure_file is given as an argument, the name of the run time
-    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.
+    If config is given as an argument, the config is output, as it was parsed,
+    any include file resolved, any comment removed.
+
+    If config_file is given as an argument, the name of the run time
+    configuration file is output. (configure_file works too, for backward
+    compatibility.) If a list of configuration files was supplied, the value
+    that is output here is the name of the file that was actually used.
 
     If the -n flag is given, then for most modes of -bP operation the name will
     not be output.
@@ -3071,6 +3084,10 @@ brief message about itself and exits.
     authenticator_list, and a complete list of all drivers with their option
     settings can be obtained by using routers, transports, or authenticators.
 
+    If environment is given as an argument, the set of environment variables is
+    output, line by line. Using the -n flag suppresses the value of the
+    variables.
+
     If invoked by an admin user, then macro, macro_list and macros are
     available, similarly to the drivers. Because macros are sometimes used for
     storing passwords, this option is restricted. The output format is one item
@@ -3189,7 +3206,7 @@ brief message about itself and exits.
     follow.
 
     As for other local message submissions, the contents of incoming batch SMTP
-    messages can be checked using the non-SMTP ACL (see chapter 42).
+    messages can be checked using the non-SMTP ACL (see chapter 43).
     Unqualified addresses are automatically qualified using qualify_domain and 
     qualify_recipient, as appropriate, unless the -bnq option is used.
 
@@ -3202,13 +3219,13 @@ brief message about itself and exits.
     error was detected; it is 1 if one or more messages were accepted before
     the error was detected; otherwise it is 2.
 
-    More details of input using batched SMTP are given in section 47.11.
+    More details of input using batched SMTP are given in section 48.11.
 
 -bs
 
     This option causes Exim to accept one or more messages by reading SMTP
     commands on the standard input, and producing SMTP replies on the standard
-    output. SMTP policy controls, as defined in ACLs (see chapter 42) are
+    output. SMTP policy controls, as defined in ACLs (see chapter 43) are
     applied. Some user agents use this interface as a way of passing
     locally-generated messages to the MTA.
 
@@ -3289,7 +3306,7 @@ brief message about itself and exits.
     is taken as a recipient address to be verified by the routers. (This does
     not involve any verification callouts). During normal operation,
     verification happens mostly as a consequence processing a verify condition
-    in an ACL (see chapter 42). If you want to test an entire ACL, possibly
+    in an ACL (see chapter 43). If you want to test an entire ACL, possibly
     including callouts, see the -bh and -bhc options.
 
     If verification fails, and the caller is not an admin user, no details of
@@ -3427,7 +3444,8 @@ brief message about itself and exits.
 
     exim '-D ABC = something' ...
 
-    -D may be repeated up to 10 times on a command line.
+    -D may be repeated up to 10 times on a command line. Only macro names up to
+    22 letters long can be set.
 
 -d<debug options>
 
@@ -3461,7 +3479,7 @@ brief message about itself and exits.
     interface       lists of local interfaces
     lists           matching things in lists
     load            system load checks
-    local_scan      can be used by local_scan() (see chapter 44)
+    local_scan      can be used by local_scan() (see chapter 45)
     lookup          general lookup code and all lookups
     memory          memory handling
     pid             add pid to debug output lines
@@ -3516,7 +3534,7 @@ brief message about itself and exits.
 
     This is an obsolete option that is now a no-op. It used to affect the way
     Exim handled CR and LF characters in incoming messages. What happens now is
-    described in section 46.2.
+    described in section 47.2.
 
 -E
 
@@ -3648,7 +3666,7 @@ brief message about itself and exits.
     This option is not intended for use by external callers. It is used
     internally by Exim to invoke another instance of itself to deliver a
     waiting message using an existing SMTP connection, which is passed as the
-    standard input. Details are given in chapter 47. This must be the final
+    standard input. Details are given in chapter 48. This must be the final
     option, and the caller must be root or the Exim user in order to use it.
 
 -MCA
@@ -3657,6 +3675,18 @@ brief message about itself and exits.
     internally by Exim in conjunction with the -MC option. It signifies that
     the connection to the remote host has been authenticated.
 
+-MCD
+
+    This option is not intended for use by external callers. It is used
+    internally by Exim in conjunction with the -MC option. It signifies that
+    the remote host supports the ESMTP DSN extension.
+
+-MCG
+
+    This option is not intended for use by external callers. It is used
+    internally by Exim in conjunction with the -MC option. It signifies that an
+    alternate queue is used, named by the following option.
+
 -MCP
 
     This option is not intended for use by external callers. It is used
@@ -3691,7 +3721,7 @@ brief message about itself and exits.
     turn, but unlike the -M option, it does check for retry hints, and respects
     any that are found. This option is not very useful to external callers. It
     is provided mainly for internal use by Exim when it needs to re-invoke
-    itself in order to regain root privilege for a delivery (see chapter 54).
+    itself in order to regain root privilege for a delivery (see chapter 55).
     However, -Mc can be useful when testing, in order to run a delivery that
     respects retry times and other options such as hold_domains that are
     overridden when -M is used. Such a delivery does not count as a queue run.
@@ -3816,8 +3846,9 @@ brief message about itself and exits.
 -n
 
     This option is interpreted by Sendmail to mean "no aliasing". For normal
-    modes of operation, it is ignored by Exim. When combined with -bP it
-    suppresses the name of an option from being output.
+    modes of operation, it is ignored by Exim. When combined with -bP it makes
+    the output more terse (suppresses option names, environment values and
+    config pretty printing).
 
 -O <data>
 
@@ -3871,7 +3902,7 @@ brief message about itself and exits.
 
     If there is a temporary delivery error during foreground delivery, the
     message is left on the queue for later delivery, and the original reception
-    process exits. See chapter 50 for a way of setting up a restricted
+    process exits. See chapter 51 for a way of setting up a restricted
     configuration that never queues messages.
 
 -odi
@@ -4069,14 +4100,14 @@ brief message about itself and exits.
     This option sets a timeout value for incoming non-SMTP messages. If it is
     not set, Exim will wait forever for the standard input. The value can also
     be set by the receive_timeout option. The format used for specifying times
-    is described in section 6.15.
+    is described in section 6.16.
 
 -os <time>
 
     This option sets a timeout value for incoming SMTP messages. The timeout
     applies to each SMTP command and block of data. The value can also be set
     by the smtp_receive_timeout option; it defaults to 5 minutes. The format
-    used for specifying times is described in section 6.15.
+    used for specifying times is described in section 6.16.
 
 -ov
 
@@ -4122,12 +4153,12 @@ brief message about itself and exits.
     relax this restriction (and also the same requirement for the -M, -R, and 
     -S options).
 
-    The -q option starts one queue runner process. This scans the queue of
-    waiting messages, and runs a delivery process for each one in turn. It
-    waits for each delivery process to finish before starting the next one. A
-    delivery process may not actually do any deliveries if the retry times for
-    the addresses have not been reached. Use -qf (see below) if you want to
-    override this.
+    If other commandline options do not specify an action, the -q option starts
+    one queue runner process. This scans the queue of waiting messages, and
+    runs a delivery process for each one in turn. It waits for each delivery
+    process to finish before starting the next one. A delivery process may not
+    actually do any deliveries if the retry times for the addresses have not
+    been reached. Use -qf (see below) if you want to override this.
 
     If the delivery process spawns other processes to deliver other messages
     down passed SMTP connections, the queue runner waits for these to finish
@@ -4193,6 +4224,20 @@ brief message about itself and exits.
     be done. If a message requires any remote deliveries, it remains on the
     queue for later delivery.
 
+-q[q][i][f[f]][l][G<name>[/<time>]]]
+
+    If the G flag and a name is present, the queue runner operates on the queue
+    with the given name rather than the default queue. The name should not
+    contain a / character. For a periodic queue run (see below) append to the
+    name a slash and a time value.
+
+    If other commandline options specify an action, a -qG<name> option will
+    specify a queue to operate on. For example:
+
+    exim -bp -qGquarantine
+    mailq -qGquarantine
+    exim -qGoffpeak -Rf @special.domain.example
+
 -q<qflags> <start id> <end id>
 
     When scanning the queue, Exim can be made to skip over messages whose ids
@@ -4218,7 +4263,7 @@ brief message about itself and exits.
 
     When a time value is present, the -q option causes Exim to run as a daemon,
     starting a queue runner process at intervals specified by the given time
-    value (whose format is described in section 6.15). This form of the -q
+    value (whose format is described in section 6.16). This form of the -q
     option is commonly combined with the -bd option, in which case a 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
@@ -4279,7 +4324,7 @@ brief message about itself and exits.
 
     The -R option makes it straightforward to initiate delivery of all messages
     to a given domain after a host has been down for some time. When the SMTP
-    command ETRN is accepted by its ACL (see chapter 42), its default effect is
+    command ETRN is accepted by its ACL (see chapter 43), its default effect is
     to run Exim with the -R option, but it can be configured to run an
     arbitrary command instead.
 
@@ -4346,7 +4391,7 @@ brief message about itself and exits.
 
     This option is available when Exim is compiled with TLS support. It forces
     all incoming SMTP connections to behave as if the incoming port is listed
-    in the tls_on_connect_ports option. See section 13.4 and chapter 41 for
+    in the tls_on_connect_ports option. See section 13.4 and chapter 42 for
     further details.
 
 -U
@@ -4378,6 +4423,12 @@ brief message about itself and exits.
     This option is interpreted by Sendmail to cause debug information to be
     sent to the named file. It is ignored by Exim.
 
+-z <log-line>
+
+    This option writes its argument to Exim's logfile. Use is restricted to
+    administrators; the intent is for operational notes. Quotes should be used
+    to maintain a multi-word item as a single argument, under most shells.
+
 
 
 ===============================================================================
@@ -4489,11 +4540,11 @@ with this. See the comments in src/EDITME for details.
 Exim's configuration file is divided into a number of different parts. General
 option settings must always appear at the start of the file. The other parts
 are all optional, and may appear in any order. Each part other than the first
-is introduced by the word "begin" followed by the name of the part. The
-optional parts are:
+is introduced by the word "begin" followed by at least one literal space, and
+the name of the part. The optional parts are:
 
   * ACL: Access control lists for controlling incoming SMTP mail (see chapter
-    42).
+    43).
 
   * authenticators: Configuration settings for the authenticator drivers. These
     are concerned with the SMTP AUTH command (see chapter 33).
@@ -4522,7 +4573,7 @@ optional parts are:
     LOCAL_SCAN_HAS_OPTIONS=yes
 
     in Local/Makefile before building Exim. Details of the local_scan()
-    facility are given in chapter 44.
+    facility are given in chapter 45.
 
 Leading and trailing white space in configuration lines is always ignored.
 
@@ -4542,9 +4593,9 @@ which is supplied in src/configure.default, and add, delete, or change settings
 as required.
 
 The ACLs, retry rules, and rewriting rules have their own syntax which is
-described in chapters 42, 32, and 31, respectively. The other parts of the
+described in chapters 43, 32, and 31, respectively. The other parts of the
 configuration file have some syntactic items in common, and these are described
-below, from section 6.10 onwards. Before that, the inclusion, macro, and
+below, from section 6.11 onwards. Before that, the inclusion, macro, and
 conditional facilities are described.
 
 
@@ -4559,8 +4610,10 @@ this syntax:
 
 on a line by itself. Double quotes round the file name are optional. If you use
 the first form, a configuration error occurs if the file does not exist; the
-second form does nothing for non-existent files. In all cases, an absolute file
-name is required.
+second form does nothing for non-existent files.
+
+The first form allows a relative name. It is resolved relative to the directory
+of the including file. For the second form an absolute file name is required.
 
 Includes may be nested to any depth, but remember that Exim reads its
 configuration file often, so it is a good idea to keep them to a minimum. If
@@ -4674,8 +4727,33 @@ address lists. In Exim 4 these are handled better by named lists - see section
 10.5.
 
 
-6.9 Conditional skips in the configuration file
------------------------------------------------
+6.9 Builtin macros
+------------------
+
+Exim defines some macros depending on facilities available, which may differ
+due to build-time definitions and from one release to another. All of these
+macros start with an underscore. They can be used to conditionally include
+parts of a configuration (see below).
+
+The following classes of macros are defined:
+
+ _HAVE_*                      build-time defines
+ _DRIVER_ROUTER_*             router drivers
+ _DRIVER_TRANSPORT_*          transport drivers
+ _DRIVER_AUTHENTICATOR_*      authenticator drivers
+ _OPT_MAIN_*                  main config options
+ _OPT_ROUTERS_*               generic router options
+ _OPT_TRANSPORTS_*            generic transport options
+ _OPT_AUTHENTICATORS_*        generic authenticator options
+ _OPT_ROUTER_*_*              private router options
+ _OPT_TRANSPORT_*_*           private transport options
+ _OPT_AUTHENTICATOR_*_*       private authenticator options
+
+Use an "exim -bP macros" command to get the list of macros.
+
+
+6.10 Conditional skips in the configuration file
+------------------------------------------------
 
 You can use the directives ".ifdef", ".ifndef", ".elifdef", ".elifndef",
 ".else", and ".endif" to dynamically include or exclude portions of the
@@ -4693,10 +4771,10 @@ message_size_limit = 50M
 message_size_limit = 100M
 .endif
 
-sets a message size limit of 50M if the macro "AAA" is defined, and 100M
-otherwise. If there is more than one macro named on the line, the condition is
-true if any of them are defined. That is, it is an "or" condition. To obtain an
-"and" condition, you need to use nested ".ifdef"s.
+sets a message size limit of 50M if the macro "AAA" is defined (or "A" or
+"AA"), and 100M otherwise. If there is more than one macro named on the line,
+the condition is true if any of them are defined. That is, it is an "or"
+condition. To obtain an "and" condition, you need to use nested ".ifdef"s.
 
 Although you can use a macro expansion to generate one of these directives, it
 is not very useful, because the condition "there was a macro substitution in
@@ -4706,7 +4784,7 @@ Text following ".else" and ".endif" is ignored, and can be used as comment to
 clarify complicated nestings.
 
 
-6.10 Common option syntax
+6.11 Common option syntax
 -------------------------
 
 For the main set of options, driver options, and local_scan() options, each
@@ -4735,7 +4813,7 @@ The following sections describe the syntax used for the different data types
 that are found in option settings.
 
 
-6.11 Boolean options
+6.12 Boolean options
 --------------------
 
 Options whose type is given as boolean are on/off switches. There are two
@@ -4757,7 +4835,7 @@ queue_only = false
 You can use whichever syntax you prefer.
 
 
-6.12 Integer values
+6.13 Integer values
 -------------------
 
 If an option's type is given as "integer", the value can be given in decimal,
@@ -4767,14 +4845,14 @@ with the characters "0x", in which case the remainder is interpreted as a
 hexadecimal number.
 
 If an integer value is followed by the letter K, it is multiplied by 1024; if
-it is followed by the letter M, it is multiplied by 1024x1024. When the values
-of integer option settings are output, values which are an exact multiple of
-1024 or 1024x1024 are sometimes, but not always, printed using the letters K
-and M. The printing style is independent of the actual input format that was
-used.
+it is followed by the letter M, it is multiplied by 1024x1024; if by the letter
+G, 1024x1024x1024. When the values of integer option settings are output,
+values which are an exact multiple of 1024 or 1024x1024 are sometimes, but not
+always, printed using the letters K and M. The printing style is independent of
+the actual input format that was used.
 
 
-6.13 Octal integer values
+6.14 Octal integer values
 -------------------------
 
 If an option's type is given as "octal integer", its value is always
@@ -4782,14 +4860,14 @@ interpreted as an octal number, whether or not it starts with the digit zero.
 Such options are always output in octal.
 
 
-6.14 Fixed point numbers
+6.15 Fixed point numbers
 ------------------------
 
 If an option's type is given as "fixed-point", its value must be a decimal
 integer, optionally followed by a decimal point and up to three further digits.
 
 
-6.15 Time intervals
+6.16 Time intervals
 -------------------
 
 A time interval is specified as a sequence of numbers, each followed by one of
@@ -4806,7 +4884,7 @@ intervals are output in the same format. Exim does not restrict the values; it
 is perfectly acceptable, for example, to specify "90m" instead of "1h30m".
 
 
-6.16 String values
+6.17 String values
 ------------------
 
 If an option's type is specified as "string", the value can be specified with
@@ -4845,7 +4923,7 @@ in order to continue lines, so you may come across older configuration files
 and examples that apparently quote unnecessarily.
 
 
-6.17 Expanded strings
+6.18 Expanded strings
 ---------------------
 
 Some strings in the configuration file are subjected to string expansion, by
@@ -4858,7 +4936,7 @@ required for that reason must be doubled if they are within a quoted
 configuration string.
 
 
-6.18 User and group names
+6.19 User and group names
 -------------------------
 
 User and group names are specified as strings, using the syntax described
@@ -4867,7 +4945,7 @@ either consist entirely of digits, or be a name that can be looked up using the
 getpwnam() or getgrnam() function, as appropriate.
 
 
-6.19 List construction
+6.20 List construction
 ----------------------
 
 The data for some configuration options is a list of items, with colon as the
@@ -4878,7 +4956,7 @@ the same; however, those other than "string list" are subject to particular
 kinds of interpretation, as described in chapter 10.
 
 In all these cases, the entire list is treated as a single string as far as the
-input syntax is concerned. The trusted_users setting in section 6.16 above is
+input syntax is concerned. The trusted_users setting in section 6.17 above is
 an example. If a colon is actually needed in an item in a list, it must be
 entered as two colons. Leading and trailing white space on each item in a list
 is ignored. This makes it possible to include items that start with a colon,
@@ -4894,7 +4972,7 @@ in the example above is necessary. If it were not there, the list would be
 interpreted as the two items 127.0.0.1:: and 1.
 
 
-6.20 Changing list separators
+6.21 Changing list separators
 -----------------------------
 
 Doubling colons in IPv6 addresses is an unwelcome chore, so a mechanism was
@@ -4932,7 +5010,7 @@ set as the separator. Two such characters in succession are interpreted as
 enclosing an empty list item.
 
 
-6.21 Empty items in lists
+6.22 Empty items in lists
 -------------------------
 
 An empty item at the end of a list is always ignored. In other words, trailing
@@ -4957,7 +5035,7 @@ In this case, the first item is empty, and the second is discarded because it
 is at the end of the list.
 
 
-6.22 Format of driver configurations
+6.23 Format of driver configurations
 ------------------------------------
 
 There are separate parts in the configuration for defining routers, transports,
@@ -5124,7 +5202,7 @@ Two commented-out option settings are next:
 These are example settings that can be used when Exim is compiled with the
 content-scanning extension. The first specifies the interface to the virus
 scanner, and the second specifies the interface to SpamAssassin. Further
-details are given in chapter 43.
+details are given in chapter 44.
 
 Three more commented-out option settings follow:
 
@@ -5138,7 +5216,7 @@ of clients that are allowed to use TLS when connecting to this server; in this
 case the wildcard means all clients. The other options specify where Exim
 should find its TLS certificate and private key, which together prove the
 server's identity to any clients that connect. More details are given in
-chapter 41.
+chapter 42.
 
 Another two commented-out option settings follow:
 
@@ -5216,7 +5294,7 @@ 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.
 
-This line enables an efficiency SMTP option. It is negociated by clients and
+This line enables an efficiency SMTP option. It is negotiated by clients and
 not expected to cause problems but can be disabled if needed.
 
 prdr_enable = true
@@ -5232,6 +5310,12 @@ find that they send unqualified addresses. The two commented-out options:
 show how you can specify hosts that are permitted to send unqualified sender
 and recipient addresses, respectively.
 
+The log_selector option is used to increase the detail of logging over the
+default:
+
+log_selector = +smtp_protocol_error +smtp_syntax_error \
+               +tls_certificate_verified
+
 The percent_hack_domains option is also commented out:
 
 # percent_hack_domains =
@@ -5240,7 +5324,7 @@ It provides a list of domains for which the "percent hack" is to operate. This
 is an almost obsolete form of explicit email routing. If you do not know
 anything about it, you can safely ignore this topic.
 
-The last two settings in the main part of the default configuration are
+The next two settings in the main part of the default configuration are
 concerned with messages that have been "frozen" on Exim's queue. When a message
 is frozen, Exim no longer continues to try to deliver it. Freezing occurs when
 a bounce message encounters a permanent failure because the sender address of
@@ -5258,6 +5342,39 @@ message (whether a bounce message or not) is to be timed out (and discarded)
 after a week. In this configuration, the first setting ensures that no failing
 bounce message ever lasts a week.
 
+Exim queues it's messages in a spool directory. If you expect to have large
+queues, you may consider using this option. It splits the spool directory into
+subdirectories to avoid file system degradation from many files in a single
+directory, resulting in better performance. Manual manipulation of queued
+messages becomes more complex (though fortunately not often needed).
+
+# split_spool_directory = true
+
+In an ideal world everybody follows the standards. For non-ASCII messages RFC
+2047 is a standard, allowing a maximum line length of 76 characters. Exim
+adheres that standard and won't process messages which violate this standard.
+(Even ${rfc2047:...} expansions will fail.) In particular, the Exim maintainers
+have had multiple reports of problems from Russian administrators of issues
+until they disable this check, because of some popular, yet buggy, mail
+composition software.
+
+# check_rfc2047_length = false
+
+If you need to be strictly RFC compliant you may wish to disable the 8BITMIME
+advertisement. Use this, if you exchange mails with systems that are not 8-bit
+clean.
+
+# accept_8bitmime = false
+
+Libraries you use may depend on specific environment settings. This imposes a
+security risk (e.g. PATH). There are two lists: keep_environment for the
+variables to import as they are, and add_environment for variables we want to
+set to a fixed value. Note that TZ is handled separately, by the $%timezone%$
+runtime option and by the TIMEZONE_DEFAULT buildtime option.
+
+# keep_environment = ^LDAP
+# add_environment = PATH=/usr/bin::/bin
+
 
 7.2 ACL configuration
 ---------------------
@@ -5369,7 +5486,7 @@ ACL statement can be used. If verification fails, the incoming recipient
 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.44 discusses the details of
+for more verification if required. Section 43.44 discusses the details of
 address verification.
 
 accept  hosts         = +relay_from_hosts
@@ -5380,7 +5497,7 @@ hosts that are defined as being allowed to relay through this host. Recipient
 verification is omitted here, because in many cases the clients are dumb MUAs
 that do not cope well with SMTP error responses. For the same reason, the
 second line specifies "submission mode" for messages that are accepted. This is
-described in detail in section 46.1; it causes Exim to fix messages that are
+described in detail in section 47.1; it causes Exim to fix messages that are
 deficient in some way, for example, because they lack a Date: header line. If
 you are actually relaying out from MTAs, you should probably add recipient
 verification here, and disable submission mode.
@@ -5679,8 +5796,8 @@ address_pipe:
 
 This transport is used for handling deliveries to pipes that are generated by
 redirection (aliasing or users' .forward files). The return_output option
-specifies that any output generated by the pipe is to be returned to the
-sender.
+specifies that any output on stdout or stderr generated by the pipe is to be
+returned to the sender.
 
 address_file:
   driver = appendfile
@@ -5716,7 +5833,8 @@ errors:
 This causes any temporarily failing address to be retried every 15 minutes for
 2 hours, then at intervals starting at one hour and increasing by a factor of
 1.5 until 16 hours have passed, then every 6 hours up to 4 days. If an address
-is not delivered after 4 days of temporary failure, it is bounced.
+is not delivered after 4 days of temporary failure, it is bounced. The time is
+measured from first failure, not from the time the message was received.
 
 If the retry section is removed from the configuration, or is empty (that is,
 if no retry rules are defined), Exim will not retry deliveries. This turns
@@ -5790,9 +5908,9 @@ usercode and password are in different positions. Chapter 34 covers both.
 Exim supports the use of regular expressions in many of its options. It uses
 the PCRE regular expression library; this provides regular expression matching
 that is compatible with Perl 5. The syntax and semantics of regular expressions
-is discussed in many Perl reference books, and also in Jeffrey Friedl's 
-Mastering Regular Expressions, which is published by O'Reilly (see http://
-www.oreilly.com/catalog/regex2/).
+is discussed in online Perl manpages, in many Perl reference books, and also in
+Jeffrey Friedl's Mastering Regular Expressions, which is published by O'Reilly
+(see http://www.oreilly.com/catalog/regex2/).
 
 The documentation for the syntax and semantics of the regular expressions that
 are supported by PCRE is included in the PCRE distribution, and no further
@@ -5847,13 +5965,15 @@ messages. Two different kinds of syntax are used:
     cause parts of the string to be replaced by data that is obtained from the
     lookup. Lookups of this type are conditional expansion items. Different
     results can be defined for the cases of lookup success and failure. See
-    chapter 11, where string expansions are described in detail.
+    chapter 11, where string expansions are described in detail. The key for
+    the lookup is specified as part of the string expansion.
 
  2. Lists of domains, hosts, and email addresses can contain lookup requests as
     a way of avoiding excessively long linear lists. In this case, the data
     that is returned by the lookup is often (but not always) discarded; whether
     the lookup succeeds or fails is what really counts. These kinds of list are
-    described in chapter 10.
+    described in chapter 10. The key for the lookup is given by the context in
+    which the list is expanded.
 
 String expansions, lists, and lookups interact with each other in such a way
 that there is no order in which to describe any one of them that does not
@@ -5984,14 +6104,14 @@ The following single-key lookup types are implemented:
     incoming SMTP calls using the passwords from Courier's /etc/
     userdbshadow.dat file. Exim's utility program for creating DBM files (
     exim_dbmbuild) includes the zeros by default, but has an option to omit
-    them (see section 52.9).
+    them (see section 53.9).
 
   * dsearch: The given file must be a directory; this is searched for an entry
     whose name is the key by calling the lstat() function. The key may not
     contain any forward slash characters. If lstat() succeeds, the result of
     the lookup is the name of the entry, which may be a file, directory,
     symbolic link, or any other kind of directory entry. An example of how this
-    lookup can be used to support virtual domains is given in section 49.7.
+    lookup can be used to support virtual domains is given in section 50.7.
 
   * iplsearch: The given file is a text file containing keys and data. A key is
     terminated by a colon or white space or the end of the line. The keys in
@@ -6042,7 +6162,7 @@ The following single-key lookup types are implemented:
     characters, or white space. However, if you need this feature, it is
     available. If a key begins with a doublequote character, it is terminated
     only by a matching quote (or end of line), and the normal escaping rules
-    apply to its contents (see section 6.16). An optional colon is permitted
+    apply to its contents (see section 6.17). An optional colon is permitted
     after quoted keys (exactly as for unquoted keys). There is no special
     handling of quotes for the data part of an lsearch line.
 
@@ -6130,16 +6250,16 @@ many of them are given in later sections.
     returns attributes from a single entry. There is a variant called ldapm
     that permits values from multiple entries to be returned. A third variant
     called ldapdn returns the Distinguished Name of a single entry instead of
-    any attribute values. See section 9.13.
+    any attribute values. See section 9.14.
 
   * mysql: The format of the query is an SQL statement that is passed to a
-    MySQL database. See section 9.20.
+    MySQL database. See section 9.21.
 
   * nisplus: This does a NIS+ lookup using a query that can specify the name of
-    the field to be returned. See section 9.19.
+    the field to be returned. See section 9.20.
 
   * oracle: The format of the query is an SQL statement that is passed to an
-    Oracle database. See section 9.20.
+    Oracle database. See section 9.21.
 
   * passwd is a query-style lookup with queries that are just user names. The
     lookup calls getpwnam() to interrogate the system password data, and on
@@ -6150,10 +6270,13 @@ many of them are given in later sections.
     *:42:42:King Rat:/home/kr:/bin/bash
 
   * pgsql: The format of the query is an SQL statement that is passed to a
-    PostgreSQL database. See section 9.20.
+    PostgreSQL database. See section 9.21.
+
+  * redis: The format of the query is either a simple get or simple set, passed
+    to a Redis database. See section 9.21.
 
   * sqlite: The format of the query is a file name followed by an SQL statement
-    that is passed to an SQLite database. See section 9.25.
+    that is passed to an SQLite database. See section 9.26.
 
   * testdb: This is a lookup type that is used for testing Exim. It is not
     likely to be useful in normal operation.
@@ -6384,23 +6507,11 @@ used on its own as the result. If the lookup does not succeed, the "fail"
 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, 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:
-
-${lookup dnsdb{ptr=192.168.4.5}{$value}fail}
-
-If the data for a PTR record is not a syntactically valid IP address, it is not
-altered and nothing is added.
-
-For an MX lookup, both the preference value and the host name are returned for
-each record, separated by a space. For an SRV lookup, the priority, weight,
-port, and host name are returned for each record, separated by spaces.
+The supported DNS record types are A, CNAME, MX, NS, PTR, SOA, SPF, SRV, TLSA
+and TXT, and, when Exim is compiled with IPv6 support, AAAA. If no type is
+given, TXT is assumed.
 
-For any record type, if multiple records are found (or, for A6 lookups, if a
-single record leads to multiple addresses), the data is returned as a
+For any record type, if multiple records are found, the data is returned as a
 concatenation, with newline as the default separator. The order, of course,
 depends on the DNS resolver. You can specify a different separator character
 between multiple records by putting a right angle-bracket followed immediately
@@ -6409,13 +6520,28 @@ by the new separator at the start of the query. For example:
 ${lookup dnsdb{>: a=host1.example}}
 
 It is permitted to specify a space as the separator character. Further white
-space is ignored.
+space is ignored. For lookup types that return multiple fields per record, an
+alternate field separator can be specified using a comma after the main
+separator character, followed immediately by the field separator.
+
+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:
+
+${lookup dnsdb{ptr=192.168.4.5}{$value}fail}
+
+If the data for a PTR record is not a syntactically valid IP address, it is not
+altered and nothing is added.
+
+For an MX lookup, both the preference value and the host name are returned for
+each record, separated by a space. For an SRV lookup, the priority, weight,
+port, and host name are returned for each record, separated by spaces. The
+field separator can be modified as above.
 
 For TXT records with multiple items of data, only the first item is returned,
-unless a separator for them is specified using a comma after the separator
-character followed immediately by the TXT record item separator. To concatenate
-items without a separator, use a semicolon instead. For SPF records the default
-behaviour is to concatenate multiple items without using a separator.
+unless a field separator is specified. To concatenate items without a
+separator, use a semicolon instead. For SPF records the default behaviour is to
+concatenate multiple items without using a separator.
 
 ${lookup dnsdb{>\n,: txt=a.b.example}}
 ${lookup dnsdb{>\n; txt=a.b.example}}
@@ -6424,8 +6550,57 @@ ${lookup dnsdb{spf=example.org}}
 It is permitted to specify a space as the separator character. Further white
 space is ignored.
 
+For an SOA lookup, while no result is obtained the lookup is redone with
+successively more leading components dropped from the given domain. Only the
+primary-nameserver field is returned unless a field separator is specified.
+
+${lookup dnsdb{>:,; soa=a.b.example.com}}
+
+
+9.11 Dnsdb lookup modifiers
+---------------------------
+
+Modifiers for dnsdb lookups are given 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 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}}
+
+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.
+
+Timeout for the dnsdb lookup can be controlled by a retrans modifier. The form
+is "retrans_VAL" where VAL is an Exim time specification (e.g. "5s"). The
+default value is set by the main configuration option dns_retrans.
+
+Retries for the dnsdb lookup can be controlled by a retry modifier. The form if
+"retry_VAL" where VAL is an integer. The default count is set by the main
+configuration option dns_retry.
+
+Dnsdb lookup results are cached within a single process (and its children). The
+cache entry lifetime is limited to the smallest time-to-live (TTL) value of the
+set of returned DNS records.
 
-9.11 Pseudo dnsdb record types
+
+9.12 Pseudo dnsdb record types
 ------------------------------
 
 By default, both the preference value and the host name are returned for each
@@ -6460,7 +6635,7 @@ for the high-level domains such as com or co.uk are not going to be on such a
 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.50.
+records according to the CSA rules, which are described in section 43.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:
@@ -6471,14 +6646,14 @@ has two space-separated fields: an authorization code and a target host name.
 The authorization code can be "Y" for yes, "N" for no, "X" for explicit
 authorization required but absent, or "?" for unknown.
 
-The pseudo-type A+ performs an A6 lookup (if configured) followed by an AAAA
-and then an A lookup. All results are returned; defer processing (see below) is
-handled separately for each lookup. Example:
+The pseudo-type A+ performs an AAAA and then an A lookup. All results are
+returned; defer processing (see below) is handled separately for each lookup.
+Example:
 
 ${lookup dnsdb {>; a+=$sender_helo_name}}
 
 
-9.12 Multiple dnsdb lookups
+9.13 Multiple dnsdb lookups
 ---------------------------
 
 In the previous sections, dnsdb lookups for a single domain are described.
@@ -6499,35 +6674,8 @@ The data from each lookup is concatenated, with newline separators by default,
 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 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}}
-
-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
+9.14 More about LDAP
 --------------------
 
 The original LDAP implementation came from the University of Michigan; this has
@@ -6566,7 +6714,7 @@ data returned by a successful lookup is described in the next section. First we
 explain how LDAP queries are coded.
 
 
-9.14 Format of LDAP queries
+9.15 Format of LDAP queries
 ---------------------------
 
 An LDAP query takes the form of a URL as defined in RFC 2255. For example, in
@@ -6594,7 +6742,7 @@ 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
+9.16 LDAP quoting
 -----------------
 
 Two levels of quoting are required in LDAP queries, the first for LDAP itself
@@ -6652,7 +6800,7 @@ There are some further comments about quoting in the section on LDAP
 authentication below.
 
 
-9.16 LDAP connections
+9.17 LDAP connections
 ---------------------
 
 The connection to an LDAP server may either be over TCP/IP, or, when OpenLDAP
@@ -6722,7 +6870,7 @@ Using "ldapi" with no host or path in the query, and no setting of
 ldap_default_servers, does whatever the library does by default.
 
 
-9.17 LDAP authentication and control information
+9.18 LDAP authentication and control information
 ------------------------------------------------
 
 The LDAP URL syntax provides no way of passing authentication and other control
@@ -6760,12 +6908,12 @@ 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.
+to use for an individual lookup. The global ldap_default_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 (colon-separated).
 
 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:
@@ -6811,13 +6959,13 @@ The LDAP authentication mechanism can be used to check passwords as part of
 SMTP authentication. See the ldapauth expansion string condition in chapter 11.
 
 
-9.18 Format of data returned by LDAP
+9.19 Format of data returned by LDAP
 ------------------------------------
 
 The ldapdn lookup type returns the Distinguished Name from a single entry as a
 sequence of values, for example
 
-cn=manager, o=University of Cambridge, c=UK
+cn=manager,o=University of Cambridge,c=UK
 
 The ldap lookup type generates an error if more than one entry matches the
 search filter, whereas ldapm permits this case, and inserts a newline in the
@@ -6828,39 +6976,50 @@ directory.
 
 In the common case where you specify a single attribute in your LDAP query, the
 result is not quoted, and does not contain the attribute name. If the attribute
-has multiple values, they are separated by commas.
+has multiple values, they are separated by commas. Any comma that is part of an
+attribute's value is doubled.
 
 If you specify multiple attributes, the result contains space-separated, quoted
 strings, each preceded by the attribute name and an equals sign. Within the
 quotes, the quote character, backslash, and newline are escaped with
 backslashes, and commas are used to separate multiple values for the attribute.
-Apart from the escaping, the string within quotes takes the same form as the
-output when a single attribute is requested. Specifying no attributes is the
-same as specifying all of an entry's attributes.
+Any commas in attribute values are doubled (permitting treatment of the values
+as a comma-separated list). Apart from the escaping, the string within quotes
+takes the same form as the output when a single attribute is requested.
+Specifying no attributes is the same as specifying all of an entry's
+attributes.
 
 Here are some examples of the output format. The first line of each pair is an
 LDAP query, and the second is the data that is returned. The attribute called 
-attr1 has two values, whereas attr2 has only one value:
+attr1 has two values, one of them with an embedded comma, whereas attr2 has
+only one value. Both attributes are derived from attr (they have SUP attr in
+their schema definitions).
 
 ldap:///o=base?attr1?sub?(uid=fred)
-value1.1, value1.2
+value1.1,value1,,2
 
 ldap:///o=base?attr2?sub?(uid=fred)
 value two
 
+ldap:///o=base?attr?sub?(uid=fred)
+value1.1,value1,,2,value two
+
 ldap:///o=base?attr1,attr2?sub?(uid=fred)
-attr1="value1.1, value1.2" attr2="value two"
+attr1="value1.1,value1,,2" attr2="value two"
 
 ldap:///o=base??sub?(uid=fred)
-objectClass="top" attr1="value1.1, value1.2" attr2="value two"
+objectClass="top" attr1="value1.1,value1,,2" attr2="value two"
 
-The extract operator in string expansions can be used to pick out individual
-fields from data that consists of key=value pairs. You can make use of Exim's 
--be option to run expansion tests and thereby check the results of LDAP
-lookups.
+You can make use of Exim's -be option to run expansion tests and thereby check
+the results of LDAP lookups. The extract operator in string expansions can be
+used to pick out individual fields from data that consists of key=value pairs.
+The listextract operator should be used to pick out individual values of
+attributes, even when only a single value is expected. The doubling of embedded
+commas allows you to use the returned data as a comma separated list (using the
+"<," syntax for changing the input list separator).
 
 
-9.19 More about NIS+
+9.20 More about NIS+
 --------------------
 
 NIS+ queries consist of a NIS+ indexed name followed by an optional colon and
@@ -6889,12 +7048,12 @@ for the given indexed key. The effect of the quote_nisplus expansion operator
 is to double any quote characters within the text.
 
 
-9.20 SQL lookups
+9.21 SQL lookups
 ----------------
 
-Exim can support lookups in InterBase, MySQL, Oracle, PostgreSQL, and SQLite
-databases. Queries for these databases contain SQL statements, so an example
-might be
+Exim can support lookups in InterBase, MySQL, Oracle, PostgreSQL, Redis, and
+SQLite databases. Queries for these databases contain SQL statements, so an
+example might be
 
 ${lookup mysql{select mailbox from users where id='userx'}\
   {$value}fail}
@@ -6919,18 +7078,18 @@ If the result of the query yields more than one row, it is all concatenated,
 with a newline between the data for each row.
 
 
-9.21 More about MySQL, PostgreSQL, Oracle, and InterBase
---------------------------------------------------------
+9.22 More about MySQL, PostgreSQL, Oracle, InterBase, and Redis
+---------------------------------------------------------------
 
-If any MySQL, PostgreSQL, Oracle, or InterBase lookups are used, the 
-mysql_servers, pgsql_servers, oracle_servers, or ibase_servers option (as
-appropriate) must be set to a colon-separated list of server information. (For
-MySQL and PostgreSQL only, the global option need not be set if all queries
-contain their own server information - see section 9.22.) Each item in the list
-is a slash-separated list of four items: host name, database name, user name,
-and password. In the case of Oracle, the host name field is used for the
-"service name", and the database name field is not used and should be empty.
-For example:
+If any MySQL, PostgreSQL, Oracle, InterBase or Redis lookups are used, the 
+mysql_servers, pgsql_servers, oracle_servers, ibase_servers, or redis_servers
+option (as appropriate) must be set to a colon-separated list of server
+information. (For MySQL and PostgreSQL, the global option need not be set if
+all queries contain their own server information - see section 9.23.) For all
+but Redis each item in the list is a slash-separated list of four items: host
+name, database name, user name, and password. In the case of Oracle, the host
+name field is used for the "service name", and the database name field is not
+used and should be empty. For example:
 
 hide oracle_servers = oracle.plc.example//userx/abcdwxyz
 
@@ -6948,21 +7107,36 @@ query is successfully processed. The result of a query may be that no data is
 found, but that is still a successful query. In other words, the list of
 servers provides a backup facility, not a list of different places to look.
 
+For Redis the global option need not be specified if all queries contain their
+own server information - see section 9.23. If specified, the option must be set
+to a colon-separated list of server information. Each item in the list is a
+slash-separated list of three items: host, database number, and password.
+
+ 1. The host is required and may be either an IPv4 address and optional port
+    number (separated by a colon, which needs doubling due to the higher-level
+    list), or a Unix socket pathname enclosed in parentheses
+
+ 2. The database number is optional; if present that number is selected in the
+    backend
+
+ 3. The password is optional; if present it is used to authenticate to the
+    backend
+
 The quote_mysql, quote_pgsql, and quote_oracle expansion operators convert
 newline, tab, carriage return, and backspace to \n, \t, \r, and \b
 respectively, and the characters single-quote, double-quote, and backslash
-itself are escaped with backslashes. The quote_pgsql expansion operator, in
-addition, escapes the percent and underscore characters. This cannot be done
-for MySQL because these escapes are not recognized in contexts where these
-characters are not special.
+itself are escaped with backslashes.
 
+The quote_redis expansion operator escapes whitespace and backslash characters
+with a backslash.
 
-9.22 Specifying the server in the query
+
+9.23 Specifying the server in the query
 ---------------------------------------
 
-For MySQL and PostgreSQL lookups (but not currently for Oracle and InterBase),
-it is possible to specify a list of servers with an individual query. This is
-done by starting the query with
+For MySQL, PostgreSQL and Redis lookups (but not currently for Oracle and
+InterBase), it is possible to specify a list of servers with an individual
+query. This is done by starting the query with
 
 servers=server1:server2:server3:...;
 
@@ -6999,17 +7173,18 @@ option, you can still update it by a query of this form:
 ${lookup pgsql{servers=master/db/name/pw; UPDATE ...} }
 
 
-9.23 Special MySQL features
+9.24 Special MySQL features
 ---------------------------
 
 For MySQL, an empty host name or the use of "localhost" in mysql_servers causes
 a connection to the server on the local host by means of a Unix domain socket.
-An alternate socket can be specified in parentheses. The full syntax of each
-item in mysql_servers is:
+An alternate socket can be specified in parentheses. An option group name for
+MySQL option files can be specified in square brackets; the default value is
+"exim". The full syntax of each item in mysql_servers is:
 
-<hostname>::<port>(<socket name>)/<database>/<user>/<password>
+<hostname>::<port>(<socket name>)[<option group>]/<database>/<user>/<password>
 
-Any of the three sub-parts of the first field can be omitted. For normal use on
+Any of the four sub-parts of the first field can be omitted. For normal use on
 the local host it can be left blank or set to just "localhost".
 
 No database need be supplied - but if it is absent here, it must be given in
@@ -7023,7 +7198,7 @@ Warning: This can be misleading. If an update does not actually change anything
 because no rows are affected.
 
 
-9.24 Special PostgreSQL features
+9.25 Special PostgreSQL features
 --------------------------------
 
 PostgreSQL lookups can also use Unix domain socket connections to the database.
@@ -7043,7 +7218,7 @@ update, or delete command), the result of the lookup is the number of rows
 affected.
 
 
-9.25 More about SQLite
+9.26 More about SQLite
 ----------------------
 
 SQLite is different to the other SQL lookups because a file name is required in
@@ -7072,6 +7247,15 @@ be released. In Exim, the default timeout is set to 5 seconds, but it can be
 changed by means of the sqlite_lock_timeout option.
 
 
+9.27 More about Redis
+---------------------
+
+Redis is a non-SQL database. Commands are simple get and set. Examples:
+
+${lookup redis{set keyname ${quote_redis:objvalue plus}}}
+${lookup redis{get keyname}}
+
+
 
 ===============================================================================
 10. DOMAIN, HOST, ADDRESS, AND LOCAL PART LISTS
@@ -7079,7 +7263,7 @@ changed by means of the sqlite_lock_timeout option.
 A number of Exim configuration options contain lists of domains, hosts, email
 addresses, or local parts. For example, the hold_domains option contains a list
 of domains whose delivery is currently suspended. These lists are also used as
-data in ACL statements (see chapter 42), and as arguments to expansion
+data in ACL statements (see chapter 43), and as arguments to expansion
 conditions such as match_domain.
 
 Each item in one of these lists is a pattern to be matched against a domain,
@@ -7087,16 +7271,23 @@ host, email address, or local part, respectively. In the sections below, the
 different types of pattern for each case are described, but first we cover some
 general facilities that apply to all four kinds of list.
 
+Note that other parts of Exim use a string list which does not support all the
+complexity available in domain, host, address and local part lists.
+
 
 10.1 Expansion of lists
 -----------------------
 
-Each list is expanded as a single string before it is used. The result of
-expansion must be a list, possibly containing empty items, which is split up
-into separate items for matching. By default, colon is the separator character,
-but this can be varied if necessary. See sections 6.19 and 6.21 for details of
-the list syntax; the second of these discusses the way to specify empty list
-items.
+Each list is expanded as a single string before it is used.
+
+Exception: the router headers_remove option, where list-item splitting is done
+before string-expansion.
+
+The result of expansion must be a list, possibly containing empty items, which
+is split up into separate items for matching. By default, colon is the
+separator character, but this can be varied if necessary. See sections 6.20 and
+6.22 for details of the list syntax; the second of these discusses the way to
+specify empty list items.
 
 If the string expansion is forced to fail, Exim behaves as if the item it is
 testing (domain, host, address, or local part) is not in the list. Other
@@ -7778,7 +7969,7 @@ hostname is one of the items in the hostlist.
     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. Alternatively, you can use "+ignore_unknown", which
+    43 for details of ACLs. Alternatively, you can use "+ignore_unknown", which
     was discussed in depth in the first example in this section.
 
 
@@ -7787,7 +7978,7 @@ hostname is one of the items in the hostlist.
 
 A temporary DNS lookup failure normally causes a defer action (except when 
 dns_again_means_nonexist converts it into a permanent error). However, host
-lists can include "+ignore_defer" and "+include_defer", analagous to
+lists can include "+ignore_defer" and "+include_defer", analogous to
 "+ignore_unknown" and "+include_unknown", as described in the previous section.
 These options should be used with care, probably only in non-critical host
 lists such as whitelists.
@@ -8100,7 +8291,7 @@ backslash in front of it. A backslash can be used to prevent any special
 character being treated specially in an expansion, including backslash itself.
 If the string appears in quotes in the configuration file, two backslashes are
 required because the quotes themselves cause interpretation of backslashes when
-the string is read in (see section 6.16).
+the string is read in (see section 6.17).
 
 A portion of the string can specified as non-expandable by placing it between
 two occurrences of "\N". This is particularly useful for protecting regular
@@ -8218,7 +8409,7 @@ ${acl{<name>}{<arg>}...}
     The name and zero to nine argument strings are first expanded separately.
     The expanded arguments are assigned to the variables $acl_arg1 to $acl_arg9
     in order. Any unused are made empty. The variable $acl_narg is set to the
-    number of arguments. The named ACL (see chapter 42) is called and may use
+    number of arguments. The named ACL (see chapter 43) is called and may use
     the variables; if another acl expansion is used the values are restored
     after it returns. If the ACL sets a value using a "message =" modifier and
     returns accept or deny, the value becomes the result of the expansion. If
@@ -8229,7 +8420,7 @@ ${acl{<name>}{<arg>}...}
 ${certextract{<field>}{<certificate>}{<string2>}{<string3>}}
 
     The <certificate> must be a variable of type certificate. The field name is
-    expanded and used to retrive the relevant field from the certificate.
+    expanded and used to retrieve the relevant field from the certificate.
     Supported fields are:
 
     version        
@@ -8257,15 +8448,17 @@ ${certextract{<field>}{<certificate>}{<string2>}{<string3>}}
 
     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
+    list (the exceptions being elements containing 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
+    may be changed by another modifier 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 "time" above take an optional modifier of
+    "int" for which the result is the number of seconds since epoch. Otherwise
+    the result is a human-readable string in the timezone selected by the main
+    "timezone" option.
 
     The field selectors marked as "list" above return a list, newline-separated
     by default, (embedded separator characters in elements are doubled). The
@@ -8274,7 +8467,7 @@ ${certextract{<field>}{<certificate>}{<string2>}{<string3>}}
 
     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
+    by a modifier which is one of "dns", "uri" or "mail"; if so the element
     tags are omitted.
 
     If not otherwise noted field values are presented in human-readable form.
@@ -8316,19 +8509,41 @@ ${dlfunc{<file>}{<function>}{<arg>}{<arg>}...}
     to add -shared to the gcc command. Also, in the Exim build-time
     configuration, you must add -export-dynamic to EXTRALIBS.
 
+${env{<key>}{<string1>}{<string2>}}
+
+    The key is first expanded separately, and leading and trailing white space
+    removed. This is then searched for as a name in the environment. If a
+    variable is found then its value is placed in $value and <string1> is
+    expanded, otherwise <string2> is expanded.
+
+    Instead of {<string2>} the word "fail" (not in curly brackets) can appear,
+    for example:
+
+    ${env{USER}{$value} fail }
+
+    This forces an expansion failure (see section 11.4); {<string1>} must be
+    present for "fail" to be recognized.
+
+    If {<string2>} is omitted an empty string is substituted on search failure.
+    If {<string1>} is omitted the search result is substituted on search
+    success.
+
+    The environment is adjusted by the keep_environment and add_environment
+    main section options.
+
 ${extract{<key>}{<string1>}{<string2>}{<string3>}}
 
     The key and <string1> are first expanded separately. Leading and trailing
     white space is removed from the key (but not from any of the strings). The
-    key must not consist entirely of digits. The expanded <string1> must be of
-    the form:
+    key must not be empty and must not consist entirely of digits. The expanded
+    <string1> must be of the form:
 
     <key1> = <value1>  <key2> = <value2> ...
 
     where the equals signs and spaces (but not both) are optional. If any of
     the values contain white space, they must be enclosed in double quotes, and
     any values that are enclosed in double quotes are subject to escape
-    processing as described in section 6.16. The expanded <string1> is searched
+    processing as described in section 6.17. The expanded <string1> is searched
     for the value that corresponds to the key. The search is case-insensitive.
     If the key is found, <string2> is expanded, and replaces the whole item;
     otherwise <string3> is used. During the expansion of <string2> the variable
@@ -8435,11 +8650,11 @@ $header_<header name>: or $h_<header name>:, $bheader_<header name>: or $bh_<
     The difference between rheader, bheader, and header is in the way the data
     in the header line is interpreted.
 
-      * rheader gives the original "raw" content of the header line, with no
+      + rheader gives the original "raw" content of the header line, with no
         processing at all, and without the removal of leading and trailing
         white space.
 
-      * bheader removes leading and trailing white space, and then decodes
+      + bheader removes leading and trailing white space, and then decodes
         base64 or quoted-printable MIME "words" within the header text, but
         does no character set translation. If decoding of what looks
         superficially like a MIME "word" fails, the raw string is returned. If
@@ -8447,7 +8662,7 @@ $header_<header name>: or $h_<header name>:, $bheader_<header name>: or $bh_<
         mark - this is what Exim does for binary zeros that are actually
         received in header lines.
 
-      * header tries to translate the string as decoded by bheader to a
+      + header tries to translate the string as decoded by bheader to a
         standard character set. This is an attempt to produce the same string
         as would be displayed on a user's MUA. If translation fails, the 
         bheader string is returned. Translation is attempted only on operating
@@ -8478,20 +8693,27 @@ $header_<header name>: or $h_<header name>:, $bheader_<header name>: or $bh_<
     filter. Header lines that are added to a particular copy of a message by a
     router or transport are not accessible.
 
-    For incoming SMTP messages, no header lines are visible in ACLs that are
-    obeyed before the DATA ACL, because the header structure is not set up
-    until the message is received. Header lines that are added in a RCPT ACL
-    (for example) are saved until the message's incoming header lines are
-    available, at which point they are added. When a DATA ACL is running,
-    however, header lines added by earlier ACLs are visible.
+    For incoming SMTP messages, no header lines are visible in
+
+    ACLs that are obeyed before the data phase completes,
+
+    because the header structure is not set up until the message is received.
+    They are visible in DKIM, PRDR and DATA ACLs. Header lines that are added
+    in a RCPT ACL (for example) are saved until the message's incoming header
+    lines are available, at which point they are added.
+
+    When any of the above ACLs ar
+
+    running, however, header lines added by earlier ACLs are visible.
 
     Upper case and lower case letters are synonymous in header names. If the
     following character is white space, the terminating colon may be omitted,
     but this is not recommended, because you may then forget it when it is
-    needed. When white space terminates the header name, it is included in the
-    expanded string. If the message does not contain the given header, the
-    expansion item is replaced by an empty string. (See the def condition in
-    section 11.7 for a means of testing for the existence of a header.)
+    needed. When white space terminates the header name, this white space is
+    included in the expanded string. If the message does not contain the given
+    header, the expansion item is replaced by an empty string. (See the def
+    condition in section 11.7 for a means of testing for the existence of a
+    header.)
 
     If there is more than one header with the same name, they are all
     concatenated to form the substitution string, up to a maximum length of
@@ -8564,6 +8786,12 @@ ${if <condition> {<string1>}{<string2>}}
 
     condition = ${if >{$acl_m4}{3}}
 
+${imapfolder{<foldername>}}
+
+    This item converts a (possibly multilevel, or with non-ASCII characters)
+    folder specification to a Maildir name for filesystem use. For information
+    on internationalisation support see 59.2.
+
 ${length{<string1>}{<string2>}}
 
     The length item is used to extract the initial portion of a string. Both
@@ -8603,7 +8831,7 @@ ${listextract{<number>}{<string1>}{<string2>}{<string3>}}
 
     ${listextract{-3}{<, x,42,99,& Mailer,,/bin/bash}{result: $value}}
 
-    yields "result: 99".
+    yields "result: 42".
 
     If {<string3>} is omitted, an empty string is used for string3. If {<
     string2>} is also omitted, the value that was extracted is used. You can
@@ -8728,7 +8956,7 @@ ${prvs{<address>}{<secret>}{<keynumber>}}
     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.51.
+    more discussion and an example, see section 43.51.
 
 ${prvscheck{<address>}{<secret>}{<string>}}
 
@@ -8754,7 +8982,7 @@ ${prvscheck{<address>}{<secret>}{<string>}}
 
     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.51.
+    set. For more discussion and an example, see section 43.51.
 
 ${readfile{<file name>}{<eol string>}}
 
@@ -8771,9 +8999,9 @@ ${readfile{<file name>}{<eol string>}}
 
 ${readsocket{<name>}{<request>}{<timeout>}{<eol string>}{<fail string>}}
 
-    This item inserts data from a Unix domain or Internet socket into the
-    expanded string. The minimal way of using it uses just two arguments, as in
-    these examples:
+    This item inserts data from a Unix domain or TCP socket into the expanded
+    string. The minimal way of using it uses just two arguments, as in these
+    examples:
 
     ${readsocket{/socket/name}{request string}}
     ${readsocket{inet:some.host:1234}{request string}}
@@ -8790,10 +9018,10 @@ ${readsocket{<name>}{<request>}{<timeout>}{<eol string>}{<fail string>}}
     Only a single host name may be given, but if looking it up yields more than
     one IP address, they are each tried in turn until a connection is made. For
     both kinds of socket, Exim makes a connection, writes the request string
-    (unless it is an empty string) and reads from the socket until an
-    end-of-file is read. A timeout of 5 seconds is applied. Additional,
-    optional arguments extend what can be done. Firstly, you can vary the
-    timeout. For example:
+    unless it is an empty string; and no terminating NUL is ever sent) and
+    reads from the socket until an end-of-file is read. A timeout of 5 seconds
+    is applied. Additional, optional arguments extend what can be done.
+    Firstly, you can vary the timeout. For example:
 
     ${readsocket{/socket/name}{request string}{3s}}
 
@@ -8807,13 +9035,13 @@ ${readsocket{<name>}{<request>}{<timeout>}{<eol string>}{<fail string>}}
     happens. Errors in these sub-expansions cause the expansion to fail. In
     addition, the following errors can occur:
 
-      * Failure to create a socket file descriptor;
+      + Failure to create a socket file descriptor;
 
-      * Failure to connect the socket;
+      + Failure to connect the socket;
 
-      * Failure to write the request string;
+      + Failure to write the request string;
 
-      * Timeout on reading from the socket.
+      + Timeout on reading from the socket.
 
     By default, any of these errors causes the expansion to fail. However, if
     you supply a fifth substring, it is expanded and used when any of the above
@@ -8948,6 +9176,30 @@ ${sg{<subject>}{<regex>}{<replacement>}}
     yields "K1=A K4=D K3=C". Note the use of "\N" to protect the contents of
     the regular expression from string expansion.
 
+${sort{<string>}{<comparator>}{<extractor>}}
+
+    After expansion, <string> is interpreted as a list, colon-separated by
+    default, but the separator can be changed in the usual way. The <comparator
+    > argument is interpreted as the operator of a two-argument expansion
+    condition. The numeric operators plus ge, gt, le, lt (and ~i variants) are
+    supported. The comparison should return true when applied to two values if
+    the first value should sort before the second value. The <extractor>
+    expansion is applied repeatedly to elements of the list, the element being
+    placed in $item, to give values for comparison.
+
+    The item result is a sorted list, with the original list separator, of the
+    list elements (in full) of the original.
+
+    Examples:
+
+    ${sort{3:2:1:4}{<}{$item}}
+
+    sorts a list of numbers, and
+
+    ${sort {${lookup dnsdb{>:,,mx=example.com}}} {<} {${listextract{1}{<,$item}}}}
+
+    will sort an MX lookup into priority order.
+
 ${substr{<string1>}{<string2>}{<string3>}}
 
     The three strings are expanded; the first two must yield numbers. Call them
@@ -9051,7 +9303,7 @@ ${addresses:<string>}
     To clarify "list of addresses in RFC 2822 format" mentioned above, Exim
     follows a strict interpretation of header line formatting. Exim parses the
     bare, unquoted portion of an email address and if it finds a comma, treats
-    it as an email address seperator. For the example header line:
+    it as an email address separator. For the example header line:
 
     From: =?iso-8859-2?Q?Last=2C_First?= <user@example.com>
 
@@ -9071,6 +9323,17 @@ ${addresses:<string>}
     # exim -be '${addresses:From: "Last, First" <user@example.com>}'
     user@example.com
 
+${base32:<digits>}
+
+    The string must consist entirely of decimal digits. The number is converted
+    to base 32 and output as a (empty, for zero) string of characters. Only
+    lowercase letters are used.
+
+${base32d:<base-32 digits>}
+
+    The string must consist entirely of base-32 digits. The number is converted
+    to decimal and output as a string.
+
 ${base62:<digits>}
 
     The string must consist entirely of decimal digits. The number is converted
@@ -9087,6 +9350,17 @@ ${base62d:<base-62 digits>}
     identifiers, base-36 digits. The number is converted to decimal and output
     as a string.
 
+${base64:<string>}
+
+    This operator converts a string into one that is base64 encoded.
+
+    If the string is a single variable of type certificate, returns the base64
+    encoding of the DER form of the certificate.
+
+${base64d:<string>}
+
+    This operator converts a base64-encoded string into the un-coded form.
+
 ${domain:<string>}
 
     The string is interpreted as an RFC 2822 address and the domain is
@@ -9100,6 +9374,12 @@ ${escape:<string>}
     most significant bit set (so-called "8-bit characters") count as printing
     or not is controlled by the print_topbitchars option.
 
+${escape8bit:<string>}
+
+    If the string contains and characters with the most significant bit set,
+    they are converted to escape sequences starting with a backslash.
+    Backslashes and DEL characters are also converted.
+
 ${eval:<string>} and ${eval10:<string>}
 
     These items supports simple arithmetic and bitwise logical operations in
@@ -9210,6 +9490,20 @@ ${hexquote:<string>}
     is, and other byte values are converted to "\xNN", for example a byte value
     127 is converted to "\x7f".
 
+${ipv6denorm:<string>}
+
+    This expands an IPv6 address to a full eight-element colon-separated set of
+    hex digits including leading zeroes. A trailing ipv4-style dotted-decimal
+    set is converted to hex. Pure IPv4 addresses are converted to IPv4-mapped
+    IPv6.
+
+${ipv6norm:<string>}
+
+    This converts an IPv6 address to canonical form. Leading zeroes of groups
+    are omitted, and the longest set of zero-valued groups is replaced with a
+    double colon. A trailing ipv4-style dotted-decimal set is converted to hex.
+    Pure IPv4 addresses are converted to IPv4-mapped IPv6.
+
 ${lc:<string>}
 
     This forces the letters in the string into lower-case, for example:
@@ -9275,6 +9569,9 @@ ${md5:<string>}
     The md5 operator computes the MD5 hash value of the string, and returns it
     as a 32-digit hexadecimal number, in which any letters are in lower case.
 
+    If the string is a single variable of type certificate, returns the MD5
+    hash fingerprint of the certificate.
+
 ${nhash_<n>_<m>:<string>}
 
     The nhash operator is a simpler interface to the numeric hashing function
@@ -9339,7 +9636,7 @@ ${randint:<n>}
 ${reverse_ip:<ipaddr>}
 
     This operator reverses an IP address; for IPv4 addresses, the result is in
-    dotted-quad decimal form, while for IPv6 addreses the result is in
+    dotted-quad decimal form, while for IPv6 addresses the result is in
     dotted-nibble hexadecimal form. In both cases, this is the "natural" form
     for DNS. For example,
 
@@ -9356,7 +9653,7 @@ ${rfc2047:<string>}
     This operator encodes text according to the rules of RFC 2047. This is an
     encoding that is used in header lines to encode non-ASCII characters. It is
     assumed that the input string is in the encoding specified by the 
-    headers_charset option, which defaults to ISO-8859-1. If the string
+    headers_charset option, which gets its default at build time. If the string
     contains only characters in the range 33-126, and no instances of the
     characters
 
@@ -9389,12 +9686,30 @@ ${sha1:<string>}
     it as a 40-digit hexadecimal number, in which any letters are in upper
     case.
 
-${sha256:<certificate>}
+    If the string is a single variable of type certificate, returns the SHA-1
+    hash fingerprint of the certificate.
+
+${sha256:<string>}
+
+    The sha256 operator computes the SHA-256 hash value of the string and
+    returns it as a 64-digit hexadecimal number, in which any letters are in
+    upper case.
+
+    If the string is a single variable of type certificate, returns the SHA-256
+    hash fingerprint of the certificate.
+
+${sha3:<string>}, ${sha3_<n>:<string>}
+
+    The sha3 operator computes the SHA3-256 hash value of the string and
+    returns it as a 64-digit hexadecimal number, in which any letters are in
+    upper case.
 
-    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.
+    If a number is appended, separated by an underbar, it specifies the output
+    length. Values of 224, 256, 384 and 512 are accepted; with 256 being the
+    default.
+
+    The sha3 expansion item is only supported if Exim has been compiled with
+    GnuTLS 3.5.0 or later.
 
 ${stat:<string>}
 
@@ -9414,7 +9729,7 @@ ${stat:<string>}
 
 ${str2b64:<string>}
 
-    This operator converts a string into one that is base64 encoded.
+    Now deprecated, a synonym for the base64 expansion operator.
 
 ${strlen:<string>}
 
@@ -9453,6 +9768,12 @@ ${utf8clean:<string>}
     This replaces any invalid utf-8 sequence in the string by the character "?
     ".
 
+${utf8_domain_to_alabel:<string>}, ${utf8_domain_from_alabel:<string>}, $
+    {utf8_localpart_to_alabel:<string>}, ${utf8_localpart_from_alabel:<string>}
+
+    These convert EAI mail name components between UTF-8 and a-label forms. For
+    information on internationalisation support see 59.1.
+
 
 11.7 Expansion conditions
 -------------------------
@@ -9497,7 +9818,7 @@ acl {{<name>}{<arg1>}{<arg2>}...}
     The name and zero to nine argument strings are first expanded separately.
     The expanded arguments are assigned to the variables $acl_arg1 to $acl_arg9
     in order. Any unused are made empty. The variable $acl_narg is set to the
-    number of arguments. The named ACL (see chapter 42) is called and may use
+    number of arguments. The named ACL (see chapter 43) is called and may use
     the variables; if another acl expansion is used the values are restored
     after it returns. If the ACL sets a value using a "message =" modifier the
     variable $value becomes the result of the expansion, otherwise it is empty.
@@ -9555,26 +9876,26 @@ crypteq {<string1>}{<string2>}
     The following encryption types (whose names are matched case-independently)
     are supported:
 
-      * {md5} computes the MD5 digest of the first string, and expresses this
+      + {md5} computes the MD5 digest of the first string, and expresses this
         as printable characters to compare with the remainder of the second
         string. If the length of the comparison string is 24, Exim assumes that
         it is base64 encoded (as in the above example). If the length is 32,
         Exim assumes that it is a hexadecimal encoding of the MD5 digest. If
         the length not 24 or 32, the comparison fails.
 
-      * {sha1} computes the SHA-1 digest of the first string, and expresses
+      + {sha1} computes the SHA-1 digest of the first string, and expresses
         this as printable characters to compare with the remainder of the
         second string. If the length of the comparison string is 28, Exim
         assumes that it is base64 encoded. If the length is 40, Exim assumes
         that it is a hexadecimal encoding of the SHA-1 digest. If the length is
         not 28 or 40, the comparison fails.
 
-      * {crypt} calls the crypt() function, which traditionally used to use
+      + {crypt} calls the crypt() function, which traditionally used to use
         only the first eight characters of the password. However, in modern
         operating systems this is no longer true, and in many cases the entire
         password is used, whatever its length.
 
-      * {crypt16} calls the crypt16() function, which was originally created to
+      + {crypt16} calls the crypt16() function, which was originally created to
         use up to 16 characters of the password in some operating systems.
         Again, in modern operating systems, more characters may be used.
 
@@ -9650,11 +9971,11 @@ forall{<a list>}{<a condition>}, forany{<a list>}{<a condition>}
     the interpretation of the condition, the current list item is placed in a
     variable called $item.
 
-      * For forany, interpretation stops if the condition is true for any item,
+      + For forany, interpretation stops if the condition is true for any item,
         and the result of the whole condition is true. If the condition is
         false for all items in the list, the overall condition is false.
 
-      * For forall, interpretation stops if the condition is false for any
+      + For forall, interpretation stops if the condition is false for any
         item, and the result of the whole condition is false. If the condition
         is true for all items in the list, the overall condition is true.
 
@@ -9723,7 +10044,7 @@ isip {<string>}, isip4 {<string>}, isip6 {<string>}
 
 ldapauth {<ldap query>}
 
-    This condition supports user authentication using LDAP. See section 9.13
+    This condition supports user authentication using LDAP. See section 9.14
     for details of how to use LDAP in lookups and the syntax of queries. For
     this use, the query must contain a user name and password. The query itself
     is not used, and can be empty. The condition is true if the password is not
@@ -9798,11 +10119,11 @@ match_ip {<string1>}{<string2>}
 
     The specific types of host list item that are permitted in the list are:
 
-      * An IP address, optionally with a CIDR mask.
+      + An IP address, optionally with a CIDR mask.
 
-      * A single asterisk, which matches any IP address.
+      + A single asterisk, which matches any IP address.
 
-      * An empty item, which matches only if the IP address is empty. This
+      + An empty item, which matches only if the IP address is empty. This
         could be useful for testing for a locally submitted message or one from
         specific hosts in a single test such as
 
@@ -9810,9 +10131,9 @@ match_ip {<string1>}{<string2>}
 
         where the first item in the list is the empty string.
 
-      * The item @[] matches any of the local host's interface addresses.
+      + The item @[] matches any of the local host's interface addresses.
 
-      * Single-key lookups are assumed to be like "net-" style lookups in host
+      + Single-key lookups are assumed to be like "net-" style lookups in host
         lists, even if "net-" is not specified. There is never any attempt to
         turn the IP address into a host name. The most common type of linear
         search for match_ip is likely to be iplsearch, in which the file can
@@ -10038,13 +10359,13 @@ $0, $1, etc
 
     When a match expansion condition succeeds, these variables contain the
     captured substrings identified by the regular expression during subsequent
-    processing of the success string of the containing if expansion item.
-    However, they do not retain their values afterwards; in fact, their
-    previous values are restored at the end of processing an if item. The
-    numerical variables may also be set externally by some other matching
-    process which precedes the expansion of the string. For example, the
-    commands available in Exim filter files include an if command with its own
-    regular expression matching condition.
+    processing of the success string of the containing if expansion item. In
+    the expansion condition case they do not retain their values afterwards; in
+    fact, their previous values are restored at the end of processing an if
+    item. The numerical variables may also be set externally by some other
+    matching process which precedes the expansion of the string. For example,
+    the commands available in Exim filter files include an if command with its
+    own regular expression matching condition.
 
 $acl_arg1, $acl_arg2, etc
 
@@ -10140,7 +10461,7 @@ $address_pipe
 
 $auth1 - $auth3
 
-    These variables are used in SMTP authenticators (see chapters 34-40).
+    These variables are used in SMTP authenticators (see chapters 34-41).
     Elsewhere, they are empty.
 
 $authenticated_id
@@ -10212,13 +10533,13 @@ $bounce_recipient
 
     This is set to the recipient address of a bounce message while Exim is
     creating it. It is useful if a customized bounce message text file is in
-    use (see chapter 48).
+    use (see chapter 49).
 
 $bounce_return_size_limit
 
     This contains the value set in the bounce_return_size_limit option, rounded
     up to a multiple of 1000. It is useful when a customized error message text
-    file is in use (see chapter 48).
+    file is in use (see chapter 49).
 
 $caller_gid
 
@@ -10234,9 +10555,10 @@ $caller_uid
     $originator_uid). If Exim re-execs itself, this variable in the new
     incarnation normally contains the Exim uid.
 
-$compile_date
+$callout_address
 
-    The date on which the Exim binary was compiled.
+    After a callout for verification, spamd or malware daemon service, the
+    address that was connected to.
 
 $compile_number
 
@@ -10244,22 +10566,39 @@ $compile_number
     been compiled. This serves to distinguish different compilations of the
     same version of the program.
 
-$demime_errorlevel
+$config_dir
 
-    This variable is available when Exim is compiled with the content-scanning
-    extension and the obsolete demime condition. For details, see section 43.6.
+    The directory name of the main configuration file. That is, the content of
+    $config_file with the last component stripped. The value does not contain
+    the trailing slash. If $config_file does not contain a slash, $config_dir
+    is ".".
 
-$demime_reason
+$config_file
 
-    This variable is available when Exim is compiled with the content-scanning
-    extension and the obsolete demime condition. For details, see section 43.6.
+    The name of the main configuration file Exim is using.
+
+$dkim_cur_signer, $dkim_verify_status, $dkim_verify_reason, $dkim_domain, 
+    $dkim_identity, $dkim_selector, $dkim_algo, $dkim_canon_body, 
+    $dkim_canon_headers, $dkim_copiedheaders, $dkim_bodylength, $dkim_created, 
+    $dkim_expires, $dkim_headernames, $dkim_key_testing, $dkim_key_nosubdomains
+    , $dkim_key_srvtype, $dkim_key_granularity, $dkim_key_notes, 
+    $dkim_key_length
+
+    These variables are only available within the DKIM ACL. For details see
+    chapter 57.
+
+$dkim_signers
+
+    When a message has been received this variable contains a colon-separated
+    list of signer domains and identities for the message. For details see
+    chapter 57.
 
 $dnslist_domain, $dnslist_matched, $dnslist_text, $dnslist_value
 
     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.32 for more details.
+    from the main A record. See section 43.32 for more details.
 
 $domain
 
@@ -10285,7 +10624,7 @@ $domain
 
     The $domain variable is also used in some other circumstances:
 
-      * When an ACL is running for a RCPT command, $domain contains the domain
+      + When an ACL is running for a RCPT command, $domain contains the domain
         of the recipient address. The domain of the sender address is in
         $sender_address_domain at both MAIL time and at RCPT time. $domain is
         not normally set during the running of the MAIL ACL. However, if the
@@ -10293,20 +10632,20 @@ $domain
         sender domain is placed in $domain during the expansions of hosts, 
         interface, and port in the smtp transport.
 
-      * When a rewrite item is being processed (see chapter 31), $domain
+      + When a rewrite item is being processed (see chapter 31), $domain
         contains the domain portion of the address that is being rewritten; it
         can be used in the expansion of the replacement address, for example,
         to rewrite domains by file lookup.
 
-      * With one important exception, whenever a domain list is being scanned,
+      + With one important exception, whenever a domain list is being scanned,
         $domain contains the subject domain. Exception: When a domain list in a
         sender_domains condition in an ACL is being processed, the subject
         domain is in $sender_address_domain and not in $domain. It works this
         way so that, in a RCPT ACL, the sender domain list can be dependent on
         the recipient domain (which is what is in $domain at this time).
 
-      * When the smtp_etrn_command option is being expanded, $domain contains
-        the complete argument of the ETRN command (see section 47.8).
+      + When the smtp_etrn_command option is being expanded, $domain contains
+        the complete argument of the ETRN command (see section 48.8).
 
 $domain_data
 
@@ -10333,10 +10672,12 @@ $exim_uid
 
     This variable contains the numerical value of the Exim user id.
 
-$found_extension
+$exim_version
 
-    This variable is available when Exim is compiled with the content-scanning
-    extension and the obsolete demime condition. For details, see section 43.6.
+    This variable contains the version string of the Exim build. The first
+    character is a major version number, currently 4. Then after a dot, the
+    next group of digits is a minor version number. There may be other
+    characters following the minor version.
 
 $header_<name>
 
@@ -10348,7 +10689,7 @@ $header_<name>
 $headers_added
 
     Within an ACL this variable contains the headers added so far by the ACL
-    modifier add_header (section 42.24). The headers are a newline-separated
+    modifier add_header (section 43.24). The headers are a newline-separated
     list.
 
 $home
@@ -10360,7 +10701,8 @@ $home
     overridden by a setting on the transport itself.
 
     When running a filter test via the -bf option, $home is set to the value of
-    the environment variable HOME.
+    the environment variable HOME, which is subject to the keep_environment and
+    add_environment main config options.
 
 $host
 
@@ -10401,11 +10743,11 @@ $host_lookup_deferred
     host's name from its IP address, and the attempt is not successful, one of
     these variables is set to "1".
 
-      * If the lookup receives a definite negative response (for example, a DNS
+      + If the lookup receives a definite negative response (for example, a DNS
         lookup succeeded, but no records were found), $host_lookup_failed is
         set to "1".
 
-      * If there is any kind of problem during the lookup, such that Exim
+      + If there is any kind of problem during the lookup, such that Exim
         cannot tell whether or not the host name is defined (for example, a
         timeout for a DNS lookup), $host_lookup_deferred is set to "1".
 
@@ -10424,6 +10766,18 @@ $host_lookup_failed
 
     See $host_lookup_deferred.
 
+$host_port
+
+    This variable is set to the remote host's TCP port whenever $host is set
+    for an outbound connection.
+
+$initial_cwd
+
+    This variable contains the full path name of the initial working directory
+    of the current Exim process. This may differ from the current working
+    directory, as Exim changes this to "/" during early startup, and to
+    $spool_directory later.
+
 $inode
 
     The only time this variable is set is while expanding the directory_file
@@ -10536,7 +10890,7 @@ $local_part_suffix
 $local_scan_data
 
     This variable contains the text returned by the local_scan() function when
-    a message is received. See chapter 44 for more details.
+    a message is received. See chapter 45 for more details.
 
 $local_user_gid
 
@@ -10576,7 +10930,9 @@ $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.
+    "yes" if it was. Results that are labelled as authoritative answer that
+    match the dns_trust_aa configuration variable count also as authenticated
+    data.
 
 $mailstore_basename
 
@@ -10591,7 +10947,7 @@ $malware_name
 
     This variable is available when Exim is compiled with the content-scanning
     extension. It is set to the name of the virus that was found when the ACL 
-    malware condition is true (see section 43.1).
+    malware condition is true (see section 44.1).
 
 $max_received_linelength
 
@@ -10653,7 +11009,7 @@ $message_headers_raw
 
 $message_id
 
-    This is an old name for $message_exim_id, which is now deprecated.
+    This is an old name for $message_exim_id. It is now deprecated.
 
 $message_linecount
 
@@ -10700,7 +11056,7 @@ $mime_xxx
 
     A number of variables whose names start with $mime are available when Exim
     is compiled with the content-scanning extension. For details, see section
-    43.4.
+    44.4.
 
 $n0 - $n9
 
@@ -10784,20 +11140,31 @@ $primary_hostname
     where available) in an attempt to acquire a fully qualified host name. See
     also $smtp_active_hostname.
 
+$proxy_external_address, $proxy_external_port, $proxy_local_address, 
+    $proxy_local_port, $proxy_session
+
+    These variables are only available when built with Proxy Protocol or SOCKS5
+    support. For details see chapter 58.1.
+
+$prdr_requested
+
+    This variable is set to "yes" if PRDR was requested by the client for the
+    current message, otherwise "no".
+
 $prvscheck_address
 
     This variable is used in conjunction with the prvscheck expansion item,
-    which is described in sections 11.5 and 42.51.
+    which is described in sections 11.5 and 43.51.
 
 $prvscheck_keynum
 
     This variable is used in conjunction with the prvscheck expansion item,
-    which is described in sections 11.5 and 42.51.
+    which is described in sections 11.5 and 43.51.
 
 $prvscheck_result
 
     This variable is used in conjunction with the prvscheck expansion item,
-    which is described in sections 11.5 and 42.51.
+    which is described in sections 11.5 and 43.51.
 
 $qualify_domain
 
@@ -10808,6 +11175,10 @@ $qualify_recipient
     The value set for the qualify_recipient option in the configuration file,
     or if not set, the value of $qualify_domain.
 
+$queue_name
+
+    The name of the spool queue in use; empty for the default queue.
+
 $rcpt_count
 
     When a message is being received by SMTP, this variable contains the number
@@ -10854,11 +11225,8 @@ $received_ip_address
     certificate depend on which interface and/or port is being used for the
     incoming connection. The values of $received_ip_address and $received_port
     are saved with any messages that are received, thus making these variables
-    available at delivery time.
-
-    Note: There are no equivalent variables for outgoing connections, because
-    the values are unknown (unless they are explicitly set by options of the 
-    smtp transport).
+    available at delivery time. For outbound connections see
+    $sending_ip_address.
 
 $received_port
 
@@ -10910,18 +11278,18 @@ $recipient_verify_failure
     In an ACL, when a recipient verification fails, this variable contains
     information about the failure. It is set to one of the following words:
 
-      * "qualify": The address was unqualified (no domain), and the message was
+      + "qualify": The address was unqualified (no domain), and the message was
         neither local nor came from an exempted host.
 
-      * "route": Routing failed.
+      + "route": Routing failed.
 
-      * "mail": Routing succeeded, and a callout was attempted; rejection
+      + "mail": Routing succeeded, and a callout was attempted; rejection
         occurred at or before the MAIL command (that is, on initial connection,
         HELO, or MAIL).
 
-      * "recipient": The RCPT command in a callout was rejected.
+      + "recipient": The RCPT command in a callout was rejected.
 
-      * "postmaster": The postmaster check in a callout was rejected.
+      + "postmaster": The postmaster check in a callout was rejected.
 
     The main use of this variable is expected to be to distinguish between
     rejections of MAIL and rejections of RCPT.
@@ -10952,7 +11320,12 @@ $recipients_count
 $regex_match_string
 
     This variable is set to contain the matching regular expression after a 
-    regex ACL condition has matched (see section 43.5).
+    regex ACL condition has matched (see section 44.5).
+
+$regex1, $regex2, etc
+
+    When a regex or mime_regex ACL condition succeeds, these variables contain
+    the captured substrings identified by the regular expression.
 
 $reply_address
 
@@ -11055,6 +11428,11 @@ $sender_fullhost
     if it is identical to the verified host name or to the host's IP address in
     square brackets.
 
+$sender_helo_dnssec
+
+    This boolean variable is true if a successful HELO verification was done
+    using DNS information the resolver library stated was authenticated data.
+
 $sender_helo_name
 
     When a message is received from a remote host that has issued a HELO or
@@ -11064,8 +11442,9 @@ $sender_helo_name
 
 $sender_host_address
 
-    When a message is received from a remote host, this variable contains that
-    host's IP address. For locally submitted messages, it is empty.
+    When a message is received from a remote host using SMTP, this variable
+    contains that host's IP address. For locally non-SMTP submitted messages,
+    it is empty.
 
 $sender_host_authenticated
 
@@ -11078,8 +11457,8 @@ $sender_host_dnssec
 
     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.
+    and only if, the resolver library states that both the reverse and forward
+    DNS were 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:
@@ -11087,14 +11466,16 @@ $sender_host_dnssec
     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).
-
-    Exim does not (currently) check to see if the forward DNS was also secured
-    with DNSSEC, only the reverse DNS.
+    validating resolver (e.g. unbound, or bind with suitable configuration).
 
     If you have changed host_lookup_order so that "bydns" is not the first
     mechanism in the list, then this variable will be false.
 
+    This requires that your system resolver library support EDNS0 (and that
+    DNSSEC flags exist in the system headers). If the resolver silently drops
+    all EDNS0 options, then this will have no effect. OpenBSD's asr resolver is
+    known to currently ignore EDNS0, documented in CAVEATS of asr_run(3).
+
 $sender_host_name
 
     When a message is received from a remote host, this variable contains the
@@ -11123,21 +11504,21 @@ $sender_host_name
     these lookups altogether. The lookup happens only if one or more of the
     following are true:
 
-      * A string containing $sender_host_name is expanded.
+      + A string containing $sender_host_name is expanded.
 
-      * The calling host matches the list in host_lookup. In the default
+      + The calling host matches the list in host_lookup. In the default
         configuration, this option is set to *, so it must be changed if
         lookups are to be avoided. (In the code, the default for host_lookup is
         unset.)
 
-      * Exim needs the host name in order to test an item in a host list. The
+      + 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.17.
 
-      * The calling host matches helo_try_verify_hosts or helo_verify_hosts. In
+      + 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
         any EHLO or HELO commands that the client issues.
 
-      * The remote host issues a EHLO or HELO command that quotes one of the
+      + The remote host issues a EHLO or HELO command that quotes one of the
         domains in helo_lookup_domains. The default value of this option is
 
           helo_lookup_domains = @ : @[]
@@ -11160,7 +11541,7 @@ $sender_ident
 $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.38.
+    the ratelimit ACL condition. Details are given in section 43.38.
 
 $sender_rcvhost
 
@@ -11254,7 +11635,7 @@ $spam_xxx
 
     A number of variables whose names start with $spam are available when Exim
     is compiled with the content-scanning extension. For details, see section
-    43.2.
+    44.2.
 
 $spool_directory
 
@@ -11309,33 +11690,38 @@ $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.
+    of a certextract expansion item, md5, sha1 or sha256 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.
+    of a certextract expansion item, md5, sha1 or sha256 operator, or a def
+    condition. If certificate verification fails it may refer to a failing
+    chain element which is not the leaf.
 
 $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.
+    expansion item, md5, sha1 or sha256 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.
+    expansion item, md5, sha1 or sha256 operator, or a def condition. If
+    certificate verification fails it may refer to a failing chain element
+    which is not the leaf.
 
 $tls_in_certificate_verified
 
     This variable is set to "1" if a TLS certificate was verified when the
     message was received, and "0" otherwise.
 
-    The deprecated $tls_certificate_verfied variable refers to the inbound side
-    except when used in the context of an outbound SMTP delivery, when it
+    The deprecated $tls_certificate_verified variable refers to the inbound
+    side except when used in the context of an outbound SMTP delivery, when it
     refers to the outbound.
 
 $tls_out_certificate_verified
@@ -11349,7 +11735,7 @@ $tls_in_cipher
     connection, this variable is set to the cipher suite that was negotiated,
     for example DES-CBC3-SHA. In other circumstances, in particular, for
     message received over unencrypted connections, the variable is empty.
-    Testing $tls_cipher for emptiness is one way of distinguishing between
+    Testing $tls_in_cipher for emptiness is one way of distinguishing between
     encrypted and non-encrypted connections during ACL processing.
 
     The deprecated $tls_cipher variable is the same as $tls_in_cipher during
@@ -11359,7 +11745,7 @@ $tls_in_cipher
 $tls_out_cipher
 
     This variable is cleared before any outgoing SMTP connection is made, and
-    then set to the outgoing cipher suite if one is negotiated. See chapter 41
+    then set to the outgoing cipher suite if one is negotiated. See chapter 42
     for details of TLS support and chapter 30 for details of the smtp
     transport.
 
@@ -11384,7 +11770,9 @@ $tls_in_peerdn
     When a message is received from a remote host over an encrypted SMTP
     connection, and Exim is configured to request a certificate from the
     client, the value of the Distinguished Name of the certificate is made
-    available in the $tls_in_peerdn during subsequent processing.
+    available in the $tls_in_peerdn during subsequent processing. If
+    certificate verification fails it may refer to a failing chain element
+    which is not the leaf.
 
     The deprecated $tls_peerdn variable refers to the inbound side except when
     used in the context of an outbound SMTP delivery, when it refers to the
@@ -11395,14 +11783,16 @@ $tls_out_peerdn
     When a message is being delivered to a remote host over an encrypted SMTP
     connection, and Exim is configured to request a certificate from the
     server, the value of the Distinguished Name of the certificate is made
-    available in the $tls_out_peerdn during subsequent processing.
+    available in the $tls_out_peerdn during subsequent processing. If
+    certificate verification fails it may refer to a failing chain element
+    which is not the leaf.
 
 $tls_in_sni
 
     When a TLS session is being established, if the client sends the Server
     Name Indication extension, the value will be placed in this variable. If
     the variable appears in tls_certificate then this option and some others,
-    described in 41.10, will be re-expanded early in the TLS session, to permit
+    described in 42.10, will be re-expanded early in the TLS session, to permit
     a different certificate to be presented (and optionally a different key to
     be used) to the client, based upon the value of the SNI extension.
 
@@ -11467,6 +11857,12 @@ $value
     operation, or external command, as described above. It is also used during
     a reduce expansion.
 
+$verify_mode
+
+    While a router or transport is being run in verify mode or for cutthrough
+    delivery, contains "S" for sender-verification or "R" for
+    recipient-verification. Otherwise, empty.
+
 $version_number
 
     The version number of Exim.
@@ -11474,12 +11870,12 @@ $version_number
 $warn_message_delay
 
     This variable is set only during the creation of a message warning about a
-    delivery delay. Details of its use are explained in section 48.2.
+    delivery delay. Details of its use are explained in section 49.2.
 
 $warn_message_recipients
 
     This variable is set only during the creation of a message warning about a
-    delivery delay. Details of its use are explained in section 48.2.
+    delivery delay. Details of its use are explained in section 49.2.
 
 
 
@@ -11533,6 +11929,11 @@ two ways:
 There is also a command line option -pd (for delay) which suppresses the
 initial startup, even if perl_at_start is set.
 
+  * To provide more security executing Perl code via the embedded Perl
+    interpreter, the perl_taintmode option can be set. This enables the taint
+    mode of the Perl interpreter. You are encouraged to set this option to a
+    true value. To avoid breaking existing installations, it defaults to false.
+
 
 12.2 Calling Perl subroutines
 -----------------------------
@@ -11665,7 +12066,7 @@ options:
     listen. Each item may optionally also specify a port.
 
 The default list separator in both cases is a colon, but this can be changed as
-described in section 6.19. When IPv6 addresses are involved, it is usually best
+described in section 6.20. When IPv6 addresses are involved, it is usually best
 to change the separator to avoid having to double all the colons. For example:
 
 local_interfaces = <; 127.0.0.1 ; \
@@ -11914,7 +12315,7 @@ The first part of the run time configuration file contains three types of item:
   * Main configuration settings: Each setting occupies one line of the file
     (with possible continuations). If any setting is preceded by the word
     "hide", the -bP command line option displays its value to admin users only.
-    See section 6.10 for a description of the syntax of these option settings.
+    See section 6.11 for a description of the syntax of these option settings.
 
 This chapter specifies all the main configuration options, along with their
 types and default values. For ease of finding a particular option, they appear
@@ -11928,6 +12329,7 @@ more than one group.
 ------------------
 
 bi_command            to run for -bi command line option
+debug_store           do extra internal checks
 disable_ipv6          do no IPv6 processing
 keep_malformed        for broken files - should not happen
 localhost_number      for unique message ids in clusters
@@ -11968,6 +12370,7 @@ trusted_users             users that are trusted
 14.4 Logging
 ------------
 
+event_action           custom logging
 hosts_connection_nolog exemption from connect logging
 log_file_path          override compiled-in value
 log_selector           set/unset optional logging
@@ -11975,8 +12378,10 @@ log_timezone           add timezone to log lines
 message_logs           create per-message logs
 preserve_message_logs  after message completion
 process_log_path       for SIGUSR1 and exiwhat
+slow_lookup_log        control logging of slow DNS lookups
 syslog_duplication     controls duplicate log lines on syslog
 syslog_facility        set syslog "facility" field
+syslog_pid             pid in syslog lines
 syslog_processname     set syslog "ident" field
 syslog_timestamp       timestamp syslog lines
 write_rejectlog        control use of message log
@@ -12021,8 +12426,9 @@ message_id_header_text   ditto
 14.8 Embedded Perl Startup
 --------------------------
 
-perl_at_start always start the interpreter
-perl_startup  code to obey when starting Perl
+perl_at_start  always start the interpreter
+perl_startup   code to obey when starting Perl
+perl_taintmode enable taint mode in Perl
 
 
 14.9 Daemon
@@ -12080,6 +12486,7 @@ acl_smtp_helo          ACL for EHLO or HELO
 acl_smtp_mail          ACL for MAIL
 acl_smtp_mailauth      ACL for AUTH on MAIL command
 acl_smtp_mime          ACL for MIME parts
+acl_smtp_notquit       ACL for non-QUIT terminations
 acl_smtp_predata       ACL for start of data
 acl_smtp_quit          ACL for QUIT
 acl_smtp_rcpt          ACL for RCPT
@@ -12098,6 +12505,7 @@ helo_try_verify_hosts  HELO soft-checked for these hosts
 helo_verify_hosts      HELO hard-checked for these hosts
 host_lookup            host name looked up for these hosts
 host_lookup_order      order of DNS and local name lookups
+hosts_proxy            use proxy protocol for these hosts
 host_reject_connection reject connection from these hosts
 hosts_treat_as_local   useful in some cluster configurations
 local_scan_timeout     timeout for local_scan()
@@ -12128,6 +12536,7 @@ tls_certificate          location of server certificate
 tls_crl                  certificate revocation list
 tls_dh_max_bits          clamp D-H bit count suggestion
 tls_dhparam              DH parameters for server
+tls_eccurve              EC curve selection 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
@@ -12175,6 +12584,7 @@ receive_timeout for non-SMTP messages
 
 See also the Policy controls section above.
 
+dkim_verify_signers              DKIM domain for which DKIM ACL is run
 host_lookup                      host name looked up for these hosts
 host_lookup_order                order of DNS and local name lookups
 recipient_unqualified_hosts      may send unqualified recipients
@@ -12212,10 +12622,13 @@ smtp_return_error_details        give detail on rejections
 
 accept_8bitmime            advertise 8BITMIME
 auth_advertise_hosts       advertise AUTH to these hosts
+chunking_advertise_hosts   advertise CHUNKING to these hosts
+dsn_advertise_hosts        advertise DSN extensions to these hosts
 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
+smtputf8_advertise_hosts   advertise SMTPUTF8 to these hosts
 tls_advertise_hosts        advertise TLS to these hosts
 
 
@@ -12260,6 +12673,7 @@ 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_trust_aa             DNS zones trusted as authentic
 dns_use_edns0            parameter for resolver
 hold_domains             hold delivery for these domains
 local_interfaces         for routing checks
@@ -12284,6 +12698,7 @@ retry_interval_max       safety net for retry rules
 bounce_message_file          content of bounce
 bounce_message_text          content of bounce
 bounce_return_body           include body if returning message
+bounce_return_linesize_limit limit on returned message line length
 bounce_return_message        include original message in bounce
 bounce_return_size_limit     limit on returned message
 bounce_sender_authentication send authenticated sender with bounce
@@ -12302,9 +12717,9 @@ warn_message_file            content of warning message
 
 Those options that undergo string expansion before use are marked with *.
 
-+---------------+---------+-------------+-------------+
++-----------------------------------------------------+
 |accept_8bitmime|Use: main|Type: boolean|Default: true|
-+---------------+---------+-------------+-------------+
++-----------------------------------------------------+
 
 This option causes Exim to send 8BITMIME in its response to an SMTP EHLO
 command, and to accept the BODY= parameter on MAIL commands. However, though
@@ -12322,141 +12737,164 @@ To log received 8BITMIME status use
 
 log_selector = +8bitmime
 
-+------------+---------+-------------+--------------+
++---------------------------------------------------+
 |acl_not_smtp|Use: main|Type: string*|Default: unset|
-+------------+---------+-------------+--------------+
++---------------------------------------------------+
 
 This option defines the ACL that is run when a non-SMTP message has been read
-and is on the point of being accepted. See chapter 42 for further details.
+and is on the point of being accepted. See chapter 43 for further details.
 
-+-----------------+---------+-------------+--------------+
++--------------------------------------------------------+
 |acl_not_smtp_mime|Use: main|Type: string*|Default: unset|
-+-----------------+---------+-------------+--------------+
++--------------------------------------------------------+
 
 This option defines the ACL that is run for individual MIME parts of non-SMTP
 messages. It operates in exactly the same way as acl_smtp_mime operates for
 SMTP messages.
 
-+------------------+---------+-------------+--------------+
++---------------------------------------------------------+
 |acl_not_smtp_start|Use: main|Type: string*|Default: unset|
-+------------------+---------+-------------+--------------+
++---------------------------------------------------------+
 
 This option defines the ACL that is run before Exim starts reading a non-SMTP
-message. See chapter 42 for further details.
+message. See chapter 43 for further details.
 
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 |acl_smtp_auth|Use: main|Type: string*|Default: unset|
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 
 This option defines the ACL that is run when an SMTP AUTH command is received.
-See chapter 42 for further details.
+See chapter 43 for further details.
 
-+----------------+---------+-------------+--------------+
++-------------------------------------------------------+
 |acl_smtp_connect|Use: main|Type: string*|Default: unset|
-+----------------+---------+-------------+--------------+
++-------------------------------------------------------+
 
 This option defines the ACL that is run when an SMTP connection is received.
-See chapter 42 for further details.
+See chapter 43 for further details.
 
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 |acl_smtp_data|Use: main|Type: string*|Default: unset|
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 
 This option defines the ACL that is run after an SMTP DATA command has been
 processed and the message itself has been received, but before the final
-acknowledgment is sent. See chapter 42 for further details.
+acknowledgment is sent. See chapter 43 for further details.
 
-+------------------+---------+-------------+--------------+
-|acl_smtp_data_prdr|Use: main|Type: string*|Default: unset|
-+------------------+---------+-------------+--------------+
++----------------------------------------------------------+
+|acl_smtp_data_prdr|Use: main|Type: string*|Default: accept|
++----------------------------------------------------------+
 
 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.
+chapter 43 for further details.
+
++----------------------------------------------------+
+|acl_smtp_dkim|Use: main|Type: string*|Default: unset|
++----------------------------------------------------+
 
-+-------------+---------+-------------+--------------+
+This option defines the ACL that is run for each DKIM signature (by default, or
+as specified in the dkim_verify_signers option) of a received message. See
+chapter 57 for further details.
+
++----------------------------------------------------+
 |acl_smtp_etrn|Use: main|Type: string*|Default: unset|
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 
 This option defines the ACL that is run when an SMTP ETRN command is received.
-See chapter 42 for further details.
+See chapter 43 for further details.
 
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 |acl_smtp_expn|Use: main|Type: string*|Default: unset|
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 
 This option defines the ACL that is run when an SMTP EXPN command is received.
-See chapter 42 for further details.
+See chapter 43 for further details.
 
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 |acl_smtp_helo|Use: main|Type: string*|Default: unset|
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 
 This option defines the ACL that is run when an SMTP EHLO or HELO command is
-received. See chapter 42 for further details.
+received. See chapter 43 for further details.
 
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 |acl_smtp_mail|Use: main|Type: string*|Default: unset|
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 
 This option defines the ACL that is run when an SMTP MAIL command is received.
-See chapter 42 for further details.
+See chapter 43 for further details.
 
-+-----------------+---------+-------------+--------------+
++--------------------------------------------------------+
 |acl_smtp_mailauth|Use: main|Type: string*|Default: unset|
-+-----------------+---------+-------------+--------------+
++--------------------------------------------------------+
 
 This option defines the ACL that is run when there is an AUTH parameter on a
-MAIL command. See chapter 42 for details of ACLs, and chapter 33 for details of
+MAIL command. See chapter 43 for details of ACLs, and chapter 33 for details of
 authentication.
 
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 |acl_smtp_mime|Use: main|Type: string*|Default: unset|
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 
 This option is available when Exim is built with the content-scanning
 extension. It defines the ACL that is run for each MIME part in a message. See
-section 43.4 for details.
+section 44.4 for details.
+
++-------------------------------------------------------+
+|acl_smtp_notquit|Use: main|Type: string*|Default: unset|
++-------------------------------------------------------+
 
-+----------------+---------+-------------+--------------+
+This option defines the ACL that is run when an SMTP session ends without a
+QUIT command being received. See chapter 43 for further details.
+
++-------------------------------------------------------+
 |acl_smtp_predata|Use: main|Type: string*|Default: unset|
-+----------------+---------+-------------+--------------+
++-------------------------------------------------------+
 
 This option defines the ACL that is run when an SMTP DATA command is received,
-before the message itself is received. See chapter 42 for further details.
+before the message itself is received. See chapter 43 for further details.
 
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 |acl_smtp_quit|Use: main|Type: string*|Default: unset|
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 
 This option defines the ACL that is run when an SMTP QUIT command is received.
-See chapter 42 for further details.
+See chapter 43 for further details.
 
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 |acl_smtp_rcpt|Use: main|Type: string*|Default: unset|
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 
 This option defines the ACL that is run when an SMTP RCPT command is received.
-See chapter 42 for further details.
+See chapter 43 for further details.
 
-+-----------------+---------+-------------+--------------+
++--------------------------------------------------------+
 |acl_smtp_starttls|Use: main|Type: string*|Default: unset|
-+-----------------+---------+-------------+--------------+
++--------------------------------------------------------+
 
 This option defines the ACL that is run when an SMTP STARTTLS command is
-received. See chapter 42 for further details.
+received. See chapter 43 for further details.
 
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 |acl_smtp_vrfy|Use: main|Type: string*|Default: unset|
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 
 This option defines the ACL that is run when an SMTP VRFY command is received.
-See chapter 42 for further details.
+See chapter 43 for further details.
+
++----------------------------------------------------------+
+|add_environment|Use: main|Type: string list|Default: empty|
++----------------------------------------------------------+
 
-+------------+---------+------------------+--------------+
+This option allows to set individual environment variables that the currently
+linked libraries and programs in child processes use. See 29.4 for the
+environment of pipe transports.
+
++--------------------------------------------------------+
 |admin_groups|Use: main|Type: string list*|Default: unset|
-+------------+---------+------------------+--------------+
++--------------------------------------------------------+
 
 This option is expanded just once, at the start of Exim's processing. If the
 current group or any of the supplementary groups of an Exim caller is in this
@@ -12466,9 +12904,9 @@ admin privileges by putting that group in admin_groups. However, this does not
 permit them to read Exim's spool files (whose group owner is the Exim gid). To
 permit this, you have to add individuals to the Exim group.
 
-+---------------------+---------+-------------+--------------+
++------------------------------------------------------------+
 |allow_domain_literals|Use: main|Type: boolean|Default: false|
-+---------------------+---------+-------------+--------------+
++------------------------------------------------------------+
 
 If this option is set, the RFC 2822 domain literal format is permitted in email
 addresses. The option is not set by default, because the domain literal format
@@ -12482,21 +12920,21 @@ true, and also to add "@[]" to the list of local domains (defined in the named
 domain list local_domains in the default configuration). This "magic string"
 matches the domain literal form of all the local host's IP addresses.
 
-+--------------+---------+-------------+--------------+
++-----------------------------------------------------+
 |allow_mx_to_ip|Use: main|Type: boolean|Default: false|
-+--------------+---------+-------------+--------------+
++-----------------------------------------------------+
 
 It appears that more and more DNS zone administrators are breaking the rules
 and putting domain names that look like IP addresses on the right hand side of
 MX records. Exim follows the rules and rejects this, giving an error message
-that explains the mis-configuration. However, some other MTAs support this
+that explains the misconfiguration. However, some other MTAs support this
 practice, so to avoid "Why can't Exim do this?" complaints, allow_mx_to_ip
 exists, in order to enable this heinous activity. It is not recommended, except
 when you have no other choice.
 
-+------------------+---------+-------------+--------------+
++---------------------------------------------------------+
 |allow_utf8_domains|Use: main|Type: boolean|Default: false|
-+------------------+---------+-------------+--------------+
++---------------------------------------------------------+
 
 Lots of discussion is going on about internationalized domain names. One camp
 is strongly in favour of just using UTF-8 characters, and it seems that at
@@ -12518,9 +12956,9 @@ dns_check_names_pattern =
 
 That is, set the option to an empty string so that no check is done.
 
-+--------------------+---------+----------------+----------+
++----------------------------------------------------------+
 |auth_advertise_hosts|Use: main|Type: host list*|Default: *|
-+--------------------+---------+----------------+----------+
++----------------------------------------------------------+
 
 If any server authentication mechanisms are configured, Exim advertises them in
 response to an EHLO command only if the calling host matches this list.
@@ -12547,9 +12985,9 @@ If $tls_in_cipher is empty, the session is not encrypted, and the result of the
 expansion is empty, thus matching no hosts. Otherwise, the result of the
 expansion is *, which matches all hosts.
 
-+---------+---------+----------+-----------+
++------------------------------------------+
 |auto_thaw|Use: main|Type: time|Default: 0s|
-+---------+---------+----------+-----------+
++------------------------------------------+
 
 If this option is set to a time greater than zero, a queue runner will try a
 new delivery attempt on any frozen message, other than a bounce message, if
@@ -12561,9 +12999,9 @@ Note: This is an old option, which predates timeout_frozen_after and
 ignore_bounce_errors_after. It is retained for compatibility, but it is not
 thought to be very useful any more, and its use should probably be avoided.
 
-+----------+---------+------------+------------------+
++----------------------------------------------------+
 |av_scanner|Use: main|Type: string|Default: see below|
-+----------+---------+------------+------------------+
++----------------------------------------------------+
 
 This option is available if Exim is built with the content-scanning extension.
 It specifies which anti-virus scanner to use. The default value is:
@@ -12571,36 +13009,36 @@ It specifies which anti-virus scanner to use. The default value is:
 sophie:/var/run/sophie
 
 If the value of av_scanner starts with a dollar character, it is expanded
-before use. See section 43.1 for further details.
+before use. See section 44.1 for further details.
 
-+----------+---------+------------+--------------+
++------------------------------------------------+
 |bi_command|Use: main|Type: string|Default: unset|
-+----------+---------+------------+--------------+
++------------------------------------------------+
 
 This option supplies the name of a command that is run when Exim is called with
 the -bi option (see chapter 5). The string value is just the command name, it
 is not a complete command line. If an argument is required, it must come from
 the -oA command line option.
 
-+-------------------+---------+------------+--------------+
++---------------------------------------------------------+
 |bounce_message_file|Use: main|Type: string|Default: unset|
-+-------------------+---------+------------+--------------+
++---------------------------------------------------------+
 
 This option defines a template file containing paragraphs of text to be used
 for constructing bounce messages. Details of the file's contents are given in
-chapter 48. See also warn_message_file.
+chapter 49. See also warn_message_file.
 
-+-------------------+---------+------------+--------------+
++---------------------------------------------------------+
 |bounce_message_text|Use: main|Type: string|Default: unset|
-+-------------------+---------+------------+--------------+
++---------------------------------------------------------+
 
 When this option is set, its contents are included in the default bounce
 message immediately after "This message was created automatically by mail
 delivery software." It is not used if bounce_message_file is set.
 
-+------------------+---------+-------------+-------------+
++--------------------------------------------------------+
 |bounce_return_body|Use: main|Type: boolean|Default: true|
-+------------------+---------+-------------+-------------+
++--------------------------------------------------------+
 
 This option controls whether the body of an incoming message is included in a
 bounce message when bounce_return_message is true. The default setting causes
@@ -12610,17 +13048,32 @@ is included. In the case of a non-SMTP message containing an error that is
 detected during reception, only those header lines preceding the point at which
 the error was detected are returned.
 
-+---------------------+---------+-------------+-------------+
++-----------------------------------------------------------------+
+|bounce_return_linesize_limit|Use: main|Type: integer|Default: 998|
++-----------------------------------------------------------------+
+
+This option sets a limit in bytes on the line length of messages that are
+returned to senders due to delivery problems, when bounce_return_message is
+true. The default value corresponds to RFC limits. If the message being
+returned has lines longer than this value it is treated as if the 
+bounce_return_size_limit (below) restriction was exceeded.
+
+The option also applies to bounces returned when an error is detected during
+reception of a message. In this case lines from the original are truncated.
+
+The option does not apply to messages generated by an autoreply transport.
+
++-----------------------------------------------------------+
 |bounce_return_message|Use: main|Type: boolean|Default: true|
-+---------------------+---------+-------------+-------------+
++-----------------------------------------------------------+
 
 If this option is set false, none of the original message is included in bounce
 messages generated by Exim. See also bounce_return_size_limit and 
 bounce_return_body.
 
-+------------------------+---------+-------------+-------------+
++--------------------------------------------------------------+
 |bounce_return_size_limit|Use: main|Type: integer|Default: 100K|
-+------------------------+---------+-------------+-------------+
++--------------------------------------------------------------+
 
 This option sets a limit in bytes on the size of messages that are returned to
 senders as part of bounce messages when bounce_return_message is true. The
@@ -12635,9 +13088,9 @@ to the use of buffering for transferring the message in chunks (typically 8K in
 size). The idea is to save bandwidth on those undeliverable 15-megabyte
 messages.
 
-+----------------------------+---------+------------+--------------+
++------------------------------------------------------------------+
 |bounce_sender_authentication|Use: main|Type: string|Default: unset|
-+----------------------------+---------+------------+--------------+
++------------------------------------------------------------------+
 
 This option provides an authenticated sender address that is sent with any
 bounce messages generated by Exim that are sent over an authenticated SMTP
@@ -12652,64 +13105,64 @@ MAIL FROM:<> AUTH=mailer-daemon@my.domain.example
 The value of bounce_sender_authentication must always be a complete email
 address.
 
-+------------------------------+---------+----------+-----------+
++---------------------------------------------------------------+
 |callout_domain_negative_expire|Use: main|Type: time|Default: 3h|
-+------------------------------+---------+----------+-----------+
++---------------------------------------------------------------+
 
 This option specifies the expiry time for negative callout cache data for a
-domain. See section 42.45 for details of callout verification, and section
-42.47 for details of the caching.
+domain. See section 43.45 for details of callout verification, and section
+43.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.45 for details of callout verification, and section
-42.47 for details of the caching.
+domain. See section 43.45 for details of callout verification, and section
+43.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.45 for details of callout verification, and section
-42.47 for details of the caching.
+address. See section 43.45 for details of callout verification, and section
+43.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.45 for details of callout verification, and section
-42.47 for details of the caching.
+address. See section 43.45 for details of callout verification, and section
+43.47 for details of the caching.
 
-+-------------------------+---------+-------------+------------------+
++--------------------------------------------------------------------+
 |callout_random_local_part|Use: main|Type: string*|Default: see below|
-+-------------------------+---------+-------------+------------------+
++--------------------------------------------------------------------+
 
 This option defines the "random" local part that can be used as part of callout
 verification. The default value is
 
 $primary_hostname-$tod_epoch-testing
 
-See section 42.46 for details of how this value is used.
+See section 43.46 for details of how this value is used.
 
-+----------------+---------+-------------+----------+
-|check_log_inodes|Use: main|Type: integer|Default: 0|
-+----------------+---------+-------------+----------+
++-----------------------------------------------------+
+|check_log_inodes|Use: main|Type: integer|Default: 100|
++-----------------------------------------------------+
 
 See check_spool_space below.
 
-+---------------+---------+-------------+----------+
-|check_log_space|Use: main|Type: integer|Default: 0|
-+---------------+---------+-------------+----------+
++----------------------------------------------------+
+|check_log_space|Use: main|Type: integer|Default: 10M|
++----------------------------------------------------+
 
 See check_spool_space below.
 
-+--------------------+---------+-------------+-------------+
++----------------------------------------------------------+
 |check_rfc2047_length|Use: main|Type: boolean|Default: true|
-+--------------------+---------+-------------+-------------+
++----------------------------------------------------------+
 
 RFC 2047 defines a way of encoding non-ASCII characters in headers using a
 system of "encoded words". The RFC specifies a maximum length for an encoded
@@ -12719,28 +13172,28 @@ exceed the maximum length. However, it seems that some software, in violation
 of the RFC, generates overlong encoded words. If check_rfc2047_length is set
 false, Exim recognizes encoded words of any length.
 
-+------------------+---------+-------------+----------+
-|check_spool_inodes|Use: main|Type: integer|Default: 0|
-+------------------+---------+-------------+----------+
++-------------------------------------------------------+
+|check_spool_inodes|Use: main|Type: integer|Default: 100|
++-------------------------------------------------------+
 
 See check_spool_space below.
 
-+-----------------+---------+-------------+----------+
-|check_spool_space|Use: main|Type: integer|Default: 0|
-+-----------------+---------+-------------+----------+
++------------------------------------------------------+
+|check_spool_space|Use: main|Type: integer|Default: 10M|
++------------------------------------------------------+
 
 The four check_... options allow for checking of disk resources before a
 message is accepted.
 
-When any of these options are set, they apply to all incoming messages. If you
-want to apply different checks to different kinds of message, you can do so by
-testing the variables $log_inodes, $log_space, $spool_inodes, and $spool_space
-in an ACL with appropriate additional conditions.
+When any of these options are nonzero, they apply to all incoming messages. If
+you want to apply different checks to different kinds of message, you can do so
+by testing the variables $log_inodes, $log_space, $spool_inodes, and
+$spool_space in an ACL with appropriate additional conditions.
 
 check_spool_space and check_spool_inodes check the spool partition if either
 value is greater than zero, for example:
 
-check_spool_space = 10M
+check_spool_space = 100M
 check_spool_inodes = 100
 
 The spool partition is the one that contains the directory defined by
@@ -12758,23 +13211,43 @@ value, and the check is performed even if check_spool_space is zero, unless
 no_smtp_check_spool_space is set.
 
 The values for check_spool_space and check_log_space are held as a number of
-kilobytes. If a non-multiple of 1024 is specified, it is rounded up.
+kilobytes (though specified in bytes). If a non-multiple of 1024 is specified,
+it is rounded up.
 
 For non-SMTP input and for batched SMTP input, the test is done at start-up; on
 failure a message is written to stderr and Exim exits with a non-zero code, as
 it obviously cannot send an error message of any kind.
 
-+-----------------+---------+------------+---------------+
+There is a slight performance penalty for these checks. Versions of Exim
+preceding 4.88 had these disabled by default; high-rate installations confident
+they will never run out of resources may wish to deliberately disable them.
+
++--------------------------------------------------------------+
+|chunking_advertise_hosts|Use: main|Type: host list*|Default: *|
++--------------------------------------------------------------+
+
+The CHUNKING extension (RFC3030) will be advertised in the EHLO message to
+these hosts. Hosts may use the BDAT command as an alternate to DATA.
+
++----------------------------------------------------+
+|debug_store|Use: main|Type: boolean|Default: "false"|
++----------------------------------------------------+
+
+This option, when true, enables extra checking in Exim's internal memory
+management. For use when a memory corruption issue is being investigated, it
+should normally be left as default.
+
++--------------------------------------------------------+
 |daemon_smtp_ports|Use: main|Type: string|Default: "smtp"|
-+-----------------+---------+------------+---------------+
++--------------------------------------------------------+
 
 This option specifies one or more default SMTP ports on which the Exim daemon
 listens. See chapter 13 for details of how it is used. For backward
 compatibility, daemon_smtp_port (singular) is a synonym.
 
-+----------------------+---------+-------------+----------+
++---------------------------------------------------------+
 |daemon_startup_retries|Use: main|Type: integer|Default: 9|
-+----------------------+---------+-------------+----------+
++---------------------------------------------------------+
 
 This option, along with daemon_startup_sleep, controls the retrying done by the
 daemon at startup when it cannot immediately bind a listening socket (typically
@@ -12782,15 +13255,15 @@ because the socket is already in use): daemon_startup_retries defines the
 number of retries after the first failure, and daemon_startup_sleep defines the
 length of time to wait between retries.
 
-+--------------------+---------+----------+------------+
++------------------------------------------------------+
 |daemon_startup_sleep|Use: main|Type: time|Default: 30s|
-+--------------------+---------+----------+------------+
++------------------------------------------------------+
 
 See daemon_startup_retries.
 
-+-------------+---------+---------------+------------+
++----------------------------------------------------+
 |delay_warning|Use: main|Type: time list|Default: 24h|
-+-------------+---------+---------------+------------+
++----------------------------------------------------+
 
 When a message is delayed, Exim sends a warning message to the sender at
 intervals specified by this option. The data is a colon-separated list of times
@@ -12818,9 +13291,9 @@ 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|
-+-----------------------+---------+-------------+------------------+
++------------------------------------------------------------------+
 
 The string is expanded at the time a warning message might be sent. If all the
 deferred addresses have the same domain, it is set in $domain during the
@@ -12840,28 +13313,28 @@ List-Post:, or List-Subscribe: headers, or have "bulk", "list" or "junk" in a
 Precedence: header, or have "auto-generated" or "auto-replied" in an 
 Auto-Submitted: header.
 
-+----------------------+---------+-------------+--------------+
++-------------------------------------------------------------+
 |deliver_drop_privilege|Use: main|Type: boolean|Default: false|
-+----------------------+---------+-------------+--------------+
++-------------------------------------------------------------+
 
 If this option is set true, Exim drops its root privilege at the start of a
 delivery process, and runs as the Exim user throughout. This severely restricts
 the kinds of local delivery that are possible, but is viable in certain types
 of configuration. There is a discussion about the use of root privilege in
-chapter 54.
+chapter 55.
 
-+----------------------+---------+-----------------+--------------+
++-----------------------------------------------------------------+
 |deliver_queue_load_max|Use: main|Type: fixed-point|Default: unset|
-+----------------------+---------+-----------------+--------------+
++-----------------------------------------------------------------+
 
 When this option is set, a queue run is abandoned if the system load average
 becomes greater than the value of the option. The option has no effect on
 ancient operating systems on which Exim cannot determine the load average. See
 also queue_only_load and smtp_load_reserve.
 
-+--------------------+---------+-------------+-------------+
++----------------------------------------------------------+
 |delivery_date_remove|Use: main|Type: boolean|Default: true|
-+--------------------+---------+-------------+-------------+
++----------------------------------------------------------+
 
 Exim's transports have an option for adding a Delivery-date: header to a
 message when it is delivered, in exactly the same way as Return-path: is
@@ -12870,9 +13343,9 @@ should not be present in incoming messages, and this option causes them to be
 removed at the time the message is received, to avoid any problems that might
 occur when a delivered message is subsequently sent on to some other recipient.
 
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 |disable_fsync|Use: main|Type: boolean|Default: false|
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 
 This option is available only if Exim was built with the compile-time option
 ENABLE_DISABLE_FSYNC. When this is not set, a reference to disable_fsync in a
@@ -12886,9 +13359,9 @@ files' data to be written to disc before continuing. Unexpected events such as
 crashes and power outages may cause data to be lost or scrambled. Here be
 Dragons. Beware.
 
-+------------+---------+-------------+--------------+
++---------------------------------------------------+
 |disable_ipv6|Use: main|Type: boolean|Default: false|
-+------------+---------+-------------+--------------+
++---------------------------------------------------+
 
 If this option is set true, even if the Exim binary has IPv6 support, no IPv6
 activities take place. AAAA records are never looked up, and any IPv6 addresses
@@ -12896,9 +13369,17 @@ that are listed in local_interfaces, data for the manualroute router, etc. are
 ignored. If IP literals are enabled, the ipliteral router declines to handle
 IPv6 literal addresses.
 
-+------------------------+---------+------------------+--------------+
++-----------------------------------------------------------------------+
+|dkim_verify_signers|Use: main|Type: domain list*|Default: $dkim_signers|
++-----------------------------------------------------------------------+
+
+This option gives a list of DKIM domains for which the DKIM ACL is run. It is
+expanded after the message is received; by default it runs the ACL once for
+each signature in the message. See chapter 57.
+
++--------------------------------------------------------------------+
 |dns_again_means_nonexist|Use: main|Type: domain list*|Default: unset|
-+------------------------+---------+------------------+--------------+
++--------------------------------------------------------------------+
 
 DNS lookups give a "try again" response for the DNS errors "non-authoritative
 host not found" and "SERVERFAIL". This can cause Exim to keep trying to deliver
@@ -12918,9 +13399,9 @@ has some options of its own for controlling what happens when lookups for MX or
 SRV records give temporary errors. These more specific options are applied
 after this global option.
 
-+-----------------------+---------+------------+------------------+
++-----------------------------------------------------------------+
 |dns_check_names_pattern|Use: main|Type: string|Default: see below|
-+-----------------------+---------+------------+------------------+
++-----------------------------------------------------------------+
 
 When this option is set to a non-empty string, it causes Exim to check domain
 names for characters that are not allowed in host names before handing them to
@@ -12939,24 +13420,24 @@ permitted in host names, but they are found in certain NS records (which can be
 accessed in Exim by using a dnsdb lookup). If you set allow_utf8_domains, you
 must modify this pattern, or set the option to an empty string.
 
-+--------------------+---------+-------------+----------+
++-------------------------------------------------------+
 |dns_csa_search_limit|Use: main|Type: integer|Default: 5|
-+--------------------+---------+-------------+----------+
++-------------------------------------------------------+
 
 This option controls the depth of parental searching for CSA SRV records in the
-DNS, as described in more detail in section 42.50.
+DNS, as described in more detail in section 43.50.
 
-+-------------------+---------+-------------+-------------+
++---------------------------------------------------------+
 |dns_csa_use_reverse|Use: main|Type: boolean|Default: true|
-+-------------------+---------+-------------+-------------+
++---------------------------------------------------------+
 
 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.50.
+section 43.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
@@ -12964,9 +13445,9 @@ 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|
-+---------------+---------+------------------+--------------+
++-----------------------------------------------------------+
 
 When Exim is compiled with IPv6 support and disable_ipv6 is not set, it looks
 for IPv6 address records (AAAA records) as well as IPv4 address records (A
@@ -12977,9 +13458,9 @@ This is a fudge to help with name servers that give big delays or otherwise do
 not work for the AAAA record type. In due course, when the world's name servers
 have all been upgraded, there should be no need for this option.
 
-+-----------+---------+----------+-----------+
++--------------------------------------------+
 |dns_retrans|Use: main|Type: time|Default: 0s|
-+-----------+---------+----------+-----------+
++--------------------------------------------+
 
 The options dns_retrans and dns_retry can be used to set the retransmission and
 retry parameters for DNS lookups. Values of zero (the defaults) leave the
@@ -12988,17 +13469,43 @@ and the second is the number of retries. It isn't totally clear exactly how
 these settings affect the total time a DNS lookup may take. I haven't found any
 documentation about timeouts on DNS lookups; these parameter values are
 available in the external resolver interface structure, but nowhere does it
-seem to describe how they are used or what you might want to set in them.
+seem to describe how they are used or what you might want to set in them. See
+also the slow_lookup_log option.
 
-+---------+---------+-------------+----------+
++--------------------------------------------+
 |dns_retry|Use: main|Type: integer|Default: 0|
-+---------+---------+-------------+----------+
++--------------------------------------------+
 
 See dns_retrans above.
 
-+-------------+---------+-------------+-----------+
++--------------------------------------------------------+
+|dns_trust_aa|Use: main|Type: domain list*|Default: unset|
++--------------------------------------------------------+
+
+If this option is set then lookup results marked with the AA bit (Authoritative
+Answer) are trusted the same way as if they were DNSSEC-verified. The authority
+section's name of the answer must match with this expanded domain list.
+
+Use this option only if you talk directly to a resolver that is authoritative
+for some zones and does not set the AD (Authentic Data) bit in the answer. Some
+DNS servers may have an configuration option to mark the answers from their own
+zones as verified (they set the AD bit). Others do not have this option. It is
+considered as poor practice using a resolver that is an authoritative server
+for some zones.
+
+Use this option only if you really have to (e.g. if you want to use DANE for
+remote delivery to a server that is listed in the DNS zones that your resolver
+is authoritative for).
+
+If the DNS answer packet has the AA bit set and contains resource record in the
+answer section, the name of the first NS record appearing in the authority
+section is compared against the list. If the answer packet is authoritative but
+the answer section is empty, the name of the first SOA record in the
+authoritative section is used instead.
+
++-------------------------------------------------+
 |dns_use_edns0|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 EDNS0 extensions, overriding the
@@ -13006,17 +13513,31 @@ system default. A value of 0 coerces EDNS0 off, a value of 1 coerces EDNS0 on.
 
 If the resolver library does not support EDNS0 then this option has no effect.
 
-+-------+---------+-------------+--------------+
+OpenBSD's asr resolver routines are known to ignore the EDNS0 option; this
+means that DNSSEC will not work with Exim on that platform either, unless Exim
+is linked against an alternative DNS client library.
+
++----------------------------------------------+
 |drop_cr|Use: main|Type: boolean|Default: false|
-+-------+---------+-------------+--------------+
++----------------------------------------------+
 
 This is an obsolete option that is now a no-op. It used to affect the way Exim
 handled CR and LF characters in incoming messages. What happens now is
-described in section 46.2.
+described in section 47.2.
 
-+--------+---------+-------------+------------------+
++-------------------------------------------------------------+
+|dsn_advertise_hosts|Use: main|Type: host list*|Default: unset|
++-------------------------------------------------------------+
+
+DSN extensions (RFC3461) will be advertised in the EHLO message to, and
+accepted from, these hosts. Hosts may use the NOTIFY and ENVID options on RCPT
+TO commands, and RET and ORCPT options on MAIL FROM commands. A NOTIFY=SUCCESS
+option requests success-DSN messages. A NOTIFY= option with no argument
+requests that no delay or failure DSNs are sent.
+
++---------------------------------------------------+
 |dsn_from|Use: main|Type: string*|Default: see below|
-+--------+---------+-------------+------------------+
++---------------------------------------------------+
 
 This option can be used to vary the contents of From: header lines in bounces
 and other automatically generated messages ("Delivery Status Notifications" -
@@ -13027,21 +13548,21 @@ dsn_from = Mail Delivery System <Mailer-Daemon@$qualify_domain>
 The value is expanded every time it is needed. If the expansion fails, a panic
 is logged, and the default value is used.
 
-+------------------+---------+-------------+-------------+
++--------------------------------------------------------+
 |envelope_to_remove|Use: main|Type: boolean|Default: true|
-+------------------+---------+-------------+-------------+
++--------------------------------------------------------+
 
 Exim's transports have an option for adding an Envelope-to: header to a message
 when it is delivered, in exactly the same way as Return-path: is handled. 
-Envelope-to: records the original recipient address from the messages's
-envelope that caused the delivery to happen. Such headers should not be present
-in incoming messages, and this option causes them to be removed at the time the
+Envelope-to: records the original recipient address from the message's envelope
+that caused the delivery to happen. Such headers should not be present in
+incoming messages, and this option causes them to be removed at the time the
 message is received, to avoid any problems that might occur when a delivered
 message is subsequently sent on to some other recipient.
 
-+-----------+---------+------------------+--------------+
++-------------------------------------------------------+
 |errors_copy|Use: main|Type: string list*|Default: unset|
-+-----------+---------+------------------+--------------+
++-------------------------------------------------------+
 
 Setting this option causes Exim to send bcc copies of bounce messages that it
 generates to other addresses. Note: This does not apply to bounce messages
@@ -13065,9 +13586,9 @@ and $domain are set from the original recipient of the error message, and if
 there was any wildcard matching in the pattern, the expansion variables $0, $1,
 etc. are set in the normal way.
 
-+---------------+---------+------------+--------------+
++-----------------------------------------------------+
 |errors_reply_to|Use: main|Type: string|Default: unset|
-+---------------+---------+------------+--------------+
++-----------------------------------------------------+
 
 By default, Exim's bounce and delivery warning messages contain the header line
 
@@ -13088,19 +13609,26 @@ address. However, if a warning message that is generated by the
 quota_warn_message option in an appendfile transport contain its own Reply-To:
 header line, the value of the errors_reply_to option is not used.
 
-+----------+---------+------------+--------------------------------+
++---------------------------------------------------+
+|event_action|Use: main|Type: string*|Default: unset|
++---------------------------------------------------+
+
+This option declares a string to be expanded for Exim's events mechanism. For
+details see chapter 60.
+
++------------------------------------------------------------------+
 |exim_group|Use: main|Type: string|Default: compile-time configured|
-+----------+---------+------------+--------------------------------+
++------------------------------------------------------------------+
 
 This option changes the gid under which Exim runs when it gives up root
 privilege. The default value is compiled into the binary. The value of this
 option is used only when exim_user is also set. Unless it consists entirely of
 digits, the string is looked up using getgrnam(), and failure causes a
-configuration error. See chapter 54 for a discussion of security issues.
+configuration error. See chapter 55 for a discussion of security issues.
 
-+---------+---------+------------+------------------+
++---------------------------------------------------+
 |exim_path|Use: main|Type: string|Default: see below|
-+---------+---------+------------+------------------+
++---------------------------------------------------+
 
 This option specifies the path name of the Exim binary, which is used when Exim
 needs to re-exec itself. The default is set up to point to the file exim in the
@@ -13111,9 +13639,9 @@ you will break those Exim utilities that scan the configuration file to find
 where the binary is. (They then use the -bP option to extract option settings
 such as the value of spool_directory.)
 
-+---------+---------+------------+--------------------------------+
++-----------------------------------------------------------------+
 |exim_user|Use: main|Type: string|Default: compile-time configured|
-+---------+---------+------------+--------------------------------+
++-----------------------------------------------------------------+
 
 This option changes the uid under which Exim runs when it gives up root
 privilege. The default value is compiled into the binary. Ownership of the run
@@ -13122,20 +13650,20 @@ checked against the values in the binary, not what is set here.
 
 Unless it consists entirely of digits, the string is looked up using getpwnam()
 , and failure causes a configuration error. If exim_group is not also supplied,
-the gid is taken from the result of getpwnam() if it is used. See chapter 54
+the gid is taken from the result of getpwnam() if it is used. See chapter 55
 for a discussion of security issues.
 
-+----------------------+---------+-----------------+--------------+
++-----------------------------------------------------------------+
 |extra_local_interfaces|Use: main|Type: string list|Default: unset|
-+----------------------+---------+-----------------+--------------+
++-----------------------------------------------------------------+
 
 This option defines network interfaces that are to be considered local when
 routing, but which are not used for listening by the daemon. See section 13.8
 for details.
 
-+-------------------------------------+---------+-------------+-------------+
-|extract_addresses_remove_   arguments|Use: main|Type: boolean|Default: true|
-+-------------------------------------+---------+-------------+-------------+
++------------------------------------------------------------------------+
+|extract_addresses_remove_arguments|Use: main|Type: boolean|Default: true|
++------------------------------------------------------------------------+
 
 According to some Sendmail documentation (Sun, IRIX, HP-UX), if any addresses
 are present on the command line when the -t option is used to build an envelope
@@ -13147,9 +13675,9 @@ extract_addresses_remove_arguments is true (the default), Exim subtracts
 argument headers. If it is set false, Exim adds rather than removes argument
 addresses.
 
-+----------------+---------+-------------+----------+
++---------------------------------------------------+
 |finduser_retries|Use: main|Type: integer|Default: 0|
-+----------------+---------+-------------+----------+
++---------------------------------------------------+
 
 On systems running NIS or other schemes in which user and group information is
 distributed from a remote system, there can be times when getpwnam() and
@@ -13162,9 +13690,9 @@ You should not set this option greater than zero if your user information is in
 a traditional /etc/passwd file, because it will cause Exim needlessly to search
 the file multiple times for non-existent users, and also cause delay.
 
-+-----------+---------+----------------------------------+--------------+
++-----------------------------------------------------------------------+
 |freeze_tell|Use: main|Type: string list, comma separated|Default: unset|
-+-----------+---------+----------------------------------+--------------+
++-----------------------------------------------------------------------+
 
 On encountering certain errors, or when configured to do so in a system filter,
 ACL, or special router, Exim freezes a message. This means that no further
@@ -13180,9 +13708,9 @@ automatic, the reason(s) for freezing can be found in the message log. If you
 configure freezing in a filter or ACL, you must arrange for any logging that
 you require.
 
-+----------+---------+-------------+--------------+
++-------------------------------------------------+
 |gecos_name|Use: main|Type: string*|Default: unset|
-+----------+---------+-------------+--------------+
++-------------------------------------------------+
 
 Some operating systems, notably HP-UX, use the "gecos" field in the system
 password file to hold other information in addition to users' real names. Exim
@@ -13203,15 +13731,15 @@ terminates at the first comma, the following can be used:
 gecos_pattern = ([^,]*)
 gecos_name = $1
 
-+-------------+---------+------------+--------------+
++---------------------------------------------------+
 |gecos_pattern|Use: main|Type: string|Default: unset|
-+-------------+---------+------------+--------------+
++---------------------------------------------------+
 
 See gecos_name above.
 
-+------------------+---------+-------------+--------------+
++---------------------------------------------------------+
 |gnutls_compat_mode|Use: main|Type: boolean|Default: unset|
-+------------------+---------+-------------+--------------+
++---------------------------------------------------------+
 
 This option controls whether GnuTLS is used in compatibility mode in an Exim
 server. This reduces security slightly, but improves interworking with older
@@ -13224,9 +13752,9 @@ in /etc/pkcs11/modules/.
 See http://www.gnutls.org/manual/gnutls.html#Smart-cards-and-HSMs for
 documentation.
 
-+---------------+---------+------------+------------------+
++---------------------------------------------------------+
 |headers_charset|Use: main|Type: string|Default: see below|
-+---------------+---------+------------+------------------+
++---------------------------------------------------------+
 
 This option sets a default character set for translating from encoded MIME
 "words" in header lines, when referenced by an $h_xxx expansion item. The
@@ -13234,26 +13762,26 @@ default is the value of HEADERS_CHARSET in Local/Makefile. The ultimate default
 is ISO-8859-1. For more details see the description of header insertions in
 section 11.5.
 
-+--------------+---------+-------------+------------------+
++---------------------------------------------------------+
 |header_maxsize|Use: main|Type: integer|Default: see below|
-+--------------+---------+-------------+------------------+
++---------------------------------------------------------+
 
 This option controls the overall maximum size of a message's header section.
 The default is the value of HEADER_MAXSIZE in Local/Makefile; the default for
 that is 1M. Messages with larger header sections are rejected.
 
-+-------------------+---------+-------------+----------+
++------------------------------------------------------+
 |header_line_maxsize|Use: main|Type: integer|Default: 0|
-+-------------------+---------+-------------+----------+
++------------------------------------------------------+
 
 This option limits the length of any individual header line in a message, after
 all the continuations have been joined together. Messages with individual
 header lines that are longer than the limit are rejected. The default value of
 zero means "no limit".
 
-+----------------------+---------+----------------+--------------+
++----------------------------------------------------------------+
 |helo_accept_junk_hosts|Use: main|Type: host list*|Default: unset|
-+----------------------+---------+----------------+--------------+
++----------------------------------------------------------------+
 
 Exim checks the syntax of HELO and EHLO commands for incoming SMTP mail, and
 gives an error response for invalid data. Unfortunately, there are some SMTP
@@ -13262,9 +13790,9 @@ option. Note that this is a syntax check only. See helo_verify_hosts if you
 want to do semantic checking. See also helo_allow_chars for a way of extending
 the permitted character set.
 
-+----------------+---------+------------+--------------+
++------------------------------------------------------+
 |helo_allow_chars|Use: main|Type: string|Default: unset|
-+----------------+---------+------------+--------------+
++------------------------------------------------------+
 
 This option can be set to a string of rogue characters that are permitted in
 all EHLO and HELO names in addition to the standard letters, digits, hyphens,
@@ -13274,18 +13802,18 @@ helo_allow_chars = _
 
 Note that the value is one string, not a list.
 
-+-------------------+---------+------------------+----------------+
++-----------------------------------------------------------------+
 |helo_lookup_domains|Use: main|Type: domain list*|Default: "@:@[]"|
-+-------------------+---------+------------------+----------------+
++-----------------------------------------------------------------+
 
 If the domain given by a client in a HELO or EHLO command matches this list, a
 reverse lookup is done in order to establish the host's true name. The default
 forces a lookup if the client host gives the server's name or any of its IP
 addresses (in brackets), something that broken clients have been seen to do.
 
-+---------------------+---------+----------------+--------------+
++---------------------------------------------------------------+
 |helo_try_verify_hosts|Use: main|Type: host list*|Default: unset|
-+---------------------+---------+----------------+--------------+
++---------------------------------------------------------------+
 
 By default, Exim just checks the syntax of HELO and EHLO commands (see 
 helo_accept_junk_hosts and helo_allow_chars). However, some sites like to do
@@ -13306,16 +13834,18 @@ command either:
   * matches the host name that Exim obtains by doing a reverse lookup of the
     calling host address, or
 
-  * when looked up using gethostbyname() (or getipnodebyname() when available)
-    yields the calling host address.
+  * when looked up in DNS yields the calling host address.
 
 However, the EHLO or HELO command is not rejected if any of the checks fail.
 Processing continues, but the result of the check is remembered, and can be
 detected later in an ACL by the "verify = helo" condition.
 
-+-----------------+---------+----------------+--------------+
+If DNS was used for successful verification, the variable $helo_verify_dnssec
+records the DNSSEC status of the lookups.
+
++-----------------------------------------------------------+
 |helo_verify_hosts|Use: main|Type: host list*|Default: unset|
-+-----------------+---------+----------------+--------------+
++-----------------------------------------------------------+
 
 Like helo_try_verify_hosts, this option is obsolete, and retained only for
 backwards compatibility. For hosts that match this option, Exim checks the host
@@ -13324,9 +13854,9 @@ the check fails, the HELO or EHLO command is rejected with a 550 error, and
 entries are written to the main and reject logs. If a MAIL command is received
 before EHLO or HELO, it is rejected with a 503 error.
 
-+------------+---------+------------------+--------------+
++--------------------------------------------------------+
 |hold_domains|Use: main|Type: domain list*|Default: unset|
-+------------+---------+------------------+--------------+
++--------------------------------------------------------+
 
 This option allows mail for particular domains to be held on the queue
 manually. The option is overridden if a message delivery is forced with the -M,
@@ -13346,9 +13876,9 @@ from the queue if they have been there longer than the longest retry time in
 any retry rule. If you want to hold messages for longer than the normal retry
 times, insert a dummy retry rule with a long retry time.
 
-+-----------+---------+----------------+--------------+
++-----------------------------------------------------+
 |host_lookup|Use: main|Type: host list*|Default: unset|
-+-----------+---------+----------------+--------------+
++-----------------------------------------------------+
 
 Exim does not look up the name of a calling host from its IP address unless it
 is required to compare against some host list, or the host matches 
@@ -13370,9 +13900,9 @@ and $host_lookup_failed is set to the string "1". See also
 dns_again_means_nonexist, helo_lookup_domains, and "verify =
 reverse_host_lookup" in ACLs.
 
-+-----------------+---------+-----------------+-----------------------+
++---------------------------------------------------------------------+
 |host_lookup_order|Use: main|Type: string list|Default: "bydns:byaddr"|
-+-----------------+---------+-----------------+-----------------------+
++---------------------------------------------------------------------+
 
 This option specifies the order of different lookup methods when Exim is trying
 to find a host name from an IP address. The default is to do a DNS lookup
@@ -13385,9 +13915,9 @@ multiple PTR records in the DNS and the IP address is not listed in /etc/hosts.
 Different operating systems give different results in this case. That is why
 the default tries a DNS lookup first.
 
-+----------------------+---------+----------------+--------------+
++----------------------------------------------------------------+
 |host_reject_connection|Use: main|Type: host list*|Default: unset|
-+----------------------+---------+----------------+--------------+
++----------------------------------------------------------------+
 
 If this option is set, incoming SMTP calls from the hosts listed are rejected
 as soon as the connection is made. This option is obsolete, and retained only
@@ -13397,12 +13927,12 @@ acl_smtp_connect can also reject incoming connections immediately.
 The ability to give an immediate rejection (either by this option or using an
 ACL) is provided for use in unusual cases. Many hosts will just try again,
 sometimes without much delay. Normally, it is better to use an ACL to reject
-incoming messages at a later stage, such as after RCPT commands. See chapter 42
+incoming messages at a later stage, such as after RCPT commands. See chapter 43
 .
 
-+----------------------+---------+----------------+--------------+
++----------------------------------------------------------------+
 |hosts_connection_nolog|Use: main|Type: host list*|Default: unset|
-+----------------------+---------+----------------+--------------+
++----------------------------------------------------------------+
 
 This option defines a list of hosts for which connection logging does not
 happen, even though the smtp_connection log selector is set. For example, you
@@ -13416,9 +13946,16 @@ hosts_connection_nolog = :
 
 If the smtp_connection log selector is not set, this option has no effect.
 
-+--------------------+---------+------------------+--------------+
++-----------------------------------------------------+
+|hosts_proxy|Use: main|Type: host list*|Default: unset|
++-----------------------------------------------------+
+
+This option enables use of Proxy Protocol proxies for incoming connections. For
+details see section 58.1.
+
++----------------------------------------------------------------+
 |hosts_treat_as_local|Use: main|Type: domain list*|Default: unset|
-+--------------------+---------+------------------+--------------+
++----------------------------------------------------------------+
 
 If this option is set, any host names that match the domain list are treated as
 if they were the local host when Exim is scanning host lists obtained from MX
@@ -13433,17 +13970,17 @@ the allow_localhost option in that transport). See also local_interfaces,
 extra_local_interfaces, and chapter 13, which contains a discussion about local
 network interfaces and recognizing the local host.
 
-+-------------+---------+-----------------+--------------+
++--------------------------------------------------------+
 |ibase_servers|Use: main|Type: string list|Default: unset|
-+-------------+---------+-----------------+--------------+
++--------------------------------------------------------+
 
 This option provides a list of InterBase servers and associated connection
-data, to be used in conjunction with ibase lookups (see section 9.21). The
+data, to be used in conjunction with ibase lookups (see section 9.22). The
 option is available only if Exim has been built with InterBase support.
 
-+--------------------------+---------+----------+------------+
++------------------------------------------------------------+
 |ignore_bounce_errors_after|Use: main|Type: time|Default: 10w|
-+--------------------------+---------+----------+------------+
++------------------------------------------------------------+
 
 This option affects the processing of bounce messages that cannot be delivered,
 that is, those that suffer a permanent delivery failure. (Bounce messages that
@@ -13466,9 +14003,9 @@ value) has the effect of disabling this option. For ways of automatically
 dealing with other kinds of frozen message, see auto_thaw and 
 timeout_frozen_after.
 
-+---------------------+---------+----------------+--------------+
++---------------------------------------------------------------+
 |ignore_fromline_hosts|Use: main|Type: host list*|Default: unset|
-+---------------------+---------+----------------+--------------+
++---------------------------------------------------------------+
 
 Some broken SMTP clients insist on sending a UUCP-like "From " line before the
 headers of a message. By default this is treated as the start of the message's
@@ -13478,84 +14015,112 @@ that insist on sending it. If the sender is actually a local process rather
 than a remote host, and is using -bs to inject the messages, 
 ignore_fromline_local must be set to achieve this effect.
 
-+---------------------+---------+-------------+--------------+
++------------------------------------------------------------+
 |ignore_fromline_local|Use: main|Type: boolean|Default: false|
-+---------------------+---------+-------------+--------------+
++------------------------------------------------------------+
 
 See ignore_fromline_hosts above.
 
-+--------------+---------+----------+-----------+
++-----------------------------------------------------------+
+|keep_environment|Use: main|Type: string list|Default: unset|
++-----------------------------------------------------------+
+
+This option contains a string list of environment variables to keep. You have
+to trust these variables or you have to be sure that these variables do not
+impose any security risk. Keep in mind that during the startup phase Exim is
+running with an effective UID 0 in most installations. As the default value is
+an empty list, the default environment for using libraries, running embedded
+Perl code, or running external binaries is empty, and does not not even contain
+PATH or HOME.
+
+Actually the list is interpreted as a list of patterns (10.1), except that it
+is not expanded first.
+
+WARNING: Macro substitution is still done first, so having a macro FOO and
+having FOO_HOME in your keep_environment option may have unexpected results.
+You may work around this using a regular expression that does not match the
+macro name: ^[F]OO_HOME$.
+
+Current versions of Exim issue a warning during startup if you do not mention 
+keep_environment in your runtime configuration file and if your current
+environment is not empty. Future versions may not issue that warning anymore.
+
+See the add_environment main config option for a way to set environment
+variables to a fixed value. The environment for pipe transports is handled
+separately, see section 29.4 for details.
+
++-----------------------------------------------+
 |keep_malformed|Use: main|Type: time|Default: 4d|
-+--------------+---------+----------+-----------+
++-----------------------------------------------+
 
 This option specifies the length of time to keep messages whose spool files
 have been corrupted in some way. This should, of course, never happen. At the
 next attempt to deliver such a message, it gets removed. The incident is
 logged.
 
-+----------------+---------+------------+--------------+
++------------------------------------------------------+
 |ldap_ca_cert_dir|Use: main|Type: string|Default: unset|
-+----------------+---------+------------+--------------+
++------------------------------------------------------+
 
 This option indicates which directory contains CA certificates for verifying a
 TLS certificate presented by an LDAP server. While Exim does not provide a
 default value, your SSL library may. Analogous to tls_verify_certificates but
 as a client-side option for LDAP and constrained to be a directory.
 
-+-----------------+---------+------------+--------------+
++-------------------------------------------------------+
 |ldap_ca_cert_file|Use: main|Type: string|Default: unset|
-+-----------------+---------+------------+--------------+
++-------------------------------------------------------+
 
 This option indicates which file contains CA certificates for verifying a TLS
 certificate presented by an LDAP server. While Exim does not provide a default
 value, your SSL library may. Analogous to tls_verify_certificates but as a
 client-side option for LDAP and constrained to be a file.
 
-+--------------+---------+------------+--------------+
++----------------------------------------------------+
 |ldap_cert_file|Use: main|Type: string|Default: unset|
-+--------------+---------+------------+--------------+
++----------------------------------------------------+
 
 This option indicates which file contains an TLS client certificate which Exim
 should present to the LDAP server during TLS negotiation. Should be used
 together with ldap_cert_key.
 
-+-------------+---------+------------+--------------+
++---------------------------------------------------+
 |ldap_cert_key|Use: main|Type: string|Default: unset|
-+-------------+---------+------------+--------------+
++---------------------------------------------------+
 
 This option indicates which file contains the secret/private key to use to
 prove identity to the LDAP server during TLS negotiation. Should be used
 together with ldap_cert_file, which contains the identity to be proven.
 
-+-----------------+---------+------------+--------------+
++-------------------------------------------------------+
 |ldap_cipher_suite|Use: main|Type: string|Default: unset|
-+-----------------+---------+------------+--------------+
++-------------------------------------------------------+
 
 This controls the TLS cipher-suite negotiation during TLS negotiation with the
-LDAP server. See 41.4 for more details of the format of cipher-suite options
+LDAP server. See 42.4 for more details of the format of cipher-suite options
 with OpenSSL (as used by LDAP client libraries).
 
-+--------------------+---------+-----------------+--------------+
++---------------------------------------------------------------+
 |ldap_default_servers|Use: main|Type: string list|Default: unset|
-+--------------------+---------+-----------------+--------------+
++---------------------------------------------------------------+
 
 This option provides a list of LDAP servers which are tried in turn when an
-LDAP query does not contain a server. See section 9.14 for details of LDAP
+LDAP query does not contain a server. See section 9.15 for details of LDAP
 queries. This option is available only when Exim has been built with LDAP
 support.
 
-+-----------------+---------+------------+---------------+
++--------------------------------------------------------+
 |ldap_require_cert|Use: main|Type: string|Default: unset.|
-+-----------------+---------+------------+---------------+
++--------------------------------------------------------+
 
 This should be one of the values "hard", "demand", "allow", "try" or "never". A
 value other than one of these is interpreted as "never". See the entry
 "TLS_REQCERT" in your system man page for ldap.conf(5). Although Exim does not
 set a default, the LDAP library probably defaults to hard/demand.
 
-+--------------+---------+-------------+--------------+
++-----------------------------------------------------+
 |ldap_start_tls|Use: main|Type: boolean|Default: false|
-+--------------+---------+-------------+--------------+
++-----------------------------------------------------+
 
 If set, Exim will attempt to negotiate TLS with the LDAP server when connecting
 on a regular LDAP port. This is the LDAP equivalent of SMTP's "STARTTLS". This
@@ -13563,9 +14128,11 @@ is distinct from using "ldaps", which is the LDAP form of SSL-on-connect. In
 the event of failure to negotiate TLS, the action taken is controlled by 
 ldap_require_cert.
 
-+------------+---------+-------------+--------------+
+This option is ignored for "ldapi" connections.
+
++---------------------------------------------------+
 |ldap_version|Use: main|Type: integer|Default: unset|
-+------------+---------+-------------+--------------+
++---------------------------------------------------+
 
 This option can be used to force Exim to set a specific protocol version for
 LDAP. If it option is unset, it is shown by the -bP command line option as -1.
@@ -13573,9 +14140,9 @@ When this is the case, the default is 3 if LDAP_VERSION3 is defined in the LDAP
 headers; otherwise it is 2. This option is available only when Exim has been
 built with LDAP support.
 
-+----------------+---------+-------------+-------------+
++------------------------------------------------------+
 |local_from_check|Use: main|Type: boolean|Default: true|
-+----------------+---------+-------------+-------------+
++------------------------------------------------------+
 
 When a message is submitted locally (that is, not over a TCP/IP connection) by
 an untrusted user, Exim removes any existing Sender: header line, and checks
@@ -13601,12 +14168,12 @@ is still forced to be the login id at the qualify domain unless
 untrusted_set_sender permits the user to supply an envelope sender.
 
 For messages received over TCP/IP, an ACL can specify "submission mode" to
-request similar header line checking. See section 46.16, which has more details
+request similar header line checking. See section 47.16, which has more details
 about Sender: processing.
 
-+-----------------+---------+------------+--------------+
++-------------------------------------------------------+
 |local_from_prefix|Use: main|Type: string|Default: unset|
-+-----------------+---------+------------+--------------+
++-------------------------------------------------------+
 
 When Exim checks the From: header line of locally submitted messages for
 matching the login id (see local_from_check above), it can be configured to
@@ -13625,15 +14192,15 @@ will not cause a Sender: header to be added if user@your.domain.example matches
 the actual sender address that is constructed from the login name and qualify
 domain.
 
-+-----------------+---------+------------+--------------+
++-------------------------------------------------------+
 |local_from_suffix|Use: main|Type: string|Default: unset|
-+-----------------+---------+------------+--------------+
++-------------------------------------------------------+
 
 See local_from_prefix above.
 
-+----------------+---------+-----------------+------------------+
++---------------------------------------------------------------+
 |local_interfaces|Use: main|Type: string list|Default: see below|
-+----------------+---------+-----------------+------------------+
++---------------------------------------------------------------+
 
 This option controls which network interfaces are used by the daemon for
 listening; they are also used to identify the local host when routing. Chapter
@@ -13647,30 +14214,30 @@ when Exim is built without IPv6 support; otherwise it is
 
 local_interfaces = <; ::0 ; 0.0.0.0
 
-+------------------+---------+----------+-----------+
++---------------------------------------------------+
 |local_scan_timeout|Use: main|Type: time|Default: 5m|
-+------------------+---------+----------+-----------+
++---------------------------------------------------+
 
-This timeout applies to the local_scan() function (see chapter 44). Zero means
+This timeout applies to the local_scan() function (see chapter 45). Zero means
 "no timeout". If the timeout is exceeded, the incoming message is rejected with
 a temporary error if it is an SMTP message. For a non-SMTP message, the message
 is dropped and Exim ends with a non-zero code. The incident is logged on the
 main and reject logs.
 
-+-------------------+---------+-------------+--------------+
++----------------------------------------------------------+
 |local_sender_retain|Use: main|Type: boolean|Default: false|
-+-------------------+---------+-------------+--------------+
++----------------------------------------------------------+
 
 When a message is submitted locally (that is, not over a TCP/IP connection) by
 an untrusted user, Exim removes any existing Sender: header line. If you do not
 want this to happen, you must set local_sender_retain, and you must also set 
 local_from_check to be false (Exim will complain if you do not). See also the
-ACL modifier "control = suppress_local_fixups". Section 46.16 has more details
+ACL modifier "control = suppress_local_fixups". Section 47.16 has more details
 about Sender: processing.
 
-+----------------+---------+-------------+--------------+
++-------------------------------------------------------+
 |localhost_number|Use: main|Type: string*|Default: unset|
-+----------------+---------+-------------+--------------+
++-------------------------------------------------------+
 
 Exim's message ids are normally unique only within the local host. If
 uniqueness among a set of hosts is required, each host must set a different
@@ -13683,26 +14250,27 @@ $localhost_number. When localhost_number is set, the final two characters of
 the message id, instead of just being a fractional part of the time, are
 computed from the time and the local host number as described in section 3.4.
 
-+-------------+---------+------------------+----------------------------+
++-----------------------------------------------------------------------+
 |log_file_path|Use: main|Type: string list*|Default: set at compile time|
-+-------------+---------+------------------+----------------------------+
++-----------------------------------------------------------------------+
 
 This option sets the path which is used to determine the names of Exim's log
 files, or indicates that logging is to be to syslog, or both. It is expanded
 when Exim is entered, so it can, for example, contain a reference to the host
-name. If no specific path is set for the log files at compile or run time, they
-are written in a sub-directory called log in Exim's spool directory. Chapter 51
-contains further details about Exim's logging, and section 51.1 describes how
-the contents of log_file_path are used. If this string is fixed at your
+name. If no specific path is set for the log files at compile or run time, or
+if the option is unset at run time (i.e. "log_file_path = ") they are written
+in a sub-directory called log in Exim's spool directory. Chapter 52 contains
+further details about Exim's logging, and section 52.1 describes how the
+contents of log_file_path are used. If this string is fixed at your
 installation (contains no expansion variables) it is recommended that you do
 not set this option in the configuration file, but instead supply the path
 using LOG_FILE_PATH in Local/Makefile so that it is available to Exim for
 logging errors detected early on - in particular, failure to read the
 configuration file.
 
-+------------+---------+------------+--------------+
++--------------------------------------------------+
 |log_selector|Use: main|Type: string|Default: unset|
-+------------+---------+------------+--------------+
++--------------------------------------------------+
 
 This option can be used to reduce or increase the number of things that Exim
 writes to its log files. Its argument is made up of names preceded by plus or
@@ -13711,11 +14279,11 @@ minus characters. For example:
 log_selector = +arguments -retry_defer
 
 A list of possible names and what they control is given in the chapter on
-logging, in section 51.15.
+logging, in section 52.15.
 
-+------------+---------+-------------+--------------+
++---------------------------------------------------+
 |log_timezone|Use: main|Type: boolean|Default: false|
-+------------+---------+-------------+--------------+
++---------------------------------------------------+
 
 By default, the timestamps on log lines are in local time without the timezone.
 This means that if your timezone changes twice a year, the timestamps in log
@@ -13727,9 +14295,9 @@ of log files because each line is extended by 6 characters. Note that the
 $tod_log variable contains the log timestamp without the zone, but there is
 another variable called $tod_zone that contains just the timezone offset.
 
-+---------------+---------+-------------+-----------+
++---------------------------------------------------+
 |lookup_open_max|Use: main|Type: integer|Default: 25|
-+---------------+---------+-------------+-----------+
++---------------------------------------------------+
 
 This option limits the number of simultaneously open files for single-key
 lookups that use regular files (that is, lsearch, dbm, and cdb). Exim normally
@@ -13740,33 +14308,33 @@ for each logical DBM database, though it still counts as one for the purposes
 of lookup_open_max. If you are getting "too many open files" errors with NDBM,
 you need to reduce the value of lookup_open_max.
 
-+-------------------+---------+-------------+----------+
++------------------------------------------------------+
 |max_username_length|Use: main|Type: integer|Default: 0|
-+-------------------+---------+-------------+----------+
++------------------------------------------------------+
 
 Some operating systems are broken in that they truncate long arguments to
 getpwnam() to eight characters, instead of returning "no such user". If this
 option is set greater than zero, any attempt to call getpwnam() with an
 argument that is longer behaves as if getpwnam() failed.
 
-+---------------------+---------+----------+--------------+
++---------------------------------------------------------+
 |message_body_newlines|Use: main|Type: bool|Default: false|
-+---------------------+---------+----------+--------------+
++---------------------------------------------------------+
 
 By default, newlines in the message body are replaced by spaces when setting
 the $message_body and $message_body_end expansion variables. If this option is
 set true, this no longer happens.
 
-+--------------------+---------+-------------+------------+
++---------------------------------------------------------+
 |message_body_visible|Use: main|Type: integer|Default: 500|
-+--------------------+---------+-------------+------------+
++---------------------------------------------------------+
 
 This option specifies how much of a message's body is to be included in the
 $message_body and $message_body_end expansion variables.
 
-+------------------------+---------+-------------+--------------+
++---------------------------------------------------------------+
 |message_id_header_domain|Use: main|Type: string*|Default: unset|
-+------------------------+---------+-------------+--------------+
++---------------------------------------------------------------+
 
 If this option is set, the string is expanded and used as the right hand side
 (domain) of the Message-ID: header that Exim creates if a locally-originated
@@ -13776,9 +14344,9 @@ dot and hyphen are accepted; any other characters are replaced by hyphens. If
 the expansion is forced to fail, or if the result is an empty string, the
 option is ignored.
 
-+----------------------+---------+-------------+--------------+
++-------------------------------------------------------------+
 |message_id_header_text|Use: main|Type: string*|Default: unset|
-+----------------------+---------+-------------+--------------+
++-------------------------------------------------------------+
 
 If this variable is set, the string is expanded and used to augment the text of
 the Message-id: header that Exim creates if a locally-originated incoming
@@ -13792,9 +14360,9 @@ that are illegal in an address are automatically converted into hyphens. This
 means that variables such as $tod_log can be used, because the spaces and
 colons will become hyphens.
 
-+------------+---------+-------------+-------------+
++--------------------------------------------------+
 |message_logs|Use: main|Type: boolean|Default: true|
-+------------+---------+-------------+-------------+
++--------------------------------------------------+
 
 If this option is turned off, per-message log files are not created in the
 msglog spool sub-directory. This reduces the amount of disk I/O required by
@@ -13803,9 +14371,9 @@ minimum of four (header spool file, body spool file, delivery journal, and
 per-message log) to three. The other major I/O activity is Exim's main log,
 which is not affected by this option.
 
-+------------------+---------+-------------+------------+
++-------------------------------------------------------+
 |message_size_limit|Use: main|Type: string*|Default: 50M|
-+------------------+---------+-------------+------------+
++-------------------------------------------------------+
 
 This option limits the maximum size of message that Exim will process. The
 value is expanded for each incoming connection so, for example, it can be made
@@ -13830,7 +14398,7 @@ If you use a virus-scanner and set this option to to a value larger than the
 maximum size that your virus-scanner is configured to support, you may get
 failures triggered by large mails. The right size to configure for the
 virus-scanner depends upon what data is passed and the options in use but it's
-probably safest to just set it to a little larger than this value. Eg, with a
+probably safest to just set it to a little larger than this value. E.g., with a
 default Exim message size of 50M and a default ClamAV StreamMaxLength of 10M,
 some problems may result.
 
@@ -13838,9 +14406,9 @@ A value of 0 will disable size limit checking; Exim will still advertise the
 SIZE extension in an EHLO response, but without a limit, so as to permit SMTP
 clients to still indicate the message size along with the MAIL verb.
 
-+--------------------+---------+-------------+--------------+
++-----------------------------------------------------------+
 |move_frozen_messages|Use: main|Type: boolean|Default: false|
-+--------------------+---------+-------------+--------------+
++-----------------------------------------------------------+
 
 This option, which is available only if Exim has been built with the setting
 
@@ -13852,25 +14420,25 @@ respectively. There is currently no support in Exim or the standard utilities
 for handling such moved messages, and they do not show up in lists generated by
 -bp or by the Exim monitor.
 
-+-----------+---------+-------------+--------------+
++--------------------------------------------------+
 |mua_wrapper|Use: main|Type: boolean|Default: false|
-+-----------+---------+-------------+--------------+
++--------------------------------------------------+
 
 Setting this option true causes Exim to run in a very restrictive mode in which
-it passes messages synchronously to a smart host. Chapter 50 contains a full
+it passes messages synchronously to a smart host. Chapter 51 contains a full
 description of this facility.
 
-+-------------+---------+-----------------+--------------+
++--------------------------------------------------------+
 |mysql_servers|Use: main|Type: string list|Default: unset|
-+-------------+---------+-----------------+--------------+
++--------------------------------------------------------+
 
 This option provides a list of MySQL servers and associated connection data, to
-be used in conjunction with mysql lookups (see section 9.21). The option is
+be used in conjunction with mysql lookups (see section 9.22). The option is
 available only if Exim has been built with MySQL support.
 
-+-----------+---------+------------------+--------------+
++-------------------------------------------------------+
 |never_users|Use: main|Type: string list*|Default: unset|
-+-----------+---------+------------------+--------------+
++-------------------------------------------------------+
 
 This option is expanded just once, at the start of Exim's processing. Local
 message deliveries are normally run in processes that are setuid to the
@@ -13894,9 +14462,9 @@ Including root is redundant if it is also on the fixed list, but it does no
 harm. This option overrides the pipe_as_creator option of the pipe transport
 driver.
 
-+---------------+---------+-----------------+------------------+
-|openssl_options|Use: main|Type: string list|Default: +no_sslv2|
-+---------------+---------+-----------------+------------------+
++-----------------------------------------------------------------------------+
+|openssl_options|Use: main|Type: string list|Default: +no_sslv2 +single_dh_use|
++-----------------------------------------------------------------------------+
 
 This option allows an administrator to adjust the SSL options applied by
 OpenSSL to connections. It is given as a space-separated list of items, each
@@ -13916,17 +14484,22 @@ the foot in various unpleasant ways. This option should not be adjusted
 lightly. An unrecognised item will be detected at startup, by invoking Exim
 with the -bV flag.
 
+The option affects Exim operating both as a server and as a client.
+
 Historical note: prior to release 4.80, Exim defaulted this value to
 "+dont_insert_empty_fragments", which may still be needed for compatibility
 with some clients, but which lowers security by increasing exposure to some now
 infamous attacks.
 
-An example:
+Examples:
 
 # Make both old MS and old Eudora happy:
 openssl_options = -all +microsoft_big_sslv3_buffer \
                        +dont_insert_empty_fragments
 
+# Disable older protocol versions:
+openssl_options = +no_sslv2 +no_sslv3
+
 Possible options may include:
 
   * "all"
@@ -13990,17 +14563,17 @@ negotiate TLS then this option value might help, provided that your OpenSSL
 release is new enough to contain this work-around. This may be a situation
 where you have to upgrade OpenSSL to get buggy clients working.
 
-+--------------+---------+-----------------+--------------+
++---------------------------------------------------------+
 |oracle_servers|Use: main|Type: string list|Default: unset|
-+--------------+---------+-----------------+--------------+
++---------------------------------------------------------+
 
 This option provides a list of Oracle servers and associated connection data,
-to be used in conjunction with oracle lookups (see section 9.21). The option is
+to be used in conjunction with oracle lookups (see section 9.22). The option is
 available only if Exim has been built with Oracle support.
 
-+--------------------+---------+------------------+--------------+
++----------------------------------------------------------------+
 |percent_hack_domains|Use: main|Type: domain list*|Default: unset|
-+--------------------+---------+------------------+--------------+
++----------------------------------------------------------------+
 
 The "percent hack" is the convention whereby a local part containing a percent
 sign is re-interpreted as a new email address, with the percent replaced by @.
@@ -14017,31 +14590,37 @@ through to internal MTAs without processing the local parts, it is a good idea
 to reject recipient addresses with percent characters in their local parts.
 Exim's default configuration does this.
 
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 |perl_at_start|Use: main|Type: boolean|Default: false|
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 
 This option is available only when Exim is built with an embedded Perl
 interpreter. See chapter 12 for details of its use.
 
-+------------+---------+------------+--------------+
++--------------------------------------------------+
 |perl_startup|Use: main|Type: string|Default: unset|
-+------------+---------+------------+--------------+
++--------------------------------------------------+
 
 This option is available only when Exim is built with an embedded Perl
 interpreter. See chapter 12 for details of its use.
 
-+-------------+---------+-----------------+--------------+
++---------------------------------------------------+
+|perl_startup|Use: main|Type: boolean|Default: false|
++---------------------------------------------------+
+
+This Option enables the taint mode of the embedded Perl interpreter.
+
++--------------------------------------------------------+
 |pgsql_servers|Use: main|Type: string list|Default: unset|
-+-------------+---------+-----------------+--------------+
++--------------------------------------------------------+
 
 This option provides a list of PostgreSQL servers and associated connection
-data, to be used in conjunction with pgsql lookups (see section 9.21). The
+data, to be used in conjunction with pgsql lookups (see section 9.22). The
 option is available only if Exim has been built with PostgreSQL support.
 
-+-------------+---------+-------------+----------------------------+
++------------------------------------------------------------------+
 |pid_file_path|Use: main|Type: string*|Default: set at compile time|
-+-------------+---------+-------------+----------------------------+
++------------------------------------------------------------------+
 
 This option sets the name of the file to which the Exim daemon writes its
 process id. The string is expanded, so it can contain, for example, references
@@ -14054,31 +14633,31 @@ spool directory. The value set by the option can be overridden by the -oP
 command line option. A pid file is not written if a "non-standard" daemon is
 run by means of the -oX option, unless a path is explicitly supplied by -oP.
 
-+--------------------------+---------+----------------+----------+
++----------------------------------------------------------------+
 |pipelining_advertise_hosts|Use: main|Type: host list*|Default: *|
-+--------------------------+---------+----------------+----------+
++----------------------------------------------------------------+
 
 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.22. When PIPELINING is not advertised and smtp_enforce_sync is true, an Exim
+43.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.
+the message content is received. See section 43.9.
 
-+---------------------+---------+-------------+--------------+
++------------------------------------------------------------+
 |preserve_message_logs|Use: main|Type: boolean|Default: false|
-+---------------------+---------+-------------+--------------+
++------------------------------------------------------------+
 
 If this option is set, message log files are not deleted when messages are
 completed. Instead, they are moved to a sub-directory of the spool directory
@@ -14086,9 +14665,9 @@ called msglog.OLD, where they remain available for statistical or debugging
 purposes. This is a dangerous option to set on systems with any appreciable
 volume of mail. Use with care!
 
-+----------------+---------+------------+------------------+
++----------------------------------------------------------+
 |primary_hostname|Use: main|Type: string|Default: see below|
-+----------------+---------+------------+------------------+
++----------------------------------------------------------+
 
 This specifies the name of the current host. It is used in the default EHLO or
 HELO command for outgoing SMTP messages (changeable via the helo_data option in
@@ -14103,9 +14682,9 @@ available) in order to obtain the fully qualified version. The variable
 $primary_hostname contains the host name, whether set explicitly by this
 option, or defaulted.
 
-+-----------------+---------+-------------+--------------+
++--------------------------------------------------------+
 |print_topbitchars|Use: main|Type: boolean|Default: false|
-+-----------------+---------+-------------+--------------+
++--------------------------------------------------------+
 
 By default, Exim considers only those characters whose codes lie in the range
 32-126 to be printing characters. In a number of circumstances (for example,
@@ -14116,13 +14695,13 @@ characters.
 
 This option also affects the header syntax checks performed by the autoreply
 transport, and whether Exim uses RFC 2047 encoding of the user's full name when
-constructing From: and Sender: addresses (as described in section 46.18).
+constructing From: and Sender: addresses (as described in section 47.18).
 Setting this option can cause Exim to generate eight bit message headers that
 do not conform to the standards.
 
-+----------------+---------+------------+--------------+
++------------------------------------------------------+
 |process_log_path|Use: main|Type: string|Default: unset|
-+----------------+---------+------------+--------------+
++------------------------------------------------------+
 
 This option sets the name of the file to which an Exim process writes its
 "process log" when sent a USR1 signal. This is used by the exiwhat utility
@@ -14131,16 +14710,16 @@ spool directory is used. The ability to specify the name explicitly can be
 useful in environments where two different Exims are running, using different
 spool directories.
 
-+-------------------+---------+-------------+-------------+
++---------------------------------------------------------+
 |prod_requires_admin|Use: main|Type: boolean|Default: true|
-+-------------------+---------+-------------+-------------+
++---------------------------------------------------------+
 
 The -M, -R, and -q command-line options require the caller to be an admin user
 unless prod_requires_admin is set false. See also queue_list_requires_admin.
 
-+--------------+---------+------------+------------------+
++--------------------------------------------------------+
 |qualify_domain|Use: main|Type: string|Default: see below|
-+--------------+---------+------------+------------------+
++--------------------------------------------------------+
 
 This option specifies the domain name that is added to any envelope sender
 addresses that do not have a domain qualification. It also applies to recipient
@@ -14156,33 +14735,33 @@ are qualified with qualify_domain or qualify_recipient as necessary.
 Internally, Exim always works with fully qualified envelope addresses. If 
 qualify_domain is not set, it defaults to the primary_hostname value.
 
-+-----------------+---------+------------+------------------+
++-----------------------------------------------------------+
 |qualify_recipient|Use: main|Type: string|Default: see below|
-+-----------------+---------+------------+------------------+
++-----------------------------------------------------------+
 
 This option allows you to specify a different domain for qualifying recipient
 addresses to the one that is used for senders. See qualify_domain above.
 
-+-------------+---------+------------------+--------------+
++---------------------------------------------------------+
 |queue_domains|Use: main|Type: domain list*|Default: unset|
-+-------------+---------+------------------+--------------+
++---------------------------------------------------------+
 
 This option lists domains for which immediate delivery is not required. A
 delivery process is started whenever a message is received, but only those
 domains that do not match are processed. All other deliveries wait until the
 next queue run. See also hold_domains and queue_smtp_domains.
 
-+-------------------------+---------+-------------+-------------+
++---------------------------------------------------------------+
 |queue_list_requires_admin|Use: main|Type: boolean|Default: true|
-+-------------------------+---------+-------------+-------------+
++---------------------------------------------------------------+
 
 The -bp command-line option, which lists the messages that are on the queue,
 requires the caller to be an admin user unless queue_list_requires_admin is set
 false. See also prod_requires_admin.
 
-+----------+---------+-------------+--------------+
++-------------------------------------------------+
 |queue_only|Use: main|Type: boolean|Default: false|
-+----------+---------+-------------+--------------+
++-------------------------------------------------+
 
 If queue_only is set, a delivery process is not automatically started whenever
 a message is received. Instead, the message waits on the queue for the next
@@ -14193,9 +14772,9 @@ The -odq command line has the same effect as queue_only. The -odb and -odi
 command line options override queue_only unless queue_only_override is set
 false. See also queue_only_file, queue_only_load, and smtp_accept_queue.
 
-+---------------+---------+------------+--------------+
++-----------------------------------------------------+
 |queue_only_file|Use: main|Type: string|Default: unset|
-+---------------+---------+------------+--------------+
++-----------------------------------------------------+
 
 This option can be set to a colon-separated list of absolute path names, each
 one optionally preceded by "smtp". When Exim is receiving a message, it tests
@@ -14209,9 +14788,9 @@ queue_only_file = smtp/some/file
 causes Exim to behave as if queue_smtp_domains were set to "*" whenever /some/
 file exists.
 
-+---------------+---------+-----------------+--------------+
++----------------------------------------------------------+
 |queue_only_load|Use: main|Type: fixed-point|Default: unset|
-+---------------+---------+-----------------+--------------+
++----------------------------------------------------------+
 
 If the system load average is higher than this value, incoming messages from
 all sources are queued, and no automatic deliveries are started. If this
@@ -14224,9 +14803,9 @@ option has no effect on ancient operating systems on which Exim cannot
 determine the load average. See also deliver_queue_load_max and 
 smtp_load_reserve.
 
-+---------------------+---------+-------------+-------------+
++-----------------------------------------------------------+
 |queue_only_load_latch|Use: main|Type: boolean|Default: true|
-+---------------------+---------+-------------+-------------+
++-----------------------------------------------------------+
 
 When this option is true (the default), once one message has been queued
 because the load average is higher than the value set by queue_only_load, all
@@ -14239,18 +14818,18 @@ where this is not the best strategy. In such cases, queue_only_load_latch
 should be set false. This causes the value of the load average to be
 re-evaluated for each message.
 
-+-------------------+---------+-------------+-------------+
++---------------------------------------------------------+
 |queue_only_override|Use: main|Type: boolean|Default: true|
-+-------------------+---------+-------------+-------------+
++---------------------------------------------------------+
 
 When this option is true, the -odx command line options override the setting of
 queue_only or queue_only_file in the configuration file. If queue_only_override
 is set false, the -odx options cannot be used to override; they are accepted,
 but ignored.
 
-+------------------+---------+-------------+--------------+
++---------------------------------------------------------+
 |queue_run_in_order|Use: main|Type: boolean|Default: false|
-+------------------+---------+-------------+--------------+
++---------------------------------------------------------+
 
 If this option is set, queue runs happen in order of message arrival instead of
 in an arbitrary order. For this to happen, a complete list of the entire queue
@@ -14264,9 +14843,9 @@ queue_run_in_order with split_spool_directory may degrade performance when the
 queue is large, because of the extra work in setting up the single, large list.
 In most situations, queue_run_in_order should not be set.
 
-+-------------+---------+-------------+----------+
-|queue_run_max|Use: main|Type: integer|Default: 5|
-+-------------+---------+-------------+----------+
++-------------------------------------------------+
+|queue_run_max|Use: main|Type: integer*|Default: 5|
++-------------------------------------------------+
 
 This controls the maximum number of queue runner processes that an Exim daemon
 can run simultaneously. This does not mean that it starts them all at once, but
@@ -14281,9 +14860,12 @@ the limit, allowing any number of simultaneous queue runner processes to be
 run. If you do not want queue runs to occur, omit the -qxx setting on the
 daemon's command line.
 
-+------------------+---------+------------------+--------------+
+To set limits for different named queues use an expansion depending on the
+$queue_name variable.
+
++--------------------------------------------------------------+
 |queue_smtp_domains|Use: main|Type: domain list*|Default: unset|
-+------------------+---------+------------------+--------------+
++--------------------------------------------------------------+
 
 When this option is set, a delivery process is started whenever a message is
 received, routing is performed, and local deliveries take place. However, if
@@ -14296,9 +14878,9 @@ connection. The -odqs command line option causes all SMTP deliveries to be
 queued in this way, and is equivalent to setting queue_smtp_domains to "*". See
 also hold_domains and queue_domains.
 
-+---------------+---------+----------+-----------+
++------------------------------------------------+
 |receive_timeout|Use: main|Type: time|Default: 0s|
-+---------------+---------+----------+-----------+
++------------------------------------------------+
 
 This option sets the timeout for accepting a non-SMTP message, that is, the
 maximum time that Exim waits when reading a message on the standard input. If
@@ -14306,9 +14888,9 @@ the value is zero, it will wait for ever. This setting is overridden by the -or
 command line option. The timeout for incoming SMTP messages is controlled by 
 smtp_receive_timeout.
 
-+--------------------+---------+-------------+------------------+
++---------------------------------------------------------------+
 |received_header_text|Use: main|Type: string*|Default: see below|
-+--------------------+---------+-------------+------------------+
++---------------------------------------------------------------+
 
 This string defines the contents of the Received: message header that is added
 to each message, except for the timestamp, which is automatically added on at
@@ -14350,18 +14932,18 @@ the message started to be received. Once the body has arrived, and all policy
 checks have taken place, the timestamp is updated to the time at which the
 message was accepted.
 
-+--------------------+---------+-------------+-----------+
++--------------------------------------------------------+
 |received_headers_max|Use: main|Type: integer|Default: 30|
-+--------------------+---------+-------------+-----------+
++--------------------------------------------------------+
 
 When a message is to be delivered, the number of Received: headers is counted,
 and if it is greater than this parameter, a mail loop is assumed to have
 occurred, the delivery is abandoned, and an error message is generated. This
 applies to both local and remote deliveries.
 
-+---------------------------+---------+----------------+--------------+
++---------------------------------------------------------------------+
 |recipient_unqualified_hosts|Use: main|Type: host list*|Default: unset|
-+---------------------------+---------+----------------+--------------+
++---------------------------------------------------------------------+
 
 This option lists those hosts from which Exim is prepared to accept unqualified
 recipient addresses in message envelopes. The addresses are made fully
@@ -14371,9 +14953,9 @@ addresses in headers, but it qualifies them only if the message came from a
 host that matches recipient_unqualified_hosts, or if the message was submitted
 locally (not using TCP/IP), and the -bnq option was not set.
 
-+--------------+---------+-------------+----------+
++-------------------------------------------------+
 |recipients_max|Use: main|Type: integer|Default: 0|
-+--------------+---------+-------------+----------+
++-------------------------------------------------+
 
 If this option is set greater than zero, it specifies the maximum number of
 original recipients for any message. Additional recipients that are generated
@@ -14385,9 +14967,9 @@ done.
 Note: The RFCs specify that an SMTP server should accept at least 100 RCPT
 commands in a single message.
 
-+---------------------+---------+-------------+--------------+
++------------------------------------------------------------+
 |recipients_max_reject|Use: main|Type: boolean|Default: false|
-+---------------------+---------+-------------+--------------+
++------------------------------------------------------------+
 
 If this option is set true, Exim rejects SMTP messages containing too many
 recipients by giving 552 errors to the surplus RCPT commands, and a 554 error
@@ -14396,9 +14978,9 @@ the surplus RCPT commands and accepts the message on behalf of the initial set
 of recipients. The remote server should then re-send the message for the
 remaining recipients at a later time.
 
-+-------------------+---------+-------------+----------+
++------------------------------------------------------+
 |remote_max_parallel|Use: main|Type: integer|Default: 2|
-+-------------------+---------+-------------+----------+
++------------------------------------------------------+
 
 This option controls parallel delivery of one message to a number of remote
 hosts. If the value is less than 2, parallel delivery is disabled, and Exim
@@ -14433,9 +15015,9 @@ instead of queue_only. This has the added benefit of doing the SMTP routing
 before queueing, so that several messages for the same host will eventually get
 delivered down the same connection.
 
-+-------------------+---------+------------------+--------------+
++---------------------------------------------------------------+
 |remote_sort_domains|Use: main|Type: domain list*|Default: unset|
-+-------------------+---------+------------------+--------------+
++---------------------------------------------------------------+
 
 When there are a number of remote deliveries for a message, they are sorted by
 domain into the order given by this list. For example,
@@ -14445,27 +15027,27 @@ remote_sort_domains = *.cam.ac.uk:*.uk
 would attempt to deliver to all addresses in the cam.ac.uk domain first, then
 to those in the uk domain, then to any others.
 
-+-----------------+---------+----------+-----------+
++--------------------------------------------------+
 |retry_data_expire|Use: main|Type: time|Default: 7d|
-+-----------------+---------+----------+-----------+
++--------------------------------------------------+
 
 This option sets a "use before" time on retry information in Exim's hints
 database. Any older retry data is ignored. This means that, for example, once a
 host has not been tried for 7 days, Exim behaves as if it has no knowledge of
 past failures.
 
-+------------------+---------+----------+------------+
++----------------------------------------------------+
 |retry_interval_max|Use: main|Type: time|Default: 24h|
-+------------------+---------+----------+------------+
++----------------------------------------------------+
 
 Chapter 32 describes Exim's mechanisms for controlling the intervals between
 delivery attempts for messages that cannot be delivered straight away. This
 option sets an overall limit to the length of time between retries. It cannot
 be set greater than 24 hours; any attempt to do so forces the default value.
 
-+------------------+---------+-------------+-------------+
++--------------------------------------------------------+
 |return_path_remove|Use: main|Type: boolean|Default: true|
-+------------------+---------+-------------+-------------+
++--------------------------------------------------------+
 
 RFC 2821, section 4.4, states that an SMTP server must insert a Return-path:
 header line into a message when it makes a "final delivery". The Return-path:
@@ -14476,29 +15058,30 @@ removed from messages at the time they are received. Exim's transports have
 options for adding Return-path: headers at the time of delivery. They are
 normally used only for final local deliveries.
 
-+-----------------+---------+-------------+-------------+
++-------------------------------------------------------+
 |return_size_limit|Use: main|Type: integer|Default: 100K|
-+-----------------+---------+-------------+-------------+
++-------------------------------------------------------+
 
 This option is an obsolete synonym for bounce_return_size_limit.
 
-+-------------+---------+----------------+----------+
-|rfc1413_hosts|Use: main|Type: host list*|Default: *|
-+-------------+---------+----------------+----------+
++-----------------------------------------------------+
+|rfc1413_hosts|Use: main|Type: host list*|Default: @[]|
++-----------------------------------------------------+
 
 RFC 1413 identification calls are made to any client host which matches an item
-in the list.
+in the list. The default value specifies just this host, being any local
+interface for the system.
 
-+---------------------+---------+----------+-----------+
-|rfc1413_query_timeout|Use: main|Type: time|Default: 5s|
-+---------------------+---------+----------+-----------+
++------------------------------------------------------+
+|rfc1413_query_timeout|Use: main|Type: time|Default: 0s|
++------------------------------------------------------+
 
 This sets the timeout on RFC 1413 identification calls. If it is set to zero,
 no RFC 1413 calls are ever made.
 
-+------------------------+---------+----------------+--------------+
++------------------------------------------------------------------+
 |sender_unqualified_hosts|Use: main|Type: host list*|Default: unset|
-+------------------------+---------+----------------+--------------+
++------------------------------------------------------------------+
 
 This option lists those hosts from which Exim is prepared to accept unqualified
 sender addresses. The addresses are made fully qualified by the addition of 
@@ -14508,9 +15091,25 @@ qualifies them only if the message came from a host that matches
 sender_unqualified_hosts, or if the message was submitted locally (not using
 TCP/IP), and the -bnq option was not set.
 
-+---------------------+---------+-------------+-------------+
++----------------------------------------------------------+
+|set_environment|Use: main|Type: string list|Default: empty|
++----------------------------------------------------------+
+
+This option allows to set individual environment variables that the currently
+linked libraries and programs in child processes use. The default list is
+empty,
+
++--------------------------------------------------+
+|slow_lookup_log|Use: main|Type: integer|Default: 0|
++--------------------------------------------------+
+
+This option controls logging of slow lookups. If the value is nonzero it is
+taken as a number of milliseconds and lookups taking longer than this are
+logged. Currently this applies only to DNS lookups.
+
++-----------------------------------------------------------+
 |smtp_accept_keepalive|Use: main|Type: boolean|Default: true|
-+---------------------+---------+-------------+-------------+
++-----------------------------------------------------------+
 
 This option controls the setting of the SO_KEEPALIVE option on incoming TCP/IP
 socket connections. When set, it causes the kernel to probe idle connections
@@ -14522,9 +15121,9 @@ get stuck when the remote host is disconnected without tidying up the TCP/IP
 call properly. The keepalive mechanism takes several hours to detect
 unreachable hosts.
 
-+---------------+---------+-------------+-----------+
++---------------------------------------------------+
 |smtp_accept_max|Use: main|Type: integer|Default: 20|
-+---------------+---------+-------------+-----------+
++---------------------------------------------------+
 
 This option specifies the maximum number of simultaneous incoming SMTP calls
 that Exim will accept. It applies only to the listening daemon; there is no
@@ -14538,9 +15137,9 @@ been reached. If not, Exim first checks smtp_accept_max_per_host. If that limit
 has not been reached for the client host, smtp_accept_reserve and 
 smtp_load_reserve are then checked before accepting the connection.
 
-+-----------------------+---------+-------------+-----------+
++-----------------------------------------------------------+
 |smtp_accept_max_nonmail|Use: main|Type: integer|Default: 10|
-+-----------------------+---------+-------------+-----------+
++-----------------------------------------------------------+
 
 Exim counts the number of "non-mail" commands in an SMTP session, and drops the
 connection if there are too many. This option defines "too many". The check
@@ -14557,17 +15156,17 @@ occurrence of AUTH in a connection, or immediately following STARTTLS is not
 counted. Otherwise, all commands other than MAIL, RCPT, DATA, and QUIT are
 counted.
 
-+-----------------------------+---------+----------------+----------+
++-------------------------------------------------------------------+
 |smtp_accept_max_nonmail_hosts|Use: main|Type: host list*|Default: *|
-+-----------------------------+---------+----------------+----------+
++-------------------------------------------------------------------+
 
 You can control which hosts are subject to the smtp_accept_max_nonmail check by
 setting this option. The default value makes it apply to all hosts. By changing
 the value, you can exclude any badly-behaved hosts that you have to live with.
 
-+------------------------------+---------+-------------+-------------+
++--------------------------------------------------------------------+
 |smtp_accept_max_per_connection|Use: main|Type: integer|Default: 1000|
-+------------------------------+---------+-------------+-------------+
++--------------------------------------------------------------------+
 
 The value of this option limits the number of MAIL commands that Exim is
 prepared to accept over a single SMTP connection, whether or not each command
@@ -14576,9 +15175,9 @@ response is given to subsequent MAIL commands. This limit is a safety
 precaution against a client that goes mad (incidents of this type have been
 seen).
 
-+------------------------+---------+-------------+--------------+
++---------------------------------------------------------------+
 |smtp_accept_max_per_host|Use: main|Type: string*|Default: unset|
-+------------------------+---------+-------------+--------------+
++---------------------------------------------------------------+
 
 This option restricts the number of simultaneous IP connections from a single
 host (strictly, from a single IP address) to the Exim daemon. The option is
@@ -14596,9 +15195,9 @@ without forking additional processes (otherwise a denial-of-service attack
 could cause a vast number or processes to be created). While the daemon is
 doing this processing, it cannot accept any other incoming connections.
 
-+-----------------+---------+-------------+----------+
++----------------------------------------------------+
 |smtp_accept_queue|Use: main|Type: integer|Default: 0|
-+-----------------+---------+-------------+----------+
++----------------------------------------------------+
 
 If the number of simultaneous incoming SMTP connections being handled via the
 listening daemon exceeds this value, messages received by SMTP are just placed
@@ -14612,9 +15211,9 @@ if it is less than the smtp_accept_max value (unless that is zero). See also
 queue_only, queue_only_load, queue_smtp_domains, and the various -odx command
 line options.
 
-+--------------------------------+---------+-------------+-----------+
++--------------------------------------------------------------------+
 |smtp_accept_queue_per_connection|Use: main|Type: integer|Default: 10|
-+--------------------------------+---------+-------------+-----------+
++--------------------------------------------------------------------+
 
 This option limits the number of delivery processes that Exim starts
 automatically when receiving messages via SMTP, whether via the daemon or by
@@ -14626,9 +15225,9 @@ restarts after downtime and there is a lot of mail waiting for it on other
 systems. On large systems, the default should probably be increased, and on
 dial-in client systems it should probably be set to zero (that is, disabled).
 
-+-------------------+---------+-------------+----------+
++------------------------------------------------------+
 |smtp_accept_reserve|Use: main|Type: integer|Default: 0|
-+-------------------+---------+-------------+----------+
++------------------------------------------------------+
 
 When smtp_accept_max is set greater than zero, this option specifies a number
 of SMTP connections that are reserved for connections from the hosts that are
@@ -14644,9 +15243,9 @@ For example, if smtp_accept_max is set to 50 and smtp_accept_reserve is set to
 accepted only from hosts listed in smtp_reserve_hosts, provided the other
 criteria for acceptance are met.
 
-+--------------------+---------+-------------+--------------+
++-----------------------------------------------------------+
 |smtp_active_hostname|Use: main|Type: string*|Default: unset|
-+--------------------+---------+-------------+--------------+
++-----------------------------------------------------------+
 
 This option is provided for multi-homed servers that want to masquerade as
 several different hosts. At the start of an incoming SMTP connection, its value
@@ -14671,9 +15270,9 @@ Although $smtp_active_hostname is primarily concerned with incoming messages,
 it is also used as the default for HELO commands in callout verification if
 there is no remote transport from which to obtain a helo_data value.
 
-+-----------+---------+-------------+------------------+
++------------------------------------------------------+
 |smtp_banner|Use: main|Type: string*|Default: see below|
-+-----------+---------+-------------+------------------+
++------------------------------------------------------+
 
 This string, which is expanded every time it is used, is output as the initial
 positive response to an SMTP connection. The default setting is:
@@ -14687,9 +15286,9 @@ appropriate points, but not at the end. Note that the 220 code is not included
 in this string. Exim adds it automatically (several times in the case of a
 multiline response).
 
-+----------------------+---------+-------------+-------------+
++------------------------------------------------------------+
 |smtp_check_spool_space|Use: main|Type: boolean|Default: true|
-+----------------------+---------+-------------+-------------+
++------------------------------------------------------------+
 
 When this option is set, if an incoming SMTP session encounters the SIZE option
 on a MAIL command, it checks that there is enough space in the spool
@@ -14697,9 +15296,9 @@ directory's partition to accept a message of that size, while still leaving
 free the amount specified by check_spool_space (even if that value is zero). If
 there isn't enough space, a temporary error code is returned.
 
-+--------------------+---------+-------------+-----------+
++--------------------------------------------------------+
 |smtp_connect_backlog|Use: main|Type: integer|Default: 20|
-+--------------------+---------+-------------+-----------+
++--------------------------------------------------------+
 
 This option specifies a maximum number of waiting SMTP connections. Exim passes
 this value to the TCP/IP system when it sets up its listener. Once this number
@@ -14710,9 +15309,9 @@ out instead. For large systems it is probably a good idea to increase the value
 (to 50, say). It also gives some protection against denial-of-service attacks
 by SYN flooding.
 
-+-----------------+---------+-------------+-------------+
++-------------------------------------------------------+
 |smtp_enforce_sync|Use: main|Type: boolean|Default: true|
-+-----------------+---------+-------------+-------------+
++-------------------------------------------------------+
 
 The SMTP protocol specification requires the client to wait for a response from
 the server at certain points in the dialogue. Without PIPELINING these
@@ -14730,15 +15329,15 @@ detect many instances.
 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.22). See also pipelining_advertise_hosts.
+section 43.22). See also pipelining_advertise_hosts.
 
-+-----------------+---------+-------------+--------------+
++--------------------------------------------------------+
 |smtp_etrn_command|Use: main|Type: string*|Default: unset|
-+-----------------+---------+-------------+--------------+
++--------------------------------------------------------+
 
 If this option is set, the given command is run whenever an SMTP ETRN command
 is received from a host that is permitted to issue such commands (see chapter
-42). The string is split up into separate arguments which are independently
+43). The string is split up into separate arguments which are independently
 expanded. The expansion variable $domain is set to the argument of the ETRN
 command, and no syntax checking is done on it. For example:
 
@@ -14752,17 +15351,17 @@ run, a line is written to the panic log, but the ETRN caller still receives a
 SMTP, so it is not possible for it to change the uid before running the
 command.
 
-+-------------------+---------+-------------+-------------+
++---------------------------------------------------------+
 |smtp_etrn_serialize|Use: main|Type: boolean|Default: true|
-+-------------------+---------+-------------+-------------+
++---------------------------------------------------------+
 
 When this option is set, it prevents the simultaneous execution of more than
 one identical command as a result of ETRN in an SMTP connection. See section
-47.8 for details.
+48.8 for details.
 
-+-----------------+---------+-----------------+--------------+
++------------------------------------------------------------+
 |smtp_load_reserve|Use: main|Type: fixed-point|Default: unset|
-+-----------------+---------+-----------------+--------------+
++------------------------------------------------------------+
 
 If the system load average ever gets higher than this, incoming SMTP calls are
 accepted only from those hosts that match an entry in smtp_reserve_hosts. If 
@@ -14771,9 +15370,9 @@ load is over the limit. The option has no effect on ancient operating systems
 on which Exim cannot determine the load average. See also 
 deliver_queue_load_max and queue_only_load.
 
-+-----------------------+---------+-------------+----------+
++----------------------------------------------------------+
 |smtp_max_synprot_errors|Use: main|Type: integer|Default: 3|
-+-----------------------+---------+-------------+----------+
++----------------------------------------------------------+
 
 Exim rejects SMTP commands that contain syntax or protocol errors. In
 particular, a syntactically invalid email address, as in this command:
@@ -14792,18 +15391,18 @@ assumes that PIPELINING will be used if it advertises it (see
 pipelining_advertise_hosts), and in this situation, "expected" errors do not
 count towards the limit.
 
-+-------------------------+---------+-------------+----------+
++------------------------------------------------------------+
 |smtp_max_unknown_commands|Use: main|Type: integer|Default: 3|
-+-------------------------+---------+-------------+----------+
++------------------------------------------------------------+
 
 If there are too many unrecognized commands in an incoming SMTP session, an
 Exim server drops the connection. This is a defence against some kinds of abuse
 that subvert web clients into making connections to SMTP ports; in these
 circumstances, a number of non-SMTP command lines are sent first.
 
-+--------------------+---------+----------------+--------------+
++--------------------------------------------------------------+
 |smtp_ratelimit_hosts|Use: main|Type: host list*|Default: unset|
-+--------------------+---------+----------------+--------------+
++--------------------------------------------------------------+
 
 Some sites find it helpful to be able to limit the rate at which certain hosts
 can send them messages, and the rate at which an individual message can specify
@@ -14811,7 +15410,7 @@ recipients.
 
 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.38 for
+ACL condition can limit rates across all connections. See section 43.38 for
 details of the newer facility.
 
 When a host matches smtp_ratelimit_hosts, the values of smtp_ratelimit_mail and
@@ -14840,21 +15439,21 @@ have been received over a single connection. The initial delay is 0.5 seconds,
 increasing by a factor of 1.05 each time. The second setting applies delays to
 RCPT commands when more than four occur in a single message.
 
-+-------------------+---------+------------+--------------+
++---------------------------------------------------------+
 |smtp_ratelimit_mail|Use: main|Type: string|Default: unset|
-+-------------------+---------+------------+--------------+
++---------------------------------------------------------+
 
 See smtp_ratelimit_hosts above.
 
-+-------------------+---------+------------+--------------+
++---------------------------------------------------------+
 |smtp_ratelimit_rcpt|Use: main|Type: string|Default: unset|
-+-------------------+---------+------------+--------------+
++---------------------------------------------------------+
 
 See smtp_ratelimit_hosts above.
 
-+--------------------+---------+----------+-----------+
-|smtp_receive_timeout|Use: main|Type: time|Default: 5m|
-+--------------------+---------+----------+-----------+
++------------------------------------------------------+
+|smtp_receive_timeout|Use: main|Type: time*|Default: 5m|
++------------------------------------------------------+
 
 This sets a timeout value for SMTP reception. It applies to all forms of SMTP
 input, including batch SMTP. If a line of input (either an SMTP command or a
@@ -14868,22 +15467,26 @@ SMTP data timeout on connection from...
 The former means that Exim was expecting to read an SMTP command; the latter
 means that it was in the DATA phase, reading the contents of a message.
 
+If the first character of the option is a "$" the option is expanded before use
+and may depend on $sender_host_name, $sender_host_address and $sender_host_port
+.
+
 The value set by this option can be overridden by the -os command-line option.
 A setting of zero time disables the timeout, but this should never be used for
 SMTP over TCP/IP. (It can be useful in some cases of local input using -bs or 
 -bS.) For non-SMTP input, the reception timeout is controlled by 
 receive_timeout and -or.
 
-+------------------+---------+----------------+--------------+
++------------------------------------------------------------+
 |smtp_reserve_hosts|Use: main|Type: host list*|Default: unset|
-+------------------+---------+----------------+--------------+
++------------------------------------------------------------+
 
 This option defines hosts for which SMTP connections are reserved; see 
 smtp_accept_reserve and smtp_load_reserve above.
 
-+-------------------------+---------+-------------+--------------+
++----------------------------------------------------------------+
 |smtp_return_error_details|Use: main|Type: boolean|Default: false|
-+-------------------------+---------+-------------+--------------+
++----------------------------------------------------------------+
 
 In the default state, Exim uses bland messages such as "Administrative
 prohibition" when it rejects SMTP commands for policy reasons. Many sysadmins
@@ -14896,9 +15499,18 @@ prohibition", it might give:
 550-Rejected after DATA: '>' missing at end of address:
 550 failing address in "From" header is: <user@dom.ain
 
-+-------------+---------+------------+------------------+
++--------------------------------------------------------------+
+|smtputf8_advertise_hosts|Use: main|Type: host list*|Default: *|
++--------------------------------------------------------------+
+
+When Exim is built with support for internationalised mail names, the
+availability thereof is advertised in response to EHLO only to those client
+hosts that match this option. See chapter 59 for details of Exim's support for
+internationalisation.
+
++-------------------------------------------------------+
 |spamd_address|Use: main|Type: string|Default: see below|
-+-------------+---------+------------+------------------+
++-------------------------------------------------------+
 
 This option is available when Exim is compiled with the content-scanning
 extension. It specifies how Exim connects to SpamAssassin's spamd daemon. The
@@ -14906,11 +15518,11 @@ default value is
 
 127.0.0.1 783
 
-See section 43.2 for more details.
+See section 44.2 for more details.
 
-+---------------------+---------+-------------+--------------+
++------------------------------------------------------------+
 |split_spool_directory|Use: main|Type: boolean|Default: false|
-+---------------------+---------+-------------+--------------+
++------------------------------------------------------------+
 
 If this option is set, it causes Exim to split its input directory into 62
 subdirectories, each with a single alphanumeric character as its name. The
@@ -14940,9 +15552,9 @@ particularly beneficial when there are lots of messages on the queue. However,
 if queue_run_in_order is set, none of this new processing happens. The entire
 queue has to be scanned and sorted before any deliveries can start.
 
-+---------------+---------+-------------+----------------------------+
++--------------------------------------------------------------------+
 |spool_directory|Use: main|Type: string*|Default: set at compile time|
-+---------------+---------+-------------+----------------------------+
++--------------------------------------------------------------------+
 
 This defines the directory in which Exim keeps its spool, that is, the messages
 it is waiting to deliver. The default value is taken from the compile-time
@@ -14959,25 +15571,25 @@ as failures in the configuration file.
 By using this option to override the compiled-in path, it is possible to run
 tests of Exim without using the standard spool.
 
-+-------------------+---------+----------+-----------+
++----------------------------------------------------+
 |sqlite_lock_timeout|Use: main|Type: time|Default: 5s|
-+-------------------+---------+----------+-----------+
++----------------------------------------------------+
 
 This option controls the timeout that the sqlite lookup uses when trying to
-access an SQLite database. See section 9.25 for more details.
+access an SQLite database. See section 9.26 for more details.
 
-+---------------+---------+-------------+--------------+
++------------------------------------------------------+
 |strict_acl_vars|Use: main|Type: boolean|Default: false|
-+---------------+---------+-------------+--------------+
++------------------------------------------------------+
 
 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.19 for
+substituted; if it is true, an error is generated. See section 43.19 for
 details of ACL variables.
 
-+---------------------------+---------+-------------+--------------+
++------------------------------------------------------------------+
 |strip_excess_angle_brackets|Use: main|Type: boolean|Default: false|
-+---------------------------+---------+-------------+--------------+
++------------------------------------------------------------------+
 
 If this option is set, redundant pairs of angle brackets round "route-addr"
 items in addresses are stripped. For example, <<xxx@a.b.c.d>> is treated as 
@@ -14985,9 +15597,9 @@ items in addresses are stripped. For example, <<xxx@a.b.c.d>> is treated as
 another MTA, the excess angle brackets are not passed on. If this option is not
 set, multiple pairs of angle brackets cause a syntax error.
 
-+------------------+---------+-------------+--------------+
++---------------------------------------------------------+
 |strip_trailing_dot|Use: main|Type: boolean|Default: false|
-+------------------+---------+-------------+--------------+
++---------------------------------------------------------+
 
 If this option is set, a trailing dot at the end of a domain in an address is
 ignored. If this is in the envelope and the message is passed on to another
@@ -14995,9 +15607,9 @@ MTA, the dot is not passed on. If this option is not set, a dot at the end of a
 domain causes a syntax error. However, addresses in header lines are checked
 only when an ACL requests header syntax checking.
 
-+------------------+---------+-------------+-------------+
++--------------------------------------------------------+
 |syslog_duplication|Use: main|Type: boolean|Default: true|
-+------------------+---------+-------------+-------------+
++--------------------------------------------------------+
 
 When Exim is logging to syslog, it writes the log lines for its three separate
 logs at different syslog priorities so that they can in principle be separated
@@ -15009,85 +15621,97 @@ reject log, the reject log version (possibly containing message header lines)
 is written, at LOG_NOTICE priority. Lines that normally go to both the main and
 the panic log are written at the LOG_ALERT priority.
 
-+---------------+---------+------------+--------------+
++-----------------------------------------------------+
 |syslog_facility|Use: main|Type: string|Default: unset|
-+---------------+---------+------------+--------------+
++-----------------------------------------------------+
 
 This option sets the syslog "facility" name, used when Exim is logging to
 syslog. The value must be one of the strings "mail", "user", "news", "uucp",
 "daemon", or "localx" where x is a digit between 0 and 7. If this option is
-unset, "mail" is used. See chapter 51 for details of Exim's logging.
+unset, "mail" is used. See chapter 52 for details of Exim's logging.
+
++------------------------------------------------+
+|syslog_pid|Use: main|Type: boolean|Default: true|
++------------------------------------------------+
 
-+------------------+---------+------------+---------------+
+If syslog_pid is set false, the PID on Exim's log lines are omitted when these
+lines are sent to syslog. (Syslog normally prefixes the log lines with the PID
+of the logging process automatically.) You need to enable the "+pid" log
+selector item, if you want Exim to write it's PID into the logs.) See chapter
+52 for details of Exim's logging.
+
++---------------------------------------------------------+
 |syslog_processname|Use: main|Type: string|Default: "exim"|
-+------------------+---------+------------+---------------+
++---------------------------------------------------------+
 
 This option sets the syslog "ident" name, used when Exim is logging to syslog.
-The value must be no longer than 32 characters. See chapter 51 for details of
+The value must be no longer than 32 characters. See chapter 52 for details of
 Exim's logging.
 
-+----------------+---------+-------------+-------------+
++------------------------------------------------------+
 |syslog_timestamp|Use: main|Type: boolean|Default: true|
-+----------------+---------+-------------+-------------+
++------------------------------------------------------+
 
 If syslog_timestamp is set false, the timestamps on Exim's log lines are
-omitted when these lines are sent to syslog. See chapter 51 for details of
+omitted when these lines are sent to syslog. See chapter 52 for details of
 Exim's logging.
 
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 |system_filter|Use: main|Type: string*|Default: unset|
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 
 This option specifies an Exim filter file that is applied to all messages at
 the start of each delivery attempt, before any routing is done. System filters
 must be Exim filters; they cannot be Sieve filters. If the system filter
 generates any deliveries to files or pipes, or any new mail messages, the
 appropriate system_filter_..._transport option(s) must be set, to define which
-transports are to be used. Details of this facility are given in chapter 45.
+transports are to be used. Details of this facility are given in chapter 46.
+
+A forced expansion failure results in no filter operation.
 
-+---------------------------------+---------+-------------+--------------+
++------------------------------------------------------------------------+
 |system_filter_directory_transport|Use: main|Type: string*|Default: unset|
-+---------------------------------+---------+-------------+--------------+
++------------------------------------------------------------------------+
 
 This sets the name of the transport driver that is to be used when the save
 command in a system message filter specifies a path ending in "/", implying
 delivery of each message into a separate file in some directory. During the
 delivery, the variable $address_file contains the path name.
 
-+----------------------------+---------+-------------+--------------+
++-------------------------------------------------------------------+
 |system_filter_file_transport|Use: main|Type: string*|Default: unset|
-+----------------------------+---------+-------------+--------------+
++-------------------------------------------------------------------+
 
 This sets the name of the transport driver that is to be used when the save
 command in a system message filter specifies a path not ending in "/". During
 the delivery, the variable $address_file contains the path name.
 
-+-------------------+---------+------------+--------------+
++---------------------------------------------------------+
 |system_filter_group|Use: main|Type: string|Default: unset|
-+-------------------+---------+------------+--------------+
++---------------------------------------------------------+
 
 This option is used only when system_filter_user is also set. It sets the gid
 under which the system filter is run, overriding any gid that is associated
 with the user. The value may be numerical or symbolic.
 
-+----------------------------+---------+-------------+--------------+
++-------------------------------------------------------------------+
 |system_filter_pipe_transport|Use: main|Type: string*|Default: unset|
-+----------------------------+---------+-------------+--------------+
++-------------------------------------------------------------------+
 
 This specifies the transport driver that is to be used when a pipe command is
 used in a system filter. During the delivery, the variable $address_pipe
 contains the pipe command.
 
-+-----------------------------+---------+-------------+--------------+
++--------------------------------------------------------------------+
 |system_filter_reply_transport|Use: main|Type: string*|Default: unset|
-+-----------------------------+---------+-------------+--------------+
++--------------------------------------------------------------------+
 
 This specifies the transport driver that is to be used when a mail command is
 used in a system filter.
 
-+------------------+---------+------------+--------------+
++--------------------------------------------------------+
 |system_filter_user|Use: main|Type: string|Default: unset|
-+------------------+---------+------------+--------------+
++--------------------------------------------------------+
 
 If this option is set to root, the system filter is run in the main Exim
 delivery process, as root. Otherwise, the system filter runs in a separate
@@ -15101,9 +15725,9 @@ If the system filter generates any pipe, file, or reply deliveries, the uid
 under which the filter is run is used when transporting them, unless a
 transport option overrides.
 
-+-----------+---------+-------------+-------------+
++-------------------------------------------------+
 |tcp_nodelay|Use: main|Type: boolean|Default: true|
-+-----------+---------+-------------+-------------+
++-------------------------------------------------+
 
 If this option is set false, it stops the Exim daemon setting the TCP_NODELAY
 option on its listening sockets. Setting TCP_NODELAY turns off the "Nagle
@@ -15114,9 +15738,9 @@ some broken clients cannot cope, and time out. Hence this option. It affects
 only those sockets that are set up for listening by the daemon. Sockets created
 by the smtp transport for delivering mail always set TCP_NODELAY.
 
-+--------------------+---------+----------+-----------+
++-----------------------------------------------------+
 |timeout_frozen_after|Use: main|Type: time|Default: 0s|
-+--------------------+---------+----------+-----------+
++-----------------------------------------------------+
 
 If timeout_frozen_after is set to a time greater than zero, a frozen message of
 any kind that has been on the queue for longer than the given time is
@@ -15130,9 +15754,9 @@ Note: the default value of zero means no timeouts; with this setting, frozen
 messages remain on the queue forever (except for any frozen bounce messages
 that are released by ignore_bounce_errors_after).
 
-+--------+---------+------------+--------------+
++----------------------------------------------+
 |timezone|Use: main|Type: string|Default: unset|
-+--------+---------+------------+--------------+
++----------------------------------------------+
 
 The value of timezone is used to set the environment variable TZ while running
 Exim (if it is different on entry). This ensures that all timestamps created by
@@ -15148,22 +15772,25 @@ existing TZ variable is removed from the environment when Exim runs. This is
 appropriate behaviour for obtaining wall-clock time on some, but unfortunately
 not all, operating systems.
 
-+-------------------+---------+----------------+--------------+
-|tls_advertise_hosts|Use: main|Type: host list*|Default: unset|
-+-------------------+---------+----------------+--------------+
++---------------------------------------------------------+
+|tls_advertise_hosts|Use: main|Type: host list*|Default: *|
++---------------------------------------------------------+
 
 When Exim is built with support for TLS encrypted connections, the availability
 of the STARTTLS command to set up an encrypted session is advertised in
 response to EHLO only to those client hosts that match this option. See chapter
-41 for details of Exim's support for TLS.
+42 for details of Exim's support for TLS. Note that the default value requires
+that a certificate be supplied using the tls_certificate option. If TLS support
+for incoming connections is not required the tls_advertise_hosts option should
+be set empty.
 
-+---------------+---------+-------------+--------------+
++------------------------------------------------------+
 |tls_certificate|Use: main|Type: string*|Default: unset|
-+---------------+---------+-------------+--------------+
++------------------------------------------------------+
 
 The value of this option is expanded, and must then be the absolute path to a
 file which contains the server's certificates. The server's private key is also
-assumed to be in this file if tls_privatekey is unset. See chapter 41 for
+assumed to be in this file if tls_privatekey is unset. See chapter 42 for
 further details.
 
 Note: The certificates defined by this option are used only when Exim is
@@ -15173,21 +15800,24 @@ in the relevant smtp transport.
 
 If the option contains $tls_out_sni and Exim is built against OpenSSL, then if
 the OpenSSL build supports TLS extensions and the TLS client sends the Server
-Name Indication extension, then this option and others documented in 41.10 will
+Name Indication extension, then this option and others documented in 42.10 will
 be re-expanded.
 
-+-------+---------+-------------+--------------+
+If this option is unset or empty a fresh self-signed certificate will be
+generated for every connection.
+
++----------------------------------------------+
 |tls_crl|Use: main|Type: string*|Default: unset|
-+-------+---------+-------------+--------------+
++----------------------------------------------+
 
 This option specifies a certificate revocation list. The expanded value must be
 the name of a file that contains a CRL in PEM format.
 
-See 41.10 for discussion of when this option might be re-expanded.
+See 42.10 for discussion of when this option might be re-expanded.
 
-+---------------+---------+-------------+-------------+
++-----------------------------------------------------+
 |tls_dh_max_bits|Use: main|Type: integer|Default: 2236|
-+---------------+---------+-------------+-------------+
++-----------------------------------------------------+
 
 The number of bits used for Diffie-Hellman key-exchange may be suggested by the
 chosen TLS library. That value might prove to be too high for interoperability.
@@ -15207,44 +15837,64 @@ Note that the value passed to GnuTLS for *generating* a new prime may be a
 little less than this figure, because GnuTLS is inexact and may produce a
 larger prime than requested.
 
-+-----------+---------+-------------+--------------+
++--------------------------------------------------+
 |tls_dhparam|Use: main|Type: string*|Default: unset|
-+-----------+---------+-------------+--------------+
++--------------------------------------------------+
 
 The value of this option is expanded and indicates the source of DH parameters
 to be used by Exim.
 
-If it is a filename starting with a "/", then it names a file from which DH
-parameters should be loaded. If the file exists, it should hold a PEM-encoded
-PKCS#3 representation of the DH prime. If the file does not exist, for OpenSSL
-it is an error. For GnuTLS, Exim will attempt to create the file and fill it
-with a generated DH prime. For OpenSSL, if the DH bit-count from loading the
-file is greater than tls_dh_max_bits then it will be ignored, and treated as
-though the tls_dhparam were set to "none".
+Note: The Exim Maintainers strongly recommend using a filename with
+site-generated local DH parameters, which has been supported across all
+versions of Exim. The other specific constants available are a fallback so that
+even when "unconfigured", Exim can offer Perfect Forward Secrecy in older
+ciphersuites in TLS.
+
+If tls_dhparam is a filename starting with a "/", then it names a file from
+which DH parameters should be loaded. If the file exists, it should hold a
+PEM-encoded PKCS#3 representation of the DH prime. If the file does not exist,
+for OpenSSL it is an error. For GnuTLS, Exim will attempt to create the file
+and fill it with a generated DH prime. For OpenSSL, if the DH bit-count from
+loading the file is greater than tls_dh_max_bits then it will be ignored, and
+treated as though the tls_dhparam were set to "none".
 
 If this option expands to the string "none", then no DH parameters will be
 loaded by Exim.
 
 If this option expands to the string "historic" and Exim is using GnuTLS, then
 Exim will attempt to load a file from inside the spool directory. If the file
-does not exist, Exim will attempt to create it. See section 41.3 for further
+does not exist, Exim will attempt to create it. See section 42.3 for further
 details.
 
 If Exim is using OpenSSL and this option is empty or unset, then Exim will load
-a default DH prime; the default is the 2048 bit prime described in section 2.2
-of RFC 5114, "2048-bit MODP Group with 224-bit Prime Order Subgroup", which in
-IKE is assigned number 23.
+a default DH prime; the default is Exim-specific but lacks verifiable
+provenance.
+
+In older versions of Exim the default was the 2048 bit prime described in
+section 2.2 of RFC 5114, "2048-bit MODP Group with 224-bit Prime Order
+Subgroup", which in IKE is assigned number 23.
 
 Otherwise, the option must expand to the name used by Exim for any of a number
-of DH primes specified in RFC 2409, RFC 3526 and RFC 5114. As names, Exim uses
-"ike" followed by the number used by IKE, of "default" which corresponds to
-"ike23".
+of DH primes specified in RFC 2409, RFC 3526, RFC 5114, RFC 7919, or from other
+sources. As names, Exim uses a standard specified name, else "ike" followed by
+the number used by IKE, or "default" which corresponds to
+"exim.dev.20160529.3".
 
-The available primes are: "ike1", "ike2", "ike5", "ike14", "ike15", "ike16",
-"ike17", "ike18", "ike22", "ike23" (aka "default") and "ike24".
+The available standard primes are: "ffdhe2048", "ffdhe3072", "ffdhe4096",
+"ffdhe6144", "ffdhe8192", "ike1", "ike2", "ike5", "ike14", "ike15", "ike16",
+"ike17", "ike18", "ike22", "ike23" and "ike24".
+
+The available additional primes are: "exim.dev.20160529.1",
+"exim.dev.20160529.2" and "exim.dev.20160529.3".
 
 Some of these will be too small to be accepted by clients. Some may be too
-large to be accepted by clients.
+large to be accepted by clients. The open cryptographic community has
+suspicions about the integrity of some of the later IKE values, which led into
+RFC7919 providing new fixed constants (the "ffdhe" identifiers).
+
+At this point, all of the "ike" values should be considered obsolete; they're
+still in Exim to avoid breaking unusual configurations, but are candidates for
+removal the next time we have backwards-incompatible changes.
 
 The TLS protocol does not negotiate an acceptable size for this; clients tend
 to hard-drop connections if what is offered by the server is unacceptable,
@@ -15260,47 +15910,65 @@ user agents (MUAs). The lower bound comes from Debian installs of Exim4 prior
 to the 4.80 release, as Debian used to patch Exim to raise the minimum
 acceptable bound from 1024 to 2048.
 
-+-------------+---------+-------------+--------------+
++---------------------------------------------------+
+|tls_eccurve|Use: main|Type: string*|Default: "auto"|
++---------------------------------------------------+
+
+This option selects a EC curve for use by Exim.
+
+After expansion it must contain a valid EC curve parameter, such as
+"prime256v1", "secp384r1", or "P-512". Consult your OpenSSL manual for valid
+selections.
+
+For OpenSSL versions before (and not including) 1.0.2, the string "auto"
+selects "prime256v1". For more recent OpenSSL versions "auto" tells the library
+to choose.
+
+If the option expands to an empty string, no EC curves will be enabled.
+
++----------------------------------------------------+
 |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.
 
-+--------------------+---------+-----------------+--------------+
+Usable for GnuTLS 3.4.4 or 3.3.17 or OpenSSL 1.1.0 (or later).
+
++---------------------------------------------------------------+
 |tls_on_connect_ports|Use: main|Type: string list|Default: unset|
-+--------------------+---------+-----------------+--------------+
++---------------------------------------------------------------+
 
 This option specifies a list of incoming SSMTP (aka SMTPS) ports that should
 operate the obsolete SSMTP (SMTPS) protocol, where a TLS session is immediately
 set up without waiting for the client to issue a STARTTLS command. For further
 details, see section 13.4.
 
-+--------------+---------+-------------+--------------+
++-----------------------------------------------------+
 |tls_privatekey|Use: main|Type: string*|Default: unset|
-+--------------+---------+-------------+--------------+
++-----------------------------------------------------+
 
 The value of this option is expanded, and must then be the absolute path to a
 file which contains the server's private key. If this option is unset, or if
 the expansion is forced to fail, or the result is an empty string, the private
 key is assumed to be in the same file as the server's certificates. See chapter
-41 for further details.
+42 for further details.
 
-See 41.10 for discussion of when this option might be re-expanded.
+See 42.10 for discussion of when this option might be re-expanded.
 
-+------------------+---------+-------------+--------------+
++---------------------------------------------------------+
 |tls_remember_esmtp|Use: main|Type: boolean|Default: false|
-+------------------+---------+-------------+--------------+
++---------------------------------------------------------+
 
 If this option is set true, Exim violates the RFCs by remembering that it is in
 "esmtp" state after successfully negotiating a TLS session. This provides
 support for broken clients that fail to send a new EHLO after starting a TLS
 session.
 
-+-------------------+---------+-------------+--------------+
++----------------------------------------------------------+
 |tls_require_ciphers|Use: main|Type: string*|Default: unset|
-+-------------------+---------+-------------+--------------+
++----------------------------------------------------------+
 
 This option controls which ciphers can be used for incoming TLS connections.
 The smtp transport has an option of the same name for controlling outgoing
@@ -15308,41 +15976,48 @@ connections. This option is expanded for each connection, so can be varied for
 different clients if required. The value of this option must be a list of
 permitted cipher suites. The OpenSSL and GnuTLS libraries handle cipher control
 in somewhat different ways. If GnuTLS is being used, the client controls the
-preference order of the available ciphers. Details are given in sections 41.4
-and 41.5.
+preference order of the available ciphers. Details are given in sections 42.4
+and 42.5.
 
-+--------------------+---------+----------------+--------------+
++--------------------------------------------------------------+
 |tls_try_verify_hosts|Use: main|Type: host list*|Default: unset|
-+--------------------+---------+----------------+--------------+
++--------------------------------------------------------------+
 
 See tls_verify_hosts below.
 
-+-----------------------+---------+-------------+--------------+
-|tls_verify_certificates|Use: main|Type: string*|Default: unset|
-+-----------------------+---------+-------------+--------------+
++---------------------------------------------------------------+
+|tls_verify_certificates|Use: main|Type: string*|Default: system|
++---------------------------------------------------------------+
 
-The value of this option is expanded, and must then be the absolute path to a
-file containing permitted certificates for clients that match tls_verify_hosts
-or tls_try_verify_hosts. Alternatively, if you are using OpenSSL, you can set 
-tls_verify_certificates to the name of a directory containing certificate
-files. This does not work with GnuTLS; the option must be set to the name of a
-single file if you are using GnuTLS.
+The value of this option is expanded, and must then be either the word "system"
+or the absolute path to a file or directory containing permitted certificates
+for clients that match tls_verify_hosts or tls_try_verify_hosts.
+
+The "system" value for the option will use a system default location compiled
+into the SSL library. This is not available for GnuTLS versions preceding
+3.0.20, and will be taken as empty; an explicit location must be specified.
+
+The use of a directory for the option value is not available for GnuTLS
+versions preceding 3.3.6 and a single file must be used.
+
+With OpenSSL the certificates specified explicitly either by file or directory
+are added to those given by the system default location.
 
 These certificates should be for the certificate authorities trusted, rather
 than the public cert of individual clients. With both OpenSSL and GnuTLS, if
 the value is a file then the certificates are sent by Exim as a server to
 connecting clients, defining the list of accepted certificate authorities. Thus
-the values defined should be considered public data. To avoid this, use OpenSSL
-with a directory.
+the values defined should be considered public data. To avoid this, use the
+explicit directory version.
 
-See 41.10 for discussion of when this option might be re-expanded.
+See 42.10 for discussion of when this option might be re-expanded.
 
 A forced expansion failure or setting to an empty string is equivalent to being
 unset.
 
-+----------------+---------+----------------+--------------+
++----------------------------------------------------------+
 |tls_verify_hosts|Use: main|Type: host list*|Default: unset|
-+----------------+---------+----------------+--------------+
++----------------------------------------------------------+
 
 This option, along with tls_try_verify_hosts, controls the checking of
 certificates from clients. The expected certificates are defined by 
@@ -15369,9 +16044,9 @@ if encrypted, even without a verified certificate".
 Client hosts that match neither of these lists are not asked to present
 certificates.
 
-+--------------+---------+------------------+--------------+
++----------------------------------------------------------+
 |trusted_groups|Use: main|Type: string list*|Default: unset|
-+--------------+---------+------------------+--------------+
++----------------------------------------------------------+
 
 This option is expanded just once, at the start of Exim's processing. If this
 option is set, any process that is running in one of the listed groups, or
@@ -15380,9 +16055,9 @@ specified numerically or by name. See section 5.2 for details of what trusted
 callers are permitted to do. If neither trusted_groups nor trusted_users is
 set, only root and the Exim user are trusted.
 
-+-------------+---------+------------------+--------------+
++---------------------------------------------------------+
 |trusted_users|Use: main|Type: string list*|Default: unset|
-+-------------+---------+------------------+--------------+
++---------------------------------------------------------+
 
 This option is expanded just once, at the start of Exim's processing. If this
 option is set, any process that is running as one of the listed users is
@@ -15390,9 +16065,9 @@ trusted. The users can be specified numerically or by name. See section 5.2 for
 details of what trusted callers are permitted to do. If neither trusted_groups
 nor trusted_users is set, only root and the Exim user are trusted.
 
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 |unknown_login|Use: main|Type: string*|Default: unset|
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 
 This is a specialized feature for use in unusual configurations. By default, if
 the uid of the caller of Exim cannot be looked up using getpwuid(), Exim gives
@@ -15401,15 +16076,15 @@ circumstance. It is expanded, so values like user$caller_uid can be set. When
 unknown_login is used, the value of unknown_username is used for the user's
 real name (gecos field), unless this has been set by the -F option.
 
-+----------------+---------+------------+--------------+
++------------------------------------------------------+
 |unknown_username|Use: main|Type: string|Default: unset|
-+----------------+---------+------------+--------------+
++------------------------------------------------------+
 
 See unknown_login.
 
-+--------------------+---------+-------------------+--------------+
++-----------------------------------------------------------------+
 |untrusted_set_sender|Use: main|Type: address list*|Default: unset|
-+--------------------+---------+-------------------+--------------+
++-----------------------------------------------------------------+
 
 When an untrusted user submits a message to Exim using the standard input, Exim
 normally creates an envelope sender address from the user's login and the
@@ -15443,16 +16118,16 @@ use the other options which trusted user can use to override message
 parameters. Furthermore, it does not stop Exim from removing an existing 
 Sender: header in the message, or from adding a Sender: header if necessary.
 See local_sender_retain and local_from_check for ways of overriding these
-actions. The handling of the Sender: header is also described in section 46.16.
+actions. The handling of the Sender: header is also described in section 47.16.
 
 The log line for a message's arrival shows the envelope sender following "<=".
 For local messages, the user's login always follows, after "U=". In -bp
 displays, and in the Exim monitor, if an untrusted user sets an envelope sender
 address, the user's login is shown in parentheses after the sender address.
 
-+-----------------+---------+------------+------------------+
++-----------------------------------------------------------+
 |uucp_from_pattern|Use: main|Type: string|Default: see below|
-+-----------------+---------+------------+------------------+
++-----------------------------------------------------------+
 
 Some applications that pass messages to an MTA via a command line interface use
 an initial line starting with "From " to pass the envelope sender. In
@@ -15475,28 +16150,28 @@ expression by a parenthesized subpattern. The default value for
 uucp_from_sender is "$1", which therefore just uses this first word ("ph10" in
 the example above) as the message's sender. See also ignore_fromline_hosts.
 
-+----------------+---------+-------------+-------------+
++------------------------------------------------------+
 |uucp_from_sender|Use: main|Type: string*|Default: "$1"|
-+----------------+---------+-------------+-------------+
++------------------------------------------------------+
 
 See uucp_from_pattern above.
 
-+-----------------+---------+------------+--------------+
++-------------------------------------------------------+
 |warn_message_file|Use: main|Type: string|Default: unset|
-+-----------------+---------+------------+--------------+
++-------------------------------------------------------+
 
 This option defines a template file containing paragraphs of text to be used
 for constructing the warning message which is sent by Exim when a message has
 been on the queue for a specified amount of time, as specified by delay_warning
-. Details of the file's contents are given in chapter 48. See also 
+. Details of the file's contents are given in chapter 49. See also 
 bounce_message_file.
 
-+---------------+---------+-------------+-------------+
++-----------------------------------------------------+
 |write_rejectlog|Use: main|Type: boolean|Default: true|
-+---------------+---------+-------------+-------------+
++-----------------------------------------------------+
 
 If this option is set false, Exim no longer writes anything to the reject log.
-See chapter 51 for details of what Exim writes to its logs.
+See chapter 52 for details of what Exim writes to its logs.
 
 
 
@@ -15511,9 +16186,9 @@ The latter specifies the order in which the preconditions are tested. The order
 of expansion of the options that provide data for a transport is: errors_to, 
 headers_add, headers_remove, transport.
 
-+------------+------------+-------------+--------------+
++------------------------------------------------------+
 |address_data|Use: routers|Type: string*|Default: unset|
-+------------+------------+-------------+--------------+
++------------------------------------------------------+
 
 The string is expanded just before the router is run, that is, after all the
 precondition tests have succeeded. If the expansion is forced to fail, the
@@ -15556,18 +16231,18 @@ $address_data is set by a router when verifying a recipient address from an
 ACL, it remains available for use in the rest of the ACL statement. After
 verifying a sender, the value is transferred to $sender_address_data.
 
-+------------+--------------+-------------+-------------+
++-------------------------------------------------------+
 |address_test|Use: routers**|Type: boolean|Default: true|
-+------------+--------------+-------------+-------------+
++-------------------------------------------------------+
 
 If this option is set false, the router is skipped when routing is being tested
 by means of the -bt command line option. This can be a convenience when your
 first router sends messages to an external scanner, because it saves you having
 to set the "already scanned" indicator when testing real address routing.
 
-+--------------------+------------+-------------+--------------+
++--------------------------------------------------------------+
 |cannot_route_message|Use: routers|Type: string*|Default: unset|
-+--------------------+------------+-------------+--------------+
++--------------------------------------------------------------+
 
 This option specifies a text message that is used when an address cannot be
 routed because Exim has run out of routers. The default message is "Unrouteable
@@ -15588,9 +16263,9 @@ this option, the default message is used. Unless the expansion failure was
 explicitly forced, a message about the failure is written to the main and panic
 logs, in addition to the normal message about the routing failure.
 
-+------------------+------------+-------------+--------------+
++------------------------------------------------------------+
 |caseful_local_part|Use: routers|Type: boolean|Default: false|
-+------------------+------------+-------------+--------------+
++------------------------------------------------------------+
 
 By default, routers handle the local parts of addresses in a case-insensitive
 manner, though the actual case is preserved for transmission with the message.
@@ -15609,11 +16284,11 @@ $parent_local_part are those that were used by the redirecting router.
 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.22).
+(see section 43.22).
 
-+----------------+--------------+-------------+--------------+
++------------------------------------------------------------+
 |check_local_user|Use: routers**|Type: boolean|Default: false|
-+----------------+--------------+-------------+--------------+
++------------------------------------------------------------+
 
 When this option is true, Exim checks that the local part of the recipient
 address (with affixes removed if relevant) is the name of an account on the
@@ -15637,9 +16312,9 @@ Note, however, that the side effects of check_local_user (such as setting up a
 home directory) do not occur when a passwd lookup is used in a local_parts (or
 any other) precondition.
 
-+---------+--------------+-------------+--------------+
++-----------------------------------------------------+
 |condition|Use: routers**|Type: string*|Default: unset|
-+---------+--------------+-------------+--------------+
++-----------------------------------------------------+
 
 This option specifies a general precondition test that has to succeed for the
 router to be called. The condition option is the last precondition to be
@@ -15674,9 +16349,46 @@ If the expansion fails (other than forced failure) delivery is deferred. Some
 of the other precondition options are common special cases that could in fact
 be specified using condition.
 
-+-----------+------------+-------------+--------------+
+Historical note: We have condition on ACLs and on Routers. Routers are far
+older, and use one set of semantics. ACLs are newer and when they were created,
+the ACL condition process was given far stricter parse semantics. The bool{}
+expansion condition uses the same rules as ACLs. The bool_lax{} expansion
+condition uses the same rules as Routers. More pointedly, the bool_lax{} was
+written to match the existing Router rules processing behavior.
+
+This is best illustrated in an example:
+
+# If used in an ACL condition will fail with a syntax error, but
+# in a router condition any extra characters are treated as a string
+
+$ exim -be '${if eq {${lc:GOOGLE.com}} {google.com}} {yes} {no}}'
+true {yes} {no}}
+
+$ exim -be '${if eq {${lc:WHOIS.com}} {google.com}} {yes} {no}}'
+ {yes} {no}}
+
+In each example above, the if statement actually ends after "{google.com}}".
+Since no true or false braces were defined, the default if behavior is to
+return a boolean true or a null answer (which evaluates to false). The rest of
+the line is then treated as a string. So the first example resulted in the
+boolean answer "true" with the string " {yes} {no}}" appended to it. The second
+example resulted in the null output (indicating false) with the string " {yes}
+{no}}" appended to it.
+
+In fact you can put excess forward braces in too. In the router condition,
+Exim's parser only looks for "{" symbols when they mean something, like after a
+"$" or when required as part of a conditional. But otherwise "{" and "}" are
+treated as ordinary string characters.
+
+Thus, in a Router, the above expansion strings will both always evaluate true,
+as the result of expansion is a non-empty string which doesn't match an
+explicit false value. This can be tricky to debug. By contrast, in an ACL
+either of those strings will always result in an expansion error because the
+result doesn't look sufficiently boolean.
+
++-----------------------------------------------------+
 |debug_print|Use: routers|Type: string*|Default: unset|
-+-----------+------------+-------------+--------------+
++-----------------------------------------------------+
 
 If this option is set and debugging is enabled (see the -d command line option)
 or in address-testing mode (see the -bt command line option), the string is
@@ -15690,18 +16402,35 @@ local_parts, and check_local_user but before any other preconditions are
 tested. A newline is added to the text if it does not end with one. The
 variable $router_name contains the name of the router.
 
-+---------------+------------+-------------+--------------+
++---------------------------------------------------------+
 |disable_logging|Use: routers|Type: boolean|Default: false|
-+---------------+------------+-------------+--------------+
++---------------------------------------------------------+
 
 If this option is set true, nothing is logged for any routing errors or for any
 deliveries caused by this router. You should not set this option unless you
 really, really know what you are doing. See also the generic transport option
 of the same name.
 
-+-------+--------------+------------------+--------------+
++---------------------------------------------------------------------+
+|dnssec_request_domains|Use: routers|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, AAAA, A lookup
+sequence.
+
++---------------------------------------------------------------------+
+|dnssec_require_domains|Use: routers|Type: domain list*|Default: unset|
++---------------------------------------------------------------------+
+
+DNS lookups for domains matching dnssec_require_domains will be done with the
+dnssec request bit set. Any returns not having the Authenticated Data bit (AD
+bit) set will be ignored and logged as a host-lookup failure. This applies to
+all of the SRV, MX, AAAA, A lookup sequence.
+
++--------------------------------------------------------+
 |domains|Use: routers**|Type: domain list*|Default: unset|
-+-------+--------------+------------------+--------------+
++--------------------------------------------------------+
 
 If this option is set, the router is skipped unless the current domain matches
 the list. If the match is achieved by means of a file lookup, the data that the
@@ -15709,16 +16438,25 @@ lookup returned for the domain is placed in $domain_data for use in string
 expansions of the driver's private options. See section 3.12 for a list of the
 order in which preconditions are evaluated.
 
-+------+------------+------------+--------------+
++-----------------------------------------------+
 |driver|Use: routers|Type: string|Default: unset|
-+------+------------+------------+--------------+
++-----------------------------------------------+
 
 This option must always be set. It specifies which of the available routers is
 to be used.
 
-+---------+------------+-------------+--------------+
++-----------------------------------------------------+
+|dsn_lasthop|Use: routers|Type: boolean|Default: false|
++-----------------------------------------------------+
+
+If this option is set true, and extended DSN (RFC3461) processing is in effect,
+Exim will not pass on DSN requests to downstream DSN-aware hosts but will
+instead send a success DSN as if the next hop does not support DSN. Not
+effective on redirect routers.
+
++---------------------------------------------------+
 |errors_to|Use: routers|Type: string*|Default: unset|
-+---------+------------+-------------+--------------+
++---------------------------------------------------+
 
 If a router successfully handles an address, it may assign the address to a
 transport for delivery or it may generate child addresses. In both cases, if
@@ -15757,12 +16495,12 @@ $address_data in the router, and reinstate it in the transport by setting
 return_path.
 
 The most common use of errors_to is to direct mailing list bounces to the
-manager of the list, as described in section 49.2, or to implement VERP
-(Variable Envelope Return Paths) (see section 49.6).
+manager of the list, as described in section 50.2, or to implement VERP
+(Variable Envelope Return Paths) (see section 50.6).
 
-+----+--------------+-------------+-------------+
++-----------------------------------------------+
 |expn|Use: routers**|Type: boolean|Default: true|
-+----+--------------+-------------+-------------+
++-----------------------------------------------+
 
 If this option is turned off, the router is skipped when testing an address as
 a result of processing an SMTP EXPN command. You might, for example, want to
@@ -15770,38 +16508,38 @@ turn it off on a router for users' .forward files, while leaving it on for the
 system alias file. See section 3.12 for a list of the order in which
 preconditions are evaluated.
 
-The use of the SMTP EXPN command is controlled by an ACL (see chapter 42). When
+The use of the SMTP EXPN command is controlled by an ACL (see chapter 43). When
 Exim is running an EXPN command, it is similar to testing an address with -bt.
 Compare VRFY, whose counterpart is -bv.
 
-+-----------+------------+-------------+--------------+
++-----------------------------------------------------+
 |fail_verify|Use: routers|Type: boolean|Default: false|
-+-----------+------------+-------------+--------------+
++-----------------------------------------------------+
 
 Setting this option has the effect of setting both fail_verify_sender and 
 fail_verify_recipient to the same value.
 
-+---------------------+------------+-------------+--------------+
++---------------------------------------------------------------+
 |fail_verify_recipient|Use: routers|Type: boolean|Default: false|
-+---------------------+------------+-------------+--------------+
++---------------------------------------------------------------+
 
 If this option is true and an address is accepted by this router when verifying
 a recipient, verification fails.
 
-+------------------+------------+-------------+--------------+
++------------------------------------------------------------+
 |fail_verify_sender|Use: routers|Type: boolean|Default: false|
-+------------------+------------+-------------+--------------+
++------------------------------------------------------------+
 
 If this option is true and an address is accepted by this router when verifying
 a sender, verification fails.
 
-+--------------+------------+-----------------+--------------+
++------------------------------------------------------------+
 |fallback_hosts|Use: routers|Type: string list|Default: unset|
-+--------------+------------+-----------------+--------------+
++------------------------------------------------------------+
 
 String expansion is not applied to this option. The argument must be a
 colon-separated list of host names or IP addresses. The list separator can be
-changed (see section 6.19), and a port can be specified with each name or
+changed (see section 6.20), and a port can be specified with each name or
 address. In fact, the format of each item is exactly the same as defined for
 the list of hosts in a manualroute router (see section 20.5).
 
@@ -15811,9 +16549,9 @@ list. If hosts_randomize is set on the transport, the order of the list is
 randomized for each use. See the fallback_hosts option of the smtp transport
 for further details.
 
-+-----+------------+-------------+------------------+
++---------------------------------------------------+
 |group|Use: routers|Type: string*|Default: see below|
-+-----+------------+-------------+------------------+
++---------------------------------------------------+
 
 When a router queues an address for a transport, and the transport does not
 specify a group, the group given here is used when running the delivery
@@ -15822,18 +16560,19 @@ the error is logged and delivery is deferred. The default is unset, unless
 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: 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.
++---------------------------------------------------+
+
+This option specifies a list of text headers, newline-separated (by default,
+changeable in the usual way), 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 47.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 an item is empty, or if an item expansion is forced to fail,
@@ -15855,18 +16594,19 @@ section 22.7), but it is undefined which of the duplicates is discarded, so
 this ambiguous situation should be avoided. The repeat_use option of the 
 redirect router may be of help.
 
-+--------------+------------+-----------+--------------+
++------------------------------------------------------+
 |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.
++------------------------------------------------------+
+
+This option specifies a list of text headers, colon-separated (by default,
+changeable in the usual way), 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 47.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 an item expansion is forced to fail, the item has no
@@ -15883,9 +16623,13 @@ requests are deleted when the address is passed on to subsequent routers, and
 this can lead to problems with duplicates -- see the similar warning for 
 headers_add above.
 
-+-------------------+------------+----------------+--------------+
+Warning 3: Because of the separate expansion of the list items, items that
+contain a list separator must have it doubled. To avoid this, change the list
+separator (6.21).
+
++----------------------------------------------------------------+
 |ignore_target_hosts|Use: routers|Type: host list*|Default: unset|
-+-------------------+------------+----------------+--------------+
++----------------------------------------------------------------+
 
 Although this option is a host list, it should normally contain IP address
 entries rather than names. If any host that is looked up by the router has an
@@ -15923,9 +16667,9 @@ domain that is being routed.
 During its expansion, $host_address is set to the IP address that is being
 checked.
 
-+----------+------------+-------------+--------------+
++----------------------------------------------------+
 |initgroups|Use: routers|Type: boolean|Default: false|
-+----------+------------+-------------+--------------+
++----------------------------------------------------+
 
 If the router queues an address for a transport, and this option is true, and
 the uid supplied by the router is not overridden by the transport, the
@@ -15933,9 +16677,9 @@ initgroups() function is called when running the transport to ensure that any
 additional groups associated with the uid are set up. See also group and user
 and the discussion in chapter 23.
 
-+-----------------+--------------+-----------------+--------------+
++-----------------------------------------------------------------+
 |local_part_prefix|Use: routers**|Type: string list|Default: unset|
-+-----------------+--------------+-----------------+--------------+
++-----------------------------------------------------------------+
 
 If this option is set, the router is skipped unless the local part starts with
 one of the given strings, or local_part_prefix_optional is true. See section
@@ -15946,7 +16690,7 @@ used. A limited form of wildcard is available; if the prefix begins with an
 asterisk, it matches the longest possible sequence of arbitrary characters at
 the start of the local part. An asterisk should therefore always be followed by
 some character that does not occur in normal local parts. Wildcarding can be
-used to set up multiple user mailboxes, as described in section 49.8.
+used to set up multiple user mailboxes, as described in section 50.8.
 
 During the testing of the local_parts option, and while the router is running,
 the prefix is removed from the local part, and is available in the expansion
@@ -15984,15 +16728,15 @@ conditions must be met if not optional. Care must be taken if wildcards are
 used in both a prefix and a suffix on the same router. Different separator
 characters must be used to avoid ambiguity.
 
-+--------------------------+------------+-------------+--------------+
++--------------------------------------------------------------------+
 |local_part_prefix_optional|Use: routers|Type: boolean|Default: false|
-+--------------------------+------------+-------------+--------------+
++--------------------------------------------------------------------+
 
 See local_part_prefix above.
 
-+-----------------+--------------+-----------------+--------------+
++-----------------------------------------------------------------+
 |local_part_suffix|Use: routers**|Type: string list|Default: unset|
-+-----------------+--------------+-----------------+--------------+
++-----------------------------------------------------------------+
 
 This option operates in the same way as local_part_prefix, except that the
 local part must end (rather than start) with the given string, the 
@@ -16001,15 +16745,15 @@ and the wildcard * character, if present, must be the last character of the
 suffix. This option facility is commonly used to handle local parts of the form
 something-request and multiple user mailboxes of the form username-foo.
 
-+--------------------------+------------+-------------+--------------+
++--------------------------------------------------------------------+
 |local_part_suffix_optional|Use: routers|Type: boolean|Default: false|
-+--------------------------+------------+-------------+--------------+
++--------------------------------------------------------------------+
 
 See local_part_suffix above.
 
-+-----------+--------------+----------------------+--------------+
++----------------------------------------------------------------+
 |local_parts|Use: routers**|Type: local part list*|Default: unset|
-+-----------+--------------+----------------------+--------------+
++----------------------------------------------------------------+
 
 The router is run only if the local part of the address matches the list. See
 section 3.12 for a list of the order in which preconditions are evaluated, and
@@ -16030,9 +16774,9 @@ postmaster:
   local_parts = postmaster
   data = postmaster@real.domain.example
 
-+------------+------------+-------------+------------------+
++----------------------------------------------------------+
 |log_as_local|Use: routers|Type: boolean|Default: see below|
-+------------+------------+-------------+------------------+
++----------------------------------------------------------+
 
 Exim has two logging styles for delivery, the idea being to make local
 deliveries stand out more visibly from remote ones. In the "local" style, the
@@ -16042,9 +16786,9 @@ router, and false for all the others. This option applies only when a router
 assigns an address to a transport. It has no effect on routers that redirect
 addresses.
 
-+----+------------+--------------+-------------+
++----------------------------------------------+
 |more|Use: routers|Type: boolean*|Default: true|
-+----+------------+--------------+-------------+
++----------------------------------------------+
 
 The result of string expansion for this option must be a valid boolean value,
 that is, one of the strings "yes", "no", "true", or "false". Any other result
@@ -16067,9 +16811,9 @@ Note that address_data is not considered to be a precondition. If its expansion
 is forced to fail, the router declines, and the value of more controls what
 happens next.
 
-+---------------+------------+-------------+--------------+
++---------------------------------------------------------+
 |pass_on_timeout|Use: routers|Type: boolean|Default: false|
-+---------------+------------+-------------+--------------+
++---------------------------------------------------------+
 
 If a router times out during a host lookup, it normally causes deferral of the
 address. If pass_on_timeout is set, the address is passed on to the next
@@ -16081,9 +16825,9 @@ There are occasional other temporary errors that can occur while doing DNS
 lookups. They are treated in the same way as a timeout, and this option applies
 to all of them.
 
-+-----------+------------+------------+--------------+
++----------------------------------------------------+
 |pass_router|Use: routers|Type: string|Default: unset|
-+-----------+------------+------------+--------------+
++----------------------------------------------------+
 
 Routers that recognize the generic self option (dnslookup, ipliteral, and 
 manualroute) are able to return "pass", forcing routing to continue, and
@@ -16095,9 +16839,9 @@ loops. Note that this option applies only to the special case of "pass". It
 does not apply when a router returns "decline" because it cannot handle an
 address.
 
-+---------------+------------+------------+--------------+
++--------------------------------------------------------+
 |redirect_router|Use: routers|Type: string|Default: unset|
-+---------------+------------+------------+--------------+
++--------------------------------------------------------+
 
 Sometimes an administrator knows that it is pointless to reprocess addresses
 generated from alias or forward files with the same router again. For example,
@@ -16109,9 +16853,9 @@ causes the routing of any generated addresses to start at the named router
 instead of at the first router. This option has no effect if the router in
 which it is set does not generate new addresses.
 
-+-------------+--------------+------------------+--------------+
++--------------------------------------------------------------+
 |require_files|Use: routers**|Type: string list*|Default: unset|
-+-------------+--------------+------------------+--------------+
++--------------------------------------------------------------+
 
 This option provides a general mechanism for predicating the running of a
 router on the existence or non-existence of certain files or directories.
@@ -16189,9 +16933,9 @@ If the router is not an essential part of verification (for example, it handles
 users' .forward files), another solution is to set the verify option false so
 that the router is skipped when verifying.
 
-+--------------------+------------+-------------+------------------+
++------------------------------------------------------------------+
 |retry_use_local_part|Use: routers|Type: boolean|Default: see below|
-+--------------------+------------+-------------+------------------+
++------------------------------------------------------------------+
 
 When a delivery suffers a temporary routing failure, a retry record is created
 in Exim's hints database. For addresses whose routing depends only on the
@@ -16211,9 +16955,9 @@ The setting of retry_use_local_part applies only to the router on which it
 appears. If the router generates child addresses, they are routed
 independently; this setting does not become attached to them.
 
-+---------------------+------------+-------------+--------------+
++---------------------------------------------------------------+
 |router_home_directory|Use: routers|Type: string*|Default: unset|
-+---------------------+------------+-------------+--------------+
++---------------------------------------------------------------+
 
 This option sets a home directory for use while the router is running. (Compare
 transport_home_directory, which sets a home directory for later transporting.)
@@ -16243,9 +16987,9 @@ of these values that is set:
 In other words, router_home_directory overrides the password data for the
 router, but not for the transport.
 
-+----+------------+------------+---------------+
++----------------------------------------------+
 |self|Use: routers|Type: string|Default: freeze|
-+----+------------+------------+---------------+
++----------------------------------------------+
 
 This option applies to those routers that use a recipient address to find a
 list of remote hosts. Currently, these are the dnslookup, ipliteral, and 
@@ -16308,9 +17052,9 @@ send
     Exim with a different configuration file that handles the domain in another
     way.
 
-+-------+--------------+-------------------+--------------+
++---------------------------------------------------------+
 |senders|Use: routers**|Type: address list*|Default: unset|
-+-------+--------------+-------------------+--------------+
++---------------------------------------------------------+
 
 If this option is set, the router is skipped unless the message's sender
 address matches something on the list. See section 3.12 for a list of the order
@@ -16324,9 +17068,9 @@ appropriate sender. For incoming mail, the sender is unset when verifying the
 sender, but is available when verifying any recipients. If the SMTP VRFY
 command is enabled, it must be used after MAIL if the sender address matters.
 
-+--------------------+------------+-------------+--------------+
++--------------------------------------------------------------+
 |translate_ip_address|Use: routers|Type: string*|Default: unset|
-+--------------------+------------+-------------+--------------+
++--------------------------------------------------------------+
 
 There exist some rare networking situations (for example, packet radio) where
 it is helpful to be able to translate IP addresses generated by normal routing
@@ -16358,9 +17102,9 @@ The file would contain lines like
 You should not make use of this facility unless you really understand what you
 are doing.
 
-+---------+------------+-------------+--------------+
++---------------------------------------------------+
 |transport|Use: routers|Type: string*|Default: unset|
-+---------+------------+-------------+--------------+
++---------------------------------------------------+
 
 This option specifies the transport to be used when a router accepts an address
 and sets it up for delivery. A transport is never needed if a router is used
@@ -16373,9 +17117,9 @@ The transport option is not used by the redirect router, but it does have some
 private options that set up transports for pipe and file deliveries (see
 chapter 22).
 
-+---------------------------+------------+-------------+--------------+
++---------------------------------------------------------------------+
 |transport_current_directory|Use: routers|Type: string*|Default: unset|
-+---------------------------+------------+-------------+--------------+
++---------------------------------------------------------------------+
 
 This option associates a current directory with any address that is routed to a
 local transport. This can happen either because a transport is explicitly
@@ -16386,9 +17130,9 @@ setting on the transport. If the expansion fails for any reason, including
 forced failure, an error is logged, and delivery is deferred. See chapter 23
 for details of the local delivery environment.
 
-+------------------------+------------+-------------+------------------+
++----------------------------------------------------------------------+
 |transport_home_directory|Use: routers|Type: string*|Default: see below|
-+------------------------+------------+-------------+------------------+
++----------------------------------------------------------------------+
 
 This option associates a home directory with any address that is routed to a
 local transport. This can happen either because a transport is explicitly
@@ -16406,9 +17150,9 @@ if not, no home directory is set for the transport.
 
 See chapter 23 for further details of the local delivery environment.
 
-+------+------------+--------------+--------------+
++-------------------------------------------------+
 |unseen|Use: routers|Type: boolean*|Default: false|
-+------+------------+--------------+--------------+
++-------------------------------------------------+
 
 The result of string expansion for this option must be a valid boolean value,
 that is, one of the strings "yes", "no", "true", or "false". Any other result
@@ -16446,9 +17190,9 @@ Unlike the handling of header modifications, any data that was set by the
 address_data option in the current or previous routers is passed on to
 subsequent routers.
 
-+----+------------+-------------+------------------+
++--------------------------------------------------+
 |user|Use: routers|Type: string*|Default: see below|
-+----+------------+-------------+------------------+
++--------------------------------------------------+
 
 When a router queues an address for a transport, and the transport does not
 specify a user, the user given here is used when running the delivery process.
@@ -16460,16 +17204,16 @@ information. If the user is specified as a name, and group is not set, the
 group associated with the user is used. See also initgroups and group and the
 discussion in chapter 23.
 
-+------+--------------+-------------+-------------+
++-------------------------------------------------+
 |verify|Use: routers**|Type: boolean|Default: true|
-+------+--------------+-------------+-------------+
++-------------------------------------------------+
 
 Setting this option has the effect of setting verify_sender and 
 verify_recipient to the same value.
 
-+-----------+--------------+-------------+--------------+
++-------------------------------------------------------+
 |verify_only|Use: routers**|Type: boolean|Default: false|
-+-----------+--------------+-------------+--------------+
++-------------------------------------------------------+
 
 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
@@ -16482,22 +17226,22 @@ message, Exim is not running as root, but under its own uid. If the router
 accesses any files, you need to make sure that they are accessible to the Exim
 user or group.
 
-+----------------+--------------+-------------+-------------+
++-----------------------------------------------------------+
 |verify_recipient|Use: routers**|Type: boolean|Default: true|
-+----------------+--------------+-------------+-------------+
++-----------------------------------------------------------+
 
 If this option is false, the router is skipped when verifying recipient
 addresses, delivering in cutthrough mode or testing recipient verification
 using -bv. See section 3.12 for a list of the order in which preconditions are
-evaluated.
+evaluated. See also the $verify_mode variable.
 
-+-------------+--------------+-------------+-------------+
++--------------------------------------------------------+
 |verify_sender|Use: routers**|Type: boolean|Default: true|
-+-------------+--------------+-------------+-------------+
++--------------------------------------------------------+
 
 If this option is false, the router is skipped when verifying sender addresses
 or testing sender verification using -bvs. See section 3.12 for a list of the
-order in which preconditions are evaluated.
+order in which preconditions are evaluated. See also the $verify_mode variable.
 
 
 
@@ -16556,7 +17300,7 @@ happens is controlled by the generic self option.
 ------------------------------
 
 There have been problems with DNS servers when SRV records are looked up. Some
-mis-behaving servers return a DNS error or timeout when a non-existent SRV
+misbehaving servers return a DNS error or timeout when a non-existent SRV
 record is sought. Similar problems have in the past been reported for MX
 records. The global dns_again_means_nonexist option can help with this problem,
 but it is heavy-handed because it is a global option.
@@ -16578,6 +17322,9 @@ There are a few cases where a dnslookup router will decline to accept an
 address; if such a router is expected to handle "all remaining non-local
 domains", then it is important to set no_more.
 
+The router will defer rather than decline if the domain is found in the 
+fail_defer_domains router option.
+
 Reasons for a dnslookup router to decline currently include:
 
   * The domain does not exist in DNS
@@ -16608,9 +17355,9 @@ Reasons for a dnslookup router to decline currently include:
 
 The private options for the dnslookup router are as follows:
 
-+------------------+--------------+-------------+--------------+
++--------------------------------------------------------------+
 |check_secondary_mx|Use: dnslookup|Type: boolean|Default: false|
-+------------------+--------------+-------------+--------------+
++--------------------------------------------------------------+
 
 If this option is set, the router declines unless the local host is found in
 (and removed from) the list of hosts obtained by MX lookup. This can be used to
@@ -16618,9 +17365,9 @@ process domains for which the local host is a secondary mail exchanger
 differently to other domains. The way in which Exim decides whether a host is
 the local host is described in section 13.8.
 
-+---------+--------------+-------------+--------------+
++-----------------------------------------------------+
 |check_srv|Use: dnslookup|Type: string*|Default: unset|
-+---------+--------------+-------------+--------------+
++-----------------------------------------------------+
 
 The dnslookup router supports the use of SRV records (see RFC 2782) in addition
 to MX and address records. The support is disabled by default. To enable SRV
@@ -16654,26 +17401,19 @@ trying to split an SMTP load between hosts of different power.
 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|
-+----------------------+--------------+------------------+--------------+
++-------------------------------------------------------------------+
+|fail_defer_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.
+DNS lookups for domains matching fail_defer_domains which find no matching
+record will cause the router to defer rather than the default behaviour of
+decline. This maybe be useful for queueing messages for a newly created domain
+while the DNS configuration is not ready. However, it will result in any
+message with mistyped domains also being queued.
 
-+----------+--------------+------------------+--------------+
++-----------------------------------------------------------+
 |mx_domains|Use: dnslookup|Type: domain list*|Default: unset|
-+----------+--------------+------------------+--------------+
++-----------------------------------------------------------+
 
 A domain that matches mx_domains is required to have either an MX or an SRV
 record in order to be recognized. (The name of this option could be improved.)
@@ -16687,17 +17427,17 @@ This specifies that messages addressed to a domain that matches the list but
 has no MX record should be bounced immediately instead of being routed using
 the address record.
 
-+---------------+--------------+------------------+--------------+
++----------------------------------------------------------------+
 |mx_fail_domains|Use: dnslookup|Type: domain list*|Default: unset|
-+---------------+--------------+------------------+--------------+
++----------------------------------------------------------------+
 
 If the DNS lookup for MX records for one of the domains in this list causes a
 DNS lookup error, Exim behaves as if no MX records were found. See section 17.1
 for more discussion.
 
-+--------------+--------------+-------------+-------------+
++---------------------------------------------------------+
 |qualify_single|Use: dnslookup|Type: boolean|Default: true|
-+--------------+--------------+-------------+-------------+
++---------------------------------------------------------+
 
 When this option is true, the resolver option RES_DEFNAMES is set for DNS
 lookups. Typically, but not standardly, this causes the resolver to qualify
@@ -16706,9 +17446,9 @@ called dictionary.ref.example, the domain thesaurus would be changed to
 thesaurus.ref.example inside the resolver. For details of what your resolver
 actually does, consult your man pages for resolver and resolv.conf.
 
-+---------------+--------------+-------------+-------------+
++----------------------------------------------------------+
 |rewrite_headers|Use: dnslookup|Type: boolean|Default: true|
-+---------------+--------------+-------------+-------------+
++----------------------------------------------------------+
 
 If the domain name in the address that is being processed is not fully
 qualified, it may be expanded to its full form by a DNS lookup. For example, if
@@ -16729,9 +17469,9 @@ some name servers have recently been seen to return the wildcard entry. If the
 name returned by a DNS lookup begins with an asterisk, it is not used for
 header rewriting.
 
-+------------------------+--------------+-------------+--------------+
++--------------------------------------------------------------------+
 |same_domain_copy_routing|Use: dnslookup|Type: boolean|Default: false|
-+------------------------+--------------+-------------+--------------+
++--------------------------------------------------------------------+
 
 Addresses with the same domain are normally routed by the dnslookup router to
 the same list of hosts. However, this cannot be presumed, because the router
@@ -16754,9 +17494,9 @@ processing them independently, provided the following conditions are met:
   * The router did not change the address in any way, for example, by
     "widening" the domain.
 
-+--------------+--------------+-------------+--------------+
++----------------------------------------------------------+
 |search_parents|Use: dnslookup|Type: boolean|Default: false|
-+--------------+--------------+-------------+--------------+
++----------------------------------------------------------+
 
 When this option is true, the resolver option RES_DNSRCH is set for DNS
 lookups. This is different from the qualify_single option in that it applies to
@@ -16771,17 +17511,17 @@ Setting this option true can cause problems in domains that have a wildcard MX
 record, because any domain that does not have its own MX record matches the
 local wildcard.
 
-+----------------+--------------+------------------+--------------+
++-----------------------------------------------------------------+
 |srv_fail_domains|Use: dnslookup|Type: domain list*|Default: unset|
-+----------------+--------------+------------------+--------------+
++-----------------------------------------------------------------+
 
 If the DNS lookup for SRV records for one of the domains in this list causes a
 DNS lookup error, Exim behaves as if no SRV records were found. See section
 17.1 for more discussion.
 
-+-------------+--------------+-----------------+--------------+
++-------------------------------------------------------------+
 |widen_domains|Use: dnslookup|Type: string list|Default: unset|
-+-------------+--------------+-----------------+--------------+
++-------------------------------------------------------------+
 
 If a DNS lookup fails and this option is set, each of its strings in turn is
 added onto the end of the domain, and the lookup is tried again. For example,
@@ -16867,40 +17607,40 @@ this process fails, the address can be passed on to other routers, or delivery
 can be deferred. Since iplookup is just a rewriting router, a transport must
 not be specified for it.
 
-+-----+-------------+------------+--------------+
++-----------------------------------------------+
 |hosts|Use: iplookup|Type: string|Default: unset|
-+-----+-------------+------------+--------------+
++-----------------------------------------------+
 
 This option must be supplied. Its value is a colon-separated list of host
 names. The hosts are looked up using gethostbyname() (or getipnodebyname() when
 available) and are tried in order until one responds to the query. If none
 respond, what happens is controlled by optional.
 
-+--------+-------------+-------------+--------------+
++---------------------------------------------------+
 |optional|Use: iplookup|Type: boolean|Default: false|
-+--------+-------------+-------------+--------------+
++---------------------------------------------------+
 
 If optional is true, if no response is obtained from any host, the address is
 passed to the next router, overriding no_more. If optional is false, delivery
 to the address is deferred.
 
-+----+-------------+-------------+----------+
++-------------------------------------------+
 |port|Use: iplookup|Type: integer|Default: 0|
-+----+-------------+-------------+----------+
++-------------------------------------------+
 
 This option must be supplied. It specifies the port number for the TCP or UDP
 call.
 
-+--------+-------------+------------+------------+
++------------------------------------------------+
 |protocol|Use: iplookup|Type: string|Default: udp|
-+--------+-------------+------------+------------+
++------------------------------------------------+
 
 This option can be set to "udp" or "tcp" to specify which of the two protocols
 is to be used.
 
-+-----+-------------+-------------+------------------+
++----------------------------------------------------+
 |query|Use: iplookup|Type: string*|Default: see below|
-+-----+-------------+-------------+------------------+
++----------------------------------------------------+
 
 This defines the content of the query that is sent to the remote hosts. The
 default value is:
@@ -16910,9 +17650,9 @@ $local_part@$domain $local_part@$domain
 The repetition serves as a way of checking that a response is to the correct
 query in the default case (see response_pattern below).
 
-+-------+-------------+-------------+--------------+
++--------------------------------------------------+
 |reroute|Use: iplookup|Type: string*|Default: unset|
-+-------+-------------+-------------+--------------+
++--------------------------------------------------+
 
 If this option is not set, the rerouted address is precisely the byte string
 returned by the remote host, up to the first white space, if any. If set, the
@@ -16922,9 +17662,9 @@ $2, etc. The variable $0 refers to the entire input string, whether or not a
 pattern is in use. In all cases, the rerouted address must end up in the form 
 local_part@domain.
 
-+----------------+-------------+------------+--------------+
++----------------------------------------------------------+
 |response_pattern|Use: iplookup|Type: string|Default: unset|
-+----------------+-------------+------------+--------------+
++----------------------------------------------------------+
 
 This option can be set to a regular expression that is applied to the string
 returned from the remote host. If the pattern does not match the response, the
@@ -16937,9 +17677,9 @@ For example, if the response is just a new domain, the following could be used:
 response_pattern = ^([^@]+)$
 reroute = $local_part@$1
 
-+-------+-------------+----------+-----------+
++--------------------------------------------+
 |timeout|Use: iplookup|Type: time|Default: 5s|
-+-------+-------------+----------+-----------+
++--------------------------------------------+
 
 This specifies the amount of time to wait for a response from the remote
 machine. The same timeout is used for the connect() function for a TCP call. It
@@ -16985,15 +17725,15 @@ list of private options.
 
 The private options for the manualroute router are as follows:
 
-+----------------+----------------+------------+--------------+
++-------------------------------------------------------------+
 |host_all_ignored|Use: manualroute|Type: string|Default: defer|
-+----------------+----------------+------------+--------------+
++-------------------------------------------------------------+
 
 See host_find_failed.
 
-+----------------+----------------+------------+---------------+
++--------------------------------------------------------------+
 |host_find_failed|Use: manualroute|Type: string|Default: freeze|
-+----------------+----------------+------------+---------------+
++--------------------------------------------------------------+
 
 This option controls what happens when manualroute tries to find an IP address
 for a host, and the host does not exist. The option can be set to one of the
@@ -17021,9 +17761,9 @@ The host_find_failed option applies only to a definite "does not exist" state;
 if a host lookup gets a temporary error, delivery is deferred unless the
 generic pass_on_timeout option is set.
 
-+---------------+----------------+-------------+--------------+
++-------------------------------------------------------------+
 |hosts_randomize|Use: manualroute|Type: boolean|Default: false|
-+---------------+----------------+-------------+--------------+
++-------------------------------------------------------------+
 
 If this option is set, the order of the items in a host list in a routing rule
 is randomized each time the list is used, unless an option in the routing rule
@@ -17046,9 +17786,9 @@ If hosts_randomize is not set, a "+" item in the list is ignored. If a
 randomized host list is passed to an smtp transport that also has 
 hosts_randomize set, the list is not re-randomized.
 
-+----------+----------------+-------------+--------------+
++--------------------------------------------------------+
 |route_data|Use: manualroute|Type: string*|Default: unset|
-+----------+----------------+-------------+--------------+
++--------------------------------------------------------+
 
 If this option is set, it must expand to yield the data part of a routing rule.
 Typically, the expansion string includes a lookup based on the domain. For
@@ -17060,17 +17800,17 @@ If the expansion is forced to fail, or the result is an empty string, the
 router declines. Other kinds of expansion failure cause delivery to be
 deferred.
 
-+----------+----------------+-----------------+--------------+
++------------------------------------------------------------+
 |route_list|Use: manualroute|Type: string list|Default: unset|
-+----------+----------------+-----------------+--------------+
++------------------------------------------------------------+
 
 This string is a list of routing rules, in the form defined below. Note that,
 unlike most string lists, the items are separated by semicolons. This is so
 that they may contain colon-separated host lists.
 
-+------------------------+----------------+-------------+--------------+
++----------------------------------------------------------------------+
 |same_domain_copy_routing|Use: manualroute|Type: boolean|Default: false|
-+------------------------+----------------+-------------+--------------+
++----------------------------------------------------------------------+
 
 Addresses with the same domain are normally routed by the manualroute router to
 the same list of hosts. However, this cannot be presumed, because the router
@@ -17095,7 +17835,7 @@ headers_remove are unset.
 The value of route_list is a string consisting of a sequence of routing rules,
 separated by semicolons. If a semicolon is needed in a rule, it can be entered
 as two semicolons. Alternatively, the list separator can be changed as
-described (for colon-separated lists) in section 6.19. Empty rules are ignored.
+described (for colon-separated lists) in section 6.20. Empty rules are ignored.
 The format of each rule is
 
 <domain pattern>  <list of hosts>  <options>
@@ -17154,7 +17894,7 @@ separately expanded before use. If the expansion fails, the router declines.
 The result of the expansion must be a colon-separated list of names and/or IP
 addresses, optionally also including ports. The format of each item in the list
 is described in the next section. The list separator can be changed as
-described in section 6.19.
+described in section 6.20.
 
 If the list of hosts was obtained from a route_list item, the following
 variables are set during its expansion:
@@ -17444,26 +18184,26 @@ it is possible to use the precondition options (domains, local_parts, etc) to
 skip this router for most addresses, it could sensibly be used in special
 cases, even on a busy host. There are the following private options:
 
-+-------+-----------------+-------------+--------------+
++------------------------------------------------------+
 |command|Use: queryprogram|Type: string*|Default: unset|
-+-------+-----------------+-------------+--------------+
++------------------------------------------------------+
 
 This option must be set. It specifies the command that is to be run. The
 command is split up into a command name and arguments, and then each is
 expanded separately (exactly as for a pipe transport, described in chapter 29).
 
-+-------------+-----------------+------------+--------------+
++-----------------------------------------------------------+
 |command_group|Use: queryprogram|Type: string|Default: unset|
-+-------------+-----------------+------------+--------------+
++-----------------------------------------------------------+
 
 This option specifies a gid to be set when running the command while routing an
 address for deliver. It must be set if command_user specifies a numerical uid.
 If it begins with a digit, it is interpreted as the numerical value of the gid.
 Otherwise it is looked up using getgrnam().
 
-+------------+-----------------+------------+--------------+
++----------------------------------------------------------+
 |command_user|Use: queryprogram|Type: string|Default: unset|
-+------------+-----------------+------------+--------------+
++----------------------------------------------------------+
 
 This option must be set. It specifies the uid which is set when running the
 command while routing an address for delivery. If the value begins with a
@@ -17479,16 +18219,16 @@ called from a non-root process, Exim cannot change uid or gid before running
 the command. In this circumstance the command runs under the current uid and
 gid.
 
-+-----------------+-----------------+------------+----------+
++-----------------------------------------------------------+
 |current_directory|Use: queryprogram|Type: string|Default: /|
-+-----------------+-----------------+------------+----------+
++-----------------------------------------------------------+
 
 This option specifies an absolute path which is made the current directory
 before running the command.
 
-+-------+-----------------+----------+-----------+
++------------------------------------------------+
 |timeout|Use: queryprogram|Type: time|Default: 1h|
-+-------+-----------------+----------+-----------+
++------------------------------------------------+
 
 If the command does not complete within the timeout period, its process group
 is killed and the message is frozen. A value of zero time specifies no timeout.
@@ -17585,6 +18325,9 @@ there are some private options which define transports for delivery to files
 and pipes, and for generating autoreplies. See the file_transport, 
 pipe_transport and reply_transport descriptions below.
 
+If success DSNs have been requested redirection triggers one and the DSN
+options are not passed any further.
+
 
 22.1 Redirection data
 ---------------------
@@ -17818,10 +18561,13 @@ lists (that is, in non-filter redirection data):
 
   * Sometimes you want to throw away mail to a particular local part. Making
     the data option expand to an empty string does not work, because that
-    causes the router to decline. Instead, the alias item :blackhole: can be
-    used. It does what its name implies. No delivery is done, and no error
-    message is generated. This has the same effect as specifing /dev/null as a
-    destination, but it can be independently disabled.
+    causes the router to decline. Instead, the alias item
+
+    :blackhole:
+
+    can be used. It does what its name implies. No delivery is done, and no
+    error message is generated. This has the same effect as specifying /dev/
+    null as a destination, but it can be independently disabled.
 
     Warning: If :blackhole: appears anywhere in a redirection list, no delivery
     is done for the original local part, even if other redirection items are
@@ -17933,23 +18679,23 @@ deferred. See also syntax_errors_to.
 
 The private options for the redirect router are as follows:
 
-+-----------+-------------+-------------+--------------+
++------------------------------------------------------+
 |allow_defer|Use: redirect|Type: boolean|Default: false|
-+-----------+-------------+-------------+--------------+
++------------------------------------------------------+
 
 Setting this option allows the use of :defer: in non-filter redirection data,
 or the defer command in an Exim filter file.
 
-+----------+-------------+-------------+--------------+
++-----------------------------------------------------+
 |allow_fail|Use: redirect|Type: boolean|Default: false|
-+----------+-------------+-------------+--------------+
++-----------------------------------------------------+
 
 If this option is true, the :fail: item can be used in a redirection list, and
 the fail command may be used in an Exim filter file.
 
-+------------+-------------+-------------+--------------+
++-------------------------------------------------------+
 |allow_filter|Use: redirect|Type: boolean|Default: false|
-+------------+-------------+-------------+--------------+
++-------------------------------------------------------+
 
 Setting this option allows Exim to interpret redirection data that starts with
 "#Exim filter" or "#Sieve filter" as a set of filtering instructions. There are
@@ -17965,18 +18711,18 @@ is set, so in the normal case of users' personal filter files, the filter is
 run as the relevant user. When allow_filter is set true, Exim insists that
 either check_local_user or user is set.
 
-+------------+-------------+-------------+--------------+
++-------------------------------------------------------+
 |allow_freeze|Use: redirect|Type: boolean|Default: false|
-+------------+-------------+-------------+--------------+
++-------------------------------------------------------+
 
 Setting this option allows the use of the freeze command in an Exim filter.
 This command is more normally encountered in system filters, and is disabled by
 default for redirection filters because it isn't something you usually want to
 let ordinary users do.
 
-+--------------+-------------+-------------+--------------+
++---------------------------------------------------------+
 |check_ancestor|Use: redirect|Type: boolean|Default: false|
-+--------------+-------------+-------------+--------------+
++---------------------------------------------------------+
 
 This option is concerned with handling generated addresses that are the same as
 some address in the list of redirection ancestors of the current address.
@@ -18001,9 +18747,9 @@ is the real mailbox name, mail to "jb" gets delivered (having been turned into
 the .forward file prevents it from turning "jb" back into "joe.bloggs" when
 that was the original address. See also the repeat_use option below.
 
-+-----------+-------------+-------------+------------------+
++----------------------------------------------------------+
 |check_group|Use: redirect|Type: boolean|Default: see below|
-+-----------+-------------+-------------+------------------+
++----------------------------------------------------------+
 
 When the file option is used, the group owner of the file is checked only when
 this option is set. The permitted groups are those listed in the owngroups
@@ -18013,9 +18759,9 @@ option is true if check_local_user is set and the modemask option permits the
 group write bit, or if the owngroups option is set. Otherwise it is false, and
 no group check occurs.
 
-+-----------+-------------+-------------+------------------+
++----------------------------------------------------------+
 |check_owner|Use: redirect|Type: boolean|Default: see below|
-+-----------+-------------+-------------+------------------+
++----------------------------------------------------------+
 
 When the file option is used, the owner of the file is checked only when this
 option is set. If check_local_user is set, the local user is permitted;
@@ -18023,9 +18769,9 @@ otherwise the owner must be one of those listed in the owners option. The
 default value for this option is true if check_local_user or owners is set.
 Otherwise the default is false, and no owner check occurs.
 
-+----+-------------+-------------+--------------+
++-----------------------------------------------+
 |data|Use: redirect|Type: string*|Default: unset|
-+----+-------------+-------------+--------------+
++-----------------------------------------------+
 
 This option is mutually exclusive with file. One or other of them must be set,
 but not both. The contents of data are expanded, and then used as the list of
@@ -18044,18 +18790,18 @@ If you are reading the data from a database where newlines cannot be included,
 you can use the ${sg} expansion item to turn the escape string of your choice
 into a newline.
 
-+-------------------+-------------+-------------+--------------+
++--------------------------------------------------------------+
 |directory_transport|Use: redirect|Type: string*|Default: unset|
-+-------------------+-------------+-------------+--------------+
++--------------------------------------------------------------+
 
 A redirect router sets up a direct delivery to a directory when a path name
 ending with a slash is specified as a new "address". The transport used is
 specified by this option, which, after expansion, must be the name of a
 configured transport. This should normally be an appendfile transport.
 
-+----+-------------+-------------+--------------+
++-----------------------------------------------+
 |file|Use: redirect|Type: string*|Default: unset|
-+----+-------------+-------------+--------------+
++-----------------------------------------------+
 
 This option specifies the name of a file that contains the redirection data. It
 is mutually exclusive with the data option. The string is expanded before use;
@@ -18072,9 +18818,9 @@ happen when users' .forward files are in NFS-mounted directories, and there is
 a mount problem. If the containing directory does exist, but the file does not,
 the router declines.
 
-+--------------+-------------+-------------+--------------+
++---------------------------------------------------------+
 |file_transport|Use: redirect|Type: string*|Default: unset|
-+--------------+-------------+-------------+--------------+
++---------------------------------------------------------+
 
 A redirect router sets up a direct delivery to a file when a path name not
 ending in a slash is specified as a new "address". The transport used is
@@ -18082,32 +18828,32 @@ specified by this option, which, after expansion, must be the name of a
 configured transport. This should normally be an appendfile transport. When it
 is running, the file name is in $address_file.
 
-+-------------------+-------------+-------------+-------------+
++-------------------------------------------------------------+
 |filter_prepend_home|Use: redirect|Type: boolean|Default: true|
-+-------------------+-------------+-------------+-------------+
++-------------------------------------------------------------+
 
 When this option is true, if a save command in an Exim filter specifies a
 relative path, and $home is defined, it is automatically prepended to the
 relative path. If this option is set false, this action does not happen. The
 relative path is then passed to the transport unmodified.
 
-+----------------+-------------+-------------+--------------+
++-----------------------------------------------------------+
 |forbid_blackhole|Use: redirect|Type: boolean|Default: false|
-+----------------+-------------+-------------+--------------+
++-----------------------------------------------------------+
 
 If this option is true, the :blackhole: item may not appear in a redirection
 list.
 
-+------------------+-------------+-------------+--------------+
++-------------------------------------------------------------+
 |forbid_exim_filter|Use: redirect|Type: boolean|Default: false|
-+------------------+-------------+-------------+--------------+
++-------------------------------------------------------------+
 
 If this option is set true, only Sieve filters are permitted when allow_filter
 is true.
 
-+-----------+-------------+-------------+--------------+
++------------------------------------------------------+
 |forbid_file|Use: redirect|Type: boolean|Default: false|
-+-----------+-------------+-------------+--------------+
++------------------------------------------------------+
 
 If this option is true, this router may not generate a new address that
 specifies delivery to a local file or directory, either from a filter or from a
@@ -18115,77 +18861,77 @@ conventional forward file. This option is forced to be true if one_time is set.
 It applies to Sieve filters as well as to Exim filters, but if true, it locks
 out the Sieve's "keep" facility.
 
-+--------------------+-------------+-------------+--------------+
++---------------------------------------------------------------+
 |forbid_filter_dlfunc|Use: redirect|Type: boolean|Default: false|
-+--------------------+-------------+-------------+--------------+
++---------------------------------------------------------------+
 
 If this option is true, string expansions in Exim filters are not allowed to
 make use of the dlfunc expansion facility to run dynamically loaded functions.
 
-+------------------------+-------------+-------------+--------------+
++-------------------------------------------------------------------+
 |forbid_filter_existstest|Use: redirect|Type: boolean|Default: false|
-+------------------------+-------------+-------------+--------------+
++-------------------------------------------------------------------+
 
 If this option is true, string expansions in Exim filters are not allowed to
 make use of the exists condition or the stat expansion item.
 
-+----------------------+-------------+-------------+--------------+
++-----------------------------------------------------------------+
 |forbid_filter_logwrite|Use: redirect|Type: boolean|Default: false|
-+----------------------+-------------+-------------+--------------+
++-----------------------------------------------------------------+
 
 If this option is true, use of the logging facility in Exim filters is not
 permitted. Logging is in any case available only if the filter is being run
 under some unprivileged uid (which is normally the case for ordinary users'
 .forward files).
 
-+--------------------+-------------+-------------+--------------+
++---------------------------------------------------------------+
 |forbid_filter_lookup|Use: redirect|Type: boolean|Default: false|
-+--------------------+-------------+-------------+--------------+
++---------------------------------------------------------------+
 
 If this option is true, string expansions in Exim filter files are not allowed
 to make use of lookup items.
 
-+------------------+-------------+-------------+--------------+
++-------------------------------------------------------------+
 |forbid_filter_perl|Use: redirect|Type: boolean|Default: false|
-+------------------+-------------+-------------+--------------+
++-------------------------------------------------------------+
 
 This option has an effect only if Exim is built with embedded Perl support. If
 it is true, string expansions in Exim filter files are not allowed to make use
 of the embedded Perl support.
 
-+----------------------+-------------+-------------+--------------+
++-----------------------------------------------------------------+
 |forbid_filter_readfile|Use: redirect|Type: boolean|Default: false|
-+----------------------+-------------+-------------+--------------+
++-----------------------------------------------------------------+
 
 If this option is true, string expansions in Exim filter files are not allowed
 to make use of readfile items.
 
-+------------------------+-------------+-------------+--------------+
++-------------------------------------------------------------------+
 |forbid_filter_readsocket|Use: redirect|Type: boolean|Default: false|
-+------------------------+-------------+-------------+--------------+
++-------------------------------------------------------------------+
 
 If this option is true, string expansions in Exim filter files are not allowed
 to make use of readsocket items.
 
-+-------------------+-------------+-------------+--------------+
++--------------------------------------------------------------+
 |forbid_filter_reply|Use: redirect|Type: boolean|Default: false|
-+-------------------+-------------+-------------+--------------+
++--------------------------------------------------------------+
 
 If this option is true, this router may not generate an automatic reply
 message. Automatic replies can be generated only from Exim or Sieve filter
 files, not from traditional forward files. This option is forced to be true if 
 one_time is set.
 
-+-----------------+-------------+-------------+--------------+
++------------------------------------------------------------+
 |forbid_filter_run|Use: redirect|Type: boolean|Default: false|
-+-----------------+-------------+-------------+--------------+
++------------------------------------------------------------+
 
 If this option is true, string expansions in Exim filter files are not allowed
 to make use of run items.
 
-+--------------+-------------+-------------+--------------+
++---------------------------------------------------------+
 |forbid_include|Use: redirect|Type: boolean|Default: false|
-+--------------+-------------+-------------+--------------+
++---------------------------------------------------------+
 
 If this option is true, items of the form
 
@@ -18193,32 +18939,32 @@ If this option is true, items of the form
 
 are not permitted in non-filter redirection lists.
 
-+-----------+-------------+-------------+--------------+
++------------------------------------------------------+
 |forbid_pipe|Use: redirect|Type: boolean|Default: false|
-+-----------+-------------+-------------+--------------+
++------------------------------------------------------+
 
 If this option is true, this router may not generate a new address which
 specifies delivery to a pipe, either from an Exim filter or from a conventional
 forward file. This option is forced to be true if one_time is set.
 
-+-------------------+-------------+-------------+--------------+
++--------------------------------------------------------------+
 |forbid_sieve_filter|Use: redirect|Type: boolean|Default: false|
-+-------------------+-------------+-------------+--------------+
++--------------------------------------------------------------+
 
 If this option is set true, only Exim filters are permitted when allow_filter
 is true.
 
-+----------------+-------------+-------------+--------------+
++-----------------------------------------------------------+
 |forbid_smtp_code|Use: redirect|Type: boolean|Default: false|
-+----------------+-------------+-------------+--------------+
++-----------------------------------------------------------+
 
 If this option is set true, any SMTP error codes that are present at the start
 of messages specified for ":defer:" or ":fail:" are quietly ignored, and the
 default codes (451 and 550, respectively) are always used.
 
-+--------------------+-------------+-------------+--------------+
++---------------------------------------------------------------+
 |hide_child_in_errmsg|Use: redirect|Type: boolean|Default: false|
-+--------------------+-------------+-------------+--------------+
++---------------------------------------------------------------+
 
 If this option is true, it prevents Exim from quoting a child address if it
 generates a bounce or delay message for it. Instead it says "an address
@@ -18226,17 +18972,17 @@ generated from <the top level address>". Of course, this applies only to
 bounces generated locally. If a message is forwarded to another host, its
 bounce may well quote the generated address.
 
-+-------------+-------------+-------------+--------------+
++--------------------------------------------------------+
 |ignore_eacces|Use: redirect|Type: boolean|Default: false|
-+-------------+-------------+-------------+--------------+
++--------------------------------------------------------+
 
 If this option is set and an attempt to open a redirection file yields the
 EACCES error (permission denied), the redirect router behaves as if the file
 did not exist.
 
-+--------------+-------------+-------------+--------------+
++---------------------------------------------------------+
 |ignore_enotdir|Use: redirect|Type: boolean|Default: false|
-+--------------+-------------+-------------+--------------+
++---------------------------------------------------------+
 
 If this option is set and an attempt to open a redirection file yields the
 ENOTDIR error (something on the path is not a directory), the redirect router
@@ -18251,23 +18997,23 @@ that option tells Exim to ignore "something on the path is not a directory"
 (the ENOTDIR error). This is a confusing area, because it seems that some
 operating systems give ENOENT where others give ENOTDIR.
 
-+-----------------+-------------+------------+--------------+
++-----------------------------------------------------------+
 |include_directory|Use: redirect|Type: string|Default: unset|
-+-----------------+-------------+------------+--------------+
++-----------------------------------------------------------+
 
 If this option is set, the path names of any :include: items in a redirection
 list must start with this directory.
 
-+--------+-------------+-------------------+------------+
++-------------------------------------------------------+
 |modemask|Use: redirect|Type: octal integer|Default: 022|
-+--------+-------------+-------------------+------------+
++-------------------------------------------------------+
 
 This specifies mode bits which must not be set for a file specified by the file
 option. If any of the forbidden bits are set, delivery is deferred.
 
-+--------+-------------+-------------+--------------+
++---------------------------------------------------+
 |one_time|Use: redirect|Type: boolean|Default: false|
-+--------+-------------+-------------+--------------+
++---------------------------------------------------+
 
 Sometimes the fact that Exim re-evaluates aliases and reprocesses redirection
 files each time it tries to deliver a message causes a problem when one or more
@@ -18301,25 +19047,25 @@ addresses are not recorded. This makes a difference to the log only if
 all_parents log selector is set. It is expected that one_time will typically be
 used for mailing lists, where there is normally just one level of expansion.
 
-+------+-------------+-----------------+--------------+
++-----------------------------------------------------+
 |owners|Use: redirect|Type: string list|Default: unset|
-+------+-------------+-----------------+--------------+
++-----------------------------------------------------+
 
 This specifies a list of permitted owners for the file specified by file. This
 list is in addition to the local user when check_local_user is set. See 
 check_owner above.
 
-+---------+-------------+-----------------+--------------+
++--------------------------------------------------------+
 |owngroups|Use: redirect|Type: string list|Default: unset|
-+---------+-------------+-----------------+--------------+
++--------------------------------------------------------+
 
 This specifies a list of permitted groups for the file specified by file. The
 list is in addition to the local user's primary group when check_local_user is
 set. See check_group above.
 
-+--------------+-------------+-------------+--------------+
++---------------------------------------------------------+
 |pipe_transport|Use: redirect|Type: string*|Default: unset|
-+--------------+-------------+-------------+--------------+
++---------------------------------------------------------+
 
 A redirect router sets up a direct delivery to a pipe when a string starting
 with a vertical bar character is specified as a new "address". The transport
@@ -18327,9 +19073,9 @@ used is specified by this option, which, after expansion, must be the name of a
 configured transport. This should normally be a pipe transport. When the
 transport is run, the pipe command is in $address_pipe.
 
-+--------------+-------------+-------------+--------------+
++---------------------------------------------------------+
 |qualify_domain|Use: redirect|Type: string*|Default: unset|
-+--------------+-------------+-------------+--------------+
++---------------------------------------------------------+
 
 If this option is set, and an unqualified address (one without a domain) is
 generated, and that address would normally be qualified by the global setting
@@ -18342,9 +19088,9 @@ This option applies to all unqualified addresses generated by Exim filters, but
 for traditional .forward files, it applies only to addresses that are not
 preceded by a backslash. Sieve filters cannot generate unqualified addresses.
 
-+-----------------------+-------------+-------------+--------------+
++------------------------------------------------------------------+
 |qualify_preserve_domain|Use: redirect|Type: boolean|Default: false|
-+-----------------------+-------------+-------------+--------------+
++------------------------------------------------------------------+
 
 If this option is set, the router's local qualify_domain option must not be set
 (a configuration error occurs if it is). If an unqualified address (one without
@@ -18353,9 +19099,9 @@ a domain) is generated, it is qualified with the domain of the parent address
 value. In the case of a traditional .forward file, this applies whether or not
 the address is preceded by a backslash.
 
-+----------+-------------+-------------+-------------+
++----------------------------------------------------+
 |repeat_use|Use: redirect|Type: boolean|Default: true|
-+----------+-------------+-------------+-------------+
++----------------------------------------------------+
 
 If this option is set false, the router is skipped for a child address that has
 any ancestor that was routed by this router. This test happens before any of
@@ -18363,9 +19109,9 @@ the other preconditions are tested. Exim's default anti-looping rules skip only
 when the ancestor is the same as the current address. See also check_ancestor
 above and the generic redirect_router option.
 
-+---------------+-------------+-------------+--------------+
++----------------------------------------------------------+
 |reply_transport|Use: redirect|Type: string*|Default: unset|
-+---------------+-------------+-------------+--------------+
++----------------------------------------------------------+
 
 A redirect router sets up an automatic reply when a mail or vacation command is
 used in a filter file. The transport used is specified by this option, which,
@@ -18373,32 +19119,32 @@ after expansion, must be the name of a configured transport. This should
 normally be an autoreply transport. Other transports are unlikely to do
 anything sensible or useful.
 
-+-------+-------------+-------------+-------------+
++-------------------------------------------------+
 |rewrite|Use: redirect|Type: boolean|Default: true|
-+-------+-------------+-------------+-------------+
++-------------------------------------------------+
 
 If this option is set false, addresses generated by the router are not subject
 to address rewriting. Otherwise, they are treated like new addresses and are
 rewritten according to the global rewriting rules.
 
-+----------------+-------------+-------------+--------------+
++-----------------------------------------------------------+
 |sieve_subaddress|Use: redirect|Type: string*|Default: unset|
-+----------------+-------------+-------------+--------------+
++-----------------------------------------------------------+
 
 The value of this option is passed to a Sieve filter to specify the :subaddress
 part of an address.
 
-+-----------------+-------------+-------------+--------------+
++------------------------------------------------------------+
 |sieve_useraddress|Use: redirect|Type: string*|Default: unset|
-+-----------------+-------------+-------------+--------------+
++------------------------------------------------------------+
 
 The value of this option is passed to a Sieve filter to specify the :user part
 of an address. However, if it is unset, the entire original local part
 (including any prefix or suffix) is used for :user.
 
-+------------------------+-------------+-------------+--------------+
++-------------------------------------------------------------------+
 |sieve_vacation_directory|Use: redirect|Type: string*|Default: unset|
-+------------------------+-------------+-------------+--------------+
++-------------------------------------------------------------------+
 
 To enable the "vacation" extension for Sieve filters, you must set 
 sieve_vacation_directory to the directory where vacation databases are held (do
@@ -18406,9 +19152,9 @@ not put anything else in that directory), and ensure that the reply_transport
 option refers to an autoreply transport. Each user needs their own directory;
 Exim will create it if necessary.
 
-+------------------+-------------+-------------+--------------+
++-------------------------------------------------------------+
 |skip_syntax_errors|Use: redirect|Type: boolean|Default: false|
-+------------------+-------------+-------------+--------------+
++-------------------------------------------------------------+
 
 If skip_syntax_errors is set, syntactically malformed addresses in non-filter
 redirection data are skipped, and each failing address is logged. If 
@@ -18476,15 +19222,15 @@ router to locally-generated messages, using a condition such as this:
   condition = ${if match {$sender_host_address}\
                          {\N^(|127\.0\.0\.1)$\N}}
 
-+------------------+-------------+-------------+--------------+
++-------------------------------------------------------------+
 |syntax_errors_text|Use: redirect|Type: string*|Default: unset|
-+------------------+-------------+-------------+--------------+
++-------------------------------------------------------------+
 
 See skip_syntax_errors above.
 
-+----------------+-------------+------------+--------------+
++----------------------------------------------------------+
 |syntax_errors_to|Use: redirect|Type: string|Default: unset|
-+----------------+-------------+------------+--------------+
++----------------------------------------------------------+
 
 See skip_syntax_errors above.
 
@@ -18527,7 +19273,7 @@ my_transport:
 
 This is supposed to write the message at the end of the file. However, if two
 messages arrive at the same time, the file will be scrambled. You can use the 
-exim_lock utility program (see section 52.15) to lock a file using the same
+exim_lock utility program (see section 53.15) to lock a file using the same
 algorithm that Exim itself uses.
 
 
@@ -18651,35 +19397,35 @@ $original_domain is never set.
 
 The following generic options apply to all transports:
 
-+---------+---------------+-------------+--------------+
++------------------------------------------------------+
 |body_only|Use: transports|Type: boolean|Default: false|
-+---------+---------------+-------------+--------------+
++------------------------------------------------------+
 
 If this option is set, the message's headers are not transported. It is
 mutually exclusive with headers_only. If it is used with the appendfile or pipe
 transports, the settings of message_prefix and message_suffix should be
 checked, because this option does not automatically suppress them.
 
-+-----------------+---------------+-------------+--------------+
++--------------------------------------------------------------+
 |current_directory|Use: transports|Type: string*|Default: unset|
-+-----------------+---------------+-------------+--------------+
++--------------------------------------------------------------+
 
 This specifies the current directory that is to be set while running the
 transport, overriding any value that may have been set by the router. If the
 expansion fails for any reason, including forced failure, an error is logged,
 and delivery is deferred.
 
-+---------------+---------------+-------------+--------------+
++------------------------------------------------------------+
 |disable_logging|Use: transports|Type: boolean|Default: false|
-+---------------+---------------+-------------+--------------+
++------------------------------------------------------------+
 
 If this option is set true, nothing is logged for any deliveries by the
 transport or for any transport errors. You should not set this option unless
 you really, really know what you are doing.
 
-+-----------+---------------+-------------+--------------+
++--------------------------------------------------------+
 |debug_print|Use: transports|Type: string*|Default: unset|
-+-----------+---------------+-------------+--------------+
++--------------------------------------------------------+
 
 If this option is set and debugging is enabled (see the -d command line
 option), the string is expanded and included in the debugging output when the
@@ -18692,9 +19438,9 @@ 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|
-+-----------------+---------------+-------------+--------------+
++--------------------------------------------------------------+
 
 If this option is true, a Delivery-date: header is added to the message. This
 gives the actual time the delivery was made. As this is not a standard header,
@@ -18702,16 +19448,16 @@ Exim has a configuration option (delivery_date_remove) which requests its
 removal from incoming messages, so that delivered messages can safely be resent
 to other recipients.
 
-+------+---------------+------------+--------------+
++--------------------------------------------------+
 |driver|Use: transports|Type: string|Default: unset|
-+------+---------------+------------+--------------+
++--------------------------------------------------+
 
 This specifies which of the available transport drivers is to be used. There is
 no default, and this option must be set for every transport.
 
-+---------------+---------------+-------------+--------------+
++------------------------------------------------------------+
 |envelope_to_add|Use: transports|Type: boolean|Default: false|
-+---------------+---------------+-------------+--------------+
++------------------------------------------------------------+
 
 If this option is true, an Envelope-to: header is added to the message. This
 gives the original address(es) in the incoming envelope that caused this
@@ -18722,54 +19468,67 @@ header, Exim has a configuration option (envelope_to_remove) which requests its
 removal from incoming messages, so that delivered messages can safely be resent
 to other recipients.
 
-+-----+---------------+-------------+-------------------+
++---------------------------------------------------------+
+|event_action|Use: transports|Type: string*|Default: unset|
++---------------------------------------------------------+
+
+This option declares a string to be expanded for Exim's events mechanism. For
+details see chapter 60.
+
++-------------------------------------------------------+
 |group|Use: transports|Type: string*|Default: Exim group|
-+-----+---------------+-------------+-------------------+
++-------------------------------------------------------+
 
 This option specifies a gid for running the transport process, overriding any
 value that the router supplies, and also overriding any value associated with 
 user (see below).
 
-+-----------+---------------+-----------+--------------+
++------------------------------------------------------+
 |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.
+This option specifies a list of text headers, newline-separated (by default,
+changeable in the usual way), which are (separately) expanded and added to the
+header portion of a message as it is transported, as described in section 47.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.
 
-+------------+---------------+-------------+--------------+
++---------------------------------------------------------+
 |headers_only|Use: transports|Type: boolean|Default: false|
-+------------+---------------+-------------+--------------+
++---------------------------------------------------------+
 
 If this option is set, the message's body is not transported. It is mutually
 exclusive with body_only. If it is used with the appendfile or pipe transports,
 the settings of message_prefix and message_suffix should be checked, since this
 option does not automatically suppress them.
 
-+--------------+---------------+-----------+--------------+
++---------------------------------------------------------+
 |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.
+This option specifies a list of header names, colon-separated (by default,
+changeable in the usual way); these headers are omitted from the message as it
+is transported, as described in section 47.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 removed.
+transport; all listed headers are removed.
+
+Warning: Because of the separate expansion of the list items, items that
+contain a list separator must have it doubled. To avoid this, change the list
+separator (6.21).
 
-+---------------+---------------+------------+--------------+
++-----------------------------------------------------------+
 |headers_rewrite|Use: transports|Type: string|Default: unset|
-+---------------+---------------+------------+--------------+
++-----------------------------------------------------------+
 
 This option allows addresses in header lines to be rewritten at transport time,
 that is, as the message is being copied to its destination. The contents of the
@@ -18790,9 +19549,9 @@ this option. These rewriting rules are not applied to the envelope. You can
 change the return path using return_path, but you cannot change envelope
 recipients at this time.
 
-+--------------+---------------+-------------+--------------+
++-----------------------------------------------------------+
 |home_directory|Use: transports|Type: string*|Default: unset|
-+--------------+---------------+-------------+--------------+
++-----------------------------------------------------------+
 
 This option specifies a home directory setting for a local transport,
 overriding any value that may be set by the router. The home directory is
@@ -18802,17 +19561,38 @@ current_directory option on the transport or the transport_current_directory
 option on the router. If the expansion fails for any reason, including forced
 failure, an error is logged, and delivery is deferred.
 
-+----------+---------------+-------------+--------------+
++-------------------------------------------------------+
 |initgroups|Use: transports|Type: boolean|Default: false|
-+----------+---------------+-------------+--------------+
++-------------------------------------------------------+
 
 If this option is true and the uid for the delivery process is provided by the
 transport, the initgroups() function is called when running the transport to
 ensure that any additional groups associated with the uid are set up.
 
-+------------------+---------------+-------------+----------+
++----------------------------------------------------------+
+|max_parallel|Use: transports|Type: integer*|Default: unset|
++----------------------------------------------------------+
+
+If this option is set and expands to an integer greater than zero it limits the
+number of concurrent runs of the transport. The control does not apply to
+shadow transports.
+
+Exim implements this control by means of a hints database in which a record is
+incremented whenever a transport process is being created. The record is
+decremented and possibly removed when the process terminates. Obviously there
+is scope for records to get left lying around if there is a system or program
+crash. To guard against this, Exim ignores any records that are more than six
+hours old.
+
+If you use this option, you should also arrange to delete the relevant hints
+database whenever your system reboots. The names of the files start with misc
+and they are kept in the spool/db directory. There may be one or two files,
+depending on the type of DBM in use. The same files are used for ETRN and smtp
+transport serialization.
+
++-----------------------------------------------------------+
 |message_size_limit|Use: transports|Type: string*|Default: 0|
-+------------------+---------------+-------------+----------+
++-----------------------------------------------------------+
 
 This option controls the size of messages passed through the transport. It is
 expanded before use; the result of the expansion must be a sequence of decimal
@@ -18824,9 +19604,9 @@ the resulting bounce message could be routed to the same transport, you should
 ensure that return_size_limit is less than the transport's message_size_limit,
 as otherwise the bounce message will fail to get delivered.
 
-+--------------------+---------------+-------------+--------------+
++-----------------------------------------------------------------+
 |rcpt_include_affixes|Use: transports|Type: boolean|Default: false|
-+--------------------+---------------+-------------+--------------+
++-----------------------------------------------------------------+
 
 When this option is false (the default), and an address that has had any
 affixes (prefixes or suffixes) removed from the local part is delivered by any
@@ -18846,9 +19626,9 @@ local part is included in the RCPT command. This option applies to BSMTP
 deliveries by the appendfile and pipe transports as well as to the lmtp and 
 smtp transports.
 
-+--------------------+---------------+-------------+------------------+
++---------------------------------------------------------------------+
 |retry_use_local_part|Use: transports|Type: boolean|Default: see below|
-+--------------------+---------------+-------------+------------------+
++---------------------------------------------------------------------+
 
 When a delivery suffers a temporary failure, a retry record is created in
 Exim's hints database. For remote deliveries, the key for the retry record is
@@ -18867,9 +19647,9 @@ For all the local transports, its default value is true. For remote transports,
 the default value is false for tidiness, but changing the value has no effect
 on a remote transport in the current implementation.
 
-+-----------+---------------+-------------+--------------+
++--------------------------------------------------------+
 |return_path|Use: transports|Type: string*|Default: unset|
-+-----------+---------------+-------------+--------------+
++--------------------------------------------------------+
 
 If this option is set, the string is expanded at transport time and replaces
 the existing return path (envelope sender) value in the copy of the message
@@ -18886,7 +19666,7 @@ The expansion can refer to the existing value via $return_path. This is either
 the message's envelope sender, or an address set by the errors_to option on a
 router. If the expansion is forced to fail, no replacement occurs; if it fails
 for another reason, delivery is deferred. This option can be used to support
-VERP (Variable Envelope Return Paths) - see section 49.6.
+VERP (Variable Envelope Return Paths) - see section 50.6.
 
 Note: If a delivery error is detected locally, including the case when a remote
 server rejects a message at SMTP time, the bounce message is not sent to the
@@ -18894,9 +19674,9 @@ value of this option. It is sent to the previously set errors address. This
 defaults to the incoming sender address, but can be changed by setting 
 errors_to in a router.
 
-+---------------+---------------+-------------+--------------+
++------------------------------------------------------------+
 |return_path_add|Use: transports|Type: boolean|Default: false|
-+---------------+---------------+-------------+--------------+
++------------------------------------------------------------+
 
 If this option is true, a Return-path: header is added to the message. Although
 the return path is normally available in the prefix line of BSD mailboxes, this
@@ -18909,15 +19689,15 @@ should not be present in incoming messages. Exim has a configuration option,
 return_path_remove, which requests removal of this header from incoming
 messages, so that delivered messages can safely be resent to other recipients.
 
-+----------------+---------------+-------------+--------------+
++-------------------------------------------------------------+
 |shadow_condition|Use: transports|Type: string*|Default: unset|
-+----------------+---------------+-------------+--------------+
++-------------------------------------------------------------+
 
 See shadow_transport below.
 
-+----------------+---------------+------------+--------------+
++------------------------------------------------------------+
 |shadow_transport|Use: transports|Type: string|Default: unset|
-+----------------+---------------+------------+--------------+
++------------------------------------------------------------+
 
 A local transport may set the shadow_transport option to the name of another
 local transport. Shadow remote transports are not supported.
@@ -18944,13 +19724,14 @@ purposes, including keeping more detailed log information than Exim normally
 provides, and implementing automatic acknowledgment policies based on message
 headers that some sites insist on.
 
-+----------------+---------------+-------------+--------------+
++-------------------------------------------------------------+
 |transport_filter|Use: transports|Type: string*|Default: unset|
-+----------------+---------------+-------------+--------------+
++-------------------------------------------------------------+
 
 This option sets up a filtering (in the Unix shell sense) process for messages
 at transport time. It should not be confused with mail filtering as set up by
-individual users or via a system filter.
+individual users or via a system filter. If unset, or expanding to an empty
+string, no filtering is done.
 
 When the message is about to be written out, the command specified by 
 transport_filter is started up in a separate, parallel process, and the entire
@@ -19049,9 +19830,9 @@ If a transport filter is set on an autoreply transport, the original message is
 passed through the filter as it is being copied into the newly generated
 message, which happens if the return_message option is set.
 
-+------------------------+---------------+----------+-----------+
++---------------------------------------------------------------+
 |transport_filter_timeout|Use: transports|Type: time|Default: 5m|
-+------------------------+---------------+----------+-----------+
++---------------------------------------------------------------+
 
 When Exim is reading the output of a transport filter, it applies a timeout
 that can be set by this option. Exceeding the timeout is normally treated as a
@@ -19061,9 +19842,9 @@ timeout in the pipe command itself. By default, a timeout is a hard error, but
 if the pipe transport's timeout_defer option is set true, it becomes a
 temporary error.
 
-+----+---------------+-------------+------------------+
++-----------------------------------------------------+
 |user|Use: transports|Type: string*|Default: Exim user|
-+----+---------------+-------------+------------------+
++-----------------------------------------------------+
 
 This option specifies the user under whose uid the delivery process is to be
 run, overriding any uid that may have been set by the router. If the user is
@@ -19146,7 +19927,7 @@ check_string = "."
 escape_string = ".."
 
 when batched SMTP is in use. A full description of the batch SMTP mechanism is
-given in section 47.10. The lmtp transport does not have a use_bsmtp option,
+given in section 48.10. The lmtp transport does not have a use_bsmtp option,
 because it always delivers using the SMTP protocol.
 
 If the generic envelope_to_add option is set for a batching transport, the 
@@ -19272,17 +20053,17 @@ directory option is still used if it is set.
 26.2 Private options for appendfile
 -----------------------------------
 
-+----------+---------------+-------------+--------------+
++-------------------------------------------------------+
 |allow_fifo|Use: appendfile|Type: boolean|Default: false|
-+----------+---------------+-------------+--------------+
++-------------------------------------------------------+
 
 Setting this option permits delivery to named pipes (FIFOs) as well as to
 regular files. If no process is reading the named pipe at delivery time, the
 delivery is deferred.
 
-+-------------+---------------+-------------+--------------+
++----------------------------------------------------------+
 |allow_symlink|Use: appendfile|Type: boolean|Default: false|
-+-------------+---------------+-------------+--------------+
++----------------------------------------------------------+
 
 By default, appendfile will not deliver if the path name for the file is that
 of a symbolic link. Setting this option relaxes that constraint, but there are
@@ -19290,40 +20071,40 @@ security issues involved in the use of symbolic links. Be sure you know what
 you are doing if you set this. Details of exactly what this option affects are
 included in the discussion which follows this list of options.
 
-+--------+---------------+-------------+--------------+
++-----------------------------------------------------+
 |batch_id|Use: appendfile|Type: string*|Default: unset|
-+--------+---------------+-------------+--------------+
++-----------------------------------------------------+
 
 See the description of local delivery batching in chapter 25. However, batching
 is automatically disabled for appendfile deliveries that happen as a result of
 forwarding or aliasing or other redirection directly to a file.
 
-+---------+---------------+-------------+----------+
++--------------------------------------------------+
 |batch_max|Use: appendfile|Type: integer|Default: 1|
-+---------+---------------+-------------+----------+
++--------------------------------------------------+
 
 See the description of local delivery batching in chapter 25.
 
-+-----------+---------------+-------------+--------------+
++--------------------------------------------------------+
 |check_group|Use: appendfile|Type: boolean|Default: false|
-+-----------+---------------+-------------+--------------+
++--------------------------------------------------------+
 
 When this option is set, the group owner of the file defined by the file option
 is checked to see that it is the same as the group under which the delivery
 process is running. The default setting is false because the default file mode
 is 0600, which means that the group is irrelevant.
 
-+-----------+---------------+-------------+-------------+
++-------------------------------------------------------+
 |check_owner|Use: appendfile|Type: boolean|Default: true|
-+-----------+---------------+-------------+-------------+
++-------------------------------------------------------+
 
 When this option is set, the owner of the file defined by the file option is
 checked to ensure that it is the same as the user under which the delivery
 process is running.
 
-+------------+---------------+------------+------------------+
++------------------------------------------------------------+
 |check_string|Use: appendfile|Type: string|Default: see below|
-+------------+---------------+------------+------------------+
++------------------------------------------------------------+
 
 As appendfile writes the message, the start of each line is tested for matching
 check_string, and if it does, the initial matching characters are replaced by
@@ -19346,9 +20127,9 @@ escape_string = "\1\1\1\1 \n"
 message_prefix = "\1\1\1\1\n"
 message_suffix = "\1\1\1\1\n"
 
-+----------------+---------------+-------------+-------------+
++------------------------------------------------------------+
 |create_directory|Use: appendfile|Type: boolean|Default: true|
-+----------------+---------------+-------------+-------------+
++------------------------------------------------------------+
 
 When this option is true, Exim attempts to create any missing superior
 directories for the file that it is about to write. A created directory's mode
@@ -19360,9 +20141,9 @@ example, in Solaris, if the parent directory has the setgid bit set, its group
 is propagated to the child; if not, the currently set group is used. However,
 in FreeBSD, the parent's group is always used.
 
-+-----------+---------------+------------+-----------------+
++----------------------------------------------------------+
 |create_file|Use: appendfile|Type: string|Default: anywhere|
-+-----------+---------------+------------+-----------------+
++----------------------------------------------------------+
 
 This option constrains the location of files and directories that are created
 by this transport. It applies to files defined by the file option and
@@ -19376,9 +20157,9 @@ given for normal mailbox deliveries. It is intended for the case when file
 names are generated from users' .forward files. These are usually handled by an
 appendfile transport called address_file. See also file_must_exist.
 
-+---------+---------------+-------------+--------------+
++------------------------------------------------------+
 |directory|Use: appendfile|Type: string*|Default: unset|
-+---------+---------------+-------------+--------------+
++------------------------------------------------------+
 
 This option is mutually exclusive with the file option, but one of file or 
 directory must be set, unless the delivery is the direct result of a
@@ -19390,9 +20171,9 @@ appended to a single mailbox file. A number of different formats are provided
 (see maildir_format and mailstore_format), and see section 26.4 for further
 details of this form of delivery.
 
-+--------------+---------------+-------------+------------------+
++---------------------------------------------------------------+
 |directory_file|Use: appendfile|Type: string*|Default: see below|
-+--------------+---------------+-------------+------------------+
++---------------------------------------------------------------+
 
 When directory is set, but neither maildir_format nor mailstore_format is set, 
 appendfile delivers each message into a file whose name is obtained by
@@ -19404,22 +20185,22 @@ This generates a unique name from the current time, in base 62 form, and the
 inode of the file. The variable $inode is available only when expanding this
 option.
 
-+--------------+---------------+-------------------+-------------+
++----------------------------------------------------------------+
 |directory_mode|Use: appendfile|Type: octal integer|Default: 0700|
-+--------------+---------------+-------------------+-------------+
++----------------------------------------------------------------+
 
 If appendfile creates any directories as a result of the create_directory
 option, their mode is specified by this option.
 
-+-------------+---------------+------------+------------------------+
++-------------------------------------------------------------------+
 |escape_string|Use: appendfile|Type: string|Default: see description|
-+-------------+---------------+------------+------------------------+
++-------------------------------------------------------------------+
 
 See check_string above.
 
-+----+---------------+-------------+--------------+
++-------------------------------------------------+
 |file|Use: appendfile|Type: string*|Default: unset|
-+----+---------------+-------------+--------------+
++-------------------------------------------------+
 
 This option is mutually exclusive with the directory option, but one of file or
 directory must be set, unless the delivery is the direct result of a
@@ -19444,9 +20225,9 @@ create a file in the directory, so the "sticky" bit must be turned on for
 deliveries to be possible, or alternatively the group option can be used to run
 the delivery under a group id which has write access to the directory.
 
-+-----------+---------------+------------+--------------+
++-------------------------------------------------------+
 |file_format|Use: appendfile|Type: string|Default: unset|
-+-----------+---------------+------------+--------------+
++-------------------------------------------------------+
 
 This option requests the transport to check the format of an existing file
 before adding to it. The check consists of matching a specific string at the
@@ -19468,17 +20249,17 @@ assumed to match the current transport. If the start of a mailbox doesn't match
 any string, or if the transport named for a given string is not defined,
 delivery is deferred.
 
-+---------------+---------------+-------------+--------------+
++------------------------------------------------------------+
 |file_must_exist|Use: appendfile|Type: boolean|Default: false|
-+---------------+---------------+-------------+--------------+
++------------------------------------------------------------+
 
 If this option is true, the file specified by the file option must exist. A
 temporary error occurs if it does not, causing delivery to be deferred. If this
 option is false, the file is created if it does not exist.
 
-+------------------+---------------+----------+-----------+
++---------------------------------------------------------+
 |lock_fcntl_timeout|Use: appendfile|Type: time|Default: 0s|
-+------------------+---------------+----------+-----------+
++---------------------------------------------------------+
 
 By default, the appendfile transport uses non-blocking calls to fcntl() when
 locking an open mailbox file. If the call fails, the delivery process sleeps
@@ -19509,54 +20290,54 @@ local deliveries because of errors of the form
 
 failed to lock mailbox /some/file (fcntl)
 
-+------------------+---------------+----------+-----------+
++---------------------------------------------------------+
 |lock_flock_timeout|Use: appendfile|Type: time|Default: 0s|
-+------------------+---------------+----------+-----------+
++---------------------------------------------------------+
 
 This timeout applies to file locking when using flock() (see use_flock); the
 timeout operates in a similar manner to lock_fcntl_timeout.
 
-+-------------+---------------+----------+-----------+
++----------------------------------------------------+
 |lock_interval|Use: appendfile|Type: time|Default: 3s|
-+-------------+---------------+----------+-----------+
++----------------------------------------------------+
 
 This specifies the time to wait between attempts to lock the file. See below
 for details of locking.
 
-+------------+---------------+-------------+-----------+
++------------------------------------------------------+
 |lock_retries|Use: appendfile|Type: integer|Default: 10|
-+------------+---------------+-------------+-----------+
++------------------------------------------------------+
 
 This specifies the maximum number of attempts to lock the file. A value of zero
 is treated as 1. See below for details of locking.
 
-+-------------+---------------+-------------------+-------------+
++---------------------------------------------------------------+
 |lockfile_mode|Use: appendfile|Type: octal integer|Default: 0600|
-+-------------+---------------+-------------------+-------------+
++---------------------------------------------------------------+
 
 This specifies the mode of the created lock file, when a lock file is being
 used (see use_lockfile and use_mbx_lock).
 
-+----------------+---------------+----------+------------+
++--------------------------------------------------------+
 |lockfile_timeout|Use: appendfile|Type: time|Default: 30m|
-+----------------+---------------+----------+------------+
++--------------------------------------------------------+
 
 When a lock file is being used (see use_lockfile), if a lock file already
 exists and is older than this value, it is assumed to have been left behind by
 accident, and Exim attempts to remove it.
 
-+-----------------+---------------+-------------+--------------+
++--------------------------------------------------------------+
 |mailbox_filecount|Use: appendfile|Type: string*|Default: unset|
-+-----------------+---------------+-------------+--------------+
++--------------------------------------------------------------+
 
 If this option is set, it is expanded, and the result is taken as the current
 number of files in the mailbox. It must be a decimal number, optionally
 followed by K or M. This provides a way of obtaining this information from an
 external source that maintains the data.
 
-+------------+---------------+-------------+--------------+
++---------------------------------------------------------+
 |mailbox_size|Use: appendfile|Type: string*|Default: unset|
-+------------+---------------+-------------+--------------+
++---------------------------------------------------------+
 
 If this option is set, it is expanded, and the result is taken as the current
 size the mailbox. It must be a decimal number, optionally followed by K or M.
@@ -19564,9 +20345,9 @@ This provides a way of obtaining this information from an external source that
 maintains the data. This is likely to be helpful for maildir deliveries where
 it is computationally expensive to compute the size of a mailbox.
 
-+--------------+---------------+-------------+--------------+
++-----------------------------------------------------------+
 |maildir_format|Use: appendfile|Type: boolean|Default: false|
-+--------------+---------------+-------------+--------------+
++-----------------------------------------------------------+
 
 If this option is set with the directory option, the delivery is into a new
 file, in the "maildir" format that is used by other mail software. When the
@@ -19576,9 +20357,9 @@ causes the path received from the router to be treated as a directory, whether
 or not it ends with "/". This option is available only if SUPPORT_MAILDIR is
 present in Local/Makefile. See section 26.5 below for further details.
 
-+-----------------------------+---------------+------------+------------------+
++-----------------------------------------------------------------------------+
 |maildir_quota_directory_regex|Use: appendfile|Type: string|Default: See below|
-+-----------------------------+---------------+------------+------------------+
++-----------------------------------------------------------------------------+
 
 This option is relevant only when maildir_use_size_file is set. It defines a
 regular expression for specifying directories, relative to the quota directory
@@ -19598,23 +20379,23 @@ directory whose name is .Trash. When a directory is excluded from quota
 calculations, quota processing is bypassed for any messages that are delivered
 directly into that directory.
 
-+---------------+---------------+-------------+-----------+
++---------------------------------------------------------+
 |maildir_retries|Use: appendfile|Type: integer|Default: 10|
-+---------------+---------------+-------------+-----------+
++---------------------------------------------------------+
 
 This option specifies the number of times to retry when writing a file in
 "maildir" format. See section 26.5 below.
 
-+-----------+---------------+-------------+--------------+
++--------------------------------------------------------+
 |maildir_tag|Use: appendfile|Type: string*|Default: unset|
-+-----------+---------------+-------------+--------------+
++--------------------------------------------------------+
 
 This option applies only to deliveries in maildir format, and is described in
 section 26.5 below.
 
-+---------------------+----------------+-------------+--------------+
++-------------------------------------------------------------------+
 |maildir_use_size_file|Use: appendfile*|Type: boolean|Default: false|
-+---------------------+----------------+-------------+--------------+
++-------------------------------------------------------------------+
 
 The result of string expansion for this option must be a valid boolean value.
 If it is true, it enables support for maildirsize files. Exim creates a
@@ -19622,9 +20403,9 @@ maildirsize file in a maildir if one does not exist, taking the quota from the
 quota option of the transport. If quota is unset, the value is zero. See 
 maildir_quota_directory_regex above and section 26.5 below for further details.
 
-+--------------------------+---------------+------------+--------------+
++----------------------------------------------------------------------+
 |maildirfolder_create_regex|Use: appendfile|Type: string|Default: unset|
-+--------------------------+---------------+------------+--------------+
++----------------------------------------------------------------------+
 
 The value of this option is a regular expression. If it is unset, it has no
 effect. Otherwise, before a maildir delivery takes place, the pattern is
@@ -19634,31 +20415,31 @@ If there is a match, Exim checks for the existence of a file called
 maildirfolder in the directory, and creates it if it does not exist. See
 section 26.5 for more details.
 
-+----------------+---------------+-------------+--------------+
++-------------------------------------------------------------+
 |mailstore_format|Use: appendfile|Type: boolean|Default: false|
-+----------------+---------------+-------------+--------------+
++-------------------------------------------------------------+
 
 If this option is set with the directory option, the delivery is into two new
 files in "mailstore" format. The option is available only if SUPPORT_MAILSTORE
 is present in Local/Makefile. See section 26.4 below for further details.
 
-+----------------+---------------+-------------+--------------+
++-------------------------------------------------------------+
 |mailstore_prefix|Use: appendfile|Type: string*|Default: unset|
-+----------------+---------------+-------------+--------------+
++-------------------------------------------------------------+
 
 This option applies only to deliveries in mailstore format, and is described in
 section 26.4 below.
 
-+----------------+---------------+-------------+--------------+
++-------------------------------------------------------------+
 |mailstore_suffix|Use: appendfile|Type: string*|Default: unset|
-+----------------+---------------+-------------+--------------+
++-------------------------------------------------------------+
 
 This option applies only to deliveries in mailstore format, and is described in
 section 26.4 below.
 
-+----------+---------------+-------------+--------------+
++-------------------------------------------------------+
 |mbx_format|Use: appendfile|Type: boolean|Default: false|
-+----------+---------------+-------------+--------------+
++-------------------------------------------------------+
 
 This option is available only if Exim has been compiled with SUPPORT_MBX set in
 Local/Makefile. If mbx_format is set with the file option, the message is
@@ -19688,9 +20469,9 @@ standard version of c-client, because as long as it has a mailbox open (this
 means for the whole of a Pine or IMAP session), Exim will not be able to append
 messages to it.
 
-+--------------+---------------+-------------+------------------+
++---------------------------------------------------------------+
 |message_prefix|Use: appendfile|Type: string*|Default: see below|
-+--------------+---------------+-------------+------------------+
++---------------------------------------------------------------+
 
 The string specified here is expanded and output at the start of every message.
 The default is unset unless file is specified and use_bsmtp is not set, in
@@ -19702,9 +20483,9 @@ message_prefix = "From ${if def:return_path{$return_path}\
 Note: If you set use_crlf true, you must change any occurrences of "\n" to "\r\
 n" in message_prefix.
 
-+--------------+---------------+-------------+------------------+
++---------------------------------------------------------------+
 |message_suffix|Use: appendfile|Type: string*|Default: see below|
-+--------------+---------------+-------------+------------------+
++---------------------------------------------------------------+
 
 The string specified here is expanded and output at the end of every message.
 The default is unset unless file is specified and use_bsmtp is not set, in
@@ -19716,9 +20497,9 @@ message_suffix =
 Note: If you set use_crlf true, you must change any occurrences of "\n" to "\r\
 n" in message_suffix.
 
-+----+---------------+-------------------+-------------+
++------------------------------------------------------+
 |mode|Use: appendfile|Type: octal integer|Default: 0600|
-+----+---------------+-------------------+-------------+
++------------------------------------------------------+
 
 If the output file is created, it is given this mode. If it already exists and
 has wider permissions, they are reduced to this mode. If it has narrower
@@ -19727,26 +20508,26 @@ the delivery is the result of a save command in a filter file specifying a
 particular mode, the mode of the output file is always forced to take that
 value, and this option is ignored.
 
-+------------------+---------------+-------------+-------------+
++--------------------------------------------------------------+
 |mode_fail_narrower|Use: appendfile|Type: boolean|Default: true|
-+------------------+---------------+-------------+-------------+
++--------------------------------------------------------------+
 
 This option applies in the case when an existing mailbox file has a narrower
 mode than that specified by the mode option. If mode_fail_narrower is true, the
 delivery is deferred ("mailbox has the wrong mode"); otherwise Exim continues
 with the delivery attempt, using the existing mode of the file.
 
-+-------------+---------------+-------------+--------------+
++----------------------------------------------------------+
 |notify_comsat|Use: appendfile|Type: boolean|Default: false|
-+-------------+---------------+-------------+--------------+
++----------------------------------------------------------+
 
 If this option is true, the comsat daemon is notified after every successful
 delivery to a user mailbox. This is the daemon that notifies logged on users
 about incoming mail.
 
-+-----+---------------+-------------+--------------+
++--------------------------------------------------+
 |quota|Use: appendfile|Type: string*|Default: unset|
-+-----+---------------+-------------+--------------+
++--------------------------------------------------+
 
 This option imposes a limit on the size of the file to which Exim is appending,
 or to the total space used in the directory tree when the directory option is
@@ -19792,18 +20573,18 @@ exceeding the quota does not include the current message. Thus, deliveries
 continue until the quota has been exceeded; thereafter, no further messages are
 delivered. See also quota_warn_threshold.
 
-+---------------+---------------+-------------+--------------+
++------------------------------------------------------------+
 |quota_directory|Use: appendfile|Type: string*|Default: unset|
-+---------------+---------------+-------------+--------------+
++------------------------------------------------------------+
 
 This option defines the directory to check for quota purposes when delivering
 into individual files. The default is the delivery directory, or, if a file
 called maildirfolder exists in a maildir directory, the parent of the delivery
 directory.
 
-+---------------+---------------+-------------+----------+
++--------------------------------------------------------+
 |quota_filecount|Use: appendfile|Type: string*|Default: 0|
-+---------------+---------------+-------------+----------+
++--------------------------------------------------------+
 
 This option applies when the directory option is set. It limits the total
 number of files in the directory (compare the inode limit in system quotas). It
@@ -19811,15 +20592,15 @@ can only be used if quota is also set. The value is expanded; an expansion
 failure causes delivery to be deferred. A value of zero is interpreted as "no
 quota".
 
-+------------------+---------------+-------------+-------------+
++--------------------------------------------------------------+
 |quota_is_inclusive|Use: appendfile|Type: boolean|Default: true|
-+------------------+---------------+-------------+-------------+
++--------------------------------------------------------------+
 
 See quota above.
 
-+----------------+---------------+------------+--------------+
++------------------------------------------------------------+
 |quota_size_regex|Use: appendfile|Type: string|Default: unset|
-+----------------+---------------+------------+--------------+
++------------------------------------------------------------+
 
 This option applies when one of the delivery modes that writes a separate file
 for each message is being used. When Exim wants to find the size of one of
@@ -19845,9 +20626,9 @@ sometimes add other information onto the ends of message file names.
 
 Section 26.7 contains further information.
 
-+------------------+---------------+-------------+------------------+
++-------------------------------------------------------------------+
 |quota_warn_message|Use: appendfile|Type: string*|Default: see below|
-+------------------+---------------+-------------+------------------+
++-------------------------------------------------------------------+
 
 See below for the use of this option. If it is not set when 
 quota_warn_threshold is set, it defaults to
@@ -19861,9 +20642,9 @@ quota_warn_message = "\
   a warning threshold that is\n\
   set by the system administrator.\n"
 
-+--------------------+---------------+-------------+----------+
++-------------------------------------------------------------+
 |quota_warn_threshold|Use: appendfile|Type: string*|Default: 0|
-+--------------------+---------------+-------------+----------+
++-------------------------------------------------------------+
 
 This option is expanded in the same way as quota (see above). If the resulting
 value is greater than zero, and delivery of the message causes the size of the
@@ -19892,18 +20673,18 @@ The quota option does not have to be set in order to use this option; they are
 independent of one another except when the threshold is specified as a
 percentage.
 
-+---------+---------------+-------------+--------------+
++------------------------------------------------------+
 |use_bsmtp|Use: appendfile|Type: boolean|Default: false|
-+---------+---------------+-------------+--------------+
++------------------------------------------------------+
 
 If this option is set true, appendfile writes messages in "batch SMTP" format,
 with the envelope sender and recipient(s) included as SMTP commands. If you
 want to include a leading HELO command with such messages, you can do so by
-setting the message_prefix option. See section 47.10 for details of batch SMTP.
+setting the message_prefix option. See section 48.10 for details of batch SMTP.
 
-+--------+---------------+-------------+--------------+
++-----------------------------------------------------+
 |use_crlf|Use: appendfile|Type: boolean|Default: false|
-+--------+---------------+-------------+--------------+
++-----------------------------------------------------+
 
 This option causes lines to be terminated with the two-character CRLF sequence
 (carriage return, linefeed) instead of just a linefeed character. In the case
@@ -19917,9 +20698,9 @@ carriage return characters if these are needed. In cases where these options
 have non-empty defaults, the values end with a single linefeed, so they must be
 changed to end with "\r\n" if use_crlf is set.
 
-+--------------+---------------+-------------+------------------+
++---------------------------------------------------------------+
 |use_fcntl_lock|Use: appendfile|Type: boolean|Default: see below|
-+--------------+---------------+-------------+------------------+
++---------------------------------------------------------------+
 
 This option controls the use of the fcntl() function to lock a file for
 exclusive use when a message is being appended. It is set by default unless 
@@ -19927,9 +20708,9 @@ use_flock_lock is set. Otherwise, it should be turned off only if you know that
 all your MUAs use lock file locking. When both use_fcntl_lock and 
 use_flock_lock are unset, use_lockfile must be set.
 
-+--------------+---------------+-------------+--------------+
++-----------------------------------------------------------+
 |use_flock_lock|Use: appendfile|Type: boolean|Default: false|
-+--------------+---------------+-------------+--------------+
++-----------------------------------------------------------+
 
 This option is provided to support the use of flock() for file locking, for the
 few situations where it is needed. Most modern operating systems support fcntl
@@ -19949,9 +20730,9 @@ use it, and any attempt to do so will cause a configuration error.
 Warning: flock() locks do not work on NFS files (unless flock() is just being
 mapped onto fcntl() by the OS).
 
-+------------+---------------+-------------+------------------+
++-------------------------------------------------------------+
 |use_lockfile|Use: appendfile|Type: boolean|Default: see below|
-+------------+---------------+-------------+------------------+
++-------------------------------------------------------------+
 
 If this option is turned off, Exim does not attempt to create a lock file when
 appending to a mailbox file. In this situation, the only locking is by fcntl().
@@ -19969,9 +20750,9 @@ The use_lockfile option is set by default unless use_mbx_lock is set. It is not
 possible to turn both use_lockfile and use_fcntl_lock off, except when 
 mbx_format is set.
 
-+------------+---------------+-------------+------------------+
++-------------------------------------------------------------+
 |use_mbx_lock|Use: appendfile|Type: boolean|Default: see below|
-+------------+---------------+-------------+------------------+
++-------------------------------------------------------------+
 
 This option is available only if Exim has been compiled with SUPPORT_MBX set in
 Local/Makefile. Setting the option specifies that special MBX locking rules be
@@ -20312,7 +21093,7 @@ $mailstore_basename is available for use during these expansions.
 If neither maildir_format nor mailstore_format is set, a single new file is
 created directly in the named directory. For example, when delivering messages
 into files in batched SMTP format for later delivery to some host (see section
-47.10), a setting such as
+48.10), a setting such as
 
 directory = /var/bsmtp/$host
 
@@ -20388,82 +21169,82 @@ are just discarded.
 27.1 Private options for autoreply
 ----------------------------------
 
-+---+--------------+-------------+--------------+
++-----------------------------------------------+
 |bcc|Use: autoreply|Type: string*|Default: unset|
-+---+--------------+-------------+--------------+
++-----------------------------------------------+
 
 This specifies the addresses that are to receive "blind carbon copies" of the
 message when the message is specified by the transport.
 
-+--+--------------+-------------+--------------+
++----------------------------------------------+
 |cc|Use: autoreply|Type: string*|Default: unset|
-+--+--------------+-------------+--------------+
++----------------------------------------------+
 
 This specifies recipients of the message and the contents of the Cc: header
 when the message is specified by the transport.
 
-+----+--------------+-------------+--------------+
++------------------------------------------------+
 |file|Use: autoreply|Type: string*|Default: unset|
-+----+--------------+-------------+--------------+
++------------------------------------------------+
 
 The contents of the file are sent as the body of the message when the message
 is specified by the transport. If both file and text are set, the text string
 comes first.
 
-+-----------+--------------+-------------+--------------+
++-------------------------------------------------------+
 |file_expand|Use: autoreply|Type: boolean|Default: false|
-+-----------+--------------+-------------+--------------+
++-------------------------------------------------------+
 
 If this is set, the contents of the file named by the file option are subjected
 to string expansion as they are added to the message.
 
-+-------------+--------------+-------------+--------------+
++---------------------------------------------------------+
 |file_optional|Use: autoreply|Type: boolean|Default: false|
-+-------------+--------------+-------------+--------------+
++---------------------------------------------------------+
 
 If this option is true, no error is generated if the file named by the file
 option or passed with the address does not exist or cannot be read.
 
-+----+--------------+-------------+--------------+
++------------------------------------------------+
 |from|Use: autoreply|Type: string*|Default: unset|
-+----+--------------+-------------+--------------+
++------------------------------------------------+
 
 This specifies the contents of the From: header when the message is specified
 by the transport.
 
-+-------+--------------+-------------+--------------+
++---------------------------------------------------+
 |headers|Use: autoreply|Type: string*|Default: unset|
-+-------+--------------+-------------+--------------+
++---------------------------------------------------+
 
 This specifies additional RFC 2822 headers that are to be added to the message
 when the message is specified by the transport. Several can be given by using "
 \n" to separate them. There is no check on the format.
 
-+---+--------------+-------------+--------------+
++-----------------------------------------------+
 |log|Use: autoreply|Type: string*|Default: unset|
-+---+--------------+-------------+--------------+
++-----------------------------------------------+
 
 This option names a file in which a record of every message sent is logged when
 the message is specified by the transport.
 
-+----+--------------+-------------------+-------------+
++-----------------------------------------------------+
 |mode|Use: autoreply|Type: octal integer|Default: 0600|
-+----+--------------+-------------------+-------------+
++-----------------------------------------------------+
 
 If either the log file or the "once" file has to be created, this mode is used.
 
-+----------+--------------+-------------------+--------------+
++------------------------------------------------------------+
 |never_mail|Use: autoreply|Type: address list*|Default: unset|
-+----------+--------------+-------------------+--------------+
++------------------------------------------------------------+
 
 If any run of the transport creates a message with a recipient that matches any
 item in the list, that recipient is quietly discarded. If all recipients are
 discarded, no message is created. This applies both when the recipients are
 generated by a filter and when they are specified in the transport.
 
-+----+--------------+-------------+--------------+
++------------------------------------------------+
 |once|Use: autoreply|Type: string*|Default: unset|
-+----+--------------+-------------+--------------+
++------------------------------------------------+
 
 This option names a file or DBM database in which a record of each To:
 recipient is kept when the message is specified by the transport. Note: This
@@ -20490,37 +21271,37 @@ that a given recipient may receive multiple messages, but at unpredictable
 intervals that depend on the rate of turnover of addresses in the file. If 
 once_repeat is set, it specifies a maximum time between repeats.
 
-+--------------+--------------+-------------+----------+
++------------------------------------------------------+
 |once_file_size|Use: autoreply|Type: integer|Default: 0|
-+--------------+--------------+-------------+----------+
++------------------------------------------------------+
 
 See once above.
 
-+-----------+--------------+-----------+-----------+
++--------------------------------------------------+
 |once_repeat|Use: autoreply|Type: time*|Default: 0s|
-+-----------+--------------+-----------+-----------+
++--------------------------------------------------+
 
 See once above. After expansion, the value of this option must be a valid time
 value.
 
-+--------+--------------+-------------+--------------+
++----------------------------------------------------+
 |reply_to|Use: autoreply|Type: string*|Default: unset|
-+--------+--------------+-------------+--------------+
++----------------------------------------------------+
 
 This specifies the contents of the Reply-To: header when the message is
 specified by the transport.
 
-+--------------+--------------+-------------+--------------+
++----------------------------------------------------------+
 |return_message|Use: autoreply|Type: boolean|Default: false|
-+--------------+--------------+-------------+--------------+
++----------------------------------------------------------+
 
 If this is set, a copy of the original message is returned with the new
 message, subject to the maximum size set in the return_size_limit global
 configuration option.
 
-+-------+--------------+-------------+--------------+
++---------------------------------------------------+
 |subject|Use: autoreply|Type: string*|Default: unset|
-+-------+--------------+-------------+--------------+
++---------------------------------------------------+
 
 This specifies the contents of the Subject: header when the message is
 specified by the transport. It is tempting to quote the original subject in
@@ -20534,17 +21315,17 @@ bounce messages as subscription confirmations. Well-managed lists require a
 non-bounce message to confirm a subscription, so the danger is relatively
 small.
 
-+----+--------------+-------------+--------------+
++------------------------------------------------+
 |text|Use: autoreply|Type: string*|Default: unset|
-+----+--------------+-------------+--------------+
++------------------------------------------------+
 
 This specifies a single string to be used as the body of the message when the
 message is specified by the transport. If both text and file are set, the text
 comes first.
 
-+--+--------------+-------------+--------------+
++----------------------------------------------+
 |to|Use: autoreply|Type: string*|Default: unset|
-+--+--------------+-------------+--------------+
++----------------------------------------------+
 
 This specifies recipients of the message and the contents of the To: header
 when the message is specified by the transport.
@@ -20567,24 +21348,24 @@ TRANSPORT_LMTP=yes
 is present in your Local/Makefile in order to have the lmtp transport included
 in the Exim binary. The private options of the lmtp transport are as follows:
 
-+--------+---------+-------------+--------------+
++-----------------------------------------------+
 |batch_id|Use: lmtp|Type: string*|Default: unset|
-+--------+---------+-------------+--------------+
++-----------------------------------------------+
 
 See the description of local delivery batching in chapter 25.
 
-+---------+---------+-------------+----------+
++--------------------------------------------+
 |batch_max|Use: lmtp|Type: integer|Default: 1|
-+---------+---------+-------------+----------+
++--------------------------------------------+
 
 This limits the number of addresses that can be handled in a single delivery.
 Most LMTP servers can handle several addresses at once, so it is normally a
 good idea to increase this value. See the description of local delivery
 batching in chapter 25.
 
-+-------+---------+-------------+--------------+
++----------------------------------------------+
 |command|Use: lmtp|Type: string*|Default: unset|
-+-------+---------+-------------+--------------+
++----------------------------------------------+
 
 This option must be set if socket is not set. The string is a command which is
 run in a separate process. It is split up into a command name and list of
@@ -20593,25 +21374,25 @@ number of arguments). The command is run directly, not via a shell. The message
 is passed to the new process using the standard input and output to operate the
 LMTP protocol.
 
-+------------+---------+-------------+--------------+
++---------------------------------------------------+
 |ignore_quota|Use: lmtp|Type: boolean|Default: false|
-+------------+---------+-------------+--------------+
++---------------------------------------------------+
 
 If this option is set true, the string "IGNOREQUOTA" is added to RCPT commands,
 provided that the LMTP server has advertised support for IGNOREQUOTA in its
 response to the LHLO command.
 
-+------+---------+-------------+--------------+
++---------------------------------------------+
 |socket|Use: lmtp|Type: string*|Default: unset|
-+------+---------+-------------+--------------+
++---------------------------------------------+
 
 This option must be set if command is not set. The result of expansion must be
 the name of a Unix domain socket. The transport connects to the socket and
 delivers the message to it using the LMTP protocol.
 
-+-------+---------+----------+-----------+
++----------------------------------------+
 |timeout|Use: lmtp|Type: time|Default: 5m|
-+-------+---------+----------+-----------+
++----------------------------------------+
 
 The transport is aborted if the created process or Unix domain socket does not
 respond to LMTP commands or message input within this timeout. Delivery is
@@ -20675,7 +21456,8 @@ and chapter 25 for a discussion of local delivery batching.
 If two messages arrive at almost the same time, and both are routed to a pipe
 delivery, the two pipe transports may be run concurrently. You must ensure that
 any pipe commands you set up are robust against this happening. If the commands
-write to a file, the exim_lock utility might be of use.
+write to a file, the exim_lock utility might be of use. Alternatively the 
+max_parallel option could be used with a value of "1" to enforce serialization.
 
 
 29.2 Returned status and data
@@ -20790,7 +21572,8 @@ mechanism is inherently less secure.
 The environment variables listed below are set up when the command is invoked.
 This list is a compromise for maximum compatibility with other MTAs. Note that
 the environment option can be used to add additional variables to this
-environment.
+environment. The environment for the pipe transport is not subject to the 
+add_environment and keep_environment main config options.
 
 DOMAIN               the domain of the address
 HOME                 the home directory, if set
@@ -20829,9 +21612,9 @@ directory if check_local_user is set.
 29.5 Private options for pipe
 -----------------------------
 
-+--------------+---------+------------------+--------------+
++----------------------------------------------------------+
 |allow_commands|Use: pipe|Type: string list*|Default: unset|
-+--------------+---------+------------------+--------------+
++----------------------------------------------------------+
 
 The string is expanded, and is then interpreted as a colon-separated list of
 permitted commands. If restrict_to_path is not set, the only commands permitted
@@ -20848,22 +21631,22 @@ allow_commands = /usr/bin/vacation
 and restrict_to_path is not set, the only permitted command is /usr/bin/
 vacation. The allow_commands option may not be set if use_shell is set.
 
-+--------+---------+-------------+--------------+
++-----------------------------------------------+
 |batch_id|Use: pipe|Type: string*|Default: unset|
-+--------+---------+-------------+--------------+
++-----------------------------------------------+
 
 See the description of local delivery batching in chapter 25.
 
-+---------+---------+-------------+----------+
++--------------------------------------------+
 |batch_max|Use: pipe|Type: integer|Default: 1|
-+---------+---------+-------------+----------+
++--------------------------------------------+
 
 This limits the number of addresses that can be handled in a single delivery.
 See the description of local delivery batching in chapter 25.
 
-+------------+---------+------------+--------------+
++--------------------------------------------------+
 |check_string|Use: pipe|Type: string|Default: unset|
-+------------+---------+------------+--------------+
++--------------------------------------------------+
 
 As pipe writes the message, the start of each line is tested for matching 
 check_string, and if it does, the initial matching characters are replaced by
@@ -20873,9 +21656,9 @@ contains is significant. When use_bsmtp is set, the contents of check_string
 and escape_string are forced to values that implement the SMTP escaping
 protocol. Any settings made in the configuration file are ignored.
 
-+-------+---------+-------------+--------------+
++----------------------------------------------+
 |command|Use: pipe|Type: string*|Default: unset|
-+-------+---------+-------------+--------------+
++----------------------------------------------+
 
 This option need not be set when pipe is being used to deliver to pipes
 obtained directly from address redirections. In other cases, the option must be
@@ -20884,41 +21667,41 @@ the path option below). The command is split up into separate arguments by
 Exim, and each argument is separately expanded, as described in section 29.3
 above.
 
-+-----------+---------+-------------+--------------+
++--------------------------------------------------+
 |environment|Use: pipe|Type: string*|Default: unset|
-+-----------+---------+-------------+--------------+
++--------------------------------------------------+
 
 This option is used to add additional variables to the environment in which the
 command runs (see section 29.4 for the default list). Its value is a string
 which is expanded, and then interpreted as a colon-separated list of
 environment settings of the form <name>=<value>.
 
-+-------------+---------+------------+--------------+
++---------------------------------------------------+
 |escape_string|Use: pipe|Type: string|Default: unset|
-+-------------+---------+------------+--------------+
++---------------------------------------------------+
 
 See check_string above.
 
-+----------------+---------+-------------+--------------+
++-------------------------------------------------------+
 |freeze_exec_fail|Use: pipe|Type: boolean|Default: false|
-+----------------+---------+-------------+--------------+
++-------------------------------------------------------+
 
 Failure to exec the command in a pipe transport is by default treated like any
 other failure while running the command. However, if freeze_exec_fail is set,
 failure to exec is treated specially, and causes the message to be frozen,
 whatever the setting of ignore_status.
 
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 |freeze_signal|Use: pipe|Type: boolean|Default: false|
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 
 Normally if the process run by a command in a pipe transport exits on a signal,
 a bounce message is sent. If freeze_signal is set, the message will be frozen
 in Exim's queue instead.
 
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 |force_command|Use: pipe|Type: boolean|Default: false|
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 
 Normally when a router redirects an address directly to a pipe command the 
 command option on the transport is ignored. If force_command is set, the 
@@ -20932,9 +21715,9 @@ Note that $address_pipe is handled specially in command when force_command is
 set, expanding out to the original argument vector as separate items, similarly
 to a Unix shell ""$@"" construct.
 
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 |ignore_status|Use: pipe|Type: boolean|Default: false|
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 
 If this option is true, the status returned by the subprocess that is set up to
 run the command is ignored, and Exim behaves as if zero had been returned.
@@ -20945,35 +21728,37 @@ temp_errors; these cause the delivery to be deferred and tried again later.
 Note: This option does not apply to timeouts, which do not return a status. See
 the timeout_defer option for how timeouts are handled.
 
-+----------------+---------+-------------+--------------+
++-------------------------------------------------------+
 |log_defer_output|Use: pipe|Type: boolean|Default: false|
-+----------------+---------+-------------+--------------+
++-------------------------------------------------------+
 
 If this option is set, and the status returned by the command is one of the
 codes listed in temp_errors (that is, delivery was deferred), and any output
-was produced, the first line of it is written to the main log.
+was produced on stdout or stderr, the first line of it is written to the main
+log.
 
-+---------------+---------+-------------+--------------+
++------------------------------------------------------+
 |log_fail_output|Use: pipe|Type: boolean|Default: false|
-+---------------+---------+-------------+--------------+
++------------------------------------------------------+
 
-If this option is set, and the command returns any output, and also ends with a
-return code that is neither zero nor one of the return codes listed in 
-temp_errors (that is, the delivery failed), the first line of output is written
-to the main log. This option and log_output are mutually exclusive. Only one of
-them may be set.
+If this option is set, and the command returns any output on stdout or stderr,
+and also ends with a return code that is neither zero nor one of the return
+codes listed in temp_errors (that is, the delivery failed), the first line of
+output is written to the main log. This option and log_output are mutually
+exclusive. Only one of them may be set.
 
-+----------+---------+-------------+--------------+
++-------------------------------------------------+
 |log_output|Use: pipe|Type: boolean|Default: false|
-+----------+---------+-------------+--------------+
++-------------------------------------------------+
 
-If this option is set and the command returns any output, the first line of
-output is written to the main log, whatever the return code. This option and 
-log_fail_output are mutually exclusive. Only one of them may be set.
+If this option is set and the command returns any output on stdout or stderr,
+the first line of output is written to the main log, whatever the return code.
+This option and log_fail_output are mutually exclusive. Only one of them may be
+set.
 
-+----------+---------+-------------+------------+
++-----------------------------------------------+
 |max_output|Use: pipe|Type: integer|Default: 20K|
-+----------+---------+-------------+------------+
++-----------------------------------------------+
 
 This specifies the maximum amount of output that the command may produce on its
 standard output and standard error file combined. If the limit is exceeded, the
@@ -20983,9 +21768,9 @@ the options that control what is done with such output (for example,
 return_output). Because of buffering effects, the amount of output may exceed
 the limit by a small amount before Exim notices.
 
-+--------------+---------+-------------+------------------+
++---------------------------------------------------------+
 |message_prefix|Use: pipe|Type: string*|Default: see below|
-+--------------+---------+-------------+------------------+
++---------------------------------------------------------+
 
 The string specified here is expanded and output at the start of every message.
 The default is unset if use_bsmtp is set. Otherwise it is
@@ -21003,9 +21788,9 @@ message_prefix =
 Note: If you set use_crlf true, you must change any occurrences of "\n" to "\r\
 n" in message_prefix.
 
-+--------------+---------+-------------+------------------+
++---------------------------------------------------------+
 |message_suffix|Use: pipe|Type: string*|Default: see below|
-+--------------+---------+-------------+------------------+
++---------------------------------------------------------+
 
 The string specified here is expanded and output at the end of every message.
 The default is unset if use_bsmtp is set. Otherwise it is a single newline. The
@@ -21016,22 +21801,20 @@ message_suffix =
 Note: If you set use_crlf true, you must change any occurrences of "\n" to "\r\
 n" in message_suffix.
 
-+----+---------+------------+------------------+
-|path|Use: pipe|Type: string|Default: see below|
-+----+---------+------------+------------------+
-
-This option specifies the string that is set up in the PATH environment
-variable of the subprocess. The default is:
++---------------------------------------------------+
+|path|Use: pipe|Type: string*|Default: /bin:/usr/bin|
++---------------------------------------------------+
 
-/bin:/usr/bin
+This option is expanded and
 
-If the command option does not yield an absolute path name, the command is
-sought in the PATH directories, in the usual way. Warning: This does not apply
-to a command specified as a transport filter.
+specifies the string that is set up in the PATH environment variable of the
+subprocess. If the command option does not yield an absolute path name, the
+command is sought in the PATH directories, in the usual way. Warning: This does
+not apply to a command specified as a transport filter.
 
-+---------------+---------+-------------+--------------+
++------------------------------------------------------+
 |permit_coredump|Use: pipe|Type: boolean|Default: false|
-+---------------+---------+-------------+--------------+
++------------------------------------------------------+
 
 Normally Exim inhibits core-dumps during delivery. If you have a need to get a
 core-dump of a pipe command, enable this command. This enables core-dumps
@@ -21042,9 +21825,9 @@ consumption can be quite high. Note also that Exim is typically installed as a
 setuid binary and most operating systems will inhibit coredumps of these by
 default, so further OS-specific action may be required.
 
-+---------------+---------+-------------+--------------+
++------------------------------------------------------+
 |pipe_as_creator|Use: pipe|Type: boolean|Default: false|
-+---------------+---------+-------------+--------------+
++------------------------------------------------------+
 
 If the generic user option is not set and this option is true, the delivery
 process is run under the uid that was in force when Exim was originally called
@@ -21052,9 +21835,9 @@ to accept the message. If the group id is not otherwise set (via the generic
 group option), the gid that was in force when Exim was originally called to
 accept the message is used.
 
-+----------------+---------+-------------+--------------+
++-------------------------------------------------------+
 |restrict_to_path|Use: pipe|Type: boolean|Default: false|
-+----------------+---------+-------------+--------------+
++-------------------------------------------------------+
 
 When this option is set, any command name not listed in allow_commands must
 contain no slashes. The command is searched for only in the directories listed
@@ -21062,9 +21845,9 @@ in the path option. This option is intended for use in the case when a pipe
 command has been generated from a user's .forward file. This is usually handled
 by a pipe transport called address_pipe.
 
-+------------------+---------+-------------+--------------+
++---------------------------------------------------------+
 |return_fail_output|Use: pipe|Type: boolean|Default: false|
-+------------------+---------+-------------+--------------+
++---------------------------------------------------------+
 
 If this option is true, and the command produced any output and ended with a
 return code other than zero or one of the codes listed in temp_errors (that is,
@@ -21073,9 +21856,9 @@ the message has a null sender (that is, it is itself a bounce message), output
 from the command is discarded. This option and return_output are mutually
 exclusive. Only one of them may be set.
 
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 |return_output|Use: pipe|Type: boolean|Default: false|
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 
 If this option is true, and the command produced any output, the delivery is
 deemed to have failed whatever the return code from the command, and the output
@@ -21085,9 +21868,9 @@ output from the command is always discarded, whatever the setting of this
 option. This option and return_fail_output are mutually exclusive. Only one of
 them may be set.
 
-+-----------+---------+-----------------+------------------+
++----------------------------------------------------------+
 |temp_errors|Use: pipe|Type: string list|Default: see below|
-+-----------+---------+-----------------+------------------+
++----------------------------------------------------------+
 
 This option contains either a colon-separated list of numbers, or a single
 asterisk. If ignore_status is false and return_output is not set, and the
@@ -21099,9 +21882,9 @@ EX_TEMPFAIL and EX_CANTCREAT in sysexits.h. If Exim is compiled on a system
 that does not define these macros, it assumes values of 75 and 73,
 respectively.
 
-+-------+---------+----------+-----------+
++----------------------------------------+
 |timeout|Use: pipe|Type: time|Default: 1h|
-+-------+---------+----------+-----------+
++----------------------------------------+
 
 If the command fails to complete within this time, it is killed. This normally
 causes the delivery to fail (but see timeout_defer). A zero time interval
@@ -21110,42 +21893,42 @@ command are also killed, Exim makes the initial process a process group leader,
 and kills the whole process group on a timeout. However, this can be defeated
 if one of the processes starts a new process group.
 
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 |timeout_defer|Use: pipe|Type: boolean|Default: false|
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 
 A timeout in a pipe transport, either in the command that the transport runs,
 or in a transport filter that is associated with it, is by default treated as a
 hard error, and the delivery fails. However, if timeout_defer is set true, both
 kinds of timeout become temporary errors, causing the delivery to be deferred.
 
-+-----+---------+-------------------+------------+
++------------------------------------------------+
 |umask|Use: pipe|Type: octal integer|Default: 022|
-+-----+---------+-------------------+------------+
++------------------------------------------------+
 
 This specifies the umask setting for the subprocess that runs the command.
 
-+---------+---------+-------------+--------------+
++------------------------------------------------+
 |use_bsmtp|Use: pipe|Type: boolean|Default: false|
-+---------+---------+-------------+--------------+
++------------------------------------------------+
 
 If this option is set true, the pipe transport writes messages in "batch SMTP"
 format, with the envelope sender and recipient(s) included as SMTP commands. If
 you want to include a leading HELO command with such messages, you can do so by
-setting the message_prefix option. See section 47.10 for details of batch SMTP.
+setting the message_prefix option. See section 48.10 for details of batch SMTP.
 
-+------------------+---------+-------------+--------------+
++---------------------------------------------------------+
 |use_classresources|Use: pipe|Type: boolean|Default: false|
-+------------------+---------+-------------+--------------+
++---------------------------------------------------------+
 
 This option is available only when Exim is running on FreeBSD, NetBSD, or BSD/
 OS. If it is set true, the setclassresources() function is used to set resource
 limits when a pipe transport is run to perform a delivery. The limits for the
 uid under which the pipe is to run are obtained from the login class database.
 
-+--------+---------+-------------+--------------+
++-----------------------------------------------+
 |use_crlf|Use: pipe|Type: boolean|Default: false|
-+--------+---------+-------------+--------------+
++-----------------------------------------------+
 
 This option causes lines to be terminated with the two-character CRLF sequence
 (carriage return, linefeed) instead of just a linefeed character. In the case
@@ -21158,9 +21941,9 @@ needed. When use_bsmtp is not set, the default values for both message_prefix
 and message_suffix end with a single linefeed, so their values must be changed
 to end with "\r\n" if use_crlf is set.
 
-+---------+---------+-------------+--------------+
++------------------------------------------------+
 |use_shell|Use: pipe|Type: boolean|Default: false|
-+---------+---------+-------------+--------------+
++------------------------------------------------+
 
 If this option is set, it causes the command to be passed to /bin/sh instead of
 being run directly from the transport, as described in section 29.3. This is
@@ -21265,7 +22048,7 @@ two ways:
     run of the smtp transport over a single TCP/IP connection. (What Exim
     actually does when it has too many addresses to send in one message also
     depends on the value of the global remote_max_parallel option. Details are
-    given in section 47.1.)
+    given in section 48.1.)
 
   * When a message has been successfully delivered over a TCP/IP connection,
     Exim looks in its hints database to see if there are any other messages
@@ -21312,9 +22095,9 @@ removed in a future release.
 
 The private options of the smtp transport are as follows:
 
-+----------------------------+---------+-------------+-------------+
++------------------------------------------------------------------+
 |address_retry_include_sender|Use: smtp|Type: boolean|Default: true|
-+----------------------------+---------+-------------+-------------+
++------------------------------------------------------------------+
 
 When an address is delayed because of a 4xx response to a RCPT command, it is
 the combination of sender and recipient that is delayed in subsequent queue
@@ -21323,9 +22106,9 @@ reference to the sender (which is what earlier versions of Exim did), by
 setting address_retry_include_sender false. However, this can lead to problems
 with servers that regularly issue 4xx responses to RCPT commands.
 
-+---------------+---------+-------------+--------------+
++------------------------------------------------------+
 |allow_localhost|Use: smtp|Type: boolean|Default: false|
-+---------------+---------+-------------+--------------+
++------------------------------------------------------+
 
 When a host specified in hosts or fallback_hosts (see below) turns out to be
 the local host, or is listed in hosts_treat_as_local, delivery is deferred by
@@ -21334,9 +22117,9 @@ anyway. This should be used only in special cases when the configuration
 ensures that no looping will result (for example, a differently configured Exim
 is listening on the port to which the message is sent).
 
-+--------------------+---------+-------------+--------------+
++-----------------------------------------------------------+
 |authenticated_sender|Use: smtp|Type: string*|Default: unset|
-+--------------------+---------+-------------+--------------+
++-----------------------------------------------------------+
 
 When Exim has authenticated as a client, or if authenticated_sender_force is
 true, this option sets a value for the AUTH= item on outgoing MAIL commands,
@@ -21365,25 +22148,25 @@ direct delivery to those subfolders.
 Because of expected uses such as that just described for Cyrus (when no domain
 is involved), there is no checking on the syntax of the provided value.
 
-+--------------------------+---------+-------------+--------------+
++-----------------------------------------------------------------+
 |authenticated_sender_force|Use: smtp|Type: boolean|Default: false|
-+--------------------------+---------+-------------+--------------+
++-----------------------------------------------------------------+
 
 If this option is set true, the authenticated_sender option's value is used for
 the AUTH= item on outgoing MAIL commands, even if Exim has not authenticated as
 a client.
 
-+---------------+---------+----------+-----------+
++------------------------------------------------+
 |command_timeout|Use: smtp|Type: time|Default: 5m|
-+---------------+---------+----------+-----------+
++------------------------------------------------+
 
 This sets a timeout for receiving a response to an SMTP command that has been
 sent out. It is also used when waiting for the initial banner line from the
 remote host. Its value must not be zero.
 
-+---------------+---------+----------+-----------+
++------------------------------------------------+
 |connect_timeout|Use: smtp|Type: time|Default: 5m|
-+---------------+---------+----------+-----------+
++------------------------------------------------+
 
 This sets a timeout for the connect() function, which sets up a TCP/IP call to
 a remote host. A setting of zero allows the system timeout (typically several
@@ -21392,25 +22175,51 @@ the system timeout. However, it has been observed that on some systems there is
 no system timeout, which is why the default value for this option is 5 minutes,
 a value recommended by RFC 1123.
 
-+-----------------------+---------+-------------+------------+
++------------------------------------------------------------+
 |connection_max_messages|Use: smtp|Type: integer|Default: 500|
-+-----------------------+---------+-------------+------------+
++------------------------------------------------------------+
 
 This controls the maximum number of separate message deliveries that are sent
 over a single TCP/IP connection. If the value is zero, there is no limit. For
 testing purposes, this value can be overridden by the -oB command line option.
 
-+------------+---------+----------+-----------+
++---------------------------------------------+
 |data_timeout|Use: smtp|Type: time|Default: 5m|
-+------------+---------+----------+-----------+
++---------------------------------------------+
 
 This sets a timeout for the transmission of each block in the data portion of
 the message. As a result, the overall timeout for a message depends on the size
 of the message. Its value must not be zero. See also final_timeout.
 
-+------------------+---------+-------------+-------------+
++--------------------------------------------------+
+|dkim_domain|Use: smtp|Type: string*|Default: unset|
++--------------------------------------------------+
+
++----------------------------------------------------+
+|dkim_selector|Use: smtp|Type: string*|Default: unset|
++----------------------------------------------------+
+
++-------------------------------------------------------+
+|dkim_private_key|Use: smtp|Type: string*|Default: unset|
++-------------------------------------------------------+
+
++-------------------------------------------------+
+|dkim_canon|Use: smtp|Type: string*|Default: unset|
++-------------------------------------------------+
+
++--------------------------------------------------+
+|dkim_strict|Use: smtp|Type: string*|Default: unset|
++--------------------------------------------------+
+
++--------------------------------------------------------+
+|dkim_sign_headers|Use: smtp|Type: string*|Default: unset|
++--------------------------------------------------------+
+
+DKIM signing options. For details see section 57.1.
+
++--------------------------------------------------------+
 |delay_after_cutoff|Use: smtp|Type: boolean|Default: true|
-+------------------+---------+-------------+-------------+
++--------------------------------------------------------+
 
 This option controls what happens when all remote IP addresses for a given
 domain have been inaccessible for so long that they have passed their retry
@@ -21432,42 +22241,42 @@ addresses that haven't been tried since the message arrived. If there is a
 continuous stream of messages for the dead hosts, unsetting delay_after_cutoff
 means that there will be many more attempts to deliver to them.
 
-+------------------+---------+-------------+-------------+
++--------------------------------------------------------+
 |dns_qualify_single|Use: smtp|Type: boolean|Default: true|
-+------------------+---------+-------------+-------------+
++--------------------------------------------------------+
 
 If the hosts or fallback_hosts option is being used, and the gethostbyname
 option is false, the RES_DEFNAMES resolver option is set. See the 
 qualify_single option in chapter 17 for more details.
 
-+------------------+---------+-------------+--------------+
++---------------------------------------------------------+
 |dns_search_parents|Use: smtp|Type: boolean|Default: false|
-+------------------+---------+-------------+--------------+
++---------------------------------------------------------+
 
 If the hosts or fallback_hosts option is being used, and the gethostbyname
 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
+dnssec request bit set. This applies to all of the SRV, MX, 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
+DNS lookups for domains matching dnssec_require_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.
+bit) set will be ignored and logged as a host-lookup failure. This applies to
+all of the SRV, MX, AAAA, A lookup sequence.
 
-+----+---------+-------------+--------------+
++-------------------------------------------+
 |dscp|Use: smtp|Type: string*|Default: unset|
-+----+---------+-------------+--------------+
++-------------------------------------------+
 
 This option causes the DSCP value associated with a socket to be set to one of
 a number of fixed strings or to numeric value. The -bI:dscp option may be used
@@ -21481,13 +22290,13 @@ that these values will have any effect, not be stripped by networking
 equipment, or do much of anything without cooperation with your Network
 Engineer and those of all network operators between the source and destination.
 
-+--------------+---------+-----------------+--------------+
++---------------------------------------------------------+
 |fallback_hosts|Use: smtp|Type: string list|Default: unset|
-+--------------+---------+-----------------+--------------+
++---------------------------------------------------------+
 
 String expansion is not applied to this option. The argument must be a
 colon-separated list of host names or IP addresses, optionally also including
-port numbers, though the separator can be changed, as described in section 6.19
+port numbers, though the separator can be changed, as described in section 6.20
 . Each individual item in the list is the same as an item in a route_list
 setting for the manualroute router, as described in section 20.5.
 
@@ -21514,16 +22323,16 @@ gethostbyname option, as for the hosts option. Fallback hosts apply both to
 cases when the host list comes with the address and when it is taken from hosts
 . This option provides a "use a smart host only if delivery fails" facility.
 
-+-------------+---------+----------+------------+
++-----------------------------------------------+
 |final_timeout|Use: smtp|Type: time|Default: 10m|
-+-------------+---------+----------+------------+
++-----------------------------------------------+
 
 This is the timeout that applies while waiting for the response to the final
 line containing just "." that terminates a message. Its value must not be zero.
 
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 |gethostbyname|Use: smtp|Type: boolean|Default: false|
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 
 If this option is true when the hosts and/or fallback_hosts options are being
 used, names are looked up using gethostbyname() (or getipnodebyname() when
@@ -21531,17 +22340,17 @@ available) instead of using the DNS. Of course, that function may in fact use
 the DNS, but it may also consult other sources of information such as /etc/
 hosts.
 
-+------------------+---------+-------------+--------------+
++---------------------------------------------------------+
 |gnutls_compat_mode|Use: smtp|Type: boolean|Default: unset|
-+------------------+---------+-------------+--------------+
++---------------------------------------------------------+
 
 This option controls whether GnuTLS is used in compatibility mode in an Exim
 server. This reduces security slightly, but improves interworking with older
 implementations of TLS.
 
-+---------+---------+-------------+------------------+
++----------------------------------------------------+
 |helo_data|Use: smtp|Type: string*|Default: see below|
-+---------+---------+-------------+------------------+
++----------------------------------------------------+
 
 The value of this option is expanded after a connection to a another host has
 been set up. The result is used as the argument for the EHLO, HELO, or LHLO
@@ -21563,9 +22372,9 @@ helo_data = ${lookup dnsdb{ptr=$sending_ip_address}{$value}\
 
 The use of helo_data applies both to sending messages and when doing callouts.
 
-+-----+---------+------------------+--------------+
++-------------------------------------------------+
 |hosts|Use: smtp|Type: string list*|Default: unset|
-+-----+---------+------------------+--------------+
++-------------------------------------------------+
 
 Hosts are associated with an address by a router such as dnslookup, which finds
 the hosts by looking up the address domain in the DNS, or by manualroute, which
@@ -21581,7 +22390,7 @@ hosts_override is set.
 The string is first expanded, before being interpreted as a colon-separated
 list of host names or IP addresses, possibly including port numbers. The
 separator may be changed to something other than colon, as described in section
-6.19. Each individual item in the list is the same as an item in a route_list
+6.20. Each individual item in the list is the same as an item in a route_list
 setting for the manualroute router, as described in section 20.5. However, note
 that the "/MX" facility of the manualroute router is not available here.
 
@@ -21596,9 +22405,9 @@ both IPv4 and IPv6 addresses, both types of address are used.
 During delivery, the hosts are tried in order, subject to their retry status,
 unless hosts_randomize is set.
 
-+-----------------+---------+----------------+--------------+
++-----------------------------------------------------------+
 |hosts_avoid_esmtp|Use: smtp|Type: host list*|Default: unset|
-+-----------------+---------+----------------+--------------+
++-----------------------------------------------------------+
 
 This option is for use with broken hosts that announce ESMTP facilities (for
 example, PIPELINING) and then fail to implement them properly. When a host
@@ -21606,63 +22415,62 @@ matches hosts_avoid_esmtp, Exim sends HELO rather than EHLO at the start of the
 SMTP session. This means that it cannot use any of the ESMTP facilities such as
 AUTH, PIPELINING, SIZE, and STARTTLS.
 
-+----------------------+---------+----------------+--------------+
++----------------------------------------------------------------+
 |hosts_avoid_pipelining|Use: smtp|Type: host list*|Default: unset|
-+----------------------+---------+----------------+--------------+
++----------------------------------------------------------------+
 
 Exim will not use the SMTP PIPELINING extension when delivering to any host
 that matches this list, even if the server host advertises PIPELINING support.
 
-+---------------+---------+----------------+--------------+
++---------------------------------------------------------+
 |hosts_avoid_tls|Use: smtp|Type: host list*|Default: unset|
-+---------------+---------+----------------+--------------+
++---------------------------------------------------------+
 
 Exim will not try to start a TLS session when delivering to any host that
-matches this list. See chapter 41 for details of TLS.
+matches this list. See chapter 42 for details of TLS.
 
-+----------------------+---------+----------------+----------+
-|hosts_verify_avoid_tls|Use: smtp|Type: host list*|Default: *|
-+----------------------+---------+----------------+----------+
++----------------------------------------------------------------+
+|hosts_verify_avoid_tls|Use: smtp|Type: host list*|Default: unset|
++----------------------------------------------------------------+
 
 Exim will not try to start a TLS session for a verify callout, or when
-delivering in cutthrough mode, to any host that matches this list. Note that
-the default is to not use TLS.
+delivering in cutthrough mode, to any host that matches this list.
 
-+-------------+---------+-------------+----------+
++------------------------------------------------+
 |hosts_max_try|Use: smtp|Type: integer|Default: 5|
-+-------------+---------+-------------+----------+
++------------------------------------------------+
 
 This option limits the number of IP addresses that are tried for any one
 delivery in cases where there are temporary delivery errors. Section 30.5
 describes in detail how the value of this option is used.
 
-+-----------------------+---------+-------------+-----------+
++-----------------------------------------------------------+
 |hosts_max_try_hardlimit|Use: smtp|Type: integer|Default: 50|
-+-----------------------+---------+-------------+-----------+
++-----------------------------------------------------------+
 
 This is an additional check on the maximum number of IP addresses that Exim
 tries for any one delivery. Section 30.5 describes its use and why it exists.
 
-+----------------+---------+----------------+--------------+
++----------------------------------------------------------+
 |hosts_nopass_tls|Use: smtp|Type: host list*|Default: unset|
-+----------------+---------+----------------+--------------+
++----------------------------------------------------------+
 
 For any host that matches this list, a connection on which a TLS session has
 been started will not be passed to a new delivery process for sending another
-message on the same connection. See section 41.11 for an explanation of when
+message on the same connection. See section 42.11 for an explanation of when
 this might be needed.
 
-+--------------+---------+-------------+--------------+
++-----------------------------------------------------+
 |hosts_override|Use: smtp|Type: boolean|Default: false|
-+--------------+---------+-------------+--------------+
++-----------------------------------------------------+
 
 If this option is set and the hosts option is also set, any hosts that are
 attached to the address are ignored, and instead the hosts specified by the 
 hosts option are always used. This option does not apply to fallback_hosts.
 
-+---------------+---------+-------------+--------------+
++------------------------------------------------------+
 |hosts_randomize|Use: smtp|Type: boolean|Default: false|
-+---------------+---------+-------------+--------------+
++------------------------------------------------------+
 
 If this option is set, and either the list of hosts is taken from the hosts or
 the fallback_hosts option, or the hosts supplied by the router were not
@@ -21682,9 +22490,9 @@ The order of the first three hosts and the order of the last two hosts is
 randomized for each use, but the first three always end up before the last two.
 If hosts_randomize is not set, a "+" item in the list is ignored.
 
-+------------------+---------+----------------+--------------+
++------------------------------------------------------------+
 |hosts_require_auth|Use: smtp|Type: host list*|Default: unset|
-+------------------+---------+----------------+--------------+
++------------------------------------------------------------+
 
 This option provides a list of servers for which authentication must succeed
 before Exim will try to transfer a message. If authentication fails for servers
@@ -21694,34 +22502,34 @@ temporary error is detectable in the retry rules, so it can be turned into a
 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|
-+-----------------+---------+----------------+--------------+
++-----------------------------------------------------------+
 
 Exim will insist on using a TLS session when delivering to any host that
-matches this list. See chapter 41 for details of TLS. Note: This option affects
+matches this list. See chapter 42 for details of TLS. Note: This option affects
 outgoing mail only. To insist on TLS for incoming messages, use an appropriate
 ACL.
 
-+--------------+---------+----------------+--------------+
++--------------------------------------------------------+
 |hosts_try_auth|Use: smtp|Type: host list*|Default: unset|
-+--------------+---------+----------------+--------------+
++--------------------------------------------------------+
 
 This option provides a list of servers to which, provided they announce
 authentication support, Exim will attempt to authenticate as a client when it
@@ -21729,16 +22537,41 @@ connects. If authentication fails, Exim will try to transfer the message
 unauthenticated. See also hosts_require_auth, and chapter 33 for details of
 authentication.
 
-+--------------+---------+----------------+--------------+
-|hosts_try_prdr|Use: smtp|Type: host list*|Default: unset|
-+--------------+---------+----------------+--------------+
++--------------------------------------------------------+
+|hosts_try_chunking|Use: smtp|Type: host list*|Default: *|
++--------------------------------------------------------+
+
+This option provides a list of servers to which, provided they announce
+CHUNKING support, Exim will attempt to use BDAT commands rather than DATA. BDAT
+will not be used in conjunction with a transport filter.
+
++-------------------------------------------------------------+
+|hosts_try_fastopen|Use: smtp|Type: host list!!|Default: unset|
++-------------------------------------------------------------+
+
+This option provides a list of servers to which, provided the facility is
+supported by this system, Exim will attempt to perform a TCP Fast Open. No data
+is sent on the SYN segment but, if the remote server also supports the
+facility, it can send its SMTP banner immediately after the SYN,ACK segment.
+This can save up to one round-trip time.
+
+The facility is only active for previously-contacted servers, as the initiator
+must present a cookie in the SYN segment.
+
+On (at least some) current Linux distributions the facility must be enabled in
+the kernel by the sysadmin before the support is usable.
+
++----------------------------------------------------+
+|hosts_try_prdr|Use: smtp|Type: host list*|Default: *|
++----------------------------------------------------+
 
 This option provides a list of servers to which, provided they announce PRDR
-support, Exim will attempt to negotiate PRDR for multi-recipient messages.
+support, Exim will attempt to negotiate PRDR for multi-recipient messages. The
+option can usually be left as default.
 
-+---------+---------+------------------+--------------+
++-----------------------------------------------------+
 |interface|Use: smtp|Type: string list*|Default: unset|
-+---------+---------+------------------+--------------+
++-----------------------------------------------------+
 
 This option specifies which interface to bind to when making an outgoing SMTP
 call. The value is an IP address, not an interface name such as "eth0". Do not
@@ -21763,9 +22596,9 @@ connection. If none of them are the correct type, the option is ignored. If
 interface is not set, or is ignored, the system's IP functions choose which
 interface to use if the host has more than one.
 
-+---------+---------+-------------+-------------+
++-----------------------------------------------+
 |keepalive|Use: smtp|Type: boolean|Default: true|
-+---------+---------+-------------+-------------+
++-----------------------------------------------+
 
 This option controls the setting of SO_KEEPALIVE on outgoing TCP/IP socket
 connections. When set, it causes the kernel to probe idle connections
@@ -21777,26 +22610,26 @@ get stuck when the remote host is disconnected without tidying up the TCP/IP
 call properly. The keepalive mechanism takes several hours to detect
 unreachable hosts.
 
-+-----------------+---------+-------------+--------------+
++--------------------------------------------------------+
 |lmtp_ignore_quota|Use: smtp|Type: boolean|Default: false|
-+-----------------+---------+-------------+--------------+
++--------------------------------------------------------+
 
 If this option is set true when the protocol option is set to "lmtp", the
 string "IGNOREQUOTA" is added to RCPT commands, provided that the LMTP server
 has advertised support for IGNOREQUOTA in its response to the LHLO command.
 
-+--------+---------+-------------+------------+
++---------------------------------------------+
 |max_rcpt|Use: smtp|Type: integer|Default: 100|
-+--------+---------+-------------+------------+
++---------------------------------------------+
 
 This option limits the number of RCPT commands that are sent in a single SMTP
 message transaction. Each set of addresses is treated independently, and so can
 cause parallel connections to the same host if remote_max_parallel permits
 this.
 
-+------------+---------+-------------+-------------+
-|multi_domain|Use: smtp|Type: boolean|Default: true|
-+------------+---------+-------------+-------------+
++---------------------------------------------------+
+|multi_domain|Use: smtp|Type: boolean*|Default: true|
++---------------------------------------------------+
 
 When this option is set, the smtp transport can handle a number of addresses
 containing a mixture of different domains provided they all resolve to the same
@@ -21805,9 +22638,12 @@ one domain at a time. This is useful if you want to use $domain in an expansion
 for the transport, because it is set only when there is a single domain
 involved in a remote delivery.
 
-+----+---------+-------------+------------------+
+It is expanded per-address and can depend on any of $address_data, $domain_data
+, $local_part_data, $host, $host_address and $host_port.
+
++-----------------------------------------------+
 |port|Use: smtp|Type: string*|Default: see below|
-+----+---------+-------------+------------------+
++-----------------------------------------------+
 
 This option specifies the TCP/IP port on the server to which Exim connects. 
 Note: Do not confuse this with the port that was used when a message was
@@ -21820,9 +22656,9 @@ otherwise it is looked up using getservbyname(). The default value is normally
 "smtp", but if protocol is set to "lmtp", the default is "lmtp". If the
 expansion fails, or if a port number cannot be found, delivery is deferred.
 
-+--------+---------+------------+-------------+
++---------------------------------------------+
 |protocol|Use: smtp|Type: string|Default: smtp|
-+--------+---------+------------+-------------+
++---------------------------------------------+
 
 If this option is set to "lmtp" instead of "smtp", the default value for the 
 port option changes to "lmtp", and the transport operates the LMTP protocol
@@ -21830,14 +22666,14 @@ port option changes to "lmtp", and the transport operates the LMTP protocol
 deliveries into closed message stores. Exim also has support for running LMTP
 over a pipe to a local process - see chapter 28.
 
-If this option is set to "smtps", the default vaule for the port option changes
+If this option is set to "smtps", the default value for the port option changes
 to "smtps", and the transport initiates TLS immediately after connecting, as an
 outbound SSL-on-connect, instead of using STARTTLS to upgrade. The Internet
 standards bodies strongly discourage use of this mode.
 
-+------------------------+---------+-------------+-------------+
-|retry_include_ip_address|Use: smtp|Type: boolean|Default: true|
-+------------------------+---------+-------------+-------------+
++---------------------------------------------------------------+
+|retry_include_ip_address|Use: smtp|Type: boolean*|Default: true|
++---------------------------------------------------------------+
 
 Exim normally includes both the host name and the IP address in the key it
 constructs for indexing retry data after a temporary delivery failure. This
@@ -21848,12 +22684,12 @@ addresses is not affected.
 However, in some dialup environments hosts are assigned a different IP address
 each time they connect. In this situation the use of the IP address as part of
 the retry key leads to undesirable behaviour. Setting this option false causes
-Exim to use only the host name. This should normally be done on a separate
-instance of the smtp transport, set up specially to handle the dialup hosts.
+Exim to use only the host name. Since it is expanded it can be made to depend
+on the host or domain.
 
-+---------------+---------+----------------+--------------+
++---------------------------------------------------------+
 |serialize_hosts|Use: smtp|Type: host list*|Default: unset|
-+---------------+---------+----------------+--------------+
++---------------------------------------------------------+
 
 Because Exim operates in a distributed manner, if several messages for the same
 host arrive at around the same time, more than one simultaneous connection to
@@ -21874,9 +22710,11 @@ start with misc and they are kept in the spool/db directory. There may be one
 or two files, depending on the type of DBM in use. The same files are used for
 ETRN serialization.
 
-+-------------+---------+-------------+-------------+
+See also the max_parallel generic transport option.
+
++---------------------------------------------------+
 |size_addition|Use: smtp|Type: integer|Default: 1024|
-+-------------+---------+-------------+-------------+
++---------------------------------------------------+
 
 If a remote SMTP server indicates that it supports the SIZE option of the MAIL
 command, Exim uses this to pass over the message size at the start of an SMTP
@@ -21888,14 +22726,21 @@ of text is added to messages.
 Alternatively, if the value of size_addition is set negative, it disables the
 use of the SIZE option altogether.
 
-+---------------+---------+-------------+--------------+
++--------------------------------------------------+
+|socks_proxy|Use: smtp|Type: string*|Default: unset|
++--------------------------------------------------+
+
+This option enables use of SOCKS proxies for connections made by the transport.
+For details see section 58.2.
+
++------------------------------------------------------+
 |tls_certificate|Use: smtp|Type: string*|Default: unset|
-+---------------+---------+-------------+--------------+
++------------------------------------------------------+
 
 The value of this option must be the absolute path to a file which contains the
 client's certificate, for possible use when sending a message over an encrypted
 connection. The values of $host and $host_address are set to the name and
-address of the server during the expansion. See chapter 41 for details of TLS.
+address of the server during the expansion. See chapter 42 for details of TLS.
 
 Note: This option must be set if you want Exim to be able to use a TLS
 certificate when sending messages as a client. The global option of the same
@@ -21903,16 +22748,16 @@ name specifies the certificate for Exim as a server; it is not automatically
 assumed that the same certificate should be used when Exim is operating as a
 client.
 
-+-------+---------+-------------+--------------+
++----------------------------------------------+
 |tls_crl|Use: smtp|Type: string*|Default: unset|
-+-------+---------+-------------+--------------+
++----------------------------------------------+
 
 This option specifies a certificate revocation list. The expanded value must be
 the name of a file that contains a CRL in PEM format.
 
-+---------------+---------+-------------+-------------+
++-----------------------------------------------------+
 |tls_dh_min_bits|Use: smtp|Type: integer|Default: 1024|
-+---------------+---------+-------------+-------------+
++-----------------------------------------------------+
 
 When establishing a TLS session, if a ciphersuite which uses Diffie-Hellman key
 agreement is negotiated, the server will provide a large prime number for use.
@@ -21921,9 +22766,9 @@ parameter offered by the server is too small, then the TLS handshake will fail.
 
 Only supported when using GnuTLS.
 
-+--------------+---------+-------------+--------------+
++-----------------------------------------------------+
 |tls_privatekey|Use: smtp|Type: string*|Default: unset|
-+--------------+---------+-------------+--------------+
++-----------------------------------------------------+
 
 The value of this option must be the absolute path to a file which contains the
 client's private key. This is used when sending a message over an encrypted
@@ -21931,37 +22776,37 @@ connection using a client certificate. The values of $host and $host_address
 are set to the name and address of the server during the expansion. If this
 option is unset, or the expansion is forced to fail, or the result is an empty
 string, the private key is assumed to be in the same file as the certificate.
-See chapter 41 for details of TLS.
+See chapter 42 for details of TLS.
 
-+-------------------+---------+-------------+--------------+
++----------------------------------------------------------+
 |tls_require_ciphers|Use: smtp|Type: string*|Default: unset|
-+-------------------+---------+-------------+--------------+
++----------------------------------------------------------+
 
 The value of this option must be a list of permitted cipher suites, for use
 when setting up an outgoing encrypted connection. (There is a global option of
 the same name for controlling incoming connections.) The values of $host and
 $host_address are set to the name and address of the server during the
-expansion. See chapter 41 for details of TLS; note that this option is used in
-different ways by OpenSSL and GnuTLS (see sections 41.4 and 41.5). For GnuTLS,
+expansion. See chapter 42 for details of TLS; note that this option is used in
+different ways by OpenSSL and GnuTLS (see sections 42.4 and 42.5). For GnuTLS,
 the order of the ciphers is a preference order.
 
-+-------+---------+-------------+--------------+
++----------------------------------------------+
 |tls_sni|Use: smtp|Type: string*|Default: unset|
-+-------+---------+-------------+--------------+
++----------------------------------------------+
 
 If this option is set then it sets the $tls_out_sni variable and causes any TLS
 session to pass this value as the Server Name Indication extension to the
 remote side, which can be used by the remote side to select an appropriate
 certificate and private key for the session.
 
-See 41.10 for more information.
+See 42.10 for more information.
 
 Note that for OpenSSL, this feature requires a build of OpenSSL that supports
 TLS extensions.
 
-+---------------------+---------+-------------+-------------+
++-----------------------------------------------------------+
 |tls_tempfail_tryclear|Use: smtp|Type: boolean|Default: true|
-+---------------------+---------+-------------+-------------+
++-----------------------------------------------------------+
 
 When the server host is not in hosts_require_tls, and there is a problem in
 setting up a TLS session, this option determines whether or not Exim should try
@@ -21972,39 +22817,60 @@ STARTTLS. Also, if STARTTLS is accepted, but the subsequent TLS negotiation
 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:|
-+--------------------+---------+----------------------+--------+
++----------------------------------------------------------+
+|tls_try_verify_hosts|Use: smtp|Type: host list*|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
+certificates when tls_verify_certificates is matched. The
 $tls_out_certificate_verified variable is set when certificate verification
 succeeds.
 
-+-----------------------+---------+-------------+--------------+
-|tls_verify_certificates|Use: smtp|Type: string*|Default: unset|
-+-----------------------+---------+-------------+--------------+
++---------------------------------------------------------------+
+|tls_verify_cert_hostnames|Use: smtp|Type: host list*|Default: *|
++---------------------------------------------------------------+
+
+This option give a list of hosts for which, while verifying the server
+certificate, checks will be included on the host name (note that this will
+generally be the result of a DNS MX lookup) versus Subject and
+Subject-Alternate-Name fields. Wildcard names are permitted limited to being
+the initial component of a 3-or-more component FQDN.
+
+There is no equivalent checking on client certificates.
+
++---------------------------------------------------------------+
+|tls_verify_certificates|Use: smtp|Type: string*|Default: system|
++---------------------------------------------------------------+
 
-The value of this option must be the absolute path to a file containing
-permitted server certificates, for use when setting up an encrypted connection.
-Alternatively, if you are using OpenSSL, you can set tls_verify_certificates to
-the name of a directory containing certificate files. This does not work with
-GnuTLS; the option must be set to the name of a single file if you are using
-GnuTLS. The values of $host and $host_address are set to the name and address
-of the server during the expansion of this option. See chapter 41 for details
-of TLS.
+The value of this option must be either the word "system" or the absolute path
+to a file or directory containing permitted certificates for servers, for use
+when setting up an encrypted connection.
 
-For back-compatability, if neither tls_verify_hosts nor tls_try_verify_hosts
-are set and certificate verification fails the TLS connection is closed.
+The "system" value for the option will use a location compiled into the SSL
+library. This is not available for GnuTLS versions preceding 3.0.20; a value of
+"system" is taken as empty and an explicit location must be specified.
 
-+----------------+---------+----------------------+--------+
-|tls_verify_hosts|Use: smtp|Type: host list* unset|Default:|
-+----------------+---------+----------------------+--------+
+The use of a directory for the option value is not available for GnuTLS
+versions preceding 3.3.6 and a single file must be used.
 
-This option gives a list of hosts for which. on encrypted connections,
+With OpenSSL the certificates specified explicitly either by file or directory
+are added to those given by the system default location.
+
+The values of $host and $host_address are set to the name and address of the
+server during the expansion of this option. See chapter 42 for details of TLS.
+
+For back-compatibility, if neither tls_verify_hosts nor tls_try_verify_hosts
+are set (a single-colon empty list counts as being set) and certificate
+verification fails the TLS connection is closed.
+
++----------------------------------------------------------+
+|tls_verify_hosts|Use: smtp|Type: host list*|Default: unset|
++----------------------------------------------------------+
+
+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.
@@ -22445,8 +23311,8 @@ correct type (matches the flags) and matches the pattern:
     RFC 2822 address, including the angle brackets if necessary. If text
     outside angle brackets contains a character whose value is greater than 126
     or less than 32 (except for tab), the text is encoded according to RFC
-    2047. The character set is taken from headers_charset, which defaults to
-    ISO-8859-1.
+    2047. The character set is taken from headers_charset, which gets its
+    default at build time.
 
     When the "w" flag is set on a rule that causes an envelope address to be
     rewritten, all but the working part of the replacement address is
@@ -22527,7 +23393,7 @@ address) basis, not on a per-message basis. Thus, if one message has recently
 been delayed, delivery of a new message to the same host is not immediately
 tried, but waits for the host's retry time to arrive. If the retry_defer log
 selector is set, the message "retry time not reached" is written to the main
-log whenever a delivery is skipped for this reason. Section 47.2 contains more
+log whenever a delivery is skipped for this reason. Section 48.2 contains more
 details of the handling of errors during remote deliveries.
 
 Retry processing applies to routing as well as to delivering, except as covered
@@ -22712,6 +23578,13 @@ lost_connection
     legitimate reasons for this (host died, network died), but if it repeats a
     lot for the same host, it indicates something odd.
 
+lookup
+
+    A DNS lookup for a host failed. Note that a dnslookup router will need to
+    have matched its fail_defer_domains option for this retry type to be
+    usable. Also note that a manualroute router will probably need its 
+    host_find_failed option set to defer.
+
 refused_MX
 
     A connection to a host obtained from an MX record was refused.
@@ -22892,7 +23765,7 @@ number of incoming messages (which might be the case on a system that is
 sending everything to a smart host, for example).
 
 The data in the retry hints database can be inspected by using the exim_dumpdb
-or exim_fixdb utility programs (see chapter 52). The latter utility can also be
+or exim_fixdb utility programs (see chapter 53). The latter utility can also be
 used to change the data. The exinext utility script can be used to find out
 what the next retry times are for the hosts associated with a particular mail
 domain, and also for local deliveries that have been deferred.
@@ -23018,7 +23891,7 @@ reached.
 
 Two exceptional actions are applied to prevent this happening. The first
 applies to errors that are related to a message rather than a remote host.
-Section 47.2 has a discussion of the different kinds of error; examples of
+Section 48.2 has a discussion of the different kinds of error; examples of
 message-related errors are 4xx responses to MAIL or DATA commands, and quota
 failures. For this type of error, if a message's arrival time is earlier than
 the "first failed" time for the error, the earlier time is used when scanning
@@ -23100,6 +23973,7 @@ AUTH_GSASL=yes
 AUTH_HEIMDAL_GSSAPI=yes
 AUTH_PLAINTEXT=yes
 AUTH_SPA=yes
+AUTH_TLS=yes
 
 in Local/Makefile, respectively. The first of these supports the CRAM-MD5
 authentication mechanism (RFC 2195), and the second provides an interface to
@@ -23111,10 +23985,11 @@ Heimdal GSSAPI, geared for Kerberos, but supporting setting a server keytab.
 The sixth can be configured to support the PLAIN authentication mechanism (RFC
 2595) or the LOGIN mechanism, which is not formally documented, but used by
 several MUAs. The seventh authenticator supports Microsoft's Secure Password
-Authentication mechanism.
+Authentication mechanism. The eighth is an Exim authenticator but not an SMTP
+one; instead it can use information from a TLS negotiation.
 
 The authenticators are configured using the same syntax as other drivers (see
-section 6.22). If no authenticators are required, no authentication section
+section 6.23). If no authenticators are required, no authentication section
 need be present in the configuration file. Each authenticator can in principle
 have both server and client functions. When Exim is receiving SMTP mail, it is
 acting as a server; when it is sending out messages over SMTP, it is acting as
@@ -23168,9 +24043,9 @@ not treat the realm as secure data to be blindly trusted.
 33.1 Generic options for authenticators
 ---------------------------------------
 
-+----------------+-------------------+-------------+--------------+
++-----------------------------------------------------------------+
 |client_condition|Use: authenticators|Type: string*|Default: unset|
-+----------------+-------------------+-------------+--------------+
++-----------------------------------------------------------------+
 
 When Exim is authenticating as a client, it skips any authenticator whose 
 client_condition expansion yields "0", "no", or "false". This can be used, for
@@ -23179,24 +24054,24 @@ by a setting such as:
 
 client_condition = ${if !eq{$tls_out_cipher}{}}
 
-+-------------+-------------------+-------------+--------------+
++--------------------------------------------------------------+
 |client_set_id|Use: authenticators|Type: string*|Default: unset|
-+-------------+-------------------+-------------+--------------+
++--------------------------------------------------------------+
 
 When client authentication succeeds, this condition is expanded; the result is
-used in the log lines for outbound messasges. Typically it will be the user
-name used for authentication.
+used in the log lines for outbound messages. Typically it will be the user name
+used for authentication.
 
-+------+-------------------+------------+--------------+
++------------------------------------------------------+
 |driver|Use: authenticators|Type: string|Default: unset|
-+------+-------------------+------------+--------------+
++------------------------------------------------------+
 
 This option must always be set. It specifies which of the available
 authenticators is to be used.
 
-+-----------+-------------------+------------+--------------+
++-----------------------------------------------------------+
 |public_name|Use: authenticators|Type: string|Default: unset|
-+-----------+-------------------+------------+--------------+
++-----------------------------------------------------------+
 
 This option specifies the name of the authentication mechanism that the driver
 implements, and by which it is known to the outside world. These names should
@@ -23204,9 +24079,9 @@ contain only upper case letters, digits, underscores, and hyphens (RFC 2222),
 but Exim in fact matches them caselessly. If public_name is not set, it
 defaults to the driver's instance name.
 
-+--------------------------+-------------------+-------------+--------------+
++---------------------------------------------------------------------------+
 |server_advertise_condition|Use: authenticators|Type: string*|Default: unset|
-+--------------------------+-------------------+-------------+--------------+
++---------------------------------------------------------------------------+
 
 When a server is about to advertise an authentication mechanism, the condition
 is expanded. If it yields the empty string, "0", "no", or "false", the
@@ -23214,9 +24089,9 @@ mechanism is not advertised. If the expansion fails, the mechanism is not
 advertised. If the failure was not forced, and was not caused by a lookup
 defer, the incident is logged. See section 33.3 below for further discussion.
 
-+----------------+-------------------+-------------+--------------+
++-----------------------------------------------------------------+
 |server_condition|Use: authenticators|Type: string*|Default: unset|
-+----------------+-------------------+-------------+--------------+
++-----------------------------------------------------------------+
 
 This option must be set for a plaintext server authenticator, where it is used
 directly to control authentication. See section 34.2 for details.
@@ -23235,9 +24110,9 @@ expansion is "1", "yes", or "true", authentication succeeds. For any other
 result, a temporary error code is returned, with the expanded string as the
 error text.
 
-+------------------+-------------------+-------------+--------------+
++-------------------------------------------------------------------+
 |server_debug_print|Use: authenticators|Type: string*|Default: unset|
-+------------------+-------------------+-------------+--------------+
++-------------------------------------------------------------------+
 
 If this option is set and authentication debugging is enabled (see the -d
 command line option), the string is expanded and included in the debugging
@@ -23245,9 +24120,9 @@ output when the authenticator is run as a server. This can help with checking
 out the values of variables. If expansion of the string fails, the error
 message is written to the debugging output, and Exim carries on processing.
 
-+-------------+-------------------+-------------+--------------+
++--------------------------------------------------------------+
 |server_set_id|Use: authenticators|Type: string*|Default: unset|
-+-------------+-------------------+-------------+--------------+
++--------------------------------------------------------------+
 
 When an Exim server successfully authenticates a client, this string is
 expanded using data from the authentication, and preserved for any incoming
@@ -23257,9 +24132,9 @@ configuration might preserve the user name that was used to authenticate, and
 refer to it subsequently during delivery of the message. If expansion fails,
 the option is ignored.
 
-+--------------------------+-------------------+-------------+--------------+
++---------------------------------------------------------------------------+
 |server_mail_auth_condition|Use: authenticators|Type: string*|Default: unset|
-+--------------------------+-------------------+-------------+--------------+
++---------------------------------------------------------------------------+
 
 This option allows a server to discard authenticated sender addresses supplied
 as part of MAIL commands in SMTP connections that are authenticated by the
@@ -23459,6 +24334,16 @@ authentication, and the host matches an entry in either of these options, Exim
     retry rules, and thereby turned into a permanent error if you wish. In the
     second case, Exim tries to deliver the message unauthenticated.
 
+Note that the hostlist test for whether to do authentication can be confused if
+name-IP lookups change between the time the peer is decided on and the
+transport running. For example, with a manualroute router given a host name,
+and DNS "round-robin" use by that name: if the local resolver cache times out
+between the router and the transport running, the transport may get an IP for
+the name for its authentication check which does not match the connection peer
+IP. No authentication will then be done, despite the names being identical.
+
+For such cases use a separate transport which always authenticates.
+
 When Exim has authenticated itself to a remote server, it adds the AUTH
 parameter to the MAIL commands it sends, if it has an authenticated sender for
 the message. If the message came from a remote host, the authenticated sender
@@ -23479,7 +24364,7 @@ The plaintext authenticator can be configured to support the PLAIN and LOGIN
 authentication mechanisms, both of which transfer authentication data as plain
 (unencrypted) text (though base64 encoded). The use of plain text is a security
 risk; you are strongly advised to insist on the use of SMTP encryption (see
-chapter 41) if you use the PLAIN or LOGIN mechanisms. If you do use unencrypted
+chapter 42) if you use the PLAIN or LOGIN mechanisms. If you do use unencrypted
 plain text, you should not use the same passwords for SMTP connections as you
 do for login accounts.
 
@@ -23489,16 +24374,16 @@ do for login accounts.
 
 When configured as a server, plaintext uses the following options:
 
-+----------------+-------------------+-------------+--------------+
++-----------------------------------------------------------------+
 |server_condition|Use: authenticators|Type: string*|Default: unset|
-+----------------+-------------------+-------------+--------------+
++-----------------------------------------------------------------+
 
 This is actually a global authentication option, but it must be set in order to
 configure the plaintext driver as a server. Its use is described below.
 
-+--------------+--------------+-------------+--------------+
++----------------------------------------------------------+
 |server_prompts|Use: plaintext|Type: string*|Default: unset|
-+--------------+--------------+-------------+--------------+
++----------------------------------------------------------+
 
 The contents of this option, after expansion, must be a colon-separated list of
 prompt strings. If expansion fails, a temporary authentication rejection is
@@ -23682,18 +24567,18 @@ Radius, ldapauth, pwcheck, and saslauthd. For details see section 11.7.
 
 The plaintext authenticator has two client options:
 
-+----------------------------+--------------+-------------+--------------+
++------------------------------------------------------------------------+
 |client_ignore_invalid_base64|Use: plaintext|Type: boolean|Default: false|
-+----------------------------+--------------+-------------+--------------+
++------------------------------------------------------------------------+
 
 If the client receives a server prompt that is not a valid base64 string,
 authentication is abandoned by default. However, if this option is set true,
 the error in the challenge is ignored and the client sends the response as
 usual.
 
-+-----------+--------------+-------------+--------------+
++-------------------------------------------------------+
 |client_send|Use: plaintext|Type: string*|Default: unset|
-+-----------+--------------+-------------+--------------+
++-------------------------------------------------------+
 
 The string is a colon-separated list of authentication data strings. Each
 string is independently expanded before being sent to the server. The first
@@ -23755,9 +24640,9 @@ available in plain text at either end.
 This authenticator has one server option, which must be set to configure the
 authenticator as a server:
 
-+-------------+-------------+-------------+--------------+
++--------------------------------------------------------+
 |server_secret|Use: cram_md5|Type: string*|Default: unset|
-+-------------+-------------+-------------+--------------+
++--------------------------------------------------------+
 
 When the server receives the client's response, the user name is placed in the
 expansion variable $auth1, and server_secret is expanded to obtain the password
@@ -23805,7 +24690,7 @@ cyrusless_crammd5:
   driver = cram_md5
   public_name = CRAM-MD5
   server_secret = ${lookup{$auth1:mail.example.org:userPassword}\
-                  dbmjz{/etc/sasldb2}}
+                  dbmjz{/etc/sasldb2}{$value}fail}
   server_set_id = $auth1
 
 
@@ -23814,16 +24699,16 @@ cyrusless_crammd5:
 
 When used as a client, the cram_md5 authenticator has two options:
 
-+-----------+-------------+-------------+------------------------------+
++----------------------------------------------------------------------+
 |client_name|Use: cram_md5|Type: string*|Default: the primary host name|
-+-----------+-------------+-------------+------------------------------+
++----------------------------------------------------------------------+
 
 This string is expanded, and the result used as the user name data when
 computing the response to the server's challenge.
 
-+-------------+-------------+-------------+--------------+
++--------------------------------------------------------+
 |client_secret|Use: cram_md5|Type: string*|Default: unset|
-+-------------+-------------+-------------+--------------+
++--------------------------------------------------------+
 
 This option must be set for the authenticator to work as a client. Its value is
 expanded and the result used as the secret string when computing the response.
@@ -23894,17 +24779,17 @@ releases of Exim, the username is also placed in $1. However, the use of this
 variable for this purpose is now deprecated, as it can lead to confusion in
 string expansions that also use numeric variables for other things.
 
-+---------------+---------------+-------------+------------------+
++----------------------------------------------------------------+
 |server_hostname|Use: cyrus_sasl|Type: string*|Default: see below|
-+---------------+---------------+-------------+------------------+
++----------------------------------------------------------------+
 
 This option selects the hostname that is used when communicating with the
 library. The default value is "$primary_hostname". It is up to the underlying
 SASL plug-in what it does with this data.
 
-+-----------+---------------+------------+------------------+
++-----------------------------------------------------------+
 |server_mech|Use: cyrus_sasl|Type: string|Default: see below|
-+-----------+---------------+------------+------------------+
++-----------------------------------------------------------+
 
 This option selects the authentication mechanism this driver should use. The
 default is the value of the generic public_name option. This option allows you
@@ -23916,15 +24801,15 @@ sasl:
   server_mech = CRAM-MD5
   server_set_id = $auth1
 
-+------------+---------------+-------------+--------------+
++---------------------------------------------------------+
 |server_realm|Use: cyrus_sasl|Type: string*|Default: unset|
-+------------+---------------+-------------+--------------+
++---------------------------------------------------------+
 
 This specifies the SASL realm that the server claims to be in.
 
-+--------------+---------------+------------+---------------+
++-----------------------------------------------------------+
 |server_service|Use: cyrus_sasl|Type: string|Default: "smtp"|
-+--------------+---------------+------------+---------------+
++-----------------------------------------------------------+
 
 This is the SASL service that the server claims to implement.
 
@@ -23954,16 +24839,14 @@ but it is present in many binary distributions.
 
 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:
 
-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:
-
-+-------------+------------+------------+--------------+
++------------------------------------------------------+
 |server_socket|Use: dovecot|Type: string|Default: unset|
-+-------------+------------+------------+--------------+
++------------------------------------------------------+
 
 This option must specify the socket that is the interface to Dovecot
 authentication. The public_name option must specify an authentication mechanism
@@ -24001,9 +24884,9 @@ future authentication mechanisms, so no guarantee can be made that any
 particular new authentication mechanism will be supported without code changes
 in Exim.
 
-+---------------------+----------+-------------+--------------+
++-------------------------------------------------------------+
 |server_channelbinding|Use: gsasl|Type: boolean|Default: false|
-+---------------------+----------+-------------+--------------+
++-------------------------------------------------------------+
 
 Some authentication mechanisms are able to use external context at both ends of
 the session to bind the authentication to that context, and fail the
@@ -24023,17 +24906,17 @@ This defaults off to ensure smooth upgrade across Exim releases, in case this
 option causes some clients to start failing. Some future release of Exim may
 switch the default to be true.
 
-+---------------+----------+-------------+------------------+
++-----------------------------------------------------------+
 |server_hostname|Use: gsasl|Type: string*|Default: see below|
-+---------------+----------+-------------+------------------+
++-----------------------------------------------------------+
 
 This option selects the hostname that is used when communicating with the
 library. The default value is "$primary_hostname". Some mechanisms will use
 this data.
 
-+-----------+----------+------------+------------------+
++------------------------------------------------------+
 |server_mech|Use: gsasl|Type: string|Default: see below|
-+-----------+----------+------------+------------------+
++------------------------------------------------------+
 
 This option selects the authentication mechanism this driver should use. The
 default is the value of the generic public_name option. This option allows you
@@ -24045,9 +24928,9 @@ sasl:
   server_mech = CRAM-MD5
   server_set_id = $auth1
 
-+---------------+----------+-------------+--------------+
++-------------------------------------------------------+
 |server_password|Use: gsasl|Type: string*|Default: unset|
-+---------------+----------+-------------+--------------+
++-------------------------------------------------------+
 
 Various mechanisms need access to the cleartext password on the server, so that
 proof-of-possession can be demonstrated on the wire, without sending the
@@ -24063,30 +24946,30 @@ A forced failure will cause authentication to defer.
 If using this option, it may make sense to set the server_condition option to
 be simply "true".
 
-+------------+----------+-------------+--------------+
++----------------------------------------------------+
 |server_realm|Use: gsasl|Type: string*|Default: unset|
-+------------+----------+-------------+--------------+
++----------------------------------------------------+
 
 This specifies the SASL realm that the server claims to be in. Some mechanisms
 will use this data.
 
-+-----------------+----------+-------------+--------------+
++---------------------------------------------------------+
 |server_scram_iter|Use: gsasl|Type: string*|Default: unset|
-+-----------------+----------+-------------+--------------+
++---------------------------------------------------------+
 
 This option provides data for the SCRAM family of mechanisms. $auth1 is not
 available at evaluation time. (This may change, as we receive feedback on use)
 
-+-----------------+----------+-------------+--------------+
++---------------------------------------------------------+
 |server_scram_salt|Use: gsasl|Type: string*|Default: unset|
-+-----------------+----------+-------------+--------------+
++---------------------------------------------------------+
 
 This option provides data for the SCRAM family of mechanisms. $auth1 is not
 available at evaluation time. (This may change, as we receive feedback on use)
 
-+--------------+----------+------------+---------------+
++------------------------------------------------------+
 |server_service|Use: gsasl|Type: string|Default: "smtp"|
-+--------------+----------+------------+---------------+
++------------------------------------------------------+
 
 This is the SASL service that the server claims to implement. Some mechanisms
 will use this data.
@@ -24142,28 +25025,28 @@ gsasl_cyrusless_crammd5:
 The heimdal_gssapi authenticator provides server integration for the Heimdal
 GSSAPI/Kerberos library, permitting Exim to set a keytab pathname reliably.
 
-+---------------+-------------------+-------------+------------------+
++--------------------------------------------------------------------+
 |server_hostname|Use: heimdal_gssapi|Type: string*|Default: see below|
-+---------------+-------------------+-------------+------------------+
++--------------------------------------------------------------------+
 
 This option selects the hostname that is used, with server_service, for
 constructing the GSS server name, as a GSS_C_NT_HOSTBASED_SERVICE identifier.
 The default value is "$primary_hostname".
 
-+-------------+-------------------+-------------+--------------+
++--------------------------------------------------------------+
 |server_keytab|Use: heimdal_gssapi|Type: string*|Default: unset|
-+-------------+-------------------+-------------+--------------+
++--------------------------------------------------------------+
 
 If set, then Heimdal will not use the system default keytab (typically /etc/
 krb5.keytab) but instead the pathname given in this option. The value should be
 a pathname, with no "file:" prefix.
 
-+--------------+-------------------+-------------+-------------+
++--------------------------------------------------------------+
 |server_service|Use: heimdal_gssapi|Type: string*|Default: smtp|
-+--------------+-------------------+-------------+-------------+
++--------------------------------------------------------------+
 
 This option specifies the service identifier used, in conjunction with 
-server_hostname, for building the identifer for finding credentials from the
+server_hostname, for building the identifier for finding credentials from the
 keytab.
 
 
@@ -24213,9 +25096,9 @@ Encryption is used to protect the password in transit.
 
 The spa authenticator has just one server option:
 
-+---------------+--------+-------------+--------------+
++-----------------------------------------------------+
 |server_password|Use: spa|Type: string*|Default: unset|
-+---------------+--------+-------------+--------------+
++-----------------------------------------------------+
 
 This option is expanded, and the result must be the cleartext password for the
 authenticating user, whose name is at this point in $auth1. For compatibility
@@ -24239,21 +25122,21 @@ failure causes a temporary error code to be returned.
 
 The spa authenticator has the following client options:
 
-+-------------+--------+-------------+--------------+
++---------------------------------------------------+
 |client_domain|Use: spa|Type: string*|Default: unset|
-+-------------+--------+-------------+--------------+
++---------------------------------------------------+
 
 This option specifies an optional domain for the authentication.
 
-+---------------+--------+-------------+--------------+
++-----------------------------------------------------+
 |client_password|Use: spa|Type: string*|Default: unset|
-+---------------+--------+-------------+--------------+
++-----------------------------------------------------+
 
 This option specifies the user's password, and must be set.
 
-+---------------+--------+-------------+--------------+
++-----------------------------------------------------+
 |client_username|Use: spa|Type: string*|Default: unset|
-+---------------+--------+-------------+--------------+
++-----------------------------------------------------+
 
 This option specifies the user name, and must be set. Here is an example of a
 configuration of this authenticator for use with the mail servers at msn.com:
@@ -24268,7 +25151,75 @@ msn:
 
 
 ===============================================================================
-41. ENCRYPTED SMTP CONNECTIONS USING TLS/SSL
+41. THE TLS AUTHENTICATOR
+
+The tls authenticator provides server support for authentication based on
+client certificates.
+
+It is not an SMTP authentication mechanism and is not advertised by the server
+as part of the SMTP EHLO response. It is an Exim authenticator in the sense
+that it affects the protocol element of the log line, can be tested for by the 
+authenticated ACL condition, and can set the $authenticated_id variable.
+
+The client must present a verifiable certificate, for which it must have been
+requested via the tls_verify_hosts or tls_try_verify_hosts main options (see 42
+).
+
+If an authenticator of this type is configured it is run before any SMTP-level
+communication is done, and can authenticate the connection. If it does, SMTP
+authentication is not offered.
+
+A maximum of one authenticator of this type may be present.
+
+The tls authenticator has three server options:
+
++---------------------------------------------------+
+|server_param1|Use: tls|Type: string*|Default: unset|
++---------------------------------------------------+
+
+This option is expanded after the TLS negotiation and the result is placed in
+$auth1. If the expansion is forced to fail, authentication fails. Any other
+expansion failure causes a temporary error code to be returned.
+
++---------------------------------------------------+
+|server_param2|Use: tls|Type: string*|Default: unset|
++---------------------------------------------------+
+
++---------------------------------------------------+
+|server_param3|Use: tls|Type: string*|Default: unset|
++---------------------------------------------------+
+
+As above, for $auth2 and $auth3.
+
+server_param1 may also be spelled server_param.
+
+Example:
+
+tls:
+  driver = tls
+  server_param1 =     ${certextract {subj_altname,mail,>:} \
+                                    {$tls_in_peercert}}
+  server_condition =  ${if forany {$auth1} \
+                            {!= {0} \
+                                {${lookup ldap{ldap:///\
+                         mailname=${quote_ldap_dn:${lc:$item}},\
+                         ou=users,LDAP_DC?mailid} {$value}{0} \
+                       }    }   } }
+  server_set_id =     ${if = {1}{${listcount:$auth1}} {$auth1}{}}
+
+This accepts a client certificate that is verifiable against any of your
+configured trust-anchors (which usually means the full set of public CAs) and
+which has a SAN with a good account name. Note that the client cert is on the
+wire in-clear, including the SAN, whereas a plaintext SMTP AUTH done inside TLS
+is not.
+
+Note that because authentication is traditionally an SMTP operation, the 
+authenticated ACL condition cannot be used in a connect- or helo-ACL.
+
+
+
+===============================================================================
+42. ENCRYPTED SMTP CONNECTIONS USING TLS/SSL
 
 Support for TLS (Transport Layer Security), formerly known as SSL (Secure
 Sockets Layer), is implemented by making use of the OpenSSL library or the
@@ -24296,7 +25247,7 @@ TLS connections. You need to turn off SMTP scanning for these products in order
 to get TLS to work.
 
 
-41.1 Support for the legacy "ssmtp" (aka "smtps") protocol
+42.1 Support for the legacy "ssmtp" (aka "smtps") protocol
 ----------------------------------------------------------
 
 Early implementations of encrypted SMTP used a different TCP port from normal
@@ -24322,7 +25273,7 @@ There is also a -tls-on-connect command line option. This overrides
 tls_on_connect_ports; it forces the legacy behaviour for all ports.
 
 
-41.2 OpenSSL vs GnuTLS
+42.2 OpenSSL vs GnuTLS
 ----------------------
 
 The first TLS support in Exim was implemented using OpenSSL. Support for GnuTLS
@@ -24340,8 +25291,9 @@ files and libraries for GnuTLS can be found.
 
 There are some differences in usage when using GnuTLS instead of OpenSSL:
 
-  * The tls_verify_certificates option must contain the name of a file, not the
-    name of a directory (for OpenSSL it can be either).
+  * The tls_verify_certificates option cannot be the path of a directory for
+    GnuTLS versions before 3.3.6 (for later versions, or OpenSSL, it can be
+    either).
 
   * The default value for tls_dhparam differs for historical reasons.
 
@@ -24359,7 +25311,7 @@ There are some differences in usage when using GnuTLS instead of OpenSSL:
     transport option).
 
   * The tls_require_ciphers options operate differently, as described in the
-    sections 41.4 and 41.5.
+    sections 42.4 and 42.5.
 
   * The tls_dh_min_bits SMTP transport option is only honoured by GnuTLS. When
     using OpenSSL, this option is ignored. (If an API is found to let OpenSSL
@@ -24372,7 +25324,7 @@ There are some differences in usage when using GnuTLS instead of OpenSSL:
     implementation, then patches are welcome.
 
 
-41.3 GnuTLS parameter computation
+42.3 GnuTLS parameter computation
 ---------------------------------
 
 This section only applies if tls_dhparam is set to "historic" or to an explicit
@@ -24455,7 +25407,7 @@ above. There is no sane procedure available to Exim to double-check the size of
 the generated prime, so it might still be too large.
 
 
-41.4 Requiring specific ciphers in OpenSSL
+42.4 Requiring specific ciphers in OpenSSL
 ------------------------------------------
 
 There is a function in the OpenSSL library that can be passed a list of cipher
@@ -24512,14 +25464,15 @@ tls_require_ciphers = ${if =={$received_port}{25}\
                            {HIGH:!MD5:!SHA1}}
 
 
-41.5 Requiring specific ciphers or other parameters in GnuTLS
+42.5 Requiring specific ciphers or other parameters in GnuTLS
 -------------------------------------------------------------
 
 The GnuTLS library allows the caller to provide a "priority string", documented
 as part of the gnutls_priority_init function. This is very similar to the
 ciphersuite specification in OpenSSL.
 
-The tls_require_ciphers option is treated as the GnuTLS priority string.
+The tls_require_ciphers option is treated as the GnuTLS priority string and
+controls both protocols and ciphers.
 
 The tls_require_ciphers option is available both as an global option,
 controlling how Exim behaves as a server, and also as an option of the smtp
@@ -24531,8 +25484,15 @@ feature enhancements of GnuTLS.
 Documentation of the strings accepted may be found in the GnuTLS manual, under
 "Priority strings". This is online as http://www.gnutls.org/manual/html_node/
 Priority-Strings.html, but beware that this relates to GnuTLS 3, which may be
-newer than the version installed on your system. If you are using GnuTLS 3, 
-then the example code on that site can be used to test a given string.
+newer than the version installed on your system. If you are using GnuTLS 3,
+then the example code http://www.gnutls.org/manual/gnutls.html#
+Listing-the-ciphersuites-in-a-priority-string on that site can be used to test
+a given string.
+
+For example:
+
+# Disable older versions of protocols
+tls_require_ciphers = NORMAL:%LATEST_RECORD_VERSION:-VERS-SSL3.0
 
 Prior to Exim 4.80, an older API of GnuTLS was used, and Exim supported three
 additional options, "gnutls_require_kx", "gnutls_require_mac" and "
@@ -24550,7 +25510,7 @@ tls_require_ciphers = ${if =={$received_port}{25}\
                            {SECURE128}}
 
 
-41.6 Configuring an Exim server to use TLS
+42.6 Configuring an Exim server to use TLS
 ------------------------------------------
 
 When Exim has been built with TLS support, it advertises the availability of
@@ -24569,10 +25529,15 @@ in trying to issue SMTP commands, all except QUIT are rejected with the error
 If a STARTTLS command is issued within an existing TLS session, it is rejected
 with a 554 error code.
 
-To enable TLS operations on a server, you must set tls_advertise_hosts to match
-some hosts. You can, of course, set it to * to match all hosts. However, this
-is not all you need to do. TLS sessions to a server won't work without some
-further configuration at the server end.
+To enable TLS operations on a server, the tls_advertise_hosts option must be
+set to match some hosts. The default is * which matches all hosts.
+
+If this is all you do, TLS encryption will be enabled but not authentication -
+meaning that the peer has no assurance it is actually you he is talking to. You
+gain protection from a passive sniffer listening on the wire but not from
+someone able to intercept the communication.
+
+Further protection requires some further configuration at the server end.
 
 It is rumoured that all existing clients that support TLS/SSL use RSA
 encryption. To make this work you need to set, in the server,
@@ -24583,17 +25548,17 @@ tls_privatekey = /some/file/name
 These options are, in fact, expanded strings, so you can make them depend on
 the identity of the client that is connected if you wish. The first file
 contains the server's X509 certificate, and the second contains the private key
-that goes with it. These files need to be readable by the Exim user, and must
-always be given as full path names. They can be the same file if both the
-certificate and the key are contained within it. If tls_privatekey is not set,
-or if its expansion is forced to fail or results in an empty string, this is
-assumed to be the case. The certificate file may also contain intermediate
-certificates that need to be sent to the client to enable it to authenticate
-the server's certificate.
+that goes with it. These files need to be PEM format and readable by the Exim
+user, and must always be given as full path names. The key must not be
+password-protected. They can be the same file if both the certificate and the
+key are contained within it. If tls_privatekey is not set, or if its expansion
+is forced to fail or results in an empty string, this is assumed to be the
+case. The certificate file may also contain intermediate certificates that need
+to be sent to the client to enable it to authenticate the server's certificate.
 
 If you do not understand about certificates and keys, please try to find a
 source of this background information, which is not Exim-specific. (There are a
-few comments below in section 41.12.)
+few comments below in section 42.12.)
 
 Note: These options do not apply when Exim is operating as a client - they
 apply only in the case of a server. If you need to use a certificate in an Exim
@@ -24646,7 +25611,7 @@ For outgoing SMTP deliveries, $tls_out_cipher is used and logged (again
 depending on the tls_cipher log selector).
 
 
-41.7 Requesting and verifying client certificates
+42.7 Requesting and verifying client certificates
 -------------------------------------------------
 
 If you want an Exim server to request a certificate when negotiating a TLS
@@ -24655,8 +25620,9 @@ tls_try_verify_hosts. You can, of course, set either of them to * to apply to
 all TLS connections. For any host that matches one of these options, Exim
 requests a certificate as part of the setup of the TLS session. The contents of
 the certificate are verified by comparing it with a list of expected
-certificates. These must be available in a file or, for OpenSSL only (not
-GnuTLS), a directory, identified by tls_verify_certificates.
+certificates. These may be the system default set (depending on library
+version), an explicit file or, depending on library version, a directory,
+identified by tls_verify_certificates.
 
 A file can contain multiple certificates, concatenated end to end. If a
 directory is used (OpenSSL only), each certificate must be in a separate file,
@@ -24690,7 +25656,7 @@ to change the Received: header. When no certificate is supplied, $tls_in_peerdn
 is empty.
 
 
-41.8 Revoked certificates
+42.8 Revoked certificates
 -------------------------
 
 Certificate issuing authorities issue Certificate Revocation Lists (CRLs) when
@@ -24699,7 +25665,7 @@ 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. The downside is that clients have to periodically re-download a
-potentially huge file from every certificate authority the know of.
+potentially huge file from every certificate authority they 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
@@ -24722,7 +25688,7 @@ 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.
+version 3.3.16 / 3.4.8 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
@@ -24759,7 +25725,7 @@ There is no current way to staple a proof for a client certificate.
   noted this as invalid overall, but the re-fetch script did not.
 
 
-41.9 Configuring an Exim client to use TLS
+42.9 Configuring an Exim client to use TLS
 ------------------------------------------
 
 The tls_cipher and tls_peerdn log selectors apply to outgoing SMTP deliveries
@@ -24796,13 +25762,14 @@ the client with a certificate, which is passed to the server if it requests it.
 If the server is Exim, it will request a certificate only if tls_verify_hosts
 or tls_try_verify_hosts matches the client.
 
-If the tls_verify_certificates option is set on the smtp transport, it must
-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. Failure to verify fails
-the TLS connection unless either of the tls_verify_hosts or 
-tls_try_verify_hosts options are set.
+If the tls_verify_certificates option is set on the smtp transport, it
+specifies a collection of expected server certificates. These may be the system
+default set (depending on library version), a file or, depending on library
+version, a directory, must name a file or, for OpenSSL only (not GnuTLS), a
+directory. 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. 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
@@ -24840,7 +25807,7 @@ received.) If STARTTLS is subsequently successfully obeyed, these variables are
 set to the relevant values for the outgoing connection.
 
 
-41.10 Use of TLS Server Name Indication
+42.10 Use of TLS Server Name Indication
 ---------------------------------------
 
 With TLS1.0 or above, there is an extension mechanism by which extra
@@ -24851,7 +25818,7 @@ initial handshake, so that the server can examine the servername within and
 possibly choose to use different certificates and keys (and more) for this
 session.
 
-This is analagous to HTTP's "Host:" header, and is the main mechanism by which
+This is analogous to HTTP's "Host:" header, and is the main mechanism by which
 HTTPS-enabled web-sites can be virtual-hosted, many sites to one IP address.
 
 With SMTP to MX, there are the same problems here as in choosing the identity
@@ -24886,17 +25853,19 @@ session handshake, to permit alternative values to be chosen:
 
   * tls_verify_certificates
 
-  * tls_verify_certificates
+  * tls_ocsp_file
 
 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
-unverified data provided prior to authentication.
+always be referenced; it is important to remember that $tls_in_sni is arbitrary
+unverified data provided prior to authentication. Further, the initial
+certificate is loaded before SNI is arrived, so an expansion for 
+tls_certificate must have a default which is used when $tls_in_sni is empty.
 
 The Exim developers are proceeding cautiously and so far no other TLS options
 are re-expanded.
 
-When Exim is built againt OpenSSL, OpenSSL must have been built with support
+When Exim is built against OpenSSL, OpenSSL must have been built with support
 for TLS Extensions. This holds true for OpenSSL 1.0.0+ and 0.9.8+ with
 enable-tlsext in EXTRACONFIGURE. If you invoke openssl s_client -h and see
 "-servername" in the output, then OpenSSL has support.
@@ -24906,7 +25875,7 @@ When Exim is built against GnuTLS, SNI support is available as of GnuTLS
 built, then you have SNI support).
 
 
-41.11 Multiple messages on the same encrypted TCP/IP connection
+42.11 Multiple messages on the same encrypted TCP/IP connection
 ---------------------------------------------------------------
 
 Exim sends multiple messages down the same TCP/IP connection by starting up an
@@ -24939,7 +25908,7 @@ transport to match those hosts for which Exim should not pass connections to
 new processes if TLS has been used.
 
 
-41.12 Certificates and all that
+42.12 Certificates and all that
 -------------------------------
 
 In order to understand fully how TLS works, you need to know about
@@ -24958,7 +25927,7 @@ descriptions. Some sample programs taken from the book are available from
 http://www.rtfm.com/openssl-examples/
 
 
-41.13 Certificate chains
+42.13 Certificate chains
 ------------------------
 
 The file named by tls_certificate may contain more than one certificate. This
@@ -24980,7 +25949,7 @@ increasingly clients will not accept such certificates. The error diagnostics
 in such a case can be frustratingly vague.
 
 
-41.14 Self-signed certificates
+42.14 Self-signed certificates
 ------------------------------
 
 You can create a self-signed certificate using the req command provided with
@@ -25024,7 +25993,7 @@ Open-source PKI book, available online at http://ospkibook.sourceforge.net/.
 
 
 ===============================================================================
-42. ACCESS CONTROL LISTS
+43. ACCESS CONTROL LISTS
 
 Access Control Lists (ACLs) are defined in a separate section of the run time
 configuration file, headed by "begin acl". Each ACL definition starts with a
@@ -25047,16 +26016,14 @@ local non-SMTP messages. The default configuration file contains an example of
 a realistic ACL for checking RCPT commands. This is discussed in chapter 7.
 
 
-42.1 Testing ACLs
+43.1 Testing ACLs
 -----------------
 
 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.53 for more details).
+locally by running a fake SMTP session with which you interact.
 
 
-42.2 Specifying when ACLs are used
+43.2 Specifying when ACLs are used
 ----------------------------------
 
 In order to cause an ACL to be used, you have to name it in one of the relevant
@@ -25069,6 +26036,7 @@ options in the main part of the configuration. These options are:
     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_dkim      ACL for each DKIM signer
     acl_smtp_etrn      ACL for ETRN
     acl_smtp_expn      ACL for EXPN
     acl_smtp_helo      ACL for HELO or EHLO
@@ -25095,7 +26063,7 @@ the message. It is therefore recommended that you do as much testing as
 possible at RCPT time.
 
 
-42.3 The non-SMTP ACLs
+43.3 The non-SMTP ACLs
 ----------------------
 
 The non-SMTP ACLs apply to all non-interactive incoming messages, that is, they
@@ -25122,14 +26090,14 @@ This cannot be used in the other non-SMTP ACLs because by the time they are
 run, it is too late.
 
 The acl_not_smtp_mime ACL is available only when Exim is compiled with the
-content-scanning extension. For details, see chapter 43.
+content-scanning extension. For details, see chapter 44.
 
 The acl_not_smtp ACL is run just before the local_scan() function. Any kind of
 rejection is treated as permanent, because there is no way of sending a
 temporary error for these kinds of message.
 
 
-42.4 The SMTP connect ACL
+43.4 The SMTP connect ACL
 -------------------------
 
 The ACL test specified by acl_smtp_connect happens at the start of an SMTP
@@ -25140,7 +26108,7 @@ message override the banner message that is otherwise specified by the
 smtp_banner option.
 
 
-42.5 The EHLO/HELO ACL
+43.5 The EHLO/HELO ACL
 ----------------------
 
 The ACL test specified by acl_smtp_helo happens when the client issues an EHLO
@@ -25150,13 +26118,17 @@ client may issue more than one EHLO or HELO command in an SMTP session, and
 indeed is required to issue a new EHLO or HELO after successfully setting up
 encryption following a STARTTLS command.
 
+Note also that a deny neither forces the client to go away nor means that mail
+will be refused on the connection. Consider checking for $sender_helo_name
+being defined in a MAIL or RCPT ACL to do that.
+
 If the command is accepted by an accept verb that has a message modifier, the
 message may not contain more than one line (it will be truncated at the first
 newline and a panic logged if it does). Such a message cannot affect the EHLO
 options that are listed on the second and subsequent lines of an EHLO response.
 
 
-42.6 The DATA ACLs
+43.6 The DATA ACLs
 ------------------
 
 Two ACLs are associated with the DATA command, because it is two-stage command,
@@ -25175,6 +26147,10 @@ received, before the final response to the DATA command is sent. This is the
 ACL specified by acl_smtp_data, which is the second ACL that is associated with
 the DATA command.
 
+If CHUNKING was advertised and a BDAT command sequence is received, the 
+acl_smtp_predata ACL is not run. The acl_smtp_data is run after the last BDAT
+command and all of the data specified is received.
+
 For both of these ACLs, it is not possible to reject individual recipients. An
 error response rejects the entire message. Unfortunately, it is known that some
 MTAs do not treat hard (5xx) responses to the DATA command (either before or
@@ -25185,7 +26161,7 @@ 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
+43.7 The SMTP DKIM ACL
 ----------------------
 
 The acl_smtp_dkim ACL is available only when Exim is compiled with DKIM support
@@ -25197,19 +26173,19 @@ otherwise specified, the default action is to accept.
 
 This ACL is evaluated before acl_smtp_mime and acl_smtp_data.
 
-For details on the operation of DKIM, see chapter 56.
+For details on the operation of DKIM, see chapter 57.
 
 
-42.8 The SMTP MIME ACL
+43.8 The SMTP MIME ACL
 ----------------------
 
 The acl_smtp_mime option is available only when Exim is compiled with the
-content-scanning extension. For details, see chapter 43.
+content-scanning extension. For details, see chapter 44.
 
 This ACL is evaluated after acl_smtp_dkim but before acl_smtp_data.
 
 
-42.9 The SMTP PRDR ACL
+43.9 The SMTP PRDR ACL
 ----------------------
 
 The acl_smtp_data_prdr ACL is available only when Exim is compiled with PRDR
@@ -25217,19 +26193,19 @@ 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.
+The ACL test specified by acl_smtp_data_prdr happens after a message has been
+received, and is executed once for each recipient of the message with
+$local_part and $domain valid. The test may accept, defer or deny for
+individual 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).
+variable $prdr_requested is "yes". 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 (e.g. single-recipient mails).
 
 See also the prdr_enable global option and the hosts_try_prdr smtp transport
 option.
@@ -25239,13 +26215,13 @@ is not defined, processing completes as if the feature was not requested by the
 client.
 
 
-42.10 The QUIT ACL
+43.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
-does not in fact control any access. For this reason, the only verbs that are
-permitted are accept and warn.
+does not in fact control any access. For this reason, it may only accept or
+warn as its final result.
 
 This ACL can be used for tasks such as custom logging at the end of an SMTP
 session. For example, you can use ACL variables in other ACLs to count
@@ -25266,7 +26242,7 @@ client are given temporary error responses until QUIT is received or the
 connection is closed. In these special cases, the QUIT ACL does not run.
 
 
-42.11 The not-QUIT ACL
+43.11 The not-QUIT ACL
 ----------------------
 
 The not-QUIT ACL, specified by acl_smtp_notquit, is run in most cases when an
@@ -25301,7 +26277,7 @@ overridden by the message modifier in the not-QUIT ACL. In the case of a drop
 verb in another ACL, it is the message from the other ACL that is used.
 
 
-42.12 Finding an ACL to use
+43.12 Finding an ACL to use
 ---------------------------
 
 The value of an acl_smtp_xxx option is expanded before use, so you can use
@@ -25352,11 +26328,11 @@ for an ACL as follows:
     file.
 
 
-42.13 ACL return codes
+43.13 ACL return codes
 ----------------------
 
 Except for the QUIT ACL, which does not affect the SMTP return code (see
-section 42.10 above), the result of running an ACL is either "accept" or
+section 43.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
@@ -25379,11 +26355,14 @@ the message's data is received, the DATA ACL is not run. A "discard" return
 from the DATA or the non-SMTP ACL discards all the remaining recipients. The
 "discard" return is not permitted for the acl_smtp_predata ACL.
 
+If the ACL for VRFY returns "accept", a recipient verify (without callout) is
+done on the address and the result determines the SMTP response.
+
 The local_scan() function is always run, even if there are no remaining
 recipients; it may create new recipients.
 
 
-42.14 Unset ACL options
+43.14 Unset ACL options
 -----------------------
 
 The default actions when any of the acl_xxx options are unset are not all the
@@ -25405,7 +26384,7 @@ acl_smtp_rcpt must be defined in order to receive any messages over an SMTP
 connection. For an example, see the ACL in the default configuration file.
 
 
-42.15 Data for message ACLs
+43.15 Data for message ACLs
 ---------------------------
 
 When a MAIL or RCPT ACL, or either of the DATA ACLs, is running, the variables
@@ -25432,7 +26411,7 @@ $rcpt_count contains the total number of RCPT commands, and $recipients_count
 contains the total number of accepted recipients.
 
 
-42.16 Data for non-message ACLs
+43.16 Data for non-message ACLs
 -------------------------------
 
 When an ACL is being run for AUTH, EHLO, ETRN, EXPN, HELO, STARTTLS, or VRFY,
@@ -25455,7 +26434,7 @@ encrypted. You can use the generic server_advertise_condition authenticator
 option to do this.)
 
 
-42.17 Format of an ACL
+43.17 Format of an ACL
 ----------------------
 
 An individual ACL consists of a number of statements. Each statement starts
@@ -25469,7 +26448,7 @@ provides a means of specifying an "and" conjunction between conditions. For
 example:
 
 deny  dnslists = list1.example
-dnslists = list2.example
+      dnslists = list2.example
 
 If there are no conditions, the verb is always obeyed. Exim stops evaluating
 the conditions and modifiers when it reaches a condition that fails. What
@@ -25478,7 +26457,7 @@ all the conditions make sense at every testing point. For example, you cannot
 test a sender address in the ACL that is run for a VRFY command.
 
 
-42.18 ACL verbs
+43.18 ACL verbs
 ---------------
 
 The ACL verbs are as follows:
@@ -25491,8 +26470,8 @@ The ACL verbs are as follows:
     RCPT command:
 
     accept domains = +local_domains
-    endpass
-    verify = recipient
+           endpass
+           verify = recipient
 
     If the recipient domain does not match the domains condition, control
     passes to the next statement. If it does match, the recipient is verified,
@@ -25571,7 +26550,7 @@ The ACL verbs are as follows:
     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.20.
+    discussed in section 43.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
@@ -25583,7 +26562,7 @@ The ACL verbs are as follows:
     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.24.
+    is more about adding header lines in section 43.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.
@@ -25609,7 +26588,7 @@ continue it onto several physical lines by the usual backslash continuation
 mechanism. It is conventional to align the conditions vertically.
 
 
-42.19 ACL variables
+43.19 ACL variables
 -------------------
 
 There are some special variables that can be set during ACL processing. They
@@ -25651,7 +26630,7 @@ Versions of Exim before 4.64 have a limited set of numbered variables, but
 their names are compatible, so there is no problem with upgrading.
 
 
-42.20 Condition and modifier processing
+43.20 Condition and modifier processing
 ---------------------------------------
 
 An exclamation mark preceding a condition negates its result. For example:
@@ -25724,7 +26703,7 @@ The "deny" result does not happen until the end of the statement is reached, by
 which time Exim has set up the message.
 
 
-42.21 ACL modifiers
+43.21 ACL modifiers
 -------------------
 
 The ACL modifiers are as follows:
@@ -25733,7 +26712,7 @@ add_header = <text>
 
     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.24.
+    accepted. For details, see section 43.24.
 
 continue = <text>
 
@@ -25759,10 +26738,10 @@ control = <text>
     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.22. The control modifier can be used in
+    described separately in section 43.22. The control modifier can be used in
     several different ways. For example:
 
-      * It can be at the end of an accept statement:
+      + It can be at the end of an accept statement:
 
             accept  ...some conditions
                     control = queue_only
@@ -25770,7 +26749,7 @@ control = <text>
         In this case, the control is applied when this statement yields
         "accept", in other words, when the conditions are all true.
 
-      * It can be in the middle of an accept statement:
+      + It can be in the middle of an accept statement:
 
             accept  ...some conditions...
                     control = queue_only
@@ -25781,7 +26760,7 @@ control = <text>
         conditions is false. In this case, some subsequent statement must yield
         "accept" for the control to be relevant.
 
-      * It can be used with warn to apply the control, leaving the decision
+      + It can be used with warn to apply the control, leaving the decision
         about accepting or denying to a subsequent verb. For example:
 
             warn    ...some conditions...
@@ -25792,7 +26771,7 @@ control = <text>
         , so it does not add anything to the message and does not write a log
         entry.
 
-      * If you want to apply a control unconditionally, you can use it with a 
+      + If you want to apply a control unconditionally, you can use it with a 
         require verb. For example:
 
             require  control = no_multiline_responses
@@ -25973,6 +26952,9 @@ message = <text>
     processed anyway. If the message contains newlines, this gives rise to a
     multi-line SMTP response.
 
+    For ACLs that are called by an acl = ACL condition, the message is stored
+    in $acl_verify_message, from which the calling ACL may use it.
+
     If message is used on a statement that verifies an address, the message
     specified overrides any message that is generated by the verification
     process. However, the original message is available in the variable
@@ -25988,15 +26970,24 @@ message = <text>
     add_header acts as soon as it is encountered. If message is used with warn
     in an ACL that is not concerned with receiving a message, it has no effect.
 
+queue = <text>
+
+    This modifier specifies the use of a named queue for spool files for the
+    message. It can only be used before the message is received (i.e. not in
+    the DATA ACL). This could be used, for example, for known high-volume burst
+    sources of traffic, or for quarantine of messages. Separate queue-runner
+    processes will be needed for named queues. If the text after expansion is
+    empty, the default queue is used.
+
 remove_header = <text>
 
     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.25.
+    the message is ultimately accepted. For details, see section 43.25.
 
 set <acl_name> = <value>
 
-    This modifier puts a value into one of the ACL variables (see section 42.19
+    This modifier puts a value into one of the ACL variables (see section 43.19
     ).
 
 udpsend = <parameters>
@@ -26013,7 +27004,7 @@ udpsend = <parameters>
                  $tod_zulu $sender_host_address
 
 
-42.22 Use of the control modifier
+43.22 Use of the control modifier
 ---------------------------------
 
 The control modifier supports the following settings:
@@ -26067,29 +27058,54 @@ control = caseful_local_part, control = caselower_local_part
     Notice that we put back the lower cased version afterwards, assuming that
     is what is wanted for subsequent tests.
 
-control = cutthrough_delivery
+control = cutthrough_delivery/<options>
 
     This option requests delivery be attempted while the item is being
-    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.
+    received.
+
+    The option is usable in the RCPT ACL. If enabled for a message received via
+    smtp and routed to an smtp transport, and only one transport, interface,
+    destination host and port combination is used for all recipients of the
+    message, then the delivery connection is made while the receiving
+    connection is open and data is copied from one to the other.
+
+    An attempt to set this option for any recipient but the first for a mail
+    will be quietly ignored. If a recipient-verify callout (with use_sender)
+    connection is subsequently requested in the same ACL it is held open and
+    used for any subsequent recipients and the data, otherwise one is made
+    after the initial RCPT 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.
 
+    All the usual ACLs are called; if one results in the message being
+    rejected, all effort spent in delivery (including the costs on the ultimate
+    destination) will be wasted. Note that in the case of data-time ACLs this
+    includes the entire message body.
+
     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.
+    destination before the entire message has been received from the source. It
+    is not supported for messages received with the SMTP PRDR
+
+    or CHUNKING
+
+    options in use.
 
     Should the ultimate destination system positively accept or reject the
     mail, a corresponding indication is given to the source system and nothing
-    is queued. If there is a temporary error the item is queued for later
-    delivery in the usual fashion. If the item is successfully delivered in
-    cutthrough mode the log line is tagged with ">>" rather than "=>" and
-    appears before the acceptance "<=" line.
+    is queued. If the item is successfully delivered in cutthrough mode the
+    delivery log lines are tagged with ">>" rather than "=>" and appear before
+    the acceptance "<=" line.
+
+    If there is a temporary error the item is queued for later delivery in the
+    usual fashion. This behaviour can be adjusted by appending the option defer
+    =<value> to the control; the default value is "spool" and the alternate
+    value "pass" copies an SMTP defer response from the target back to the
+    initiator and does not queue the message. Note that this is independent of
+    any recipient verify conditions in the ACL.
 
     Delivery in this mode avoids the generation of a bounce mail to a (possibly
     faked) sender when the destination system is doing content-scan based
@@ -26102,17 +27118,19 @@ control = debug/<options>
     debuglog. The filename can be adjusted with the tag option, which may
     access any variables already defined. The logging may be adjusted with the 
     opts option, which takes the same values as the "-d" command-line option.
-    Some examples (which depend on variables that don't exist in all contexts):
+    Logging may be stopped, and the file removed, with the kill option. Some
+    examples (which depend on variables that don't exist in all contexts):
 
           control = debug
           control = debug/tag=.$sender_host_address
           control = debug/opts=+expand+acl
           control = debug/tag=.$message_exim_id/opts=+expand
+          control = debug/kill
 
 control = dkim_disable_verify
 
     This control turns off DKIM verification processing entirely. For details
-    on the operation and configuration of DKIM, see chapter 56.
+    on the operation and configuration of DKIM, see chapter 57.
 
 control = dscp/<value>
 
@@ -26227,11 +27245,11 @@ control = no_multiline_responses
     after all only a sop to broken clients, is implemented by doing two very
     easy things:
 
-      * Extra information that is normally output as part of a rejection caused
+      + Extra information that is normally output as part of a rejection caused
         by sender verification failure is omitted. Only the final line
         (typically "sender verification failed") is sent.
 
-      * If a message modifier supplies a multiline response, only the first
+      + If a message modifier supplies a multiline response, only the first
         line is output.
 
     The setting of the switch can, of course, be made conditional on the
@@ -26265,8 +27283,8 @@ control = submission/<options>
     not present. This control is not permitted in the acl_smtp_data ACL,
     because that is too late (the message has already been created).
 
-    Chapter 46 describes the processing that Exim applies to messages. Section
-    46.1 covers the processing that happens in submission mode; the available
+    Chapter 47 describes the processing that Exim applies to messages. Section
+    47.1 covers the processing that happens in submission mode; the available
     options for this control are described there. The control applies only to
     the current message, not to any subsequent ones that may be received in the
     same SMTP connection.
@@ -26277,12 +27295,12 @@ control = suppress_local_fixups
     complement of "control = submission". It disables the fixups that are
     normally applied to locally-submitted messages. Specifically:
 
-      * Any Sender: header line is left alone (in this respect, it is a dynamic
+      + Any Sender: header line is left alone (in this respect, it is a dynamic
         version of local_sender_retain).
 
-      * No Message-ID:, From:, or Date: header lines are added.
+      + No Message-ID:, From:, or Date: header lines are added.
 
-      * There is no check that From: corresponds to the actual sender.
+      + There is no check that From: corresponds to the actual sender.
 
     This control may be useful when a remotely-originated message is accepted,
     passed to some scanning program, and then re-submitted for delivery. It can
@@ -26293,8 +27311,13 @@ control = suppress_local_fixups
     Note: This control applies only to the current message, not to any others
     that are being submitted at the same time using -bs or -bS.
 
+control = utf8_downconvert
+
+    This control enables conversion of UTF-8 in message addresses to a-label
+    form. For details see section 59.1.
+
 
-42.23 Summary of message fixup control
+43.23 Summary of message fixup control
 --------------------------------------
 
 All four possibilities for message fixups can be specified:
@@ -26309,7 +27332,7 @@ All four possibilities for message fixups can be specified:
   * Remotely submitted, fixups applied: use "control = submission".
 
 
-42.24 Adding header lines in ACLs
+43.24 Adding header lines in ACLs
 ---------------------------------
 
 The add_header modifier can be used to add one or more extra header lines to an
@@ -26326,7 +27349,7 @@ 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.
+or DKIM ACLs for a message 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
@@ -26352,7 +27375,7 @@ RCPT, and predata ACLs are not visible until the DATA ACL and MIME ACLs are
 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.19.
+want to do this, you can use ACL variables, as described in section 43.19.
 
 The list of headers yet to be added is given by the $headers_added variable.
 
@@ -26402,7 +27425,7 @@ an ACL. It does NOT work for header lines that are added in a system filter or
 in a router or transport.
 
 
-42.25 Removing header lines in ACLs
+43.25 Removing header lines in ACLs
 -----------------------------------
 
 The remove_header modifier can be used to remove one or more header lines from
@@ -26418,8 +27441,8 @@ 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.
+Headers will not be removed from the message if the modifier is used in DATA,
+MIME or DKIM ACLs for a message 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
@@ -26449,7 +27472,7 @@ not visible in the DATA ACL and MIME ACLs. Similarly, header lines that are
 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.19.
+you should instead use ACL variables, as described in section 43.19.
 
 The remove_header modifier acts immediately as it is encountered during the
 processing of an ACL. Notice the difference between these two cases:
@@ -26471,13 +27494,13 @@ during ACL processing. It does NOT remove header lines that are added in a
 system filter or in a router or transport.
 
 
-42.26 ACL conditions
+43.26 ACL conditions
 --------------------
 
 Some of the conditions listed in this section are available only when Exim is
 compiled with the content-scanning extension. They are included here briefly
 for completeness. More detailed descriptions can be found in the discussion on
-content scanning in chapter 43.
+content scanning in chapter 44.
 
 Not all conditions are relevant in all circumstances. For example, testing
 senders and recipients does not make sense in an ACL that is being run as the
@@ -26501,7 +27524,8 @@ acl = <name of acl or ACL string or file name >
     can be appended; they appear within the called ACL in $acl_arg1 to
     $acl_arg9, and $acl_narg is set to the count of values. Previous values of
     these variables are restored after the call returns. The name and values
-    are expanded separately.
+    are expanded separately. Note that spaces in complex expansions which are
+    used as arguments will act as argument separators.
 
     If the nested acl returns "drop" and the outer condition denies access, the
     connection is dropped. If it returns "discard", the verb must be accept or 
@@ -26539,12 +27563,7 @@ decode = <location>
     acl_smtp_mime. It causes the current MIME part to be decoded into a file.
     If all goes well, the condition is true. It is false only if there are
     problems such as a syntax error or a memory shortage. For more details, see
-    chapter 43.
-
-demime = <extension list>
-
-    This condition is available only when Exim is compiled with the
-    content-scanning extension. Its use is described in section 43.6.
+    chapter 44.
 
 dnslists = <list of domain names and other data>
 
@@ -26552,7 +27571,7 @@ dnslists = <list of domain names and other data>
     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.27-42.37 for details.
+    sections 43.27-43.37 for details.
 
 domains = <domain list>
 
@@ -26625,19 +27644,19 @@ malware = <option>
 
     This condition is available only when Exim is compiled with the
     content-scanning extension. It causes the incoming message to be scanned
-    for viruses. For details, see chapter 43.
+    for viruses. For details, see chapter 44.
 
 mime_regex = <list of regular expressions>
 
     This condition is available only when Exim is compiled with the
     content-scanning extension, and it is allowed only in the ACL defined by 
     acl_smtp_mime. It causes the current MIME part to be scanned for a match
-    with any of the regular expressions. For details, see chapter 43.
+    with any of the regular expressions. For details, see chapter 44.
 
 ratelimit = <parameters>
 
     This condition can be used to limit the rate at which a user or host
-    submits messages. Details are given in section 42.38.
+    submits messages. Details are given in section 43.38.
 
 recipients = <address list>
 
@@ -26649,7 +27668,7 @@ regex = <list of regular expressions>
     This condition is available only when Exim is compiled with the
     content-scanning extension, and is available only in the DATA, MIME, and
     non-SMTP ACLs. It causes the incoming message to be scanned for a match
-    with any of the regular expressions. For details, see chapter 43.
+    with any of the regular expressions. For details, see chapter 44.
 
 sender_domains = <domain list>
 
@@ -26678,19 +27697,19 @@ spam = <username>
 
     This condition is available only when Exim is compiled with the
     content-scanning extension. It causes the incoming message to be scanned by
-    SpamAssassin. For details, see chapter 43.
+    SpamAssassin. For details, see chapter 44.
 
 verify = certificate
 
     This condition is true in an SMTP session if the session is encrypted, and
     a certificate was received from the client, and the certificate was
     verified. The server requests a certificate only if the client matches 
-    tls_verify_hosts or tls_try_verify_hosts (see chapter 41).
+    tls_verify_hosts or tls_try_verify_hosts (see chapter 42).
 
 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.50.
+    to send email. Details of how this works are given in section 43.50.
 
 verify = header_names_ascii
 
@@ -26718,7 +27737,7 @@ verify = header_sender/<options>
     command.
 
     Details of address verification and the options are given later, starting
-    at section 42.44 (callouts are described in section 42.45). You can combine
+    at section 43.44 (callouts are described in section 43.45). You can combine
     this condition with the senders condition to restrict it to bounce messages
     only:
 
@@ -26731,10 +27750,11 @@ verify = header_syntax
     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 the syntax of all header lines that can contain
-    lists of addresses (Sender:, From:, Reply-To:, To:, Cc:, and Bcc:).
-    Unqualified addresses (local parts without domains) are permitted only in
-    locally generated messages and from hosts that match 
-    sender_unqualified_hosts or recipient_unqualified_hosts, as appropriate.
+    lists of addresses (Sender:, From:, Reply-To:, To:, Cc:, and Bcc:),
+    returning true if there are no problems. Unqualified addresses (local parts
+    without domains) are permitted only in locally generated messages and from
+    hosts that match sender_unqualified_hosts or recipient_unqualified_hosts,
+    as appropriate.
 
     Note that this condition is a syntax check only. However, a common spamming
     ploy used to be to send syntactically invalid headers such as
@@ -26772,14 +27792,14 @@ verify = recipient/<options>
 
     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.44. After a recipient has been verified, the value
+    starting at section 43.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
     new address, and in that case, the subsequent value of $address_data is the
     value for the child address.
 
-verify = reverse_host_lookup
+verify = reverse_host_lookup/<options>
 
     This condition ensures that a verified host name has been looked up from
     the IP address of the client host. (This may have happened already if the
@@ -26788,6 +27808,9 @@ verify = reverse_host_lookup
     reverse DNS lookup, or one of its aliases, does, when it is itself looked
     up in the DNS, yield the original IP address.
 
+    There is one possible option, "defer_ok". If this is present and a DNS
+    operation returns a temporary error, the verify condition succeeds.
+
     If this condition is used for a locally generated message (that is, when
     there is no client host involved), it always succeeds.
 
@@ -26805,7 +27828,7 @@ verify = sender/<options>
     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.44. Exim
+    Details of verification are given later, starting at section 43.44. Exim
     caches the result of sender verification, to avoid doing it more than once
     per message.
 
@@ -26814,8 +27837,14 @@ verify = sender=<address>/<options>
     This is a variation of the previous option, in which a modified address is
     verified as a sender.
 
+    Note that '/' is legal in local-parts; if the address may have such (eg. is
+    generated from the received message) they must be protected from the
+    options parsing by doubling:
+
+    verify = sender=${sg{${address:$h_sender:}}{/}{//}}
 
-42.27 Using DNS lists
+
+43.27 Using DNS lists
 ---------------------
 
 In its simplest form, the dnslists condition tests whether the calling host is
@@ -26866,13 +27895,14 @@ deny  dnslists = blackholes.mail-abuse.org
 warn  message  = X-Warn: sending host is on dialups list
       dnslists = dialups.mail-abuse.org
 
-DNS list lookups are cached by Exim for the duration of the SMTP session, so a
-lookup based on the IP address is done at most once for any incoming
-connection. Exim does not share information between multiple incoming
-connections (but your local name server cache should be active).
+DNS list lookups are cached by Exim for the duration of the SMTP session (but
+limited by the DNS return TTL value), so a lookup based on the IP address is
+done at most once for any incoming connection (assuming long-enough TTL). Exim
+does not share information between multiple incoming connections (but your
+local name server cache should be active).
 
 
-42.28 Specifying the IP address for a DNS list lookup
+43.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
@@ -26884,10 +27914,10 @@ deny dnslists = black.list.tld/192.168.1.2
 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.30 below.
+43.30 below.
 
 
-42.29 DNS lists keyed on domain names
+43.29 DNS lists keyed on domain names
 -------------------------------------
 
 There are some lists that are keyed on domain names rather than inverted IP
@@ -26916,7 +27946,7 @@ The first item checks the sending host's IP address; the second checks a domain
 name. The whole condition is true if either of the DNS lookups succeeds.
 
 
-42.30 Multiple explicit keys for a DNS list
+43.30 Multiple explicit keys for a DNS list
 -------------------------------------------
 
 The syntax described above for looking up explicitly-defined values (either
@@ -26944,7 +27974,7 @@ The DNS lookups that occur are:
 a.domain.black.list.tld
 
 Once a DNS record has been found (that matches a specific IP return address, if
-specified - see section 42.33), no further lookups are done. If there is a
+specified - see section 43.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
@@ -26973,16 +28003,16 @@ DNS records. The inner dnsdb lookup produces a list of MX hosts and the outer
 dnsdb lookup finds the IP addresses for these hosts. The result of expanding
 the condition might be something like this:
 
-dnslists = sbl.spahmaus.org/<|192.168.2.3|192.168.5.6|...
+dnslists = sbl.spamhaus.org/<|192.168.2.3|192.168.5.6|...
 
 Thus, this example checks whether or not the IP addresses of the sender
 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.32).
+$dnslist_matched (see section 43.32).
 
 
-42.31 Data returned by DNS lists
+43.31 Data returned by DNS lists
 --------------------------------
 
 DNS lists are constructed using address records in the DNS. The original RBL
@@ -26998,12 +28028,12 @@ The values used on the RBL+ list are:
 127.1.0.6  RSS and DUL
 127.1.0.7  RSS and DUL and RBL
 
-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
+Section 43.33 below describes how you can distinguish between different values.
+Some DNS lists may return more than one address record; see section 43.35 for
 details of how they are checked.
 
 
-42.32 Variables set from DNS lists
+43.32 Variables set from DNS lists
 ----------------------------------
 
 When an entry is found in a DNS list, the variable $dnslist_domain contains the
@@ -27017,7 +28047,7 @@ deny dnslists = spamhaus.example
 
 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.30) might generate
+For example, using a data lookup (as described in section 43.30) might generate
 a dnslists lookup like this:
 
 deny dnslists = spamhaus.example/<|192.168.1.2|192.168.6.7|...
@@ -27029,7 +28059,7 @@ If more than one address record is returned by the DNS lookup, all the IP
 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.36 for a way of obtaining more information.
+meaningful. See section 43.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
@@ -27041,7 +28071,7 @@ deny    hosts = !+local_networks
         dnslists = rbl-plus.mail-abuse.example
 
 
-42.33 Additional matching conditions for DNS lists
+43.33 Additional matching conditions for DNS lists
 --------------------------------------------------
 
 You can add an equals sign and an IP address after a dnslists domain name in
@@ -27052,7 +28082,7 @@ deny dnslists = rblplus.mail-abuse.org=127.0.0.2
 
 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.35 describes how multiple
+the DNS lookup returns just one record. Section 43.35 describes how multiple
 records are handled.
 
 More than one IP address may be given for checking, using a comma as a
@@ -27086,7 +28116,7 @@ matches if the final component of the address is an odd number or two times an
 odd number.
 
 
-42.34 Negated DNS matching conditions
+43.34 Negated DNS matching conditions
 -------------------------------------
 
 You can supply a negative list of IP addresses as part of a dnslists condition.
@@ -27132,7 +28162,7 @@ deny  dnslists = relays.ordb.org
 which is less clear, and harder to maintain.
 
 
-42.35 Handling multiple DNS records from a DNS list
+43.35 Handling multiple DNS records from a DNS list
 ---------------------------------------------------
 
 A DNS lookup for a dnslists condition may return more than one DNS record,
@@ -27194,7 +28224,7 @@ When the DNS lookup yields only a single IP address, there is no difference
 between "=" and "==" and between "&" and "=&".
 
 
-42.36 Detailed information from merged DNS lists
+43.36 Detailed information from merged DNS lists
 ------------------------------------------------
 
 When the facility for restricting the matching IP values in a DNS list is used,
@@ -27243,7 +28273,7 @@ values matches (or if no record is found), this is the only lookup that is
 done. Only if there is a match is one of the more specific lists consulted.
 
 
-42.37 DNS lists and IPv6
+43.37 DNS lists and IPv6
 ------------------------
 
 If Exim is asked to do a dnslist lookup for an IPv6 address, it inverts it
@@ -27275,7 +28305,7 @@ list and inner (lookup keys) list:
        dnslists = <; dnsbl.example.com/<|$acl_m_addrslist
 
 
-42.38 Rate limiting incoming messages
+43.38 Rate limiting incoming messages
 -------------------------------------
 
 The ratelimit ACL condition can be used to measure and control the rate at
@@ -27339,7 +28369,7 @@ unique= option, the lookup key changes so Exim will forget past behaviour. The
 lookup key is not affected by changes to the update mode and the count= option.
 
 
-42.39 Ratelimit options for what is being measured
+43.39 Ratelimit options for what is being measured
 --------------------------------------------------
 
 The per_conn option limits the client's connection rate. It is not normally
@@ -27362,8 +28392,9 @@ The per_rcpt option causes Exim to limit the rate at which recipients are
 accepted. It can be used in the acl_smtp_rcpt, acl_smtp_predata, acl_smtp_mime,
 acl_smtp_data, or acl_smtp_rcpt ACLs. In acl_smtp_rcpt the rate is updated one
 recipient at a time; in the other ACLs the rate is updated with the total
-recipient count in one go. Note that in either case the rate limiting engine
-will see a message with many recipients as a large high-speed burst.
+(accepted) recipient count in one go. Note that in either case the rate
+limiting engine will see a message with many recipients as a large high-speed
+burst.
 
 The per_addr option is like the per_rcpt option, except it counts the number of
 different recipients that the client has sent messages to in the last time
@@ -27382,10 +28413,10 @@ count=$message_size". If there is no count= option, Exim increases the measured
 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.42 below.
+The unique= option is described in section 43.42 below.
 
 
-42.40 Ratelimit update modes
+43.40 Ratelimit update modes
 ----------------------------
 
 You can specify one of three options with the ratelimit condition to control
@@ -27424,7 +28455,7 @@ ACLs the default update mode is leaky (see the next section) so you must
 specify the readonly option explicitly.
 
 
-42.41 Ratelimit options for handling fast clients
+43.41 Ratelimit options for handling fast clients
 -------------------------------------------------
 
 If a client's average rate is greater than the maximum, the rate limiting
@@ -27454,7 +28485,7 @@ with this formula:
         ln(peakrate/maxrate)
 
 
-42.42 Limiting the rate of different events
+43.42 Limiting the rate of different events
 -------------------------------------------
 
 The ratelimit unique= option controls a mechanism for counting the rate of
@@ -27491,7 +28522,7 @@ logged incorrectly; any countermeasures you configure will be as effective as
 intended.
 
 
-42.43 Using rate limiting
+43.43 Using rate limiting
 -------------------------
 
 Exim's other ACL facilities are used to define what counter-measures are taken
@@ -27535,11 +28566,11 @@ its hints data after a reboot (including retry hints, the callout cache, and
 ratelimit data).
 
 
-42.44 Address verification
+43.44 Address 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
+Several of the verify conditions described in section 43.26 cause addresses to
+be verified. Section 43.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:
@@ -27563,13 +28594,13 @@ follows:
     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.48, which discusses the
+  * The no_details option is covered in section 43.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.49.
+    discussion in section 43.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
@@ -27601,7 +28632,7 @@ The main use of these variables is expected to be to distinguish between
 rejections of MAIL and rejections of RCPT in callouts.
 
 
-42.45 Callout verification
+43.45 Callout verification
 --------------------------
 
 For non-local addresses, routing verifies the domain, but is unable to do any
@@ -27619,7 +28650,7 @@ request them by setting appropriate options on the verify condition, as
 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.47.
+caching are in section 43.47.
 
 Recipient callouts are usually used only between hosts that are controlled by
 the same administration. For example, a corporate gateway host could use
@@ -27678,7 +28709,7 @@ clients when the SMTP PIPELINING extension is in use. The flushing can be
 disabled by using a control modifier to set no_callout_flush.
 
 
-42.46 Additional parameters for callouts
+43.46 Additional parameters for callouts
 ----------------------------------------
 
 The callout option can be followed by an equals sign and a number of optional
@@ -27843,7 +28874,7 @@ sender/recipient combination; thus, for any given recipient, many more actual
 callouts are performed than when an empty sender or postmaster is used.
 
 
-42.47 Callout caching
+43.47 Callout caching
 ---------------------
 
 Exim caches the results of callouts in order to reduce the amount of resources
@@ -27884,10 +28915,10 @@ being tested. If the domain routes to several hosts, it is assumed that their
 behaviour will be the same.
 
 
-42.48 Sender address verification reporting
+43.48 Sender address verification reporting
 -------------------------------------------
 
-See section 42.44 for a general discussion of verification. When sender
+See section 43.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:
@@ -27909,7 +28940,7 @@ the ACL statement that requests sender verification. For example:
 verify = sender/no_details
 
 
-42.49 Redirection while verifying
+43.49 Redirection while verifying
 ---------------------------------
 
 A dilemma arises when a local address is redirected by aliasing or forwarding
@@ -27952,7 +28983,7 @@ also specified. In that case, full verification is done for every generated
 address and a report is output for each of them.
 
 
-42.50 Client SMTP authorization (CSA)
+43.50 Client SMTP authorization (CSA)
 -------------------------------------
 
 Client SMTP Authorization is a system that allows a site to advertise which
@@ -28022,7 +29053,7 @@ The authorization code can be "Y" for yes, "N" for no, "X" for explicit
 authorization required but absent, or "?" for unknown.
 
 
-42.51 Bounce address tag validation
+43.51 Bounce address tag validation
 -----------------------------------
 
 Bounce address tag validation (BATV) is a scheme whereby the envelope senders
@@ -28105,7 +29136,7 @@ external_smtp_batv:
 If no key can be found for the existing return path, no signing takes place.
 
 
-42.52 Using an ACL to control relaying
+43.52 Using an ACL to control relaying
 --------------------------------------
 
 An MTA is said to relay a message if it receives it from some host and delivers
@@ -28160,25 +29191,17 @@ default configuration includes a more comprehensive example, which is described
 in chapter 7.
 
 
-42.53 Checking a relay configuration
+43.53 Checking a relay configuration
 ------------------------------------
 
 You can check the relay characteristics of your configuration in the same way
 that you can test any ACL behaviour for an incoming SMTP connection, by using
 the -bh option to run a fake SMTP session with which you interact.
 
-For specifically testing for unwanted relaying, the host 
-relay-test.mail-abuse.org provides a useful service. If you telnet to this host
-from the host on which Exim is running, using the normal telnet port, you will
-see a normal telnet connection message and then quite a long delay. Be patient.
-The remote host is making an SMTP connection back to your host, and trying a
-number of common probes to test for open relay vulnerability. The results of
-the tests will eventually appear on your terminal.
-
 
 
 ===============================================================================
-43. CONTENT SCANNING AT ACL TIME
+44. CONTENT SCANNING AT ACL TIME
 
 The extension of Exim to include content scanning at ACL time, formerly known
 as "exiscan", was originally implemented as a patch by Tom Kistner. The code
@@ -28187,7 +29210,7 @@ maintain it. Most of the wording of this chapter is taken from Tom's
 specification.
 
 It is also possible to scan the content of messages at other times. The
-local_scan() function (see chapter 44) allows for content scanning after all
+local_scan() function (see chapter 45) allows for content scanning after all
 the ACLs have run. A transport filter can be used to scan messages at delivery
 time (see the transport_filter option, described in chapter 24).
 
@@ -28210,10 +29233,6 @@ Makefile. When you do that, the Exim binary is built with:
 
   * Two new main configuration options: av_scanner and spamd_address.
 
-There is another content-scanning configuration option for Local/Makefile,
-called WITH_OLD_DEMIME. If this is set, the old, deprecated demime ACL
-condition is compiled, in addition to all the other content-scanning features.
-
 Content-scanning is continually evolving, and new features are still being
 added. While such features are still unstable and liable to incompatible
 changes, they are made available in Exim by setting options whose names begin
@@ -28238,7 +29257,7 @@ has been encountered. When the MIME ACL decodes files, they are put into the
 same directory by default.
 
 
-43.1 Scanning for viruses
+44.1 Scanning for viruses
 -------------------------
 
 The malware ACL condition lets you connect virus scanner software to Exim. It
@@ -28246,8 +29265,11 @@ supports a "generic" interface to scanners called via the shell, and
 specialized interfaces for "daemon" type virus scanners, which are resident in
 memory and thus are much faster.
 
-You can set the av_scanner option in first part of the Exim configuration file
-to specify which scanner to use, together with any additional options that are
+A timeout of 2 minutes is applied to a scanner call (by default); if it expires
+then a defer action is taken.
+
+You can set the av_scanner option in the main part of the configuration to
+specify which scanner to use, together with any additional options that are
 needed. The basic syntax is as follows:
 
 av_scanner = <scanner-type>:<option1>:<option2>:[...]
@@ -28257,9 +29279,34 @@ If you do not set av_scanner, it defaults to
 av_scanner = sophie:/var/run/sophie
 
 If the value of av_scanner starts with a dollar character, it is expanded
-before use. The usual list-parsing of the content (see 6.19) applies. The
+before use. The usual list-parsing of the content (see 6.20) applies. The
 following scanner types are supported in this release:
 
+avast
+
+    This is the scanner daemon of Avast. It has been tested with Avast Core
+    Security (currently at version 1.1.7). You can get a trial version at http:
+    //www.avast.com or for Linux at http://www.avast.com/linux-server-antivirus
+    . This scanner type takes one option, which can be either a full path to a
+    UNIX socket, or host and port specifiers separated by white space. The host
+    may be a name or an IP address; the port is either a single number or a
+    pair of numbers with a dash between. Any further options are given, on
+    separate lines, to the daemon as options before the main scan command. For
+    example:
+
+    av_scanner = avast:/var/run/avast/scan.sock:FLAGS -fullfiles:SENSITIVITY -pup
+    av_scanner = avast:192.168.2.22 5036
+
+    If you omit the argument, the default path /var/run/avast/scan.sock is
+    used. If you use a remote host, you need to make Exim's spool directory
+    available to it, as the scanner is passed a file path, not file contents.
+    For information about available commands and their options you may use
+
+    $ socat UNIX:/var/run/avast/scan.sock STDIO:
+        FLAGS
+        SENSITIVITY
+        PACK
+
 aveserver
 
     This is the scanner daemon of Kaspersky Version 5. You can get a trial
@@ -28274,17 +29321,34 @@ clamd
     This daemon-type scanner is GPL and free. You can get it at http://
     www.clamav.net/. Some older versions of clamd do not seem to unpack MIME
     containers, so it used to be recommended to unpack MIME attachments in the
-    MIME ACL. This no longer believed to be necessary. One option is required:
-    either the path and name of a UNIX socket file, or a hostname or IP number,
-    and a port, separated by space, as in the second of these examples:
+    MIME ACL. This is no longer believed to be necessary.
+
+    The options are a list of server specifiers, which may be a UNIX socket
+    specification, a TCP socket specification, or a (global) option.
+
+    A socket specification consists of a space-separated list. For a Unix
+    socket the first element is a full path for the socket, for a TCP socket
+    the first element is the IP address and the second a port number, Any
+    further elements are per-server (non-global) options. These per-server
+    options are supported:
+
+    retry=<timespec>        Retry on connect fail
+
+    The "retry" option specifies a time after which a single retry for a failed
+    connect is made. The default is to not retry.
+
+    If a Unix socket file is specified, only one server is supported.
+
+    Examples:
 
     av_scanner = clamd:/opt/clamd/socket
     av_scanner = clamd:192.0.2.3 1234
     av_scanner = clamd:192.0.2.3 1234:local
+    av_scanner = clamd:192.0.2.3 1234 retry=10s
     av_scanner = clamd:192.0.2.3 1234 : 192.0.2.4 1234
 
     If the value of av_scanner points to a UNIX socket file or contains the
-    local keyword, then the ClamAV interface will pass a filename containing
+    "local" option, then the ClamAV interface will pass a filename containing
     the data to be scanned, which will should normally result in less I/O
     happening and be more efficient. Normally in the TCP case, the data is
     streamed to ClamAV as Exim does not assume that there is a common
@@ -28343,9 +29407,10 @@ cmdline
 
 drweb
 
-    The DrWeb daemon scanner (http://www.sald.com/) interface takes one
-    argument, either a full path to a UNIX socket, or an IP address and port
-    separated by white space, as in these examples:
+    The DrWeb daemon scanner (http://www.sald.com/) interface takes one option,
+    either a full path to a UNIX socket, or host and port specifiers separated
+    by white space. The host may be a name or an IP address; the port is either
+    a single number or a pair of numbers with a dash between. For example:
 
     av_scanner = drweb:/var/run/drwebd.sock
     av_scanner = drweb:192.168.2.20 31337
@@ -28353,6 +29418,16 @@ drweb
     If you omit the argument, the default path /usr/local/drweb/run/drwebd.sock
     is used. Thanks to Alex Miller for contributing the code for this scanner.
 
+f-protd
+
+    The f-protd scanner is accessed via HTTP over TCP. One argument is taken,
+    being a space-separated hostname and port number (or port-range). For
+    example:
+
+    av_scanner = f-protd:localhost 10200-10204
+
+    If you omit the argument, the default values show above are used.
+
 fsecure
 
     The F-Secure daemon scanner (http://www.f-secure.com) takes one argument
@@ -28380,8 +29455,8 @@ mksd
     some parts of documentation are now available in English. You can get it at
     http://linux.mks.com.pl/. The only option for this scanner type is the
     maximum number of processes used simultaneously to scan the attachments,
-    provided that the demime facility is employed and also provided that mksd
-    has been run with at least the same number of child processes. For example:
+    provided that mksd has been run with at least the same number of child
+    processes. For example:
 
     av_scanner = mksd:2
 
@@ -28391,7 +29466,7 @@ 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
+    IP address 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:
@@ -28423,8 +29498,9 @@ same message, the actual scanning process is only carried out once. However,
 using expandable items in av_scanner disables this caching, in which case each
 use of the malware condition causes a new scan of the message.
 
-The malware condition takes a right-hand argument that is expanded before use.
-It can then be one of
+The malware condition takes a right-hand argument that is expanded before use
+and taken as a list, slash-separated by default. The first element can then be
+one of
 
   * "true", "*", or "1", in which case the message is scanned for viruses. The
     condition succeeds if a virus was found, and fail otherwise. This is the
@@ -28436,33 +29512,39 @@ It can then be one of
   * A regular expression, in which case the message is scanned for viruses. The
     condition succeeds if a virus is found and its name matches the regular
     expression. This allows you to take special actions on certain types of
-    virus.
+    virus. Note that "/" characters in the RE must be doubled due to the
+    list-processing, unless the separator is changed (in the usual way).
 
-You can append "/defer_ok" to the malware condition to accept messages even if
-there is a problem with the virus scanner. Otherwise, such a problem causes the
-ACL to defer.
+You can append a "defer_ok" element to the malware argument list to accept
+messages even if there is a problem with the virus scanner. Otherwise, such a
+problem causes the ACL to defer.
+
+You can append a "tmo=<val>" element to the malware argument list to specify a
+non-default timeout. The default is two minutes. For example:
+
+malware = * / defer_ok / tmo=10s
+
+A timeout causes the ACL to defer.
+
+When a connection is made to the scanner the expansion variable
+$callout_address is set to record the actual address used.
 
 When a virus is found, the condition sets up an expansion variable called
 $malware_name that contains the name of the virus. You can use it in a message
 modifier that specifies the error returned to the sender, and/or in logging
 data.
 
-If your virus scanner cannot unpack MIME and TNEF containers itself, you should
-use the demime condition (see section 43.6) before the malware condition.
-
 Beware the interaction of Exim's message_size_limit with any size limits
 imposed by your anti-virus scanner.
 
 Here is a very simple scanning example:
 
 deny message = This message contains malware ($malware_name)
-     demime = *
      malware = *
 
 The next example accepts messages when there is a problem with the scanner:
 
 deny message = This message contains malware ($malware_name)
-     demime = *
      malware = */defer_ok
 
 The next example shows how to use an ACL variable to scan with both sophie and
@@ -28481,13 +29563,17 @@ deny message = This message contains malware ($malware_name)
      malware = *
 
 
-43.2 Scanning with SpamAssassin
--------------------------------
+44.2 Scanning with SpamAssassin and Rspamd
+------------------------------------------
 
 The spam ACL condition calls SpamAssassin's spamd daemon to get a spam score
-and a report for the message. You can get SpamAssassin at http://
-www.spamassassin.org, or, if you have a working Perl installation, you can use
-CPAN by running:
+and a report for the message. Support is also provided for Rspamd.
+
+For more information about installation and configuration of SpamAssassin or
+Rspamd refer to their respective websites at http://spamassassin.apache.org and
+http://www.rspamd.com
+
+SpamAssassin can be installed with CPAN by running:
 
 perl -MCPAN -e 'install Mail::SpamAssassin'
 
@@ -28495,42 +29581,93 @@ SpamAssassin has its own set of configuration files. Please review its
 documentation to see how you can tweak it. The default installation should work
 nicely, however.
 
-After having installed and configured SpamAssassin, start the spamd daemon. By
-default, it listens on 127.0.0.1, TCP port 783. If you use another host or port
-for spamd, you must set the spamd_address option in the global part of the Exim
-configuration as follows (example):
+By default, SpamAssassin listens on 127.0.0.1, TCP port 783 and if you intend
+to use an instance running on the local host you do not need to set 
+spamd_address. If you intend to use another host or port for SpamAssassin, you
+must set the spamd_address option in the global part of the Exim configuration
+as follows (example):
 
 spamd_address = 192.168.99.45 387
 
-You do not need to set this option if you use the default. As of version 2.60, 
-spamd also supports communication over UNIX sockets. If you want to use these,
-supply spamd_address with an absolute file name instead of a address/port pair:
+The SpamAssassin protocol relies on a TCP half-close from the client. If your
+SpamAssassin client side is running a Linux system with an iptables firewall,
+consider setting net.netfilter.nf_conntrack_tcp_timeout_close_wait to at least
+the timeout, Exim uses when waiting for a response from the SpamAssassin server
+(currently defaulting to 120s). With a lower value the Linux connection
+tracking may consider your half-closed connection as dead too soon.
+
+To use Rspamd (which by default listens on all local addresses on TCP port
+11333) you should add variant=rspamd after the address/port pair, for example:
+
+spamd_address = 127.0.0.1 11333 variant=rspamd
+
+As of version 2.60, SpamAssassin also supports communication over UNIX sockets.
+If you want to us these, supply spamd_address with an absolute file name
+instead of an address/port pair:
 
 spamd_address = /var/run/spamd_socket
 
 You can have multiple spamd servers to improve scalability. These can reside on
 other hardware reachable over the network. To specify multiple spamd servers,
 put multiple address/port pairs in the spamd_address option, separated with
-colons:
+colons (the separator can be changed in the usual way):
 
 spamd_address = 192.168.2.10 783 : \
                 192.168.2.11 783 : \
                 192.168.2.12 783
 
-Up to 32 spamd servers are supported. The servers are queried in a random
-fashion. When a server fails to respond to the connection attempt, all other
-servers are tried until one succeeds. If no server responds, the spam condition
-defers.
+Up to 32 spamd servers are supported. When a server fails to respond to the
+connection attempt, all other servers are tried until one succeeds. If no
+server responds, the spam condition defers.
+
+Unix and TCP socket specifications may be mixed in any order. Each element of
+the list is a list itself, space-separated by default and changeable in the
+usual way; take care to not double the separator.
+
+For TCP socket specifications a host name or IP (v4 or v6, but subject to
+list-separator quoting rules) address can be used, and the port can be one or a
+dash-separated pair. In the latter case, the range is tried in strict order.
+
+Elements after the first for Unix sockets, or second for TCP socket, are
+options. The supported options are:
+
+pri=<priority>      Selection priority
+weight=<value>      Selection bias
+time=<start>-<end>  Use only between these times of day
+retry=<timespec>    Retry on connect fail
+tmo=<timespec>      Connection time limit
+variant=rspamd      Use Rspamd rather than SpamAssassin protocol
 
-Warning: It is not possible to use the UNIX socket connection method with
-multiple spamd servers.
+The "pri" option specifies a priority for the server within the list, higher
+values being tried first. The default priority is 1.
+
+The "weight" option specifies a selection bias. Within a priority set servers
+are queried in a random fashion, weighted by this value. The default value for
+selection bias is 1.
+
+Time specifications for the "time" option are <hour>.<minute>.<second> in the
+local time zone; each element being one or more digits. Either the seconds or
+both minutes and seconds, plus the leading "." characters, may be omitted and
+will be taken as zero.
+
+Timeout specifications for the "retry" and "tmo" options are the usual Exim
+time interval standard, e.g. "20s" or "1m".
+
+The "tmo" option specifies an overall timeout for communication. The default
+value is two minutes.
+
+The "retry" option specifies a time after which a single retry for a failed
+connect is made. The default is to not retry.
 
 The spamd_address variable is expanded before use if it starts with a dollar
 sign. In this case, the expansion may return a string that is used as the list
 so that multiple spamd servers can be the result of an expansion.
 
+When a connection is made to the server the expansion variable $callout_address
+is set to record the actual address used.
 
-43.3 Calling SpamAssassin from an Exim ACL
+
+44.3 Calling SpamAssassin from an Exim ACL
 ------------------------------------------
 
 Here is a simple example of the use of the spam condition in a DATA ACL:
@@ -28541,14 +29678,17 @@ deny message = This message was classified as SPAM
 The right-hand side of the spam condition specifies a name. This is relevant if
 you have set up multiple SpamAssassin profiles. If you do not want to scan
 using a specific profile, but rather use the SpamAssassin system-wide default
-profile, you can scan for an unknown name, or simply use "nobody". However, you
-must put something on the right-hand side.
+profile, you can scan for an unknown name, or simply use "nobody". Rspamd does
+not use this setting. However, you must put something on the right-hand side.
 
 The name allows you to use per-domain or per-user antispam profiles in
 principle, but this is not straightforward in practice, because a message may
 have multiple recipients, not necessarily all in the same domain. Because the 
-spam condition has to be called from a DATA ACL in order to be able to read the
-contents of the message, the variables $local_part and $domain are not set.
+spam condition has to be called from a DATA-time ACL in order to be able to
+read the contents of the message, the variables $local_part and $domain are not
+set. Careful enforcement of single-recipient messages (e.g. by responding with
+defer in the recipient ACL for all recipients after the first), or the use of
+PRDR, are needed to use this feature.
 
 The right-hand side of the spam condition is expanded before being used, so you
 can put lookups or conditions there. When the right-hand side evaluates to "0"
@@ -28569,8 +29709,8 @@ condition for its side effects (see the variables below), you can make it
 always return "true" by appending ":true" to the username.
 
 When the spam condition is run, it sets up a number of expansion variables.
-These variables are saved with the received message, thus they are available
-for use at delivery time.
+Except for $spam_report, these variables are saved with the received message so
+are available for use at delivery time.
 
 $spam_score
 
@@ -28589,12 +29729,21 @@ $spam_bar
     A string consisting of a number of "+" or "-" characters, representing the
     integer part of the spam score value. A spam score of 4.4 would have a
     $spam_bar value of "++++". This is useful for inclusion in warning headers,
-    since MUAs can match on such strings.
+    since MUAs can match on such strings. The maximum length of the spam bar is
+    50 characters.
 
 $spam_report
 
     A multiline text table, containing the full SpamAssassin report for the
-    message. Useful for inclusion in headers or reject messages.
+    message. Useful for inclusion in headers or reject messages. This variable
+    is only usable in a DATA-time ACL. Beware that SpamAssassin may return
+    non-ASCII characters, especially when running in country-specific locales,
+    which are not legal unencoded in headers.
+
+$spam_action
+
+    For SpamAssassin either 'reject' or 'no action' depending on the spam score
+    versus threshold. For Rspamd, the recommended action.
 
 The spam condition caches its results unless expansion in spamd_address was
 used. If you call it again with the same user name, it does not scan again, but
@@ -28628,7 +29777,7 @@ deny  message = This message scored $spam_score spam points.
       condition = ${if >{$spam_score_int}{120}{1}{0}}
 
 
-43.4 Scanning MIME parts
+44.4 Scanning MIME parts
 ------------------------
 
 The acl_smtp_mime global option specifies an ACL that is called once for each
@@ -28649,7 +29798,7 @@ when this happens.
 You cannot use the malware or spam conditions in a MIME ACL; these can only be
 used in the DATA or non-SMTP ACLs. However, you can use the regex condition to
 match against the raw MIME part. You can also use the mime_regex condition to
-match against the decoded MIME part (see section 43.5).
+match against the decoded MIME part (see section 44.5).
 
 At the start of a MIME ACL, a number of variables are set from the header
 information for the relevant MIME part. These are described below. The contents
@@ -28694,7 +29843,7 @@ checked, and the files are unlinked once the check is done.
 
 The MIME ACL supports the regex and mime_regex conditions. These can be used to
 match regular expressions against raw and decoded MIME parts, respectively.
-They are described in section 43.5.
+They are described in section 44.5.
 
 The following list describes all expansion variables that are available in the
 MIME ACL:
@@ -28775,8 +29924,8 @@ $mime_filename
     This is perhaps the most important of the MIME variables. It contains a
     proposed filename for an attachment, if one was found in either the 
     Content-Type: or Content-Disposition: headers. The filename will be RFC2047
-    decoded, but no additional sanity checks are done. If no filename was
-    found, this variable contains the empty string.
+    or RFC2231 decoded, but no additional sanity checks are done. If no
+    filename was found, this variable contains the empty string.
 
 $mime_is_coverletter
 
@@ -28833,7 +29982,7 @@ $mime_part_count
     -1.
 
 
-43.5 Scanning with regular expressions
+44.5 Scanning with regular expressions
 --------------------------------------
 
 You can specify your own custom regular expression matches on the full body of
@@ -28863,99 +30012,24 @@ deny message = contains blacklisted regex ($regex_match_string)
 
 The conditions returns true if any one of the regular expressions matches. The
 $regex_match_string expansion variable is then set up and contains the matching
-regular expression.
+regular expression. The expansion variables $regex1 $regex2 etc are set to any
+substrings captured by the regular expression.
 
 Warning: With large messages, these conditions can be fairly CPU-intensive.
 
 
-43.6 The demime condition
--------------------------
-
-The demime ACL condition provides MIME unpacking, sanity checking and file
-extension blocking. It is usable only in the DATA and non-SMTP ACLs. The demime
-condition uses a simpler interface to MIME decoding than the MIME ACL
-functionality, but provides no additional facilities. Please note that this
-condition is deprecated and kept only for backward compatibility. You must set
-the WITH_OLD_DEMIME option in Local/Makefile at build time to be able to use
-the demime condition.
-
-The demime condition unpacks MIME containers in the message. It detects errors
-in MIME containers and can match file extensions found in the message against a
-list. Using this facility produces files containing the unpacked MIME parts of
-the message in the temporary scan directory. If you do antivirus scanning, it
-is recommended that you use the demime condition before the antivirus (malware)
-condition.
-
-On the right-hand side of the demime condition you can pass a colon-separated
-list of file extensions that it should match against. For example:
-
-deny message = Found blacklisted file attachment
-     demime  = vbs:com:bat:pif:prf:lnk
-
-If one of the file extensions is found, the condition is true, otherwise it is
-false. If there is a temporary error while demimeing (for example, "disk
-full"), the condition defers, and the message is temporarily rejected (unless
-the condition is on a warn verb).
-
-The right-hand side is expanded before being treated as a list, so you can have
-conditions and lookups there. If it expands to an empty string, "false", or
-zero ("0"), no demimeing is done and the condition is false.
-
-The demime condition set the following variables:
-
-$demime_errorlevel
-
-    When an error is detected in a MIME container, this variable contains the
-    severity of the error, as an integer number. The higher the value, the more
-    severe the error (the current maximum value is 3). If this variable is
-    unset or zero, no error occurred.
-
-$demime_reason
-
-    When $demime_errorlevel is greater than zero, this variable contains a
-    human-readable text string describing the MIME error that occurred.
-
-$found_extension
-
-    When the demime condition is true, this variable contains the file
-    extension it found.
-
-Both $demime_errorlevel and $demime_reason are set by the first call of the 
-demime condition, and are not changed on subsequent calls.
-
-If you do not want to check for file extensions, but rather use the demime
-condition for unpacking or error checking purposes, pass "*" as the right-hand
-side value. Here is a more elaborate example of how to use this facility:
-
-# Reject messages with serious MIME container errors
-deny  message = Found MIME error ($demime_reason).
-      demime = *
-      condition = ${if >{$demime_errorlevel}{2}{1}{0}}
-
-# Reject known virus spreading file extensions.
-# Accepting these is pretty much braindead.
-deny  message = contains $found_extension file (blacklisted).
-      demime  = com:vbs:bat:pif:scr
-
-# Freeze .exe and .doc files. Postmaster can
-# examine them and eventually thaw them.
-deny  log_message = Another $found_extension file.
-      demime = exe:doc
-      control = freeze
-
-
 
 ===============================================================================
-44. ADDING A LOCAL SCAN FUNCTION TO EXIM
+45. ADDING A LOCAL SCAN FUNCTION TO EXIM
 
 In these days of email worms, viruses, and ever-increasing spam, some sites
 want to apply a lot of checking to messages before accepting them.
 
-The content scanning extension (chapter 43) has facilities for passing messages
+The content scanning extension (chapter 44) has facilities for passing messages
 to external virus and spam scanning software. You can also do a certain amount
 in Exim itself through string expansions and the condition condition in the ACL
 that runs after the SMTP DATA command or the ACL for non-SMTP messages (see
-chapter 42), but this has its limitations.
+chapter 43), but this has its limitations.
 
 To allow for further customization to a site's own requirements, there is the
 possibility of linking Exim with a private message scanning function, written
@@ -28977,7 +30051,7 @@ it is an SMTP message. For a non-SMTP message, the message is dropped and Exim
 ends with a non-zero code. The incident is logged on the main and reject logs.
 
 
-44.1 Building Exim to use a local scan function
+45.1 Building Exim to use a local scan function
 -----------------------------------------------
 
 To make use of the local scan function feature, you must tell Exim where your
@@ -28999,10 +30073,10 @@ for your local_scan() function, you must also set
 
 LOCAL_SCAN_HAS_OPTIONS=yes
 
-in Local/Makefile (see section 44.3 below).
+in Local/Makefile (see section 45.3 below).
 
 
-44.2 API for local_scan()
+45.2 API for local_scan()
 -------------------------
 
 You must include this line near the start of your code:
@@ -29081,7 +30155,7 @@ The function must return an int value which is one of the following macros:
     message is not written to the reject log. It has the effect of unsetting
     the rejected_header log selector for just this rejection. If 
     rejected_header is already unset (see the discussion of the log_selection
-    option in section 51.15), this code is the same as LOCAL_SCAN_REJECT.
+    option in section 52.15), this code is the same as LOCAL_SCAN_REJECT.
 
 "LOCAL_SCAN_TEMPREJECT_NOLOGHDR"
 
@@ -29093,7 +30167,7 @@ reported by writing to stderr or by sending an email, as configured by the -oe
 command line options.
 
 
-44.3 Configuration options for local_scan()
+45.3 Configuration options for local_scan()
 -------------------------------------------
 
 It is possible to have option settings in the main configuration file that set
@@ -29182,7 +30256,7 @@ If the -bP command line option is followed by "local_scan", Exim prints out the
 values of all the local_scan() options.
 
 
-44.4 Available Exim variables
+45.4 Available Exim variables
 -----------------------------
 
 The header local_scan.h gives you access to a number of C variables. These are
@@ -29206,11 +30280,11 @@ unsigned int debug_selector
     it is a bitmap of debugging selectors. Two bits are identified for use in
     local_scan(); they are defined as macros:
 
-      * The "D_v" bit is set when -v was present on the command line. This is a
+      + The "D_v" bit is set when -v was present on the command line. This is a
         testing option that is not privileged - any caller may set it. All the
         other selector bits can be set only by admin users.
 
-      * The "D_local_scan" bit is provided for use by local_scan(); it is set
+      + The "D_local_scan" bit is provided for use by local_scan(); it is set
         by the "+local_scan" debug selector. It is not included in the default
         set of debugging bits.
 
@@ -29312,10 +30386,10 @@ BOOL smtp_batched_input
 int store_pool
 
     The contents of this variable control which pool of memory is used for new
-    requests. See section 44.8 for details.
+    requests. See section 45.8 for details.
 
 
-44.5 Structure of header lines
+45.5 Structure of header lines
 ------------------------------
 
 The header_line structure contains the members listed below. You can add
@@ -29329,7 +30403,7 @@ struct header_line *next
 int type
 
     A code identifying certain headers that Exim recognizes. The codes are
-    printing characters, and are documented in chapter 55 of this manual.
+    printing characters, and are documented in chapter 56 of this manual.
     Notice in particular that any header line whose type is * is not
     transmitted with the message. This flagging is used for header lines that
     have been rewritten, or are to be removed (for example, Envelope-sender:
@@ -29346,7 +30420,7 @@ uschar *text
     followed by a zero byte. Internal newlines are preserved.
 
 
-44.6 Structure of recipient items
+45.6 Structure of recipient items
 ---------------------------------
 
 The recipient_item structure contains these members:
@@ -29372,7 +30446,7 @@ uschar *errors_to
     field is NULL for all recipients.
 
 
-44.7 Available Exim functions
+45.7 Available Exim functions
 -----------------------------
 
 The header local_scan.h gives you access to a number of Exim functions. These
@@ -29401,21 +30475,21 @@ int child_close(pid_t pid, int timeout)
     seconds) to expire. A timeout value of zero means wait as long as it takes.
     The return value is as follows:
 
-      * >= 0
+      + >= 0
 
         The process terminated by a normal exit and the value is the process
         ending status.
 
-      * < 0 and > -256
+      + < 0 and > -256
 
         The process was terminated by a signal and the value is the negation of
         the signal number.
 
-      * -256
+      + -256
 
         The process timed out.
 
-      * -257
+      + -257
 
         The was some other error in wait(); errno is still set.
 
@@ -29465,7 +30539,7 @@ uschar *expand_string(uschar *string)
     failure. If expansion does not change the string, the return value is the
     pointer to the input string. Otherwise, the return value points to a new
     block of memory that was obtained by a call to store_get(). See section
-    44.8 below for a discussion of memory handling.
+    45.8 below for a discussion of memory handling.
 
 void header_add(int type, char *format, ...)
 
@@ -29705,7 +30779,7 @@ uschar *string_sprintf(char *format, ...)
     See the next section for more discussion.
 
 
-44.8 More about Exim's memory handling
+45.8 More about Exim's memory handling
 --------------------------------------
 
 No function is provided for freeing memory, because that is never needed. The
@@ -29737,7 +30811,7 @@ the permanent pool while preserving the value of store_pool.
 
 
 ===============================================================================
-45. SYSTEM-WIDE MESSAGE FILTERING
+46. SYSTEM-WIDE MESSAGE FILTERING
 
 The previous chapters (on ACLs and the local scan function) describe checks
 that can be applied to messages before they are accepted by a host. There is
@@ -29761,11 +30835,11 @@ Warning: Because the system filter runs just once, variables that are specific
 to individual recipient addresses, such as $local_part and $domain, are not
 set, and the "personal" condition is not meaningful. If you want to run a
 centrally-specified filter for each recipient address independently, you can do
-so by setting up a suitable redirect router, as described in section 45.8
+so by setting up a suitable redirect router, as described in section 46.8
 below.
 
 
-45.1 Specifying a system filter
+46.1 Specifying a system filter
 -------------------------------
 
 The name of the file that contains the system filter must be specified by
@@ -29783,7 +30857,7 @@ respectively. Similarly, system_filter_reply_transport must be set to handle
 any messages generated by the reply command.
 
 
-45.2 Testing a system filter
+46.2 Testing a system filter
 ----------------------------
 
 You can run simple tests of a system filter in the same way as for a user
@@ -29794,7 +30868,7 @@ If you want to test the combined effect of a system filter and a user filter,
 you can use both -bF and -bf on the same command line.
 
 
-45.3 Contents of a system filter
+46.3 Contents of a system filter
 --------------------------------
 
 The language used to specify system filters is the same as for users' filter
@@ -29822,7 +30896,7 @@ files. Thus a system filter can, for example, set up "scores" to which users'
 filter files can refer.
 
 
-45.4 Additional variable for system filters
+46.4 Additional variable for system filters
 -------------------------------------------
 
 The expansion variable $recipients, containing a list of all the recipients of
@@ -29830,7 +30904,7 @@ the message (separated by commas and white space), is available in system
 filters. It is not available in users' filters for privacy reasons.
 
 
-45.5 Defer, freeze, and fail commands for system filters
+46.5 Defer, freeze, and fail commands for system filters
 --------------------------------------------------------
 
 There are three extra commands (defer, freeze and fail) which are always
@@ -29891,7 +30965,7 @@ failing) a message. The normal deliveries for the message do not, of course,
 take place.
 
 
-45.6 Adding and removing headers in a system filter
+46.6 Adding and removing headers in a system filter
 ---------------------------------------------------
 
 Two filter commands that are available only in system filters are:
@@ -29936,7 +31010,7 @@ header lines refer only to those lines that are in this set. Thus, header lines
 that are added by a system filter are visible to users' filter files and to all
 routers and transports. This contrasts with the manipulation of header lines by
 routers and transports, which is not immediate, but which instead is saved up
-until the message is actually being written (see section 46.17).
+until the message is actually being written (see section 47.17).
 
 If the message is not delivered at the first attempt, header lines that were
 added by the system filter are stored with the message, and so are still
@@ -29956,7 +31030,7 @@ headers add "Subject: new subject (was: $h_old-subject:)"
 headers remove "Old-Subject"
 
 
-45.7 Setting an errors address in a system filter
+46.7 Setting an errors address in a system filter
 -------------------------------------------------
 
 In a system filter, if a deliver command is followed by
@@ -29974,7 +31048,7 @@ to take a copy which would not be sent back to the normal error reporting
 address if its delivery failed.
 
 
-45.8 Per-address filtering
+46.8 Per-address filtering
 --------------------------
 
 In contrast to the system filter, which is run just once per message for each
@@ -30007,7 +31081,7 @@ normal way.
 
 
 ===============================================================================
-46. MESSAGE PROCESSING
+47. MESSAGE PROCESSING
 
 Exim performs various transformations on the sender and recipient addresses of
 all messages that it handles, and also on the messages' header lines. Some of
@@ -30030,7 +31104,7 @@ If you want the loopback interface to be treated specially, you must ensure
 that there are appropriate entries in your ACLs.
 
 
-46.1 Submission mode for non-local messages
+47.1 Submission mode for non-local messages
 -------------------------------------------
 
 Processing that happens automatically for locally-originated messages (unless 
@@ -30040,8 +31114,8 @@ state. Submission mode is set by the modifier
 
 control = submission
 
-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
+in a MAIL, RCPT, or pre-data ACL for an incoming message (see sections 43.21
+and 43.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
@@ -30068,8 +31142,8 @@ to be used when generating a From: or Sender: header line. For example:
 
 control = submission/domain=some.domain
 
-The domain may be empty. How this value is used is described in sections 46.11
-and 46.16. There is also a name option that allows you to specify the user's
+The domain may be empty. How this value is used is described in sections 47.11
+and 47.16. There is also a name option that allows you to specify the user's
 full name for inclusion in a created Sender: or From: header line. For example:
 
 accept authenticated = *
@@ -30100,7 +31174,7 @@ does mean that you can configure ACL checks to spot that a user is trying to
 spoof another's address.
 
 
-46.2 Line endings
+47.2 Line endings
 -----------------
 
 RFC 2821 specifies that CRLF (two characters: carriage-return, followed by
@@ -30138,7 +31212,7 @@ follows:
     header line.
 
 
-46.3 Unqualified addresses
+47.3 Unqualified addresses
 --------------------------
 
 By default, Exim expects every envelope address it receives from an external
@@ -30161,7 +31235,7 @@ other words, such qualification is also controlled by sender_unqualified_hosts
 and recipient_unqualified_hosts,
 
 
-46.4 The UUCP From line
+47.4 The UUCP From line
 -----------------------
 
 Messages that have come from UUCP (and some other applications) often begin
@@ -30198,7 +31272,7 @@ as a header line. This also happens if a "From" line is present in an incoming
 SMTP message from a source that is not permitted to send them.
 
 
-46.5 Resent- header lines
+47.5 Resent- header lines
 -------------------------
 
 RFC 2822 makes provision for sets of header lines starting with the string
@@ -30231,7 +31305,7 @@ address rewriting are concerned. Exim treats Resent- header lines as follows:
     Resent- header lines are present.
 
 
-46.6 The Auto-Submitted: header line
+47.6 The Auto-Submitted: header line
 ------------------------------------
 
 Whenever Exim generates an autoreply, a bounce, or a delay warning message, it
@@ -30240,7 +31314,7 @@ includes the header line:
 Auto-Submitted: auto-replied
 
 
-46.7 The Bcc: header line
+47.7 The Bcc: header line
 -------------------------
 
 If Exim is called with the -t option, to take recipient addresses from a
@@ -30249,7 +31323,7 @@ extracting its addresses). If -t is not present on the command line, any
 existing Bcc: is not removed.
 
 
-46.8 The Date: header line
+47.8 The Date: header line
 --------------------------
 
 If a locally-generated or submission-mode message has no Date: header line,
@@ -30257,7 +31331,7 @@ Exim adds one, using the current date and time, unless the
 suppress_local_fixups control has been specified.
 
 
-46.9 The Delivery-date: header line
+47.9 The Delivery-date: header line
 -----------------------------------
 
 Delivery-date: header lines are not part of the standard RFC 2822 header set.
@@ -30267,7 +31341,7 @@ messages in transit. If the delivery_date_remove configuration option is set
 (the default), Exim removes Delivery-date: header lines from incoming messages.
 
 
-46.10 The Envelope-to: header line
+47.10 The Envelope-to: header line
 ----------------------------------
 
 Envelope-to: header lines are not part of the standard RFC 2822 header set.
@@ -30277,7 +31351,7 @@ messages in transit. If the envelope_to_remove configuration option is set (the
 default), Exim removes Envelope-to: header lines from incoming messages.
 
 
-46.11 The From: header line
+47.11 The From: header line
 ---------------------------
 
 If a submission-mode message does not contain a From: header line, Exim adds
@@ -30302,17 +31376,17 @@ A non-empty envelope sender takes precedence.
 If a locally-generated incoming message does not contain a From: header line,
 and the suppress_local_fixups control is not set, Exim adds one containing the
 sender's address. The calling user's login name and full name are used to
-construct the address, as described in section 46.18. They are obtained from
+construct the address, as described in section 47.18. They are obtained from
 the password data by calling getpwuid() (but see the unknown_login
 configuration option). The address is qualified with qualify_domain.
 
 For compatibility with Sendmail, if an incoming, non-SMTP message has a From:
 header line containing just the unqualified login name of the calling user,
 this is replaced by an address containing the user's login name and full name
-as described in section 46.18.
+as described in section 47.18.
 
 
-46.12 The Message-ID: header line
+47.12 The Message-ID: header line
 ---------------------------------
 
 If a locally-generated or submission-mode incoming message does not contain a 
@@ -30325,7 +31399,7 @@ Additional information can be included in this header line by setting the
 message_id_header_text and/or message_id_header_domain options.
 
 
-46.13 The Received: header line
+47.13 The Received: header line
 -------------------------------
 
 A Received: header line is added at the start of every message. The contents
@@ -30342,7 +31416,7 @@ changed to the time of acceptance, which is (apart from a small delay while the
 -H spool file is written) the earliest time at which delivery could start.
 
 
-46.14 The References: header line
+47.14 The References: header line
 ---------------------------------
 
 Messages created by the autoreply transport include a References: header line.
@@ -30356,7 +31430,7 @@ more than 12, the first one and then the final 11 are copied, before adding the
 message ID of the incoming message.
 
 
-46.15 The Return-path: header line
+47.15 The Return-path: header line
 ----------------------------------
 
 Return-path: header lines are defined as something an MTA may insert when it
@@ -30366,7 +31440,7 @@ return_path_remove configuration option is set (the default), Exim removes
 Return-path: header lines from incoming messages.
 
 
-46.16 The Sender: header line
+47.16 The Sender: header line
 -----------------------------
 
 For a locally-originated message from an untrusted user, Exim may remove an
@@ -30416,14 +31490,14 @@ message (the envelope sender address) is changed to be the same address, except
 in the case of submission mode when sender_retain is specified.
 
 
-46.17 Adding and removing header lines in routers and transports
+47.17 Adding and removing header lines in routers and transports
 ----------------------------------------------------------------
 
 When a message is delivered, the addition and removal of header lines can be
 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
+process the message. Section 46.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.24).
+received (see section 43.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
@@ -30458,7 +31532,8 @@ headers_remove = return-receipt-to:acknowledge-to
 
 Multiple headers_remove options for a single router or transport can be
 specified; the arguments will append to a single header-names list. Each item
-is separately expanded.
+is separately expanded. Note that colons in complex expansions which are used
+to form all or part of a headers_remove list will act as list separators.
 
 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
@@ -30514,7 +31589,7 @@ Warning: The headers_add and headers_remove options cannot be used for a
 redirect router that has the one_time option set.
 
 
-46.18 Constructed addresses
+47.18 Constructed addresses
 ---------------------------
 
 When Exim constructs a sender address for a locally-generated message, it uses
@@ -30545,7 +31620,7 @@ print_topbitchars controls whether characters with the top bit set (that is,
 with codes greater than 127) count as printing characters or not.
 
 
-46.19 Case of local parts
+47.19 Case of local parts
 -------------------------
 
 RFC 2822 states that the case of letters in the local parts of addresses cannot
@@ -30575,7 +31650,7 @@ subsequent routers which process your domains, they will operate on local parts
 with the correct case in a case-sensitive manner.
 
 
-46.20 Dots in local parts
+47.20 Dots in local parts
 -------------------------
 
 RFC 2822 forbids empty components in local parts. That is, an unquoted local
@@ -30584,7 +31659,7 @@ middle. However, it seems that many MTAs do not enforce this, so Exim permits
 empty components for compatibility.
 
 
-46.21 Rewriting addresses
+47.21 Rewriting addresses
 -------------------------
 
 Rewriting of sender and recipient addresses, and addresses in headers, can
@@ -30617,7 +31692,7 @@ more addresses is deferred.
 
 
 ===============================================================================
-47. SMTP PROCESSING
+48. SMTP PROCESSING
 
 Exim supports a number of different ways of using the SMTP protocol, and its
 LMTP variant, which is an interactive protocol for transferring messages into a
@@ -30648,7 +31723,7 @@ in or read from files (or pipes), in a format in which SMTP commands are used
 to contain the envelope information.
 
 
-47.1 Outgoing SMTP and LMTP over TCP/IP
+48.1 Outgoing SMTP and LMTP over TCP/IP
 ---------------------------------------
 
 Outgoing SMTP and LMTP over TCP/IP is implemented by the smtp transport. The 
@@ -30668,7 +31743,7 @@ required for the transaction.
 
 If the remote server advertises support for the STARTTLS command, and Exim was
 built to support TLS encryption, it tries to start a TLS session unless the
-server matches hosts_avoid_tls. See chapter 41 for more details. Either a match
+server matches hosts_avoid_tls. See chapter 42 for more details. Either a match
 in that or hosts_verify_avoid_tls apply when the transport is called for
 verification.
 
@@ -30717,7 +31792,7 @@ identified in the main log by the addition of an asterisk after the closing
 square bracket of the IP address.
 
 
-47.2 Errors in outgoing SMTP
+48.2 Errors in outgoing SMTP
 ----------------------------
 
 Three different kinds of error are recognized for outgoing SMTP: host errors,
@@ -30728,17 +31803,17 @@ Host errors
     A host error is not associated with a particular message or with a
     particular recipient of a message. The host errors are:
 
-      * Connection refused or timed out,
+      + Connection refused or timed out,
 
-      * Any error response code on connection,
+      + Any error response code on connection,
 
-      * Any error response code to EHLO or HELO,
+      + Any error response code to EHLO or HELO,
 
-      * Loss of connection at any time, except after ".",
+      + Loss of connection at any time, except after ".",
 
-      * I/O errors at any time,
+      + I/O errors at any time,
 
-      * Timeouts during the session, other than in response to MAIL, RCPT or
+      + Timeouts during the session, other than in response to MAIL, RCPT or
         the "." at the end of the data.
 
     For a host error, a permanent error response on connection, or in response
@@ -30757,12 +31832,12 @@ Message errors
     particular host, but not with a particular recipient of the message. The
     message errors are:
 
-      * Any error response code to MAIL, DATA, or the "." that terminates the
+      + Any error response code to MAIL, DATA, or the "." that terminates the
         data,
 
-      * Timeout after MAIL,
+      + Timeout after MAIL,
 
-      * Timeout or loss of connection after the "." that terminates the data. A
+      + Timeout or loss of connection after the "." that terminates the data. A
         timeout after the DATA command itself is treated as a host error, as is
         loss of connection at any other time.
 
@@ -30786,9 +31861,9 @@ Recipient errors
     A recipient error is associated with a particular recipient of a message.
     The recipient errors are:
 
-      * Any error response to RCPT,
+      + Any error response to RCPT,
 
-      * Timeout after RCPT.
+      + Timeout after RCPT.
 
     For a recipient error, a permanent error response (5xx) causes the
     recipient address to be failed, and a bounce message to be returned to the
@@ -30847,7 +31922,7 @@ be given. That is why Exim treats this case as a message rather than a host
 error, in order not to delay other messages to the same host.
 
 
-47.3 Incoming SMTP messages over TCP/IP
+48.3 Incoming SMTP messages over TCP/IP
 ---------------------------------------
 
 Incoming SMTP messages can be accepted in one of two ways: by running a
@@ -30930,7 +32005,7 @@ entirely independent Exim process. Control by load average is, however,
 available with inetd.
 
 Exim can be configured to verify addresses in incoming SMTP commands as they
-are received. See chapter 42 for details. It can also be configured to rewrite
+are received. See chapter 43 for details. It can also be configured to rewrite
 addresses at this time - before any syntax checking is done. See section 31.9.
 
 Exim can also be configured to limit the rate at which a client host submits
@@ -30938,7 +32013,7 @@ MAIL and RCPT commands in a single SMTP session. See the smtp_ratelimit_hosts
 option.
 
 
-47.4 Unrecognized SMTP commands
+48.4 Unrecognized SMTP commands
 -------------------------------
 
 If Exim receives more than smtp_max_unknown_commands unrecognized SMTP commands
@@ -30949,7 +32024,7 @@ that subvert web servers into making connections to SMTP ports; in these
 circumstances, a number of non-SMTP lines are sent first.
 
 
-47.5 Syntax and protocol errors in SMTP commands
+48.5 Syntax and protocol errors in SMTP commands
 ------------------------------------------------
 
 A syntax error is detected if an SMTP command is recognized, but there is
@@ -30962,7 +32037,7 @@ smtp_max_synprot_errors is 3. This is a defence against broken clients that
 loop sending bad commands (yes, it has been seen).
 
 
-47.6 Use of non-mail SMTP commands
+48.6 Use of non-mail SMTP commands
 ----------------------------------
 
 The "non-mail" SMTP commands are those other than MAIL, RCPT, and DATA. Exim
@@ -30988,26 +32063,29 @@ value is "*", which makes the limit apply to all hosts. This option means that
 you can exclude any specific badly-behaved hosts that you have to live with.
 
 
-47.7 The VRFY and EXPN commands
+48.7 The VRFY and EXPN commands
 -------------------------------
 
 When Exim receives a VRFY or EXPN command on a TCP/IP connection, it runs the
 ACL specified by acl_smtp_vrfy or acl_smtp_expn (as appropriate) in order to
-decide whether the command should be accepted or not. If no ACL is defined, the
-command is rejected.
+decide whether the command should be accepted or not.
 
-When VRFY is accepted, it runs exactly the same code as when Exim is called
-with the -bv option.
+When no ACL is defined for VRFY, or if it rejects without setting an explicit
+response code, the command is accepted (with a 252 SMTP response code) in order
+to support awkward clients that do a VRFY before every RCPT. When VRFY is
+accepted, it runs exactly the same code as when Exim is called with the -bv
+option, and returns 250/451/550 SMTP response codes.
 
-When EXPN is accepted, a single-level expansion of the address is done. EXPN is
-treated as an "address test" (similar to the -bt option) rather than a
-verification (the -bv option). If an unqualified local part is given as the
-argument to EXPN, it is qualified with qualify_domain. Rejections of VRFY and
-EXPN commands are logged on the main and reject logs, and VRFY verification
-failures are logged on the main log for consistency with RCPT failures.
+If no ACL for EXPN is defined, the command is rejected. When EXPN is accepted,
+a single-level expansion of the address is done. EXPN is treated as an "address
+test" (similar to the -bt option) rather than a verification (the -bv option).
+If an unqualified local part is given as the argument to EXPN, it is qualified
+with qualify_domain. Rejections of VRFY and EXPN commands are logged on the
+main and reject logs, and VRFY verification failures are logged on the main log
+for consistency with RCPT failures.
 
 
-47.8 The ETRN command
+48.8 The ETRN command
 ---------------------
 
 RFC 1985 describes an SMTP command called ETRN that is designed to overcome the
@@ -31059,7 +32137,7 @@ its own uid and gid when receiving incoming SMTP, so it is not possible for it
 to change them before running the command.
 
 
-47.9 Incoming local SMTP
+48.9 Incoming local SMTP
 ------------------------
 
 Some user agents use SMTP to pass messages to their local MTA using the
@@ -31077,7 +32155,7 @@ accept hosts = :
 This accepts SMTP messages from local processes without doing any other tests.
 
 
-47.10 Outgoing batched SMTP
+48.10 Outgoing batched SMTP
 ---------------------------
 
 Both the appendfile and pipe transports can be used for handling batched SMTP.
@@ -31122,7 +32200,7 @@ to /var/bsmtp/batch.host.example, with only a single copy of each message
 (unless there are more than 1000 recipients).
 
 
-47.11 Incoming batched SMTP
+48.11 Incoming batched SMTP
 ---------------------------
 
 The -bS command line option causes Exim to accept one or more messages by
@@ -31168,7 +32246,7 @@ accepted.
 
 
 ===============================================================================
-48. CUSTOMIZING BOUNCE AND WARNING MESSAGES
+49. CUSTOMIZING BOUNCE AND WARNING MESSAGES
 
 When a message fails to be delivered, or remains on the queue for more than a
 configured amount of time, Exim sends a message to the original sender, or to
@@ -31185,7 +32263,7 @@ Auto-Submitted: auto-generated
 to all warning and bounce messages,
 
 
-48.1 Customizing bounce messages
+49.1 Customizing bounce messages
 --------------------------------
 
 If bounce_message_text is set, its contents are included in the default message
@@ -31217,13 +32295,8 @@ The items must appear in the file in the following order:
   * The third item is used to introduce any text from pipe transports that is
     to be returned to the sender. It is omitted if there is no such text.
 
-  * The fourth item is used to introduce the copy of the message that is
-    returned as part of the error report.
-
-  * The fifth item is added after the fourth one if the returned message is
-    truncated because it is bigger than return_size_limit.
-
-  * The sixth item is added after the copy of the original message.
+  * The fourth, fifth and sixth items will be ignored and may be empty. The
+    fields exist for back-compatibility
 
 The default state (bounce_message_file unset) is equivalent to the following
 file, in which the sixth item is empty. The Subject: and some other lines have
@@ -31254,7 +32327,7 @@ The following text was generated during the delivery attempt(s):
 ****
 
 
-48.2 Customizing warning messages
+49.2 Customizing warning messages
 ---------------------------------
 
 The option warn_message_file can be pointed at a template file for use when
@@ -31307,13 +32380,13 @@ with different errors_to settings on the routers that handled them.
 
 
 ===============================================================================
-49. SOME COMMON CONFIGURATION SETTINGS
+50. SOME COMMON CONFIGURATION SETTINGS
 
 This chapter discusses some configuration settings that seem to be fairly
 common. More examples and discussion can be found in the Exim book.
 
 
-49.1 Sending mail to a smart host
+50.1 Sending mail to a smart host
 ---------------------------------
 
 If you want to send all mail for non-local domains to a "smart host", you
@@ -31328,10 +32401,10 @@ send_to_smart_host:
 You can use the smart host's IP address instead of the name if you wish. If you
 are using Exim only to submit messages to a smart host, and not for receiving
 incoming messages, you can arrange for it to do the submission synchronously by
-setting the mua_wrapper option (see chapter 50).
+setting the mua_wrapper option (see chapter 51).
 
 
-49.2 Using Exim to handle mailing lists
+50.2 Using Exim to handle mailing lists
 ---------------------------------------
 
 Exim can be used to run simple mailing lists, but for large and/or complicated
@@ -31376,7 +32449,7 @@ local_part_suffix options) to handle addresses of the form owner-xxx or xxx-
 request, are also possible.
 
 
-49.3 Syntax errors in mailing lists
+50.3 Syntax errors in mailing lists
 -----------------------------------
 
 If an entry in redirection data contains a syntax error, Exim normally defers
@@ -31392,7 +32465,7 @@ whenever a broken address is skipped. It is usually appropriate to set
 syntax_errors_to to the same address as errors_to.
 
 
-49.4 Re-expansion of mailing lists
+50.4 Re-expansion of mailing lists
 ----------------------------------
 
 Exim remembers every individual address to which a message has been delivered,
@@ -31420,7 +32493,7 @@ all_parents selector is set, but for mailing lists there is normally only one
 level of expansion anyway.
 
 
-49.5 Closed mailing lists
+50.5 Closed mailing lists
 -------------------------
 
 The examples so far have assumed open mailing lists, to which anybody may send
@@ -31474,7 +32547,7 @@ a mailing list exists, but the sender is not on it. This router forcibly fails
 the address, giving a suitable error message.
 
 
-49.6 Variable Envelope Return Paths (VERP)
+50.6 Variable Envelope Return Paths (VERP)
 ------------------------------------------
 
 Variable Envelope Return Paths - see http://cr.yp.to/proto/verp.txt - are a way
@@ -31562,7 +32635,7 @@ than sending a single copy with many recipients (for which VERP cannot be
 used).
 
 
-49.7 Virtual domains
+50.7 Virtual domains
 --------------------
 
 The phrase virtual domain is unfortunately used with two rather different
@@ -31631,7 +32704,7 @@ up, for example, by using a database instead of separate files to hold all the
 information about the domains.
 
 
-49.8 Multiple user mailboxes
+50.8 Multiple user mailboxes
 ----------------------------
 
 Heavy email users often want to operate with multiple mailboxes, into which
@@ -31681,7 +32754,7 @@ routers, which could, if required, look for an unqualified .forward file to use
 as a default.
 
 
-49.9 Simplified vacation processing
+50.9 Simplified vacation processing
 -----------------------------------
 
 The traditional way of running the vacation program is for a user to set up a
@@ -31706,7 +32779,7 @@ Another advantage of both these methods is that they both work even when the
 use of arbitrary pipes by users is locked out.
 
 
-49.10 Taking copies of mail
+50.10 Taking copies of mail
 ---------------------------
 
 Some installations have policies that require archive copies of all messages to
@@ -31720,7 +32793,7 @@ delivery. This could be used, inter alia, to implement automatic notification
 of delivery by sites that insist on doing such things.
 
 
-49.11 Intermittently connected hosts
+50.11 Intermittently connected hosts
 ------------------------------------
 
 It has become quite common (because it is cheaper) for hosts to connect to the
@@ -31733,7 +32806,7 @@ particularly well-suited to use in an intermittently connected environment.
 Nevertheless there are some features that can be used.
 
 
-49.12 Exim on the upstream server host
+50.12 Exim on the upstream server host
 --------------------------------------
 
 It is tempting to arrange for incoming mail for the intermittently connected
@@ -31761,7 +32834,7 @@ cheshire.wonderland.fict.example    *   F,5d,24h
 This stops a lot of failed delivery attempts from occurring, but Exim remembers
 which messages it has queued up for that host. Once the intermittent host comes
 online, forcing delivery of one message (either by using the -M or -R options,
-or by using the ETRN SMTP command (see section 47.8) causes all the queued up
+or by using the ETRN SMTP command (see section 48.8) causes all the queued up
 messages to be delivered, often down a single SMTP connection. While the host
 remains connected, any new messages get delivered immediately.
 
@@ -31774,7 +32847,7 @@ has disadvantages for permanently connected hosts, it is best to arrange a
 separate transport for the intermittently connected ones.
 
 
-49.13 Exim on the intermittently connected client host
+50.13 Exim on the intermittently connected client host
 ------------------------------------------------------
 
 The value of smtp_accept_queue_per_connection should probably be increased, or
@@ -31794,7 +32867,7 @@ are likely to get sent as multiple deliveries in a single SMTP connection.
 
 
 ===============================================================================
-50. USING EXIM AS A NON-QUEUEING CLIENT
+51. USING EXIM AS A NON-QUEUEING CLIENT
 
 On a personal computer, it is a common requirement for all email to be sent to
 a "smart host". There are plenty of MUAs that can be configured to operate that
@@ -31883,13 +32956,13 @@ The overall effect is that Exim makes a single synchronous attempt to deliver
 the message, failing if there is any kind of problem. Because no local
 deliveries are done and no daemon can be run, Exim does not need root
 privilege. It should be possible to run it setuid to exim instead of setuid to 
-root. See section 54.3 for a general discussion about the advantages and
+root. See section 55.3 for a general discussion about the advantages and
 disadvantages of running without root privilege.
 
 
 
 ===============================================================================
-51. LOG FILES
+52. LOG FILES
 
 Exim writes three different logs, referred to as the main log, the reject log,
 and the panic log:
@@ -31901,7 +32974,7 @@ and the panic log:
     in the main log. Some of them are optional, in which case the log_selector
     option controls whether they are included or not. A Perl script called 
     eximstats, which does simple analysis of main log files, is provided in the
-    Exim distribution (see section 52.7).
+    Exim distribution (see section 53.7).
 
   * The reject log records information from messages that are rejected as a
     result of a configuration option (that is, for policy reasons). The first
@@ -31947,12 +33020,12 @@ changing this:
     2003-04-25 11:17:07 +0100 Start queue run: pid=12762
 
 Exim does not include its process id in log lines by default, but you can
-request that it does so by specifying the "pid" log selector (see section 51.15
+request that it does so by specifying the "pid" log selector (see section 52.15
 ). When this is set, the process id is output, in square brackets, immediately
 after the time and date.
 
 
-51.1 Where the logs are written
+52.1 Where the logs are written
 -------------------------------
 
 The logs may be written to local files, or to syslog, or both. However, it
@@ -31993,11 +33066,12 @@ the setting:
 
 log_file_path = $spool_directory/log/%slog
 
-If you do not specify anything at build time or run time, that is where the
-logs are written.
+If you do not specify anything at build time or run time, or if you unset the
+option at run time (i.e. "log_file_path = "), that is where the logs are
+written.
 
 A log file path may also contain "%D" or "%M" if datestamped log file names are
-in use - see section 51.3 below.
+in use - see section 52.3 below.
 
 Here are some examples of possible settings:
 
@@ -32010,12 +33084,12 @@ If there are more than two paths in the list, the first is used and a panic
 error is logged.
 
 
-51.2 Logging to local files that are periodically "cycled"
+52.2 Logging to local files that are periodically "cycled"
 ----------------------------------------------------------
 
 Some operating systems provide centralized and standardized methods for cycling
 log files. For those that do not, a utility script called exicyclog is provided
-(see section 52.6). This renames and compresses the main and reject logs each
+(see section 53.6). This renames and compresses the main and reject logs each
 time it is called. The maximum number of old logs to keep can be set. It is
 suggested this script is run as a daily cron job.
 
@@ -32032,7 +33106,7 @@ open the main log from scratch. Thus, an old log file may remain open for quite
 some time, but no Exim processes should write to it once it has been renamed.
 
 
-51.3 Datestamped log files
+52.3 Datestamped log files
 --------------------------
 
 Instead of cycling the main and reject log files by renaming them periodically,
@@ -32073,7 +33147,7 @@ removed. Thus, the four examples above would give these panic log names:
 /var/log/exim/panic
 
 
-51.4 Logging to syslog
+52.4 Logging to syslog
 ----------------------
 
 The use of syslog does not change what Exim logs or the format of its messages,
@@ -32158,7 +33232,7 @@ environment variable EXIMON_LOG_FILE_PATH is set to tell the monitor where it
 is.
 
 
-51.5 Log line flags
+52.5 Log line flags
 -------------------
 
 One line is written to the main log for each message received, and for each
@@ -32167,6 +33241,7 @@ picked out by the distinctive two-character flags that immediately follow the
 timestamp. The flags are:
 
 <=     message arrival
+(=     message fakereject
 =>     normal message delivery
 ->     additional address in same delivery
 >>     cutthrough message delivery
@@ -32175,7 +33250,7 @@ timestamp. The flags are:
 ==     delivery deferred; temporary problem
 
 
-51.6 Logging message reception
+52.6 Logging message reception
 ------------------------------
 
 The format of the single-line entry in the main log that is written for every
@@ -32240,16 +33315,16 @@ message may not correspond with this value (and indeed may be different to each
 other).
 
 The log_selector option can be used to request the logging of additional data
-when a message is received. See section 51.15 below.
+when a message is received. See section 52.15 below.
 
 
-51.7 Logging deliveries
+52.7 Logging deliveries
 -----------------------
 
 The format of the single-line entry in the main log that is written for every
 delivery is shown in one of the examples below, for local and remote
-deliveries, respectively. Each example has been split into two lines in order
-to fit it on the page:
+deliveries, respectively. Each example has been split into multiple lines in
+order to fit it on the page:
 
 2002-10-31 08:59:13 16ZCW1-0005MB-00 => marv
   <marv@hitch.fict.example> R=localuser T=local_delivery
@@ -32290,10 +33365,10 @@ The generation of a reply message by a filter file gets logged as a "delivery"
 to the addressee, preceded by ">".
 
 The log_selector option can be used to request the logging of additional data
-when a message is delivered. See section 51.15 below.
+when a message is delivered. See section 52.15 below.
 
 
-51.8 Discarded deliveries
+52.8 Discarded deliveries
 -------------------------
 
 When a message is discarded as a result of the command "seen finish" being
@@ -32309,7 +33384,7 @@ because it is aliased to ":blackhole:" the log line is like this:
   <hole@nowhere.example> R=blackhole_router
 
 
-51.9 Deferred deliveries
+52.9 Deferred deliveries
 ------------------------
 
 When a delivery is deferred, a line of the following form is logged:
@@ -32329,7 +33404,7 @@ a message is written to the log, but this can be suppressed by setting an
 appropriate value in log_selector.
 
 
-51.10 Delivery failures
+52.10 Delivery failures
 -----------------------
 
 If a delivery fails because an address cannot be routed, a line of the
@@ -32353,7 +33428,7 @@ PIPELINING. The log lines for all forms of delivery failure are flagged with
 "**".
 
 
-51.11 Fake deliveries
+52.11 Fake deliveries
 ---------------------
 
 If a delivery does not actually take place because the -N option has been used
@@ -32361,7 +33436,7 @@ to suppress it, a normal delivery line is written to the log, except that "=>"
 is replaced by "*>".
 
 
-51.12 Completion
+52.12 Completion
 ----------------
 
 A line of the form
@@ -32372,7 +33447,7 @@ is written to the main log when a message is about to be removed from the spool
 at the end of its processing.
 
 
-51.13 Summary of Fields in Log Lines
+52.13 Summary of Fields in Log Lines
 ------------------------------------
 
 A summary of the field identifiers that are used in log lines is shown in the
@@ -32384,18 +33459,23 @@ C           SMTP confirmation on delivery
 CV          certificate verification status
 D           duration of "no mail in SMTP session"
 DN          distinguished name from peer certificate
+DS          DNSSEC secured lookups
 DT          on => lines: time taken for a delivery
 F           sender address (on delivery lines)
 H           host name and IP address
 I           local interface used
+K           CHUNKING extension used
 id          message id for incoming message
 P           on <= lines: protocol used
             on => and ** lines: return path
+PRDR        PRDR extension used
+PRX         on <= and => lines: proxy address
+Q           alternate queue name
 QT          on => lines: time spent on queue so far
             on "Completed" lines: time spent on queue
 R           on <= lines: reference for local bounce
-            on =>  ** and == lines: router name
-S           size of message
+            on =>  >> ** and == lines: router name
+S           size of message in bytes
 SNI         server name indication from TLS client hello
 ST          shadow transport name
 T           on <= lines: message subject (topic)
@@ -32404,7 +33484,7 @@ U           local user or RFC 1413 identity
 X           TLS cipher suite
 
 
-51.14 Other log entries
+52.14 Other log entries
 -----------------------
 
 Various other types of log entry are written from time to time. Most should be
@@ -32441,7 +33521,7 @@ self-explanatory. Among the more common are:
         failed. The delivery was discarded.
 
 
-51.15 Reducing or increasing what is logged
+52.15 Reducing or increasing what is logged
 -------------------------------------------
 
 By setting the log_selector global option, you can disable some of Exim's
@@ -32464,17 +33544,20 @@ selection marked by asterisks:
  deliver_time                 time taken to perform delivery
  delivery_size                add S=nnn to => lines
 *dnslist_defer                defers of DNS list (aka RBL) lookups
+ dnssec                       DNSSEC secured lookups
 *etrn                         ETRN commands
 *host_lookup_failed           as it says
  ident_timeout                timeout for ident connection
- incoming_interface           incoming interface on <= lines
- incoming_port                incoming port on <= lines
+ incoming_interface           local interface on <= and => lines
+ incoming_port                remote port on <= lines
 *lost_incoming_connection     as it says (includes timeouts)
+ outgoing_interface           local interface on => lines
  outgoing_port                add remote port to => lines
 *queue_run                    start and end queue runs
  queue_time                   time on queue for one recipient
  queue_time_overall           time on queue for whole message
  pid                          Exim process id
+ proxy                        proxy address on <= and => lines
  received_recipients          recipients on <= lines
  received_sender              sender on <= lines
 *rejected_header              header contents on reject log
@@ -32485,14 +33568,14 @@ selection marked by asterisks:
 *size_reject                  rejection because too big
 *skip_delivery                delivery skipped in a queue run
 *smtp_confirmation            SMTP confirmation on => lines
- smtp_connection              SMTP connections
+ smtp_connection              incoming SMTP connections
  smtp_incomplete_transaction  incomplete SMTP transactions
  smtp_mailauth                AUTH argument to MAIL commands
  smtp_no_mail                 session with no MAIL commands
  smtp_protocol_error          SMTP protocol errors
  smtp_syntax_error            SMTP syntax errors
  subject                      contents of Subject: on <= lines
- tls_certificate_verified     certificate verification status
+*tls_certificate_verified     certificate verification status
 *tls_cipher                   TLS cipher suite on <= and => lines
  tls_peerdn                   TLS peer DN on <= and => lines
  tls_sni                      TLS SNI on <= lines
@@ -32500,6 +33583,8 @@ selection marked by asterisks:
 
  all                          all of the above
 
+See also the slow_lookup_log main configuration option, section 14.4
+
 More details on each of these items follows:
 
   * 8bitmime: This causes Exim to log any 8BITMIME status of received messages,
@@ -32548,6 +33633,12 @@ More details on each of these items follows:
   * dnslist_defer: A log entry is written if an attempt to look up a host in a
     DNS black list suffers a temporary error.
 
+  * dnssec: For message acceptance and (attempted) delivery log lines, when dns
+    lookups gave secure results a tag of DS is added. For acceptance this
+    covers the reverse and forward lookups for host name verification. It does
+    not cover helo-name verification. For delivery this covers the SRV, MX, A
+    and/or AAAA lookups.
+
   * etrn: Every valid ETRN command that is received is logged, before the ACL
     is run to determine whether or not it is actually accepted. An invalid ETRN
     command, or one received within a message transaction is not logged by this
@@ -32564,8 +33655,14 @@ More details on each of these items follows:
   * incoming_interface: The interface on which a message was received is added
     to the "<=" line as an IP address in square brackets, tagged by I= and
     followed by a colon and the port number. The local interface and port are
-    also added to other SMTP log lines, for example "SMTP connection from", and
-    to rejection lines.
+    also added to other SMTP log lines, for example "SMTP connection from", to
+    rejection lines, and (despite the name) to outgoing "=>" and "->" lines.
+    The latter can be disabled by turning off the outgoing_interface option.
+
+  * proxy: The internal (closest to the system running Exim) IP address of the
+    proxy, tagged by PRX=, on the "<=" line for a message accepted on a proxied
+    connection or the "=>" line for a message delivered on a proxied
+    connection. See 58.1 for more information.
 
   * incoming_port: The remote port number from which a message was received is
     added to log entries and Received: header lines, following the IP address
@@ -32577,10 +33674,17 @@ More details on each of these items follows:
   * lost_incoming_connection: A log line is written when an incoming SMTP
     connection is unexpectedly dropped.
 
+  * outgoing_interface: If incoming_interface is turned on, then the interface
+    on which a message was sent is added to delivery lines as an I= tag
+    followed by IP address in square brackets. You can disable this by turning
+    off the outgoing_interface option.
+
   * outgoing_port: The remote port number is added to delivery log lines (those
-    containing => tags) following the IP address. This option is not included
-    in the default setting, because for most ordinary configurations, the
-    remote port number is always 25 (the SMTP port).
+    containing => tags) following the IP address. The local port is also added
+    if incoming_interface and outgoing_interface are both enabled. This option
+    is not included in the default setting, because for most ordinary
+    configurations, the remote port number is always 25 (the SMTP port), and
+    the local port is a random ephemeral port.
 
   * pid: The current process id is added to every log line, in square brackets,
     immediately after the time and date.
@@ -32614,7 +33718,7 @@ More details on each of these items follows:
   * rejected_header: If a message's header has been received at the time a
     rejection is written to the reject log, the complete header is added to the
     log. Header logging can be turned off individually for messages that are
-    rejected by the local_scan() function (see section 44.2).
+    rejected by the local_scan() function (see section 45.2).
 
   * retry_defer: A log line is written if a delivery is deferred because a
     retry time has not yet been reached. However, this "retry time not reached"
@@ -32648,8 +33752,8 @@ More details on each of these items follows:
     "C="<text>. 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 
+  * smtp_connection: A log line is written whenever an incoming SMTP connection
+    is established or closed, unless the connection is from a host that matches
     hosts_connection_nolog. (In contrast, lost_incoming_connection applies only
     when the closure is unexpected.) This applies to connections from local
     processes that use -bs as well as to TCP/IP connections. If a connection is
@@ -32693,7 +33797,7 @@ More details on each of these items follows:
     shows that the client issued QUIT straight after EHLO. If there were fewer
     than 20 commands, they are all listed. If there were more than 20 commands,
     the last 20 are listed, preceded by "...". However, with the default
-    setting of 10 for smtp_accep_max_nonmail, the connection will in any case
+    setting of 10 for smtp_accept_max_nonmail, the connection will in any case
     have been aborted before 20 non-mail commands are processed.
 
   * smtp_mailauth: A third subfield with the authenticated sender,
@@ -32738,7 +33842,7 @@ More details on each of these items follows:
     result of a list match is failure because a DNS lookup failed.
 
 
-51.16 Message log
+52.16 Message log
 -----------------
 
 In addition to the general log files, Exim writes a log file for each message
@@ -32757,33 +33861,33 @@ message_logs option false.
 
 
 ===============================================================================
-52. EXIM UTILITIES
+53. EXIM UTILITIES
 
 A number of utility scripts and programs are supplied with Exim and are
 described in this chapter. There is also the Exim Monitor, which is covered in
 the next chapter. The utilities described here are:
 
-    52.1  exiwhat          list what Exim processes are doing
-    52.2  exiqgrep         grep the queue
-    52.3  exiqsumm         summarize the queue
-    52.4  exigrep          search the main log
-    52.5  exipick          select messages on various criteria
-    52.6  exicyclog        cycle (rotate) log files
-    52.7  eximstats        extract statistics from the log
-    52.8  exim_checkaccess check address acceptance from given IP
-    52.9  exim_dbmbuild    build a DBM file
-    52.10 exinext          extract retry information
-    52.11 exim_dumpdb      dump a hints database
-    52.11 exim_tidydb      clean up a hints database
-    52.11 exim_fixdb       patch a hints database
-    52.15 exim_lock        lock a mailbox file
+    53.1  exiwhat          list what Exim processes are doing
+    53.2  exiqgrep         grep the queue
+    53.3  exiqsumm         summarize the queue
+    53.4  exigrep          search the main log
+    53.5  exipick          select messages on various criteria
+    53.6  exicyclog        cycle (rotate) log files
+    53.7  eximstats        extract statistics from the log
+    53.8  exim_checkaccess check address acceptance from given IP
+    53.9  exim_dbmbuild    build a DBM file
+    53.10 exinext          extract retry information
+    53.11 exim_dumpdb      dump a hints database
+    53.11 exim_tidydb      clean up a hints database
+    53.11 exim_fixdb       patch a hints database
+    53.15 exim_lock        lock a mailbox file
 
 Another utility that might be of use to sites with many MTAs is Tom Kistner's 
 exilog. It provides log visualizations across multiple Exim servers. See http:/
 /duncanthrax.net/exilog/ for details.
 
 
-52.1 Finding out what Exim processes are doing (exiwhat)
+53.1 Finding out what Exim processes are doing (exiwhat)
 --------------------------------------------------------
 
 On operating systems that can restart a system call after receiving a signal
@@ -32823,7 +33927,7 @@ The first number in the output line is the process number. The third line has
 been split here, in order to fit it on the page.
 
 
-52.2 Selective queue listing (exiqgrep)
+53.2 Selective queue listing (exiqgrep)
 ---------------------------------------
 
 This utility is a Perl script contributed by Matt Hubbard. It runs
@@ -32850,8 +33954,8 @@ match given criteria. The following selection options are available:
 
 -r <regex>
 
-    Match a recipient address using a case-insensitve search. The field that is
-    tested is not enclosed in angle brackets.
+    Match a recipient address using a case-insensitive search. The field that
+    is tested is not enclosed in angle brackets.
 
 -s <regex>
 
@@ -32903,7 +34007,7 @@ The following options control the format of the output:
 There is one more option, -h, which outputs a list of options.
 
 
-52.3 Summarizing the queue (exiqsumm)
+53.3 Summarizing the queue (exiqsumm)
 -------------------------------------
 
 The exiqsumm utility is a Perl script which reads the output of "exim -bp" and
@@ -32935,7 +34039,7 @@ by aliasing or forwarding are included (unless the one_time option of the
 redirect router has been used to convert them into "top level" addresses).
 
 
-52.4 Extracting specific information from the log (exigrep)
+53.4 Extracting specific information from the log (exigrep)
 -----------------------------------------------------------
 
 The exigrep utility is a Perl script that searches one or more main log files
@@ -32947,7 +34051,7 @@ can be in Exim log format or syslog format. If a matching log line is not
 associated with a specific message, it is included in exigrep's output without
 any additional lines. The usage is:
 
-exigrep [-t<n>] [-I] [-l] [-v] <pattern> [<log file>] ...
+exigrep [-t<n>] [-I] [-l] [-M] [-v] <pattern> [<log file>] ...
 
 If no log file names are given on the command line, the standard input is read.
 
@@ -32968,12 +34072,24 @@ expression.
 The -v option inverts the matching condition. That is, a line is selected if it
 does not match the pattern.
 
+The -M options means "related messages". exigrep will show messages that are
+generated as a result/response to a message that exigrep matched normally.
+
+Example of -M: user_a sends a message to user_b, which generates a bounce back
+to user_b. If exigrep is used to search for "user_a", only the first message
+will be displayed. But if exigrep is used to search for "user_b", the first and
+the second (bounce) message will be displayed. Using -M with exigrep when
+searching for "user_a" will show both messages since the bounce is "related" to
+or a "result" of the first message that was found by the search term.
+
 If the location of a zcat command is known from the definition of ZCAT_COMMAND
 in Local/Makefile, exigrep automatically passes any file whose name ends in
-COMPRESS_SUFFIX through zcat as it searches it.
+COMPRESS_SUFFIX through zcat as it searches it. If the ZCAT_COMMAND is not
+executable, exigrep tries to use autodetection of some well known compression
+extensions.
 
 
-52.5 Selecting messages by various criteria (exipick)
+53.5 Selecting messages by various criteria (exipick)
 -----------------------------------------------------
 
 John Jetmore's exipick utility is included in the Exim distribution. It lists
@@ -32982,12 +34098,12 @@ exipick's facilities, visit the web page at http://www.exim.org/eximwiki/
 ToolExipickManPage or run exipick with the --help option.
 
 
-52.6 Cycling log files (exicyclog)
+53.6 Cycling log files (exicyclog)
 ----------------------------------
 
 The exicyclog script can be used to cycle (rotate) mainlog and rejectlog files.
 This is not necessary if only syslog is being used, or if you are using log
-files with datestamps in their names (see section 51.3). Some operating systems
+files with datestamps in their names (see section 52.3). Some operating systems
 have their own standard mechanisms for log cycling, and these can be used
 instead of exicyclog if preferred. There are two command line options for 
 exicyclog:
@@ -33023,7 +34139,7 @@ assuming you have used the name "exim" for the Exim user. You can run exicyclog
 as root if you wish, but there is no need.
 
 
-52.7 Mail statistics (eximstats)
+53.7 Mail statistics (eximstats)
 --------------------------------
 
 A Perl script called eximstats is provided for extracting statistical
@@ -33085,7 +34201,7 @@ the command perldoc on the script. For example:
 perldoc /usr/exim/bin/eximstats
 
 
-52.8 Checking access policy (exim_checkaccess)
+53.8 Checking access policy (exim_checkaccess)
 ----------------------------------------------
 
 The -bh command line argument allows you to run a fake SMTP session with
@@ -33124,7 +34240,7 @@ running its checks. You can run checks that include callouts by using -bhc, but
 this is not yet available in a "packaged" form.
 
 
-52.9 Making DBM files (exim_dbmbuild)
+53.9 Making DBM files (exim_dbmbuild)
 -------------------------------------
 
 The exim_dbmbuild program reads an input file containing keys and data in the
@@ -33168,7 +34284,7 @@ which stops it listing duplicate keys to stderr. For other errors, where it
 doesn't actually make a new file, the return code is 2.
 
 
-52.10 Finding individual retry times (exinext)
+53.10 Finding individual retry times (exinext)
 ----------------------------------------------
 
 A utility called exinext (mostly a Perl script) provides the ability to fish
@@ -33193,7 +34309,7 @@ You can also give exinext a local part, without a domain, and it will give any
 retry information for that local part in your default domain. A message id can
 be used to obtain retry information pertaining to a specific message. This
 exists only when an attempt to deliver a message to a remote host suffers a
-message-specific error (see section 47.2). exinext is not particularly
+message-specific error (see section 48.2). exinext is not particularly
 efficient, but then it is not expected to be run very often.
 
 The exinext utility calls Exim to find out information such as the location of
@@ -33204,7 +34320,7 @@ features are mainly to help in testing, but might also be useful in
 environments where more than one configuration file is in use.
 
 
-52.11 Hints database maintenance
+53.11 Hints database maintenance
 --------------------------------
 
 Three utility programs are provided for maintaining the DBM files that Exim
@@ -33230,8 +34346,11 @@ The misc database is used for
   * Serializing delivery to a specific host (when serialize_hosts is set in an 
     smtp transport)
 
+  * Limiting the concurrency of specific transports (when max_parallel is set
+    in a transport)
+
 
-52.12 exim_dumpdb
+53.12 exim_dumpdb
 -----------------
 
 The entire contents of a database are written to the standard output by the 
@@ -33268,7 +34387,7 @@ routed to several alternative hosts, and Exim makes no effort to keep
 cross-references.
 
 
-52.13 exim_tidydb
+53.13 exim_tidydb
 -----------------
 
 The exim_tidydb utility program is used to tidy up the contents of a hints
@@ -33316,7 +34435,7 @@ Warning: If you never run exim_tidydb, the space used by the hints databases is
 likely to keep on increasing.
 
 
-52.14 exim_fixdb
+53.14 exim_fixdb
 ----------------
 
 The exim_fixdb program is a utility for interactively modifying databases. Its
@@ -33339,7 +34458,7 @@ sequence of digit pairs for year, month, day, hour, and minute. Colons can be
 used as optional separators.
 
 
-52.15 Mailbox maintenance (exim_lock)
+53.15 Mailbox maintenance (exim_lock)
 -------------------------------------
 
 The exim_lock utility locks a mailbox file using the same algorithm as Exim.
@@ -33441,7 +34560,7 @@ second argument - hence the quotes.
 
 
 ===============================================================================
-53. THE EXIM MONITOR
+54. THE EXIM MONITOR
 
 The Exim monitor is an application which displays in an X window information
 about the state of Exim's queue and what Exim is doing. An admin user can
@@ -33450,7 +34569,7 @@ such facilities are also available from the command line, and indeed, the
 monitor itself makes use of the command line to perform any actions requested.
 
 
-53.1 Running the monitor
+54.1 Running the monitor
 ------------------------
 
 The monitor is started by running the script called eximon. This is a shell
@@ -33504,7 +34623,7 @@ delivery, with two more action buttons. The following sections describe these
 different parts of the display.
 
 
-53.2 The stripcharts
+54.2 The stripcharts
 --------------------
 
 The first stripchart is always a count of messages on the queue. Its name can
@@ -33534,7 +34653,7 @@ SIZE_STRIPCHART and (optionally) SIZE_STRIPCHART_NAME in the Local/eximon.conf
 file.
 
 
-53.3 Main action buttons
+54.3 Main action buttons
 ------------------------
 
 Below the stripcharts there is an action button for quitting the monitor. Next
@@ -33561,7 +34680,7 @@ built so that it starts up with the window at its smallest size, by setting
 START_SMALL=yes in Local/eximon.conf.
 
 
-53.4 The log display
+54.4 The log display
 --------------------
 
 The second section of the window is an area in which a display of the tail of
@@ -33613,7 +34732,7 @@ modified TextPop, making it possible to build Eximon on these systems, at the
 expense of having unwanted items in the search popup window.
 
 
-53.5 The queue display
+54.5 The queue display
 ----------------------
 
 The bottom section of the monitor window contains a list of all messages that
@@ -33664,7 +34783,7 @@ available, but the caret is always moved to the end of the text when the queue
 display is updated.
 
 
-53.6 The queue menu
+54.6 The queue menu
 -------------------
 
 If the shift key is held down and the left button is clicked when the mouse
@@ -33687,7 +34806,7 @@ follows:
     in a new text window.
 
   * headers: Information from the spool file that contains the envelope
-    information and headers is displayed in a new text window. See chapter 55
+    information and headers is displayed in a new text window. See chapter 56
     for a description of the format of spool files.
 
   * body: The contents of the spool file containing the body of the message are
@@ -33762,7 +34881,7 @@ and ^S, as described above for the log tail window.
 
 
 ===============================================================================
-54. SECURITY CONSIDERATIONS
+55. SECURITY CONSIDERATIONS
 
 This chapter discusses a number of issues concerned with security, some of
 which are also covered in other parts of this manual.
@@ -33780,7 +34899,7 @@ absence of bugs can never be guaranteed. Any that are reported will get fixed
 as soon as possible.
 
 
-54.1 Building a more "hardened" Exim
+55.1 Building a more "hardened" Exim
 ------------------------------------
 
 There are a number of build-time options that can be set in Local/Makefile to
@@ -33831,7 +34950,7 @@ penetrated the Exim (but not the root) account. These options are as follows:
     to get root.
 
 
-54.2 Root privilege
+55.2 Root privilege
 -------------------
 
 The Exim binary is normally setuid to root, which means that it gains root
@@ -33916,7 +35035,7 @@ The processes that initially retain root privilege behave as follows:
     the routing is done in the same environment as a message delivery.
 
 
-54.3 Running Exim without privilege
+55.3 Running Exim without privilege
 -----------------------------------
 
 Some installations like to run Exim in an unprivileged state for more of its
@@ -33997,18 +35116,18 @@ However, there are no restrictions on remote deliveries. If you are running a
 gateway host that does no local deliveries, setting deliver_drop_privilege
 gives more security at essentially no cost.
 
-If you are using the mua_wrapper facility (see chapter 50), 
+If you are using the mua_wrapper facility (see chapter 51), 
 deliver_drop_privilege is forced to be true.
 
 
-54.4 Delivering to local files
+55.4 Delivering to local files
 ------------------------------
 
 Full details of the checks applied by appendfile before it writes to a file are
 given in chapter 26.
 
 
-54.5 Running local commands
+55.5 Running local commands
 ---------------------------
 
 There are a number of ways in which an administrator can configure Exim to run
@@ -34038,7 +35157,7 @@ commands. Configuration to check includes, but is not limited to:
   * Administrators who use embedded Perl are advised to explore how Perl's
     taint checking might apply to their usage.
 
-  * Use of ${expand...} is somewhat analagous to shell's eval builtin and
+  * Use of ${expand...} is somewhat analogous to shell's eval builtin and
     administrators are well advised to view its use with suspicion, in case
     (for instance) it allows a local-part to contain embedded Exim directives.
 
@@ -34051,7 +35170,7 @@ commands. Configuration to check includes, but is not limited to:
     the use of the inlisti expansion condition instead.
 
 
-54.6 Trust in configuration data
+55.6 Trust in configuration data
 --------------------------------
 
 If configuration data for Exim can come from untrustworthy sources, there are
@@ -34077,7 +35196,7 @@ some issues to be aware of:
     only expected to yield one result.
 
 
-54.7 IPv4 source routing
+55.7 IPv4 source routing
 ------------------------
 
 Many operating systems suppress IP source-routed packets in the kernel, but
@@ -34086,14 +35205,14 @@ IPv4 source-routed TCP calls, and then drops them. Things are all different in
 IPv6. No special checking is currently done.
 
 
-54.8 The VRFY, EXPN, and ETRN commands in SMTP
+55.8 The VRFY, EXPN, and ETRN commands in SMTP
 ----------------------------------------------
 
 Support for these SMTP commands is disabled by default. If required, they can
 be enabled by defining suitable ACLs.
 
 
-54.9 Privileged users
+55.9 Privileged users
 ---------------------
 
 Exim recognizes two sets of users with special privileges. Trusted users are
@@ -34137,7 +35256,7 @@ unprivileged), Exim must be built to allow group read access to its spool
 files.
 
 
-54.10 Spool files
+55.10 Spool files
 -----------------
 
 Exim's spool directory and everything it contains is owned by the Exim user and
@@ -34146,7 +35265,7 @@ Makefile configuration file, and defaults to 0640. This means that any user who
 is a member of the Exim group can access these files.
 
 
-54.11 Use of argv[0]
+55.11 Use of argv[0]
 --------------------
 
 Exim examines the last component of argv[0], and if it matches one of a set of
@@ -34155,7 +35274,7 @@ the last component of argv[0] set to "rsmtp" is exactly equivalent to calling
 it with the option -bS. There are no security implications in this.
 
 
-54.12 Use of %f formatting
+55.12 Use of %f formatting
 --------------------------
 
 The only use made of "%f" by Exim is in formatting load average values. These
@@ -34164,7 +35283,7 @@ Consequently, their range is limited and so therefore is the length of the
 converted output.
 
 
-54.13 Embedded Exim path
+55.13 Embedded Exim path
 ------------------------
 
 Exim uses its own path name, which is embedded in the code, only when it needs
@@ -34173,14 +35292,14 @@ does so. If some bug allowed the path to get overwritten, it would lead to an
 arbitrary program's being run as exim, not as root.
 
 
-54.14 Dynamic module directory
+55.14 Dynamic module directory
 ------------------------------
 
 Any dynamically loadable modules must be installed into the directory defined
 in "LOOKUP_MODULE_DIR" in Local/Makefile for Exim to permit loading it.
 
 
-54.15 Use of sprintf()
+55.15 Use of sprintf()
 ----------------------
 
 A large number of occurrences of "sprintf" in the code are actually calls to 
@@ -34193,7 +35312,7 @@ The remaining uses of sprintf() happen in controlled circumstances where the
 output buffer is known to be sufficiently long to contain the converted string.
 
 
-54.16 Use of debug_printf() and log_write()
+55.16 Use of debug_printf() and log_write()
 -------------------------------------------
 
 Arbitrary strings are passed to both these functions, but they do their
@@ -34201,7 +35320,7 @@ formatting by calling the function string_vformat(), which runs through the
 format string itself, and checks the length of each conversion.
 
 
-54.17 Use of strcat() and strcpy()
+55.17 Use of strcat() and strcpy()
 ----------------------------------
 
 These are used only in cases where the output buffer is known to be large
@@ -34210,7 +35329,7 @@ enough to hold the result.
 
 
 ===============================================================================
-55. FORMAT OF SPOOL FILES
+56. FORMAT OF SPOOL FILES
 
 A message on Exim's queue consists of two files, whose names are the message id
 followed by -D and -H, respectively. The data portion of the message is kept in
@@ -34231,9 +35350,8 @@ on your own if you do it. Here are some of the pitfalls:
     the lock will be lost at the instant of rename.
 
   * If you change the number of lines in the file, the value of $body_linecount
-    , which is stored in the -H file, will be incorrect. At present, this value
-    is not used by Exim, but there is no guarantee that this will always be the
-    case.
+    , which is stored in the -H file, will be incorrect and can cause
+    incomplete transmission of messages or undeliverable messages.
 
   * If the message is in MIME format, you must take care not to break it.
 
@@ -34253,7 +35371,7 @@ file remains in existence. When Exim next processes the message, it notices the
 attempt.
 
 
-55.1 Format of the -H file
+56.1 Format of the -H file
 --------------------------
 
 The second line of the -H file contains the login name for the uid of the
@@ -34541,23 +35659,23 @@ unqualified domain foundation.
 
 
 ===============================================================================
-56. SUPPORT FOR DKIM (DOMAINKEYS IDENTIFIED MAIL)
+57. SUPPORT FOR DKIM (DOMAINKEYS IDENTIFIED MAIL)
 
 DKIM is a mechanism by which messages sent by some entity can be provably
 linked to a domain which that entity controls. It permits reputation to be
 tracked on a per-domain basis, rather than merely upon source IP address. DKIM
 is documented in RFC 4871.
 
-Since version 4.70, DKIM support is compiled into Exim by default. It can be
-disabled by setting DISABLE_DKIM=yes in Local/Makefile.
+DKIM support is compiled into Exim by default if TLS support is present. It can
+be disabled by setting DISABLE_DKIM=yes in Local/Makefile.
 
-Exim's DKIM implementation allows to
+Exim's DKIM implementation allows for
 
- 1. Sign outgoing messages: This function is implemented in the SMTP transport.
-    It can co-exist with all other Exim features (including transport filters)
-    except cutthrough delivery.
+ 1. Signing outgoing messages: This function is implemented in the SMTP
+    transport. 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
+ 2. Verifying signatures in incoming messages: This is implemented by an
     additional ACL (acl_smtp_dkim), which can be called several times per
     message, with different signature contexts.
 
@@ -34581,31 +35699,32 @@ modifier. This should typically be done in the RCPT ACL, at points where you
 accept mail from relay sources (internal hosts or authenticated senders).
 
 
-56.1 Signing outgoing messages
+57.1 Signing outgoing messages
 ------------------------------
 
-Signing is implemented by setting private options on the SMTP transport. These
+Signing is enabled by setting private options on the SMTP transport. These
 options take (expandable) strings as arguments.
 
-+-----------+---------+-------------+--------------+
++--------------------------------------------------+
 |dkim_domain|Use: smtp|Type: string*|Default: unset|
-+-----------+---------+-------------+--------------+
++--------------------------------------------------+
 
 MANDATORY: The domain you want to sign with. The result of this expanded option
-is put into the $dkim_domain expansion variable.
+is put into the $dkim_domain expansion variable. If it is empty after
+expansion, DKIM signing is not done.
 
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 |dkim_selector|Use: smtp|Type: string*|Default: unset|
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 
 MANDATORY: This sets the key selector string. You can use the $dkim_domain
 expansion variable to look up a matching selector. The result is put in the
-expansion variable $dkim_selector which should be used in the dkim_private_key
+expansion variable $dkim_selector which may be used in the dkim_private_key
 option along with $dkim_domain.
 
-+----------------+---------+-------------+--------------+
++-------------------------------------------------------+
 |dkim_private_key|Use: smtp|Type: string*|Default: unset|
-+----------------+---------+-------------+--------------+
++-------------------------------------------------------+
 
 MANDATORY: This sets the private key to use. You can use the $dkim_domain and 
 $dkim_selector expansion variables to determine the private key to use. The
@@ -34619,27 +35738,27 @@ result can either
   * be "0", "false" or the empty string, in which case the message will not be
     signed. This case will not result in an error, even if dkim_strict is set.
 
-+----------+---------+-------------+--------------+
++-------------------------------------------------+
 |dkim_canon|Use: smtp|Type: string*|Default: unset|
-+----------+---------+-------------+--------------+
++-------------------------------------------------+
 
 OPTIONAL: This option sets the canonicalization method used when signing a
 message. The DKIM RFC currently supports two methods: "simple" and "relaxed".
 The option defaults to "relaxed" when unset. Note: the current implementation
 only supports using the same canonicalization method for both headers and body.
 
-+-----------+---------+-------------+--------------+
++--------------------------------------------------+
 |dkim_strict|Use: smtp|Type: string*|Default: unset|
-+-----------+---------+-------------+--------------+
++--------------------------------------------------+
 
 OPTIONAL: This option defines how Exim behaves when signing a message that
 should be signed fails for some reason. When the expansion evaluates to either
 "1" or "true", Exim will defer. Otherwise Exim will send the message unsigned.
 You can use the $dkim_domain and $dkim_selector expansion variables here.
 
-+-----------------+---------+-------------+--------------+
++--------------------------------------------------------+
 |dkim_sign_headers|Use: smtp|Type: string*|Default: unset|
-+-----------------+---------+-------------+--------------+
++--------------------------------------------------------+
 
 OPTIONAL: When set, this option must expand to (or be specified as) a
 colon-separated list of header names. Headers with these names will be included
@@ -34647,13 +35766,13 @@ in the message signature. When unspecified, the header names recommended in
 RFC4871 will be used.
 
 
-56.2 Verifying DKIM signatures in incoming mail
+57.2 Verifying DKIM signatures in incoming mail
 -----------------------------------------------
 
-Verification of DKIM signatures in incoming email is implemented via the 
+Verification of DKIM signatures in SMTP 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. A missing ACL definition
-defaults to accept. If any ACL call does not acccept, the message is not
+defaults to accept. If any ACL call does not accept, the message is not
 accepted. If a cutthrough delivery was in progress for the message it is
 summarily dropped (having wasted the transmission effort).
 
@@ -34703,33 +35822,33 @@ $dkim_verify_status
 
     A string describing the general status of the signature. One of
 
-      * none: There is no signature in the message for the current domain or
+      + none: There is no signature in the message for the current domain or
         identity (as reflected by $dkim_cur_signer).
 
-      * invalid: The signature could not be verified due to a processing error.
+      + invalid: The signature could not be verified due to a processing error.
         More detail is available in $dkim_verify_reason.
 
-      * fail: Verification of the signature failed. More detail is available in
+      + fail: Verification of the signature failed. More detail is available in
         $dkim_verify_reason.
 
-      * pass: The signature passed verification. It is valid.
+      + pass: The signature passed verification. It is valid.
 
 $dkim_verify_reason
 
-    A string giving a litte bit more detail when $dkim_verify_status is either
+    A string giving a little bit more detail when $dkim_verify_status is either
     "fail" or "invalid". One of
 
-      * pubkey_unavailable (when $dkim_verify_status="invalid"): The public key
+      + pubkey_unavailable (when $dkim_verify_status="invalid"): The public key
         for the domain could not be retrieved. This may be a temporary problem.
 
-      * pubkey_syntax (when $dkim_verify_status="invalid"): The public key
+      + pubkey_syntax (when $dkim_verify_status="invalid"): The public key
         record for the domain is syntactically invalid.
 
-      * bodyhash_mismatch (when $dkim_verify_status="fail"): The calculated
+      + bodyhash_mismatch (when $dkim_verify_status="fail"): The calculated
         body hash does not match the one specified in the signature header.
         This means that the message body was modified in transit.
 
-      * signature_incorrect (when $dkim_verify_status="fail"): The signature
+      + signature_incorrect (when $dkim_verify_status="fail"): The signature
         could not be verified. This may mean that headers were modified,
         re-written or otherwise changed in a way which is incompatible with
         DKIM verification. It may of course also mean that the signature is
@@ -34766,7 +35885,10 @@ dkim_canon_headers
 $dkim_copiedheaders
 
     A transcript of headers and their values which are included in the
-    signature (copied from the 'z=' tag of the signature).
+    signature (copied from the 'z=' tag of the signature). Note that RFC6376
+    requires that verification fail if the From: header is not included in the
+    signature. Exim does not enforce this; sites wishing strict enforcement
+    should code the check explicitly.
 
 $dkim_bodylength
 
@@ -34812,6 +35934,10 @@ $dkim_key_notes
 
     Notes from the key record (tag n=).
 
+$dkim_key_length
+
+    Number of bits in the key.
+
 In addition, two ACL conditions are provided:
 
 dkim_signers
@@ -34821,12 +35947,15 @@ dkim_signers
     verifying (reflected by $dkim_cur_signer). This is typically used to
     restrict an ACL verb to a group of domains or identities. For example:
 
-    # Warn when Mail purportedly from GMail has no signature at all
-    warn log_message = GMail sender without DKIM signature
+    # Warn when Mail purportedly from GMail has no gmail signature
+    warn log_message = GMail sender without gmail.com DKIM signature
          sender_domains = gmail.com
          dkim_signers = gmail.com
          dkim_status = none
 
+    Note that the above does not check for a total lack of DKIM signing; for
+    that check for empty $h_DKIM-Signature: in the data ACL.
+
 dkim_status
 
     ACL condition that checks a colon-separated list of possible DKIM
@@ -34846,7 +35975,344 @@ dkim_status
 
 
 ===============================================================================
-57. ADDING NEW DRIVERS OR LOOKUP TYPES
+58. PROXIES
+
+A proxy is an intermediate system through which communication is passed.
+Proxies may provide a security, availability or load-distribution function.
+
+
+58.1 Inbound proxies
+--------------------
+
+Exim has support for receiving inbound SMTP connections via a proxy that uses
+"Proxy Protocol" to speak to it. To include this support, include
+"SUPPORT_PROXY=yes" in Local/Makefile.
+
+It was built on specifications from: (http://haproxy.1wt.eu/download/1.5/doc/
+proxy-protocol.txt). That URL was revised in May 2014 to version 2 spec: (http:
+//git.1wt.eu/web?p=haproxy.git;a=commitdiff;h=afb768340c9d7e50d8e).
+
+The purpose of this facility is so that an application load balancer, such as
+HAProxy, can sit in front of several Exim servers to distribute load. Exim uses
+the local protocol communication with the proxy to obtain the remote SMTP
+system IP address and port information. 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).
+
+Use of a proxy is enabled by setting the hosts_proxy main configuration option
+to a hostlist; connections from these hosts will use Proxy Protocol. Exim
+supports both version 1 and version 2 of the Proxy Protocol and automatically
+determines which version is in use.
+
+The Proxy Protocol header is the first data received on a TCP connection and is
+inserted before any TLS-on-connect handshake from the client; Exim negotiates
+TLS between Exim-as-server and the remote client, not between Exim and the
+proxy server.
+
+The following expansion variables are usable ("internal" and "external" here
+refer to the interfaces of the proxy):
+
+proxy_external_address   
+ IP of host being proxied or IP of remote interface of proxy
+proxy_external_port      
+ Port of host being proxied or Port on remote interface of proxy
+proxy_local_address 
+ IP of proxy server inbound or IP of local interface of proxy
+proxy_local_port    
+ Port of proxy server inbound or Port on local interface of proxy
+proxy_session         boolean: SMTP connection via proxy
+
+If $proxy_session is set but $proxy_external_address is empty there was a
+protocol error.
+
+Since the real connections are all coming from the 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 option set so high, you lose the
+ability to protect your server from many connections from one IP. In order to
+prevent your server from overload, you need to add a per connection ratelimit
+to your connect ACL. A possible solution is:
+
+  # 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
+
+
+58.2 Outbound proxies
+---------------------
+
+Exim has support for sending outbound SMTP via a proxy using a protocol called
+SOCKS5 (defined by RFC1928). The support can be optionally included by defining
+SUPPORT_SOCKS=yes in Local/Makefile.
+
+Use of a proxy is enabled by setting the socks_proxy option on an smtp
+transport. The option value is expanded and should then be a list
+(colon-separated by default) of proxy specifiers. Each proxy specifier is a
+list (space-separated by default) where the initial element is an IP address
+and any subsequent elements are options.
+
+Options are a string <name>=<value>. The list of options is in the following
+table:
+
+auth    authentication method
+name    authentication username
+pass    authentication password
+port    tcp port
+tmo     connection timeout
+pri     priority
+weight  selection bias
+
+More details on each of these options follows:
+
+  * auth: Either "none" (default) or "name". Using "name" selects username/
+    password authentication per RFC 1929 for access to the proxy. Default is
+    "none".
+
+  * name: sets the username for the "name" authentication method. Default is
+    empty.
+
+  * pass: sets the password for the "name" authentication method. Default is
+    empty.
+
+  * port: the TCP port number to use for the connection to the proxy. Default
+    is 1080.
+
+  * tmo: sets a connection timeout in seconds for this proxy. Default is 5.
+
+  * pri: specifies a priority for the proxy within the list, higher values
+    being tried first. The default priority is 1.
+
+  * weight: specifies a selection bias. Within a priority set servers are
+    queried in a random fashion, weighted by this value. The default value for
+    selection bias is 1.
+
+Proxies from the list are tried according to their priority and weight settings
+until one responds. The timeout for the overall connection applies to the set
+of proxied attempts.
+
+
+58.3 Logging
+------------
+
+To log the (local) IP of a proxy in the incoming or delivery logline, add
+"+proxy" to the log_selector option. This will add a component tagged with "PRX
+=" to the line.
+
+
+
+===============================================================================
+59. INTERNATIONALISATION
+
+Exim has support for Internationalised mail names. To include this it must be
+built with SUPPORT_I18N and the libidn library. Standards supported are RFCs
+2060, 5890, 6530 and 6533.
+
+If Exim is built with SUPPORT_I18N_2008 (in addition to SUPPORT_I18N, not
+instead of it) then IDNA2008 is supported; this adds an extra library
+requirement, upon libidn2.
+
+
+59.1 MTA operations
+-------------------
+
+The main configuration option smtputf8_advertise_hosts specifies a host list.
+If this matches the sending host and accept_8bitmime is true (the default) then
+the ESMTP option SMTPUTF8 will be advertised.
+
+If the sender specifies the SMTPUTF8 option on a MAIL command international
+handling for the message is enabled and the expansion variable
+$message_smtputf8 will have value TRUE.
+
+The option allow_utf8_domains is set to true for this message. All DNS lookups
+are converted to a-label form whatever the setting of allow_utf8_domains when
+Exim is built with SUPPORT_I18N.
+
+Both localparts and domain are maintained as the original UTF-8 form
+internally; any comparison or regular-expression use will require appropriate
+care. Filenames created, eg. by the appendfile transport, will have UTF-8
+names.
+
+HELO names sent by the smtp transport will have any UTF-8 components expanded
+to a-label form, and any certificate name checks will be done using the a-label
+form of the name.
+
+Log lines and Received-by: header lines will acquire a "utf8" prefix on the
+protocol element, eg. utf8esmtp.
+
+The following expansion operators can be used:
+
+${utf8_domain_to_alabel:str}
+${utf8_domain_from_alabel:str}
+${utf8_localpart_to_alabel:str}
+${utf8_localpart_from_alabel:str}
+
+ACLs may use the following modifier:
+
+control = utf8_downconvert
+control = utf8_downconvert/<value>
+
+This sets a flag requiring that addresses are converted to a-label form before
+smtp delivery, for use in a Message Submission Agent context. If a value is
+appended it may be:
+
+1   (default) mandatory downconversion
+0   no downconversion
+-1  if SMTPUTF8 not supported by destination host
+
+If mua_wrapper is set, the utf8_downconvert control is initially set to -1.
+
+There is no explicit support for VRFY and EXPN. Configurations supporting these
+should inspect $smtp_command_argument for an SMTPUTF8 argument.
+
+There is no support for LMTP on Unix sockets. Using the "lmtp" protocol option
+on an smtp transport, for LMTP over TCP, should work as expected.
+
+There is no support for DSN unitext handling, and no provision for converting
+logging from or to UTF-8.
+
+
+59.2 MDA operations
+-------------------
+
+To aid in constructing names suitable for IMAP folders the following expansion
+operator can be used:
+
+${imapfolder {<string>} {<sep>} {<specials>}}
+
+The string is converted from the charset specified by the "headers charset"
+command (in a filter file) or headers_charset main configuration option
+(otherwise), to the modified UTF-7 encoding specified by RFC 2060, with the
+following exception: All occurences of <sep> (which has to be a single
+character) are replaced with periods ("."), and all periods and slashes that
+are not <sep> and are not in the <specials> string are BASE64 encoded.
+
+The third argument can be omitted, defaulting to an empty string. The second
+argument can be omitted, defaulting to "/".
+
+This is the encoding used by Courier for Maildir names on disk, and followed by
+many other IMAP servers.
+
+Examples:
+
+${imapfolder {Foo/Bar}}        yields Foo.Bar
+${imapfolder {Foo/Bar}{.}{/}}  yields Foo&AC8-Bar
+${imapfolder {R?ksm?rg?s}}     yields R&AOQ-ksm&APY-rg&AOU-s
+
+Note that the source charset setting is vital, and also that characters must be
+representable in UTF-16.
+
+
+
+===============================================================================
+60. EVENTS
+
+The events mechanism in Exim can be used to intercept processing at a number of
+points. It was originally invented to give a way to do customised logging
+actions (for example, to a database) but can also be used to modify some
+processing actions.
+
+Most installations will never need to use Events. The support can be left out
+of a build by defining DISABLE_EVENT=yes in Local/Makefile.
+
+There are two major classes of events: main and transport. The main
+configuration option event_action controls reception events; a transport option
+event_action controls delivery events.
+
+Both options are a string which is expanded when the event fires. An example
+might look like:
+
+event_action = ${if eq {msg:delivery}{$event_name} \
+{${lookup pgsql {SELECT * FROM record_Delivery( \
+    '${quote_pgsql:$sender_address_domain}',\
+    '${quote_pgsql:${lc:$sender_address_local_part}}', \
+    '${quote_pgsql:$domain}', \
+    '${quote_pgsql:${lc:$local_part}}', \
+    '${quote_pgsql:$host_address}', \
+    '${quote_pgsql:${lc:$host}}', \
+    '${quote_pgsql:$message_exim_id}')}} \
+} {}}
+
+Events have names which correspond to the point in process at which they fire.
+The name is placed in the variable $event_name and the event action expansion
+must check this, as it will be called for every possible event type.
+
+The current list of events is:
+
+msg:complete           after    main        per message
+msg:delivery           after    transport   per recipient
+msg:rcpt:host:defer    after    transport   per recipient per host
+msg:rcpt:defer         after    transport   per recipient
+msg:host:defer         after    transport   per attempt
+msg:fail:delivery      after    main        per recipient
+msg:fail:internal      after    main        per recipient
+tcp:connect            before   transport   per connection
+tcp:close              after    transport   per connection
+tls:cert               before   both        per certificate in verification chain
+smtp:connect           after    transport   per connection
+
+New event types may be added in future.
+
+The event name is a colon-separated list, defining the type of event in a tree
+of possibilities. It may be used as a list or just matched on as a whole. There
+will be no spaces in the name.
+
+The second column in the table above describes whether the event fires before
+or after the action is associates with. Those which fire before can be used to
+affect that action (more on this below).
+
+An additional variable, $event_data, is filled with information varying with
+the event type:
+
+msg:delivery          smtp confirmation message
+msg:rcpt:host:defer   error string
+msg:rcpt:defer        error string
+msg:host:defer        error string
+tls:cert              verification chain depth
+smtp:connect          smtp banner
+
+The :defer events populate one extra variable: $event_defer_errno.
+
+For complex operations an ACL expansion can be used in event_action however due
+to the multiple contexts that Exim operates in during the course of its
+processing:
+
+  * variables set in transport events will not be visible outside that
+    transport call
+
+  * acl_m variables in a server context are lost on a new connection, and after
+    smtp helo/ehlo/mail/starttls/rset commands
+
+Using an ACL expansion with the logwrite modifier can be a useful way of
+writing to the main log.
+
+The expansion of the event_action option should normally return an empty
+string. Should it return anything else the following will be forced:
+
+msg:delivery       (ignored)
+msg:host:defer     (ignored)
+msg:fail:delivery  (ignored)
+tcp:connect        do not connect
+tcp:close          (ignored)
+tls:cert           refuse verification
+smtp:connect       close connection
+
+No other use is made of the result string.
+
+For a tcp:connect event, if the connection is being made to a proxy then the
+address and port variables will be that of the proxy and not the target system.
+
+For tls:cert events, if GnuTLS is in use this will trigger only per chain
+element received on the connection. For OpenSSL it will trigger for every chain
+element including those loaded locally.
+
+
+
+===============================================================================
+61. ADDING NEW DRIVERS OR LOOKUP TYPES
 
 The following actions have to be taken in order to add a new router, transport,
 authenticator, or lookup type to Exim:
index 2409c40..3b94c22 100644 (file)
@@ -210,7 +210,7 @@ static void Destroy (gw)
 }
 
 /*
- * NOTE: This function really needs to recieve graphics exposure
+ * NOTE: This function really needs to receive graphics exposure
  *       events, but since this is not easily supported until R4 I am
  *       going to hold off until then.
  */
index faa51ed..7e8a2eb 100644 (file)
@@ -47,7 +47,7 @@ SOFTWARE.
  * used by all more than one of these dialogs.
  *
  * The following functions are the only non-static ones defined
- * in this module.  They are located at the begining of the
+ * in this module.  They are located at the beginning of the
  * section that contains this dialog box that uses them.
  *
  * void _XawTextInsertFileAction(w, event, params, num_params);
@@ -127,7 +127,7 @@ static char search_text_trans[] =
  *
  * Note:
  *
- * If the search was sucessful and the argument popdown is passed to
+ * If the search was successful and the argument popdown is passed to
  * this action routine then the widget will automatically popdown the
  * search widget.
  */
@@ -171,7 +171,7 @@ Cardinal * num_params;
   PopdownSearch(w, (XtPointer) tw->text.search, NULL);
 }
 
-/*     Function Name: PopdownSeach
+/*     Function Name: PopdownSearch
  *     Description: Pops down the search widget and resets it.
  *     Arguments: w - *** NOT USED ***.
  *                 closure - a pointer to the search structure.
@@ -223,7 +223,7 @@ XtPointer call_data;
  * The parameter list contains one or two entries that may be the following.
  *
  * First Entry:   The first entry is the direction to search by default.
- *                This arguement must be specified and may have a value of
+ *                This argument must be specified and may have a value of
  *                "left" or "right".
  *
  * Second Entry:  This entry is optional and contains the value of the default
@@ -448,8 +448,8 @@ char * ptr;
 
 /*     Function Name: DoSearch
  *     Description: Performs a search.
- *     Arguments: search - the serach structure.
- *     Returns: TRUE if sucessful.
+ *     Arguments: search - the search structure.
+ *     Returns: TRUE if successful.
  */
 
 /* ARGSUSED */
@@ -468,7 +468,7 @@ struct SearchAndReplace * search;
   text.firstPos = 0;
   text.format = FMT8BIT;
 
-  dir = (XawTextScanDirection)(int) ((caddr_t)XawToggleGetCurrent(search->left_toggle) -
+  dir = (XawTextScanDirection) ((long)XawToggleGetCurrent(search->left_toggle) -
                                R_OFFSET);
 
   pos = XawTextSearch( tw, dir, &text);
@@ -628,7 +628,7 @@ XEvent *event;
  *
  * NOTE:
  *
- * The function argument is passed the following arguements.
+ * The function argument is passed the following arguments.
  *
  * form - the from widget that is the dialog.
  * ptr - the initial string for the dialog's text widget.
index 671bd7f..5403236 100644 (file)
@@ -2,7 +2,7 @@
 *                Exim Monitor                    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -145,10 +145,8 @@ BOOL    dkim_disable_verify      = FALSE;
 
 BOOL    dont_deliver           = FALSE;
 
-#ifdef EXPERIMENTAL_DSN
 int     dsn_ret                = 0;
 uschar *dsn_envid              = NULL;
-#endif
 
 #ifdef WITH_CONTENT_SCAN
 int     fake_response          = OK;
@@ -189,13 +187,15 @@ uid_t   originator_uid;
 
 uschar *primary_hostname       = NULL;
 
+uschar *queue_name             = US"";
+
 int     received_count         = 0;
 uschar *received_protocol      = NULL;
 int     received_time          = 0;
 int     recipients_count       = 0;
 recipient_item *recipients_list = NULL;
 int     recipients_list_max    = 0;
-int     running_in_test_harness=FALSE;
+BOOL    running_in_test_harness=FALSE;
 
 uschar *sender_address         = NULL;
 uschar *sender_fullhost        = NULL;
@@ -220,6 +220,10 @@ tls_support tls_in = {
  -1,   /* tls_active */
  0,    /* bits */
  FALSE,        /* tls_certificate_verified */
+#ifdef EXPERIMENTAL_DANE
+ FALSE, /* dane_verified */
+ 0,     /* tlsa_usage */
+#endif
  NULL, /* tls_cipher */
  FALSE,        /* tls_on_connect */
  NULL, /* tls_on_connect_ports */
index ed95716..a7e874a 100644 (file)
@@ -92,6 +92,7 @@ the benefit of structs.h. One of these days I should tidy up this interface so
 that this kind of kludge isn't needed. */
 
 #define MAXPACKET 1024
+typedef void hctx;
 
 #include "config.h"
 #include "mytypes.h"
index 6efd9c0..9ff994c 100644 (file)
@@ -2,7 +2,7 @@
 *                 Exim Monitor                   *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* This module contains code for scanning the main log,
@@ -217,7 +217,11 @@ uschar buffer[log_buffer_len];
 
 if (LOG != NULL)
   {
-  fseek(LOG, log_position, SEEK_SET);
+  if (fseek(LOG, log_position, SEEK_SET))
+    {
+    perror("logfile fseek");
+    exit(1);
+    }
 
   while (Ufgets(buffer, log_buffer_len, LOG) != NULL)
     {
@@ -393,7 +397,11 @@ if (LOG == NULL ||
     {
     if (LOG != NULL) fclose(LOG);
     LOG = TEST;
-    fstat(fileno(LOG), &statdata);
+    if (fstat(fileno(LOG), &statdata))
+      {
+      fprintf(stderr, "fstat %s: %s\n", log_file_open, strerror(errno));
+      exit(1);
+      }
     log_inode = statdata.st_ino;
     }
   }
index d210a07..3034a04 100644 (file)
@@ -2,7 +2,7 @@
 *                  Exim Monitor                  *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -613,7 +613,6 @@ message_subdir[1] = 0;
 constructing file names and things. This call will initialize
 the store_get() function. */
 
-big_buffer_size = 1024;
 big_buffer = store_get(big_buffer_size);
 
 /* Set up the version string and date and output them */
@@ -670,8 +669,14 @@ if (log_file[0] != 0)
     {
     fseek(LOG, 0, SEEK_END);
     log_position = ftell(LOG);
-    fstat(fileno(LOG), &statdata);
-    log_inode = statdata.st_ino;
+    if (fstat(fileno(LOG), &statdata))
+      {
+      perror("log file fstat");
+      fclose(LOG);
+      LOG=NULL;
+      }
+    else
+      log_inode = statdata.st_ino;
     }
   }
 else
index 9d3ca1c..6deb909 100644 (file)
@@ -2,7 +2,7 @@
 *                  Exim Monitor                  *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -133,32 +133,33 @@ menu_is_up = FALSE;
 *          Display the message log               *
 *************************************************/
 
-static void msglogAction(Widget w, XtPointer client_data, XtPointer call_data)
+static void
+msglogAction(Widget w, XtPointer client_data, XtPointer call_data)
 {
 int i;
-uschar buffer[256];
-Widget text = text_create((uschar *)client_data, text_depth);
-FILE *f = NULL;
+Widget text = text_create(US client_data, text_depth);
+uschar * fname = NULL;
+FILE * f = NULL;
 
 w = w;      /* Keep picky compilers happy */
 call_data = call_data;
 
 /* End up with the split version, so message looks right when non-exist */
 
-for (i = 0; i < (spool_is_split? 2:1); i++)
+for (i = 0; i < (spool_is_split ? 2:1); i++)
   {
-  message_subdir[0] = (i != 0)? ((uschar *)client_data)[5] : 0;
-  sprintf(CS buffer, "%s/msglog/%s/%s", spool_directory, message_subdir,
-    (uschar *)client_data);
-  f = fopen(CS buffer, "r");
-  if (f != NULL) break;
+  message_subdir[0] = i != 0 ? (US client_data)[5] : 0;
+  fname = spool_fname(US"msglog", message_subdir, US client_data, US"");
+  if ((f = fopen(CS fname, "r")))
+    break;
   }
 
-if (f == NULL)
-  text_showf(text, "%s: %s\n", buffer, strerror(errno));
+if (!f)
+  text_showf(text, "%s: %s\n", fname, strerror(errno));
 else
   {
-  while (Ufgets(buffer, 256, f) != NULL) text_show(text, buffer);
+  uschar buffer[256];
+  while (Ufgets(buffer, sizeof(buffer), f) != NULL) text_show(text, buffer);
   fclose(f);
   }
 }
@@ -169,10 +170,10 @@ else
 *          Display the message body               *
 *************************************************/
 
-static void bodyAction(Widget w, XtPointer client_data, XtPointer call_data)
+static void
+bodyAction(Widget w, XtPointer client_data, XtPointer call_data)
 {
 int i;
-uschar buffer[256];
 Widget text = text_create((uschar *)client_data, text_depth);
 FILE *f = NULL;
 
@@ -181,19 +182,21 @@ call_data = call_data;
 
 for (i = 0; i < (spool_is_split? 2:1); i++)
   {
-  message_subdir[0] = (i != 0)? ((uschar *)client_data)[5] : 0;
-  sprintf(CS buffer, "%s/input/%s/%s-D", spool_directory, message_subdir,
-    (uschar *)client_data);
-  f = fopen(CS buffer, "r");
-  if (f != NULL) break;
+  uschar * fname;
+  message_subdir[0] = i != 0 ? ((uschar *)client_data)[5] : 0;
+  fname = spool_fname(US"input", message_subdir, US client_data, US"-D");
+  if ((f = fopen(CS fname, "r")))
+    break;
   }
 
 if (f == NULL)
   text_showf(text, "Failed to open file: %s\n", strerror(errno));
 else
   {
+  uschar buffer[256];
   int count = 0;
-  while (Ufgets(buffer, 256, f) != NULL)
+
+  while (Ufgets(buffer, sizeof(buffer), f) != NULL)
     {
     text_show(text, buffer);
     count += Ustrlen(buffer);
@@ -273,8 +276,12 @@ if (pipe(pipe_fd) != 0)
   return;
   }
 
-fcntl(pipe_fd[0], F_SETFL, O_NONBLOCK);
-fcntl(pipe_fd[1], F_SETFL, O_NONBLOCK);
+if (  fcntl(pipe_fd[0], F_SETFL, O_NONBLOCK)
+   || fcntl(pipe_fd[1], F_SETFL, O_NONBLOCK))
+  {
+  perror("set nonblocking on pipe");
+  exit(1);
+  }
 
 /* Delivering a message can take some time, and we want to show the
 output as it goes along. This requires subprocesses and is coded below. For
@@ -373,7 +380,7 @@ if ((pid = fork()) == 0)
 
 /* Main process - set up an item for the main ticker to watch. */
 
-if (pid < 0) text_showf(text, "Failed to fork: %s\n", strerror(pid)); else
+if (pid < 0) text_showf(text, "Failed to fork: %s\n", strerror(errno)); else
   {
   pipe_item *p = (pipe_item *)store_malloc(sizeof(pipe_item));
 
@@ -551,7 +558,8 @@ static void addrecipAction(Widget w, XtPointer client_data, XtPointer call_data)
 {
 w = w;      /* Keep picky compilers happy */
 call_data = call_data;
-Ustrcpy(actioned_message, (uschar *)client_data);
+Ustrncpy(actioned_message, client_data, 24);
+actioned_message[23] = '\0';
 action_required = US"-Mar";
 dialog_ref_widget = menushell;
 create_dialog(US"Recipient address to add?", US"");
@@ -567,7 +575,8 @@ static void markdelAction(Widget w, XtPointer client_data, XtPointer call_data)
 {
 w = w;      /* Keep picky compilers happy */
 call_data = call_data;
-Ustrcpy(actioned_message, (uschar *)client_data);
+Ustrncpy(actioned_message, client_data, 24);
+actioned_message[23] = '\0';
 action_required = US"-Mmd";
 dialog_ref_widget = menushell;
 create_dialog(US"Recipient address to mark delivered?", US"");
@@ -582,7 +591,7 @@ static void markalldelAction(Widget w, XtPointer client_data, XtPointer call_dat
 {
 w = w;      /* Keep picky compilers happy */
 call_data = call_data;
-ActOnMessage((uschar *)client_data, US"-Mmad", US"");
+ActOnMessage(US client_data, US"-Mmad", US"");
 }
 
 
@@ -597,9 +606,10 @@ queue_item *q;
 uschar *sender;
 w = w;      /* Keep picky compilers happy */
 call_data = call_data;
-Ustrcpy(actioned_message, (uschar *)client_data);
+Ustrncpy(actioned_message, client_data, 24);
+actioned_message[23] = '\0';
 q = find_queue(actioned_message, queue_noop, 0);
-sender = (q == NULL)? US"" : (q->sender[0] == 0)? US"<>" : q->sender;
+sender = !q ? US"" : q->sender[0] == 0 ? US"<>" : q->sender;
 action_required = US"-Mes";
 dialog_ref_widget = menushell;
 create_dialog(US"New sender address?", sender);
index c01a80f..4bdc57a 100644 (file)
@@ -263,7 +263,8 @@ else
 
 sender_address = NULL;
 
-sprintf(CS buffer, "%s/input/%s/%s-D", spool_directory, message_subdir, name);
+snprintf(CS buffer, sizeof(buffer), "%s/input/%s/%s/%s-D",
+  spool_directory, queue_name, message_subdir, name);
 if (Ustat(buffer, &statdata) == 0)
   q->size = message_size + statdata.st_size - SPOOL_DATA_START_OFFSET + 1;
 
@@ -271,7 +272,6 @@ if (Ustat(buffer, &statdata) == 0)
 been delivered, and removing visible names. */
 
 if (recipients_list != NULL)
-  {
   for (i = 0; i < recipients_count; i++)
     {
     uschar *r = recipients_list[i].address;
@@ -282,7 +282,6 @@ if (recipients_list != NULL)
       (void)find_dest(q, r, dest_add, FALSE);
       }
     }
-  }
 
 /* Recover the dynamic store used by spool_read_header(). */
 
@@ -617,11 +616,13 @@ uschar buffer[1024];
 
 message_subdir[0] = p->dir_char;
 
-sprintf(CS buffer, "%s/input/%s/%s-J", spool_directory, message_subdir, p->name);
-jread = fopen(CS buffer, "r");
-if (jread == NULL)
+snprintf(CS buffer, sizeof(buffer), "%s/input/%s/%s/%s-J",
+  spool_directory, queue_name, message_subdir, p->name);
+
+if (!(jread = fopen(CS buffer, "r")))
   {
-  sprintf(CS buffer, "%s/input/%s/%s-H", spool_directory, message_subdir, p->name);
+  snprintf(CS buffer, sizeof(buffer), "%s/input/%s/%s/%s-H",
+    spool_directory, queue_name, message_subdir, p->name);
   if (Ustat(buffer, &statdata) < 0 || p->update_time == statdata.st_mtime)
     return;
   }
@@ -655,32 +656,23 @@ if (jread != NULL)
 been delivered, and removing visible names. In the nonrecipients tree,
 domains are lower cased. */
 
-if (recipients_list != NULL)
-  {
+if (recipients_list)
   for (i = 0; i < recipients_count; i++)
     {
-    uschar *pp;
-    uschar *r = recipients_list[i].address;
-    tree_node *node = tree_search(tree_nonrecipients, r);
+    uschar * pp;
+    uschar * r = recipients_list[i].address;
+    tree_node * node;
 
-    if (node == NULL)
-      {
-      uschar temp[256];
-      uschar *rr = temp;
-      Ustrcpy(temp, r);
-      while (*rr != 0 && *rr != '@') rr++;
-      while (*rr != 0) { *rr = tolower(*rr); rr++; }
-      node = tree_search(tree_nonrecipients, temp);
-      }
+    if (!(node = tree_search(tree_nonrecipients, r)))
+      node = tree_search(tree_nonrecipients, string_copylc(r));
 
-    if ((pp = strstric(r+1, qualify_domain, FALSE)) != NULL &&
-      *(--pp) == '@') *pp = 0;
-    if (node == NULL)
+    if ((pp = strstric(r+1, qualify_domain, FALSE)) && *(--pp) == '@')
+       *pp = 0;
+    if (!node)
       (void)find_dest(p, r, dest_add, FALSE);
     else
       (void)find_dest(p, r, dest_remove, FALSE);
     }
-  }
 
 /* We also need to scan the tree of non-recipients, which might
 contain child addresses that are not in the recipients list, but
index 00675d4..2a5f0b8 100644 (file)
@@ -21,7 +21,7 @@ static int     size_first_time = 1;          /* and another */
 static int     stripchart_count = 0;         /* count stripcharts created */
 static int    *stripchart_delay;             /* vector of delay counts */
 static Widget *stripchart_label;             /* vector of label widgets */
-static int    *stripchart_last_total;        /* vector of prevous values */
+static int    *stripchart_last_total;        /* vector of previous values */
 static int    *stripchart_max;               /* vector of maxima */
 static int    *stripchart_middelay;          /* vector of */
 static int    *stripchart_midmax;            /* vector of */
@@ -58,12 +58,13 @@ a little game in order to ensure that the double value is correctly
 passed back via the value pointer without the compiler doing an
 unwanted cast. */
 
-static void stripchartAction(Widget w, XtPointer client_data, XtPointer value)
+static void
+stripchartAction(Widget w, XtPointer client_data, XtPointer value)
 {
-double *ptr = (double *)value;
+double * ptr = (double *)value;
 static int thresholds[] =
   {10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 0};
-int num = (int)client_data;
+int num = (long)client_data;
 int oldmax = 0;
 int newmax = 0;
 int newvalue = 0;
@@ -213,7 +214,8 @@ and for the second if it is a partition size display; its update time is
 initially set to 1 second so that it gives an immediate display of the queue.
 The first time its callback function is obeyed, the update time gets reset. */
 
-void create_stripchart(Widget parent, uschar *title)
+void
+create_stripchart(Widget parent, uschar *title)
 {
 Widget chart;
 
@@ -249,7 +251,7 @@ xs_SetValues(chart, 11,
   XtNfromVert,  label);
 
 XtAddCallback(chart, "getValue", stripchartAction,
-  (XtPointer)stripchart_count);
+  (XtPointer)(long)stripchart_count);
 
 stripchart_last_total[stripchart_count] = 0;
 stripchart_max[stripchart_count] = 10;
index 87371b5..b145fb9 100644 (file)
@@ -2,7 +2,7 @@
 *               Exim Monitor                     *
 *************************************************/
 
-/* Copyright (c) University of Cambridge, 1995 - 2007 */
+/* Copyright (c) University of Cambridge, 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* This file contains a number of subroutines that are in effect
@@ -37,6 +37,7 @@ for (i = 0; i < num_args; i++)
   aa[i].name = va_arg(ap, String);
   aa[i].value = va_arg(ap, XtArgVal);
   }
+va_end(ap);
 XtSetValues(w, aa, num_args);
 if (num_args > 15) free(aa);
 }
index eeb26ee..3e486a6 100755 (executable)
@@ -4,6 +4,8 @@
 # from inside the directory. It does its own checking of when to rebuild; it
 # just got too horrendous to get it right in "make", because of the optionally
 # existing configuration files.
+#
+# Copyright (c) The Exim Maintainers 2016
 
 
 # First off, get the OS type, and check that there is a make file for it.
@@ -110,7 +112,9 @@ do   if test -r ../$f
             echo "# End of $f"
             echo ""
      fi
-done >> $mft || exit 1
+done \
+     | sed 's/^TMPDIR=/EXIM_&/' \
+     >> $mft || exit 1
 
 # handle pkg-config
 # beware portability of extended regexps with sed.
@@ -132,13 +136,16 @@ then
       USE_*_PC)
         eval "pc_value=\"\$$var\""
         need_this=''
+        need_core=''
         if [ ".$SUPPORT_TLS" = "." ]; then
           # no TLS, not referencing
           true
         elif [ ".$var" = ".USE_GNUTLS_PC" ] && [ ".$USE_GNUTLS" != "." ]; then
           need_this=t
+          need_core="gnutls-special"
         elif [ ".$var" = ".USE_OPENSSL_PC" ] && [ ".$USE_GNUTLS" = "." ]; then
           need_this=t
+          need_core=t
         fi
         if [ ".$need_this" != "." ]; then
           tls_include=`pkg-config --cflags $pc_value`
@@ -149,6 +156,19 @@ then
           tls_libs=`pkg-config --libs $pc_value`
           echo "TLS_INCLUDE=$tls_include"
           echo "TLS_LIBS=$tls_libs"
+          # With hash.h pulling crypto into the core, we need to also handle that
+          if [ ".$need_this" = ".t" ]; then
+            echo "CFLAGS += $tls_include"
+            echo "LDFLAGS += $tls_libs"
+          elif [ ".$need_this" = ".gnutls-special" ]; then
+            if pkg-config --atleast-version=2.10 gnutls ; then
+              echo "CFLAGS += $tls_include"
+              echo "LDFLAGS += $tls_libs"
+            else
+              echo "CFLAGS += $(libgcrypt-config --cflags)"
+              echo "LDFLAGS += $(libgcrypt-config --libs)"
+            fi
+          fi
         fi
         ;;
 
dissimilarity index 86%
index 01cd21f..b710c2f 100755 (executable)
-#!/bin/sh
-
-# Script to build links for all the exim source files from the system-
-# specific build directory. It should be run from within that directory.
-
-test ! -d ../src && \
-  echo "*** $0 should be run in a system-specific subdirectory." && \
-  exit 1
-test -r version.c && \
-  echo "*** It appears that $0 has already been run." && \
-  exit 1
-if [ -r pcre/Makefile ] ; then
-  echo "*** It appears that $0 was previously interrupted."
-  echo "*** You need to remove the build directory, and then run 'make' again."
-  exit 1
-fi
-
-
-echo ""
-echo ">>> Creating links to source files..."
-
-
-# The sources for modular parts of Exim have to be linked independently
-# in their own sub-directories, since their .o files are built using
-# their own Makefile in their sub-directory.
-
-# Firstly the lookups
-mkdir lookups
-cd lookups
-ln -s ../../src/lookups/README           README
-# Makefile is generated
-ln -s ../../src/lookups/cdb.c            cdb.c
-ln -s ../../src/lookups/dbmdb.c          dbmdb.c
-ln -s ../../src/lookups/dnsdb.c          dnsdb.c
-ln -s ../../src/lookups/dsearch.c        dsearch.c
-ln -s ../../src/lookups/ibase.c          ibase.c
-ln -s ../../src/lookups/ldap.h           ldap.h
-ln -s ../../src/lookups/ldap.c           ldap.c
-ln -s ../../src/lookups/lsearch.c        lsearch.c
-ln -s ../../src/lookups/mysql.c          mysql.c
-ln -s ../../src/lookups/redis.c          redis.c
-ln -s ../../src/lookups/nis.c            nis.c
-ln -s ../../src/lookups/nisplus.c        nisplus.c
-ln -s ../../src/lookups/oracle.c         oracle.c
-ln -s ../../src/lookups/passwd.c         passwd.c
-ln -s ../../src/lookups/pgsql.c          pgsql.c
-ln -s ../../src/lookups/spf.c            spf.c
-ln -s ../../src/lookups/sqlite.c         sqlite.c
-ln -s ../../src/lookups/testdb.c         testdb.c
-ln -s ../../src/lookups/whoson.c         whoson.c
-
-ln -s ../../src/lookups/lf_functions.h   lf_functions.h
-ln -s ../../src/lookups/lf_check_file.c  lf_check_file.c
-ln -s ../../src/lookups/lf_quote.c       lf_quote.c
-ln -s ../../src/lookups/lf_sqlperform.c  lf_sqlperform.c
-
-cd ..
-
-# Likewise for the code for the routers
-mkdir routers
-cd routers
-ln -s ../../src/routers/README           README
-ln -s ../../src/routers/Makefile         Makefile
-ln -s ../../src/routers/accept.h         accept.h
-ln -s ../../src/routers/accept.c         accept.c
-ln -s ../../src/routers/dnslookup.h      dnslookup.h
-ln -s ../../src/routers/dnslookup.c      dnslookup.c
-ln -s ../../src/routers/ipliteral.h      ipliteral.h
-ln -s ../../src/routers/ipliteral.c      ipliteral.c
-ln -s ../../src/routers/iplookup.h       iplookup.h
-ln -s ../../src/routers/iplookup.c       iplookup.c
-ln -s ../../src/routers/manualroute.h    manualroute.h
-ln -s ../../src/routers/manualroute.c    manualroute.c
-ln -s ../../src/routers/queryprogram.h   queryprogram.h
-ln -s ../../src/routers/queryprogram.c   queryprogram.c
-ln -s ../../src/routers/redirect.h       redirect.h
-ln -s ../../src/routers/redirect.c       redirect.c
-
-ln -s ../../src/routers/rf_functions.h          rf_functions.h
-ln -s ../../src/routers/rf_change_domain.c      rf_change_domain.c
-ln -s ../../src/routers/rf_expand_data.c        rf_expand_data.c
-ln -s ../../src/routers/rf_get_errors_address.c rf_get_errors_address.c
-ln -s ../../src/routers/rf_get_munge_headers.c  rf_get_munge_headers.c
-ln -s ../../src/routers/rf_get_transport.c      rf_get_transport.c
-ln -s ../../src/routers/rf_get_ugid.c           rf_get_ugid.c
-ln -s ../../src/routers/rf_queue_add.c          rf_queue_add.c
-ln -s ../../src/routers/rf_lookup_hostlist.c    rf_lookup_hostlist.c
-ln -s ../../src/routers/rf_self_action.c        rf_self_action.c
-ln -s ../../src/routers/rf_set_ugid.c           rf_set_ugid.c
-
-cd ..
-
-# Likewise for the code for the transports
-mkdir transports
-cd transports
-ln -s ../../src/transports/README           README
-ln -s ../../src/transports/Makefile         Makefile
-ln -s ../../src/transports/appendfile.h     appendfile.h
-ln -s ../../src/transports/appendfile.c     appendfile.c
-ln -s ../../src/transports/autoreply.h      autoreply.h
-ln -s ../../src/transports/autoreply.c      autoreply.c
-ln -s ../../src/transports/lmtp.h           lmtp.h
-ln -s ../../src/transports/lmtp.c           lmtp.c
-ln -s ../../src/transports/pipe.h           pipe.h
-ln -s ../../src/transports/pipe.c           pipe.c
-ln -s ../../src/transports/smtp.h           smtp.h
-ln -s ../../src/transports/smtp.c           smtp.c
-
-ln -s ../../src/transports/tf_maildir.c     tf_maildir.c
-ln -s ../../src/transports/tf_maildir.h     tf_maildir.h
-
-cd ..
-
-# Likewise for the code for the authorization functions
-mkdir auths
-cd auths
-ln -s ../../src/auths/README             README
-ln -s ../../src/auths/Makefile           Makefile
-ln -s ../../src/auths/b64encode.c        b64encode.c
-ln -s ../../src/auths/b64decode.c        b64decode.c
-ln -s ../../src/auths/call_pam.c         call_pam.c
-ln -s ../../src/auths/call_pwcheck.c     call_pwcheck.c
-ln -s ../../src/auths/call_radius.c      call_radius.c
-ln -s ../../src/auths/check_serv_cond.c  check_serv_cond.c
-ln -s ../../src/auths/cyrus_sasl.c       cyrus_sasl.c
-ln -s ../../src/auths/cyrus_sasl.h       cyrus_sasl.h
-ln -s ../../src/auths/gsasl_exim.c       gsasl_exim.c
-ln -s ../../src/auths/gsasl_exim.h       gsasl_exim.h
-ln -s ../../src/auths/get_data.c         get_data.c
-ln -s ../../src/auths/get_no64_data.c    get_no64_data.c
-ln -s ../../src/auths/heimdal_gssapi.c   heimdal_gssapi.c
-ln -s ../../src/auths/heimdal_gssapi.h   heimdal_gssapi.h
-ln -s ../../src/auths/md5.c              md5.c
-ln -s ../../src/auths/xtextencode.c      xtextencode.c
-ln -s ../../src/auths/xtextdecode.c      xtextdecode.c
-ln -s ../../src/auths/cram_md5.c         cram_md5.c
-ln -s ../../src/auths/cram_md5.h         cram_md5.h
-ln -s ../../src/auths/plaintext.c        plaintext.c
-ln -s ../../src/auths/plaintext.h        plaintext.h
-ln -s ../../src/auths/pwcheck.c          pwcheck.c
-ln -s ../../src/auths/pwcheck.h          pwcheck.h
-ln -s ../../src/auths/auth-spa.c         auth-spa.c
-ln -s ../../src/auths/auth-spa.h         auth-spa.h
-ln -s ../../src/auths/dovecot.c          dovecot.c
-ln -s ../../src/auths/dovecot.h          dovecot.h
-ln -s ../../src/auths/sha1.c             sha1.c
-ln -s ../../src/auths/spa.c              spa.c
-ln -s ../../src/auths/spa.h              spa.h
-cd ..
-
-# Likewise for the code for the PDKIM library
-mkdir pdkim
-cd pdkim
-ln -s ../../src/pdkim/README             README
-ln -s ../../src/pdkim/Makefile           Makefile
-ln -s ../../src/pdkim/base64.c           base64.c
-ln -s ../../src/pdkim/base64.h           base64.h
-ln -s ../../src/pdkim/bignum.c           bignum.c
-ln -s ../../src/pdkim/bignum.h           bignum.h
-ln -s ../../src/pdkim/bn_mul.h           bn_mul.h
-ln -s ../../src/pdkim/pdkim.c            pdkim.c
-ln -s ../../src/pdkim/pdkim.h            pdkim.h
-ln -s ../../src/pdkim/rsa.c              rsa.c
-ln -s ../../src/pdkim/rsa.h              rsa.h
-ln -s ../../src/pdkim/sha1.c             sha1.c
-ln -s ../../src/pdkim/sha1.h             sha1.h
-ln -s ../../src/pdkim/sha2.c             sha2.c
-ln -s ../../src/pdkim/sha2.h             sha2.h
-cd ..
-
-# The basic source files for Exim and utilities. NB local_scan.h gets linked,
-# but local_scan.c does not, because its location is taken from the build-time
-# configuration. Likewise for the os.c file, which gets build dynamically.
-
-ln -s ../src/dbfunctions.h     dbfunctions.h
-ln -s ../src/dbstuff.h         dbstuff.h
-ln -s ../src/exim.h            exim.h
-ln -s ../src/functions.h       functions.h
-ln -s ../src/globals.h         globals.h
-ln -s ../src/local_scan.h      local_scan.h
-ln -s ../src/macros.h          macros.h
-ln -s ../src/mytypes.h         mytypes.h
-ln -s ../src/osfunctions.h     osfunctions.h
-ln -s ../src/store.h           store.h
-ln -s ../src/structs.h         structs.h
-ln -s ../src/lookupapi.h       lookupapi.h
-
-ln -s ../src/acl.c             acl.c
-ln -s ../src/buildconfig.c     buildconfig.c
-ln -s ../src/child.c           child.c
-ln -s ../src/crypt16.c         crypt16.c
-ln -s ../src/daemon.c          daemon.c
-ln -s ../src/dbfn.c            dbfn.c
-ln -s ../src/debug.c           debug.c
-ln -s ../src/deliver.c         deliver.c
-ln -s ../src/directory.c       directory.c
-ln -s ../src/dns.c             dns.c
-ln -s ../src/drtables.c        drtables.c
-ln -s ../src/dummies.c         dummies.c
-ln -s ../src/enq.c             enq.c
-ln -s ../src/exim.c            exim.c
-ln -s ../src/exim_dbmbuild.c   exim_dbmbuild.c
-ln -s ../src/exim_dbutil.c     exim_dbutil.c
-ln -s ../src/exim_lock.c       exim_lock.c
-ln -s ../src/expand.c          expand.c
-ln -s ../src/filter.c          filter.c
-ln -s ../src/filtertest.c      filtertest.c
-ln -s ../src/globals.c         globals.c
-ln -s ../src/header.c          header.c
-ln -s ../src/host.c            host.c
-ln -s ../src/ip.c              ip.c
-ln -s ../src/log.c             log.c
-ln -s ../src/lss.c             lss.c
-ln -s ../src/match.c           match.c
-ln -s ../src/moan.c            moan.c
-ln -s ../src/parse.c           parse.c
-ln -s ../src/perl.c            perl.c
-ln -s ../src/queue.c           queue.c
-ln -s ../src/rda.c             rda.c
-ln -s ../src/readconf.c        readconf.c
-ln -s ../src/receive.c         receive.c
-ln -s ../src/retry.c           retry.c
-ln -s ../src/rewrite.c         rewrite.c
-ln -s ../src/rfc2047.c         rfc2047.c
-ln -s ../src/route.c           route.c
-ln -s ../src/search.c          search.c
-ln -s ../src/sieve.c           sieve.c
-ln -s ../src/smtp_in.c         smtp_in.c
-ln -s ../src/smtp_out.c        smtp_out.c
-ln -s ../src/spool_in.c        spool_in.c
-ln -s ../src/spool_out.c       spool_out.c
-ln -s ../src/std-crypto.c      std-crypto.c
-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
-ln -s ../src/transport.c       transport.c
-ln -s ../src/tree.c            tree.c
-ln -s ../src/verify.c          verify.c
-ln -s ../src/version.c         version.c
-ln -s ../src/dkim.c            dkim.c
-ln -s ../src/dkim.h            dkim.h
-ln -s ../src/dmarc.c           dmarc.c
-ln -s ../src/dmarc.h           dmarc.h
-ln -s ../src/valgrind.h        valgrind.h
-ln -s ../src/memcheck.h        memcheck.h
-
-# WITH_CONTENT_SCAN
-ln -s ../src/spam.c            spam.c
-ln -s ../src/spam.h            spam.h
-ln -s ../src/spool_mbox.c      spool_mbox.c
-ln -s ../src/regex.c           regex.c
-ln -s ../src/mime.c            mime.c
-ln -s ../src/mime.h            mime.h
-ln -s ../src/malware.c         malware.c
-
-# WITH_OLD_DEMIME
-ln -s ../src/demime.c          demime.c
-ln -s ../src/demime.h          demime.h
-
-# EXPERIMENTAL_*
-ln -s ../src/bmi_spam.c        bmi_spam.c
-ln -s ../src/bmi_spam.h        bmi_spam.h
-ln -s ../src/spf.c             spf.c
-ln -s ../src/spf.h             spf.h
-ln -s ../src/srs.c             srs.c
-ln -s ../src/srs.h             srs.h
-ln -s ../src/dcc.c             dcc.c
-ln -s ../src/dcc.h             dcc.h
-
-
-# End of MakeLinks
+#!/bin/sh
+
+# Script to build links for all the exim source files from the system-
+# specific build directory. It should be run from within that directory.
+#
+# Copyright (c) The Exim Maintainers 2016
+
+test ! -d ../src && \
+  echo "*** $0 should be run in a system-specific subdirectory." && \
+  exit 1
+test -r version.c && \
+  echo "*** It appears that $0 has already been run." && \
+  exit 1
+if [ -r pcre/Makefile ] ; then
+  echo "*** It appears that $0 was previously interrupted."
+  echo "*** You need to remove the build directory, and then run 'make' again."
+  exit 1
+fi
+
+
+echo ""
+echo ">>> Creating links to source files..."
+
+
+# The sources for modular parts of Exim have to be linked independently
+# in their own sub-directories, since their .o files are built using
+# their own Makefile in their sub-directory.
+
+# Firstly the lookups
+mkdir lookups
+cd lookups
+# Makefile is generated
+for f in README cdb.c dbmdb.c dnsdb.c dsearch.c ibase.c ldap.h ldap.c \
+  lmdb.c lsearch.c mysql.c redis.c nis.c nisplus.c oracle.c passwd.c \
+  pgsql.c spf.c sqlite.c testdb.c whoson.c \
+  lf_functions.h lf_check_file.c lf_quote.c lf_sqlperform.c
+do
+  ln -s ../../src/lookups/$f $f
+done
+
+cd ..
+
+# Likewise for the code for the routers
+mkdir routers
+cd routers
+for f in README Makefile accept.h accept.c dnslookup.h dnslookup.c \
+  ipliteral.h ipliteral.c iplookup.h iplookup.c manualroute.h \
+  manualroute.c queryprogram.h queryprogram.c redirect.h redirect.c \
+  rf_functions.h rf_change_domain.c rf_expand_data.c rf_get_errors_address.c \
+  rf_get_munge_headers.c rf_get_transport.c rf_get_ugid.c rf_queue_add.c \
+  rf_lookup_hostlist.c rf_self_action.c rf_set_ugid.c
+do
+  ln -s ../../src/routers/$f $f
+done
+
+cd ..
+
+# Likewise for the code for the transports
+mkdir transports
+cd transports
+for f in README Makefile appendfile.h appendfile.c autoreply.h \
+  autoreply.c lmtp.h lmtp.c pipe.h pipe.c queuefile.c queuefile.h \
+  smtp.h smtp.c smtp_socks.c tf_maildir.c tf_maildir.h
+do
+  ln -s ../../src/transports/$f $f
+done
+
+cd ..
+
+# Likewise for the code for the authorization functions
+mkdir auths
+cd auths
+for f in README Makefile call_pam.c call_pwcheck.c \
+  call_radius.c check_serv_cond.c cyrus_sasl.c cyrus_sasl.h gsasl_exim.c \
+  gsasl_exim.h get_data.c get_no64_data.c heimdal_gssapi.c heimdal_gssapi.h \
+  md5.c xtextencode.c xtextdecode.c cram_md5.c cram_md5.h plaintext.c plaintext.h \
+  pwcheck.c pwcheck.h auth-spa.c auth-spa.h dovecot.c dovecot.h sha1.c spa.c \
+  spa.h tls.c tls.h
+do
+  ln -s ../../src/auths/$f $f
+done
+cd ..
+
+# Likewise for the code for the PDKIM library
+mkdir pdkim
+cd pdkim
+for f in README Makefile crypt_ver.h pdkim.c \
+  pdkim.h hash.c hash.h rsa.c rsa.h blob.h
+do
+  ln -s ../../src/pdkim/$f $f
+done
+cd ..
+
+# The basic source files for Exim and utilities. NB local_scan.h gets linked,
+# but local_scan.c does not, because its location is taken from the build-time
+# configuration. Likewise for the os.c file, which gets build dynamically.
+
+for f in blob.h dbfunctions.h dbstuff.h exim.h functions.h globals.h \
+  hash.h local_scan.h \
+  macros.h mytypes.h osfunctions.h store.h structs.h lookupapi.h sha_ver.h \
+  \
+  acl.c buildconfig.c base64.c child.c crypt16.c daemon.c dbfn.c debug.c deliver.c \
+  directory.c dns.c drtables.c dummies.c enq.c exim.c exim_dbmbuild.c \
+  exim_dbutil.c exim_lock.c expand.c filter.c filtertest.c globals.c \
+  hash.c header.c host.c ip.c log.c lss.c match.c moan.c parse.c perl.c queue.c \
+  rda.c readconf.c receive.c retry.c rewrite.c rfc2047.c route.c search.c \
+  setenv.c environment.c \
+  sieve.c smtp_in.c smtp_out.c spool_in.c spool_out.c std-crypto.c store.c \
+  string.c tls.c tlscert-gnu.c tlscert-openssl.c tls-gnu.c tls-openssl.c \
+  tod.c transport.c tree.c verify.c version.c dkim.c dkim.h dmarc.c dmarc.h \
+  valgrind.h memcheck.h
+do
+  ln -s ../src/$f $f
+done
+
+# WITH_CONTENT_SCAN
+for f in  spam.c spam.h spool_mbox.c regex.c mime.c mime.h malware.c
+do
+  ln -s ../src/$f $f
+done
+
+# EXPERIMENTAL_*
+for f in  bmi_spam.c bmi_spam.h dcc.c dcc.h dane.c dane-gnu.c dane-openssl.c \
+  danessl.h imap_utf7.c spf.c spf.h srs.c srs.h utf8.c
+do
+  ln -s ../src/$f $f
+done
+
+
+# End of MakeLinks
index b9330ae..1e88050 100755 (executable)
@@ -217,13 +217,15 @@ while [ $# -gt 0 ]; do
   # The exim binary is handled specially
 
   if [ $name = exim${EXE} ]; then
-    version=exim-`./exim -bV -C /dev/null | \
+    exim="./exim -bV -C /dev/null"
+    version=exim-`$exim 2>/dev/null | \
       awk '/Exim version/ { OFS=""; print $3,"-",substr($4,2,length($4)-1) }'`${EXE}
 
     if [ "${version}" = "exim-${EXE}" ]; then
       echo $com ""
-      echo $com "*** Could not run ./exim to find version number ***"
+      echo $com "*** Could not run $exim to find version number ***"
       echo $com "*** Exim installation ${ver}failed ***"
+      $exim
       exit 1
     fi
 
index 61493c6..db2d184 100755 (executable)
@@ -161,7 +161,7 @@ sed -n "1,/$tag_marker/p" < "$input"
 
 for name_mod in \
     CDB DBM:dbmdb DNSDB DSEARCH IBASE LSEARCH MYSQL NIS NISPLUS ORACLE \
-    PASSWD PGSQL SQLITE TESTDB WHOSON
+    PASSWD PGSQL REDIS SQLITE TESTDB WHOSON
 do
   emit_module_rule $name_mod
 done
@@ -177,12 +177,9 @@ fi
 
 OBJ="${OBJ} spf.o"
 
-# Because the variable is EXPERIMENTAL_REDIS and not LOOKUP_REDIS we
-# use a different function to check for EXPERIMENTAL_* features
-# requested. Don't use the SPF method with dummy functions above.
-if want_experimental REDIS
+if want_experimental LMDB
 then
-  OBJ="${OBJ} redis.o"
+  OBJ="${OBJ} lmdb.o"
 fi
 
 echo "MODS = $MODS"
index c461886..9707b9c 100755 (executable)
@@ -1,4 +1,9 @@
 #!/bin/sh
+# Copyright (c) The Exim Maintainers 2016
+
+set -e
+LC_ALL=C
+export LC_ALL
 
 # Update Exim's version header file.
 
@@ -37,8 +42,13 @@ then
        # Modify the output of git describe into separate parts for
        # the name "exim" and the release and variant versions.
        # Put a dot in the version number and remove a spurious g.
-       set $(git describe --dirty=-XX --match 'exim-4*' |
-               sed 's|-| |;s|_|.|;s|[-_]| _|;s|-g|-|')
+       if [ "$2" ]
+       then
+           description=$(git describe "$2")
+       else
+           description=$(git describe --dirty=-XX --match 'exim-4*')
+       fi
+       set $(echo "$description" | sed 's|-| |;s|_|.|;s|[-_]| _|;s|-g|-|')
        # Only update if we need to
        if [ "$2 $3" != "$EXIM_RELEASE_VERSION $EXIM_VARIANT_VERSION" ]
        then
index eac4b8d..918a6f8 100644 (file)
@@ -19,6 +19,7 @@ done <<-END
        globals.c       header_names
        globals.c       log_options
        expand.c        item_table
+       std-crypto.c    dh_constants
        transport.c     optionlist_transports
        route.c         optionlist_routers
        transports/appendfile.c appendfile_transport_options
@@ -27,6 +28,8 @@ done <<-END
        transports/pipe.c       pipe_transport_options
        transports/smtp.c       smtp_transport_options
        expand.c        var_table
+       acl.c           conditions
+       acl.c           controls_list
 END
 
 # Tables with just string items
@@ -45,6 +48,5 @@ done <<-END
        expand.c        op_table_main
        expand.c        cond_table
        acl.c           verbs
-       acl.c           conditions
 END
 
index d576fd7..df74aac 100644 (file)
@@ -288,6 +288,11 @@ TRANSPORT_SMTP=yes
 # library.
 # NOTE: LDAP cannot be built as a module!
 #
+# For Redis you need to have hiredis installed on your system
+# (https://github.com/redis/hiredis).
+# Depending on where it is installed you may have to edit the CFLAGS
+# (often += -I/usr/local/include) and LDFLAGS (-lhiredis) lines.
+
 # If your system has pkg-config then the _INCLUDE/_LIBS setting can be
 # handled for you automatically by also defining the _PC variable to reference
 # the name of the pkg-config package, if such is available.
@@ -306,6 +311,7 @@ LOOKUP_DNSDB=yes
 # LOOKUP_ORACLE=yes
 # LOOKUP_PASSWD=yes
 # LOOKUP_PGSQL=yes
+# LOOKUP_REDIS=yes
 # LOOKUP_SQLITE=yes
 # LOOKUP_SQLITE_PC=sqlite3
 # LOOKUP_WHOSON=yes
@@ -336,7 +342,7 @@ LOOKUP_DNSDB=yes
 
 
 #------------------------------------------------------------------------------
-# The PCRE library is required for exim.  There is no longer an embedded
+# The PCRE library is required for Exim.  There is no longer an embedded
 # version of the PCRE library included with the source code, instead you
 # must use a system library or build your own copy of PCRE.
 # In either case you must specify the library link info here.  If the
@@ -357,7 +363,8 @@ PCRE_CONFIG=yes
 # the command for linking Exim itself, not on any auxiliary programs. You
 # don't need to set LOOKUP_INCLUDE if the relevant directories are already
 # specified in INCLUDE. The settings below are just examples; -lpq is for
-# PostgreSQL, -lgds is for Interbase, -lsqlite3 is for SQLite.
+# PostgreSQL, -lgds is for Interbase, -lsqlite3 is for SQLite, -lhiredis
+# is for Redis.
 #
 # You do not need to use this for any lookup information added via pkg-config.
 
@@ -385,13 +392,7 @@ EXIM_MONITOR=eximon.bin
 
 # WITH_CONTENT_SCAN=yes
 
-# If you want to use the deprecated "demime" condition in the DATA ACL,
-# uncomment the line below. Doing so will also explicitly turn on the
-# WITH_CONTENT_SCAN option. If possible, use the MIME ACL instead of
-# the "demime" condition.
-
-# WITH_OLD_DEMIME=yes
-
+#------------------------------------------------------------------------------
 # If you're using ClamAV and are backporting fixes to an old version, instead
 # of staying current (which is the more usual approach) then you may need to
 # use an older API which uses a STREAM command, now deprecated, instead of
@@ -401,8 +402,9 @@ EXIM_MONITOR=eximon.bin
 #
 # WITH_OLD_CLAMAV_STREAM=yes
 
+
 #------------------------------------------------------------------------------
-# By default Exim includes code to support DKIM (DomainKeys Identified
+# If built with TLS, Exim includes code to support DKIM (DomainKeys Identified
 # Mail, RFC4871) signing and verification.  Verification of signatures is
 # turned on by default.  See the spec for information on conditionally
 # disabling it.  To disable the inclusion of the entire feature, set
@@ -426,9 +428,14 @@ EXIM_MONITOR=eximon.bin
 # By default, Exim has support for checking the AD bit in a DNS response, to
 # determine if DNSSEC validation was successful.  If your system libraries
 # do not support that bit, then set DISABLE_DNSSEC to "yes"
+# Note: Enabling EXPERIMENTAL_DANE unconditionally overrides this setting.
 
 # DISABLE_DNSSEC=yes
 
+# To disable support for Events set DISABLE_EVENT to "yes"
+
+# DISABLE_EVENT=yes
+
 
 #------------------------------------------------------------------------------
 # Compiling Exim with experimental features. These are documented in
@@ -437,6 +444,7 @@ EXIM_MONITOR=eximon.bin
 
 # Uncomment the following line to add support for talking to dccifd.  This
 # defaults the socket path to /usr/local/dcc/var/dccifd.
+# Doing so will also explicitly turn on the WITH_CONTENT_SCAN option.
 
 # EXPERIMENTAL_DCC=yes
 
@@ -457,6 +465,12 @@ EXIM_MONITOR=eximon.bin
 # CFLAGS  += -I/usr/local/include
 # LDFLAGS += -lsrs_alt
 
+# Uncomment the following line to add DMARC checking capability, implemented
+# using libopendmarc libraries.  You must have SPF support enabled also.
+# EXPERIMENTAL_DMARC=yes
+# CFLAGS += -I/usr/local/include
+# LDFLAGS += -lopendmarc
+
 # Uncomment the following lines to add Brightmail AntiSpam support. You need
 # to have the Brightmail client SDK installed. Please check the experimental
 # documentation for implementation details. You need to edit the CFLAGS and
@@ -466,33 +480,23 @@ EXIM_MONITOR=eximon.bin
 # 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 DMARC checking capability, implemented
-# using libopendmarc libraries.
-# EXPERIMENTAL_DMARC=yes
-# CFLAGS += -I/usr/local/include
-# LDFLAGS += -lopendmarc
+# Uncomment the following line to add DANE support
+# Note: Enabling this unconditionally overrides DISABLE_DNSSEC
+# Note: DANE is only supported when using OpenSSL
+# EXPERIMENTAL_DANE=yes
 
+# Uncomment the following to include extra information in fail DSN message (bounces)
+# EXPERIMENTAL_DSN_INFO=yes
 
-# Uncomment the following line to support Transport post-delivery actions,
-# eg. for logging to a database.
-# EXPERIMENTAL_TPDA=yes
-
-# Uncomment the following line to add Redis lookup support
-# You need to have hiredis installed on your system (https://github.com/redis/hiredis).
+# Uncomment the following to add LMDB lookup support
+# You need to have LMDB installed on your system (https://github.com/LMDB/lmdb)
 # Depending on where it is installed you may have to edit the CFLAGS and LDFLAGS lines.
-# EXPERIMENTAL_REDIS=yes
+# EXPERIMENTAL_LMDB=yes
 # CFLAGS += -I/usr/local/include
-# LDFLAGS += -lhiredis
-
-# Uncomment the following line to enable Experimental Proxy Protocol
-# EXPERIMENTAL_PROXY=yes
+# LDFLAGS += -llmdb
 
-# 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
+# Uncomment the following line to add queuefile transport support
+# EXPERIMENTAL_QUEUEFILE=yes
 
 ###############################################################################
 #                 THESE ARE THINGS YOU MIGHT WANT TO SPECIFY                  #
@@ -593,7 +597,7 @@ FIXED_NEVER_USERS=root
 # That shim can set macros before .include'ing your main configuration file.
 #
 # As a strictly transient measure to ease migration to 4.73, the
-# WHITELIST_D_MACROS value definies a colon-separated list of macro-names
+# WHITELIST_D_MACROS value defines a colon-separated list of macro-names
 # which are permitted to be overridden from the command-line which will be
 # honoured by the Exim user.  So these are macros that can persist to delivery
 # time.
@@ -625,9 +629,14 @@ FIXED_NEVER_USERS=root
 # AUTH_GSASL_PC=libgsasl
 # AUTH_HEIMDAL_GSSAPI=yes
 # AUTH_HEIMDAL_GSSAPI_PC=heimdal-gssapi
+# AUTH_HEIMDAL_GSSAPI_PC=heimdal-gssapi heimdal-krb5
 # AUTH_PLAINTEXT=yes
 # AUTH_SPA=yes
+# AUTH_TLS=yes
 
+# Heimdal through 1.5 required pkg-config 'heimdal-gssapi'; Heimdal 7.1
+# requires multiple pkg-config files to work with Exim, so the second example
+# above is needed.
 
 #------------------------------------------------------------------------------
 # If you specified AUTH_CYRUS_SASL above, you should ensure that you have the
@@ -680,6 +689,13 @@ HEADERS_CHARSET="ISO-8859-1"
 #
 # but of course there may need to be other things in CFLAGS and EXTRALIBS_EXIM
 # as well.
+#
+# nb: FreeBSD as of 4.89 defines LIBICONV_PLUG to pick up the system iconv
+# more reliably.  If you explicitly want the libiconv Port then as well
+# as adding -liconv you'll want to unset LIBICONV_PLUG.  If you actually need
+# this, let us know, but for now the Exim Maintainers are assuming that this
+# is uncommon and so you'll need to edit OS/os.h-FreeBSD yourself to remove
+# the define.
 
 
 #------------------------------------------------------------------------------
@@ -741,6 +757,10 @@ HEADERS_CHARSET="ISO-8859-1"
 # USE_GNUTLS_PC=gnutls
 # TLS_LIBS=-lgnutls -ltasn1 -lgcrypt
 
+# If using GnuTLS older than 2.10 and using pkg-config then note that Exim's
+# build process will require libgcrypt-config to exist in your $PATH.  A
+# version that old is likely to become unsupported by Exim in 2017.
+
 # The security fix we provide with the gnutls_allow_auto_pkcs11 option
 # (4.82 PP/09) introduces a compatibility regression.  The symbol is
 # not available if GnuTLS is build without p11-kit (--without-p11-kit
@@ -790,7 +810,7 @@ HEADERS_CHARSET="ISO-8859-1"
 # with the extension "texinfo" in the doc directory. You may find that the
 # version number of the texinfo files is different to your Exim version number,
 # because the main documentation isn't updated as often as the code. For
-# example, if you have Exim version 4.43, the source tarball upacks into a
+# example, if you have Exim version 4.43, the source tarball unpacks into a
 # directory called exim-4.43, but the texinfo tarball unpacks into exim-4.40.
 # In this case, move the contents of exim-4.40/doc into exim-4.43/doc after you
 # have unpacked them. Then set INFO_DIRECTORY to the location of your info
@@ -868,9 +888,15 @@ COMPRESS_SUFFIX=gz
 # If the exigrep utility is fed compressed log files, it tries to uncompress
 # them using this command.
 
+# Leave it empty to enforce autodetection at runtime:
+# ZCAT_COMMAND=
+#
+# Omit the path if you want to use your system's PATH:
+# ZCAT_COMMAND=zcat
+#
+# Or specify the full pathname:
 ZCAT_COMMAND=/usr/bin/zcat
 
-
 #------------------------------------------------------------------------------
 # Compiling in support for embedded Perl: If you want to be able to
 # use Perl code in Exim's string manipulation language and you have Perl
@@ -903,6 +929,36 @@ ZCAT_COMMAND=/usr/bin/zcat
 # GNU/Linux -ldl is also needed.
 
 
+#------------------------------------------------------------------------------
+# Proxying.
+#
+# If you may want to use outbound (client-side) proxying, using Socks5,
+# uncomment the line below.
+
+# SUPPORT_SOCKS=yes
+
+# If you may want to use inbound (server-side) proxying, using Proxy Protocol,
+# uncomment the line below.
+
+# SUPPORT_PROXY=yes
+
+
+#------------------------------------------------------------------------------
+# Internationalisation.
+#
+# Uncomment the following to include Internationalisation features.  This is the
+# SMTPUTF8 ESMTP extension, and associated facilities for handling UTF8 domain
+# and localparts, per RFC 3490 (IDNA2003).
+# You need to have the IDN library installed.
+# If you want IDNA2008 mappings per RFCs 5890, 6530 and 6533, you additionally
+# need libidn2 and SUPPORT_I18N_2008.
+
+# SUPPORT_I18N=yes
+# LDFLAGS += -lidn
+# SUPPORT_I18N_2008=yes
+# LDFLAGS += -lidn -lidn2
+
+
 #------------------------------------------------------------------------------
 # Support for authentication via Radius is also available. The Exim support,
 # which is intended for use in conjunction with the SMTP AUTH facilities,
@@ -945,7 +1001,7 @@ ZCAT_COMMAND=/usr/bin/zcat
 # There is no need to install all of SASL on your system. You just need to run
 # ./configure --with-pwcheck, cd to the pwcheck directory within the sources,
 # make and make install. You must create the socket directory (default
-# /var/pwcheck) and chown it to exim's user and group. Once you have installed
+# /var/pwcheck) and chown it to Exim's user and group. Once you have installed
 # pwcheck, you should arrange for it to be started by root at boot time.
 
 # CYRUS_PWCHECK_SOCKET=/var/pwcheck/pwcheck
@@ -953,7 +1009,7 @@ ZCAT_COMMAND=/usr/bin/zcat
 
 #------------------------------------------------------------------------------
 # Support for authentication via the Cyrus SASL saslauthd daemon is available.
-# The Exim support, which is intented for use in conjunction with the SMTP AUTH
+# The Exim support, which is intended for use in conjunction with the SMTP AUTH
 # facilities, is included only when requested by setting the following
 # parameter to the location of the saslauthd daemon's socket.
 #
@@ -961,7 +1017,7 @@ ZCAT_COMMAND=/usr/bin/zcat
 # ./configure --with-saslauthd (and any other options you need, for example, to
 # select or deselect authentication mechanisms), cd to the saslauthd directory
 # within the sources, make and make install. You must create the socket
-# directory (default /var/state/saslauthd) and chown it to exim's user and
+# directory (default /var/state/saslauthd) and chown it to Exim's user and
 # group. Once you have installed saslauthd, you should arrange for it to be
 # started by root at boot time.
 
@@ -1088,9 +1144,11 @@ SYSTEM_ALIASES_FILE=/etc/aliases
 # files, and thus be influenced by the value of TMPDIR. For this reason, when
 # Exim starts, it checks the environment for TMPDIR, and if it finds it is set,
 # it replaces the value with what is defined here. Commenting this setting
-# suppresses the check altogether.
+# suppresses the check altogether. Older installations call this macro
+# just TMPDIR, but this has side effects at build time. At runtime
+# TMPDIR is checked as before.
 
-TMPDIR="/tmp"
+EXIM_TMPDIR="/tmp"
 
 
 #------------------------------------------------------------------------------
@@ -1122,7 +1180,7 @@ TMPDIR="/tmp"
 # to handle the different cases. If CONFIGURE_FILE_USE_EUID is defined, then
 # Exim will first look for a configuration file whose name is that defined
 # by CONFIGURE_FILE, with the effective uid tacked on the end, separated by
-# a period (for eximple, /usr/exim/configure.0). If this file does not exist,
+# a period (for example, /usr/exim/configure.0). If this file does not exist,
 # then the bare configuration file name is tried. In the case when both
 # CONFIGURE_FILE_USE_EUID and CONFIGURE_FILE_USE_NODE are set, four files
 # are tried: <name>.<euid>.<node>, <name>.<node>, <name>.<euid>, and <name>.
@@ -1306,7 +1364,7 @@ TMPDIR="/tmp"
 
 
 #------------------------------------------------------------------------------
-# Expanding match_* second paramters: BE CAREFUL IF ENABLING THIS!
+# Expanding match_* second parameters: BE CAREFUL IF ENABLING THIS!
 # It has proven too easy in practice for administrators to configure security
 # problems into their Exim install, by treating match_domain{}{} and friends
 # as a form of string comparison, where the second string comes from untrusted
index 6e635fb..60fa977 100644 (file)
--- a/src/acl.c
+++ b/src/acl.c
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Code for handling Access Control Lists (ACLs) */
@@ -46,7 +46,7 @@ static int msgcond[] = {
   };
 
 /* ACL condition and modifier codes - keep in step with the table that
-follows, and the cond_expand_at_top and uschar cond_modifiers tables lower
+follows.
 down. */
 
 enum { ACLC_ACL,
@@ -65,9 +65,6 @@ enum { ACLC_ACL,
        ACLC_DECODE,
 #endif
        ACLC_DELAY,
-#ifdef WITH_OLD_DEMIME
-       ACLC_DEMIME,
-#endif
 #ifndef DISABLE_DKIM
        ACLC_DKIM_SIGNER,
        ACLC_DKIM_STATUS,
@@ -91,6 +88,7 @@ enum { ACLC_ACL,
 #ifdef WITH_CONTENT_SCAN
        ACLC_MIME_REGEX,
 #endif
+       ACLC_QUEUE,
        ACLC_RATELIMIT,
        ACLC_RECIPIENTS,
 #ifdef WITH_CONTENT_SCAN
@@ -111,636 +109,409 @@ enum { ACLC_ACL,
        ACLC_VERIFY };
 
 /* ACL conditions/modifiers: "delay", "control", "continue", "endpass",
-"message", "log_message", "log_reject_target", "logwrite", and "set" are
+"message", "log_message", "log_reject_target", "logwrite", "queue" and "set" are
 modifiers that look like conditions but always return TRUE. They are used for
 their side effects. */
 
-static uschar *conditions[] = {
-  US"acl",
-  US"add_header",
-  US"authenticated",
+typedef struct condition_def {
+  uschar       *name;
+
+/* Flag to indicate the condition/modifier has a string expansion done
+at the outer level. In the other cases, expansion already occurs in the
+checking functions. */
+  BOOL         expand_at_top:1;
+
+  BOOL         is_modifier:1;
+
+/* Bit map vector of which conditions and modifiers are not allowed at certain
+times. For each condition and modifier, there's a bitmap of dis-allowed times.
+For some, it is easier to specify the negation of a small number of allowed
+times. */
+  unsigned     forbids;
+
+} condition_def;
+
+static condition_def conditions[] = {
+  { US"acl",           FALSE, FALSE,   0 },
+
+  { US"add_header",    TRUE, TRUE,
+    (unsigned int)
+    ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|
+      (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+#ifndef DISABLE_PRDR
+      (1<<ACL_WHERE_PRDR)|
+#endif
+      (1<<ACL_WHERE_MIME)|(1<<ACL_WHERE_NOTSMTP)|
+      (1<<ACL_WHERE_DKIM)|
+      (1<<ACL_WHERE_NOTSMTP_START)),
+  },
+
+  { US"authenticated", FALSE, FALSE,
+    (1<<ACL_WHERE_NOTSMTP)|
+      (1<<ACL_WHERE_NOTSMTP_START)|
+      (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO),
+  },
 #ifdef EXPERIMENTAL_BRIGHTMAIL
-  US"bmi_optin",
+  { US"bmi_optin",     TRUE, TRUE,
+    (1<<ACL_WHERE_AUTH)|
+      (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
+      (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_MIME)|
+# ifndef DISABLE_PRDR
+      (1<<ACL_WHERE_PRDR)|
+# endif
+      (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
+      (1<<ACL_WHERE_MAILAUTH)|
+      (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
+      (1<<ACL_WHERE_VRFY)|(1<<ACL_WHERE_PREDATA)|
+      (1<<ACL_WHERE_NOTSMTP_START),
+  },
 #endif
-  US"condition",
-  US"continue",
-  US"control",
+  { US"condition",     TRUE, FALSE,    0 },
+  { US"continue",      TRUE, TRUE,     0 },
+
+  /* Certain types of control are always allowed, so we let it through
+  always and check in the control processing itself. */
+  { US"control",       TRUE, TRUE,     0 },
+
 #ifdef EXPERIMENTAL_DCC
-  US"dcc",
+  { US"dcc",           TRUE, FALSE,
+    (unsigned int)
+    ~((1<<ACL_WHERE_DATA)|
+# ifndef DISABLE_PRDR
+      (1<<ACL_WHERE_PRDR)|
+# endif
+      (1<<ACL_WHERE_NOTSMTP)),
+  },
 #endif
 #ifdef WITH_CONTENT_SCAN
-  US"decode",
-#endif
-  US"delay",
-#ifdef WITH_OLD_DEMIME
-  US"demime",
+  { US"decode",                TRUE, FALSE, (unsigned int) ~(1<<ACL_WHERE_MIME) },
+
 #endif
+  { US"delay",         TRUE, TRUE, (1<<ACL_WHERE_NOTQUIT) },
 #ifndef DISABLE_DKIM
-  US"dkim_signers",
-  US"dkim_status",
+  { US"dkim_signers",  TRUE, FALSE, (unsigned int) ~(1<<ACL_WHERE_DKIM) },
+  { US"dkim_status",   TRUE, FALSE, (unsigned int) ~(1<<ACL_WHERE_DKIM) },
 #endif
 #ifdef EXPERIMENTAL_DMARC
-  US"dmarc_status",
-#endif
-  US"dnslists",
-  US"domains",
-  US"encrypted",
-  US"endpass",
-  US"hosts",
-  US"local_parts",
-  US"log_message",
-  US"log_reject_target",
-  US"logwrite",
-#ifdef WITH_CONTENT_SCAN
-  US"malware",
-#endif
-  US"message",
-#ifdef WITH_CONTENT_SCAN
-  US"mime_regex",
-#endif
-  US"ratelimit",
-  US"recipients",
-#ifdef WITH_CONTENT_SCAN
-  US"regex",
-#endif
-  US"remove_header",
-  US"sender_domains", US"senders", US"set",
-#ifdef WITH_CONTENT_SCAN
-  US"spam",
-#endif
-#ifdef EXPERIMENTAL_SPF
-  US"spf",
-  US"spf_guess",
+  { US"dmarc_status",  TRUE, FALSE, (unsigned int) ~(1<<ACL_WHERE_DATA) },
 #endif
-  US"udpsend",
-  US"verify" };
 
+  /* Explicit key lookups can be made in non-smtp ACLs so pass
+  always and check in the verify processing itself. */
+  { US"dnslists",      TRUE, FALSE,    0 },
 
-/* Return values from decode_control(); keep in step with the table of names
-that follows! */
+  { US"domains",       FALSE, FALSE,
+    (unsigned int)
+    ~((1<<ACL_WHERE_RCPT)
+      |(1<<ACL_WHERE_VRFY)
+#ifndef DISABLE_PRDR
+      |(1<<ACL_WHERE_PRDR)
+#endif
+      ),
+  },
+  { US"encrypted",     FALSE, FALSE,
+    (1<<ACL_WHERE_NOTSMTP)|
+      (1<<ACL_WHERE_CONNECT)|
+      (1<<ACL_WHERE_NOTSMTP_START)|
+      (1<<ACL_WHERE_HELO),
+  },
 
-enum {
-  CONTROL_AUTH_UNADVERTISED,
-  #ifdef EXPERIMENTAL_BRIGHTMAIL
-  CONTROL_BMI_RUN,
-  #endif
-  CONTROL_DEBUG,
-  #ifndef DISABLE_DKIM
-  CONTROL_DKIM_VERIFY,
-  #endif
-  #ifdef EXPERIMENTAL_DMARC
-  CONTROL_DMARC_VERIFY,
-  CONTROL_DMARC_FORENSIC,
-  #endif
-  CONTROL_DSCP,
-  CONTROL_ERROR,
-  CONTROL_CASEFUL_LOCAL_PART,
-  CONTROL_CASELOWER_LOCAL_PART,
-  CONTROL_CUTTHROUGH_DELIVERY,
-  CONTROL_ENFORCE_SYNC,
-  CONTROL_NO_ENFORCE_SYNC,
-  CONTROL_FREEZE,
-  CONTROL_QUEUE_ONLY,
-  CONTROL_SUBMISSION,
-  CONTROL_SUPPRESS_LOCAL_FIXUPS,
-  #ifdef WITH_CONTENT_SCAN
-  CONTROL_NO_MBOX_UNSPOOL,
-  #endif
-  CONTROL_FAKEDEFER,
-  CONTROL_FAKEREJECT,
-  CONTROL_NO_MULTILINE,
-  CONTROL_NO_PIPELINING,
-  CONTROL_NO_DELAY_FLUSH,
-  CONTROL_NO_CALLOUT_FLUSH
-};
+  { US"endpass",       TRUE, TRUE,     0 },
 
-/* ACL control names; keep in step with the table above! This list is used for
-turning ids into names. The actual list of recognized names is in the variable
-control_def controls_list[] below. The fact that there are two lists is a mess
-and should be tidied up. */
-
-static uschar *controls[] = {
-  US"allow_auth_unadvertised",
-  #ifdef EXPERIMENTAL_BRIGHTMAIL
-  US"bmi_run",
-  #endif
-  US"debug",
-  #ifndef DISABLE_DKIM
-  US"dkim_disable_verify",
-  #endif
-  #ifdef EXPERIMENTAL_DMARC
-  US"dmarc_disable_verify",
-  US"dmarc_enable_forensic",
-  #endif
-  US"dscp",
-  US"error",
-  US"caseful_local_part",
-  US"caselower_local_part",
-  US"cutthrough_delivery",
-  US"enforce_sync",
-  US"no_enforce_sync",
-  US"freeze",
-  US"queue_only",
-  US"submission",
-  US"suppress_local_fixups",
-  #ifdef WITH_CONTENT_SCAN
-  US"no_mbox_unspool",
-  #endif
-  US"fakedefer",
-  US"fakereject",
-  US"no_multiline_responses",
-  US"no_pipelining",
-  US"no_delay_flush",
-  US"no_callout_flush"
-};
+  { US"hosts",         FALSE, FALSE,
+    (1<<ACL_WHERE_NOTSMTP)|
+      (1<<ACL_WHERE_NOTSMTP_START),
+  },
+  { US"local_parts",   FALSE, FALSE,
+    (unsigned int)
+    ~((1<<ACL_WHERE_RCPT)
+      |(1<<ACL_WHERE_VRFY)
+    #ifndef DISABLE_PRDR
+      |(1<<ACL_WHERE_PRDR)
+    #endif
+      ),
+  },
 
-/* Flags to indicate for which conditions/modifiers a string expansion is done
-at the outer level. In the other cases, expansion already occurs in the
-checking functions. */
+  { US"log_message",   TRUE, TRUE,     0 },
+  { US"log_reject_target", TRUE, TRUE, 0 },
+  { US"logwrite",      TRUE, TRUE,     0 },
 
-static uschar cond_expand_at_top[] = {
-  FALSE,   /* acl */
-  TRUE,    /* add_header */
-  FALSE,   /* authenticated */
-#ifdef EXPERIMENTAL_BRIGHTMAIL
-  TRUE,    /* bmi_optin */
-#endif
-  TRUE,    /* condition */
-  TRUE,    /* continue */
-  TRUE,    /* control */
-#ifdef EXPERIMENTAL_DCC
-  TRUE,    /* dcc */
-#endif
 #ifdef WITH_CONTENT_SCAN
-  TRUE,    /* decode */
-#endif
-  TRUE,    /* delay */
-#ifdef WITH_OLD_DEMIME
-  TRUE,    /* demime */
-#endif
-#ifndef DISABLE_DKIM
-  TRUE,    /* dkim_signers */
-  TRUE,    /* dkim_status */
+  { US"malware",       TRUE, FALSE,
+    (unsigned int)
+    ~((1<<ACL_WHERE_DATA)|
+# ifndef DISABLE_PRDR
+      (1<<ACL_WHERE_PRDR)|
+# endif
+      (1<<ACL_WHERE_NOTSMTP)),
+  },
 #endif
-#ifdef EXPERIMENTAL_DMARC
-  TRUE,    /* dmarc_status */
-#endif
-  TRUE,    /* dnslists */
-  FALSE,   /* domains */
-  FALSE,   /* encrypted */
-  TRUE,    /* endpass */
-  FALSE,   /* hosts */
-  FALSE,   /* local_parts */
-  TRUE,    /* log_message */
-  TRUE,    /* log_reject_target */
-  TRUE,    /* logwrite */
+
+  { US"message",       TRUE, TRUE,     0 },
 #ifdef WITH_CONTENT_SCAN
-  TRUE,    /* malware */
+  { US"mime_regex",    TRUE, FALSE, (unsigned int) ~(1<<ACL_WHERE_MIME) },
 #endif
-  TRUE,    /* message */
-#ifdef WITH_CONTENT_SCAN
-  TRUE,    /* mime_regex */
+
+  { US"queue",         TRUE, TRUE,
+    (1<<ACL_WHERE_NOTSMTP)|
+#ifndef DISABLE_PRDR
+      (1<<ACL_WHERE_PRDR)|
 #endif
-  TRUE,    /* ratelimit */
-  FALSE,   /* recipients */
+      (1<<ACL_WHERE_DATA),
+  },
+
+  { US"ratelimit",     TRUE, FALSE,    0 },
+  { US"recipients",    FALSE, FALSE, (unsigned int) ~(1<<ACL_WHERE_RCPT) },
+
 #ifdef WITH_CONTENT_SCAN
-  TRUE,    /* regex */
+  { US"regex",         TRUE, FALSE,
+    (unsigned int)
+    ~((1<<ACL_WHERE_DATA)|
+# ifndef DISABLE_PRDR
+      (1<<ACL_WHERE_PRDR)|
+# endif
+      (1<<ACL_WHERE_NOTSMTP)|
+      (1<<ACL_WHERE_MIME)),
+  },
+
 #endif
-  TRUE,    /* remove_header */
-  FALSE,   /* sender_domains */
-  FALSE,   /* senders */
-  TRUE,    /* set */
+  { US"remove_header", TRUE, TRUE,
+    (unsigned int)
+    ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|
+      (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+#ifndef DISABLE_PRDR
+      (1<<ACL_WHERE_PRDR)|
+#endif
+      (1<<ACL_WHERE_MIME)|(1<<ACL_WHERE_NOTSMTP)|
+      (1<<ACL_WHERE_NOTSMTP_START)),
+  },
+  { US"sender_domains",        FALSE, FALSE,
+    (1<<ACL_WHERE_AUTH)|(1<<ACL_WHERE_CONNECT)|
+      (1<<ACL_WHERE_HELO)|
+      (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
+      (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
+      (1<<ACL_WHERE_STARTTLS)|(1<<ACL_WHERE_VRFY),
+  },
+  { US"senders",       FALSE, FALSE,
+    (1<<ACL_WHERE_AUTH)|(1<<ACL_WHERE_CONNECT)|
+      (1<<ACL_WHERE_HELO)|
+      (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
+      (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
+      (1<<ACL_WHERE_STARTTLS)|(1<<ACL_WHERE_VRFY),
+  },
+
+  { US"set",           TRUE, TRUE,     0 },
+
 #ifdef WITH_CONTENT_SCAN
-  TRUE,    /* spam */
+  { US"spam",          TRUE, FALSE,
+    (unsigned int)
+    ~((1<<ACL_WHERE_DATA)|
+# ifndef DISABLE_PRDR
+      (1<<ACL_WHERE_PRDR)|
+# endif
+      (1<<ACL_WHERE_NOTSMTP)),
+  },
 #endif
 #ifdef EXPERIMENTAL_SPF
-  TRUE,    /* spf */
-  TRUE,    /* spf_guess */
+  { US"spf",           TRUE, FALSE,
+    (1<<ACL_WHERE_AUTH)|(1<<ACL_WHERE_CONNECT)|
+      (1<<ACL_WHERE_HELO)|
+      (1<<ACL_WHERE_MAILAUTH)|
+      (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
+      (1<<ACL_WHERE_STARTTLS)|(1<<ACL_WHERE_VRFY)|
+      (1<<ACL_WHERE_NOTSMTP)|
+      (1<<ACL_WHERE_NOTSMTP_START),
+  },
+  { US"spf_guess",     TRUE, FALSE,
+    (1<<ACL_WHERE_AUTH)|(1<<ACL_WHERE_CONNECT)|
+      (1<<ACL_WHERE_HELO)|
+      (1<<ACL_WHERE_MAILAUTH)|
+      (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
+      (1<<ACL_WHERE_STARTTLS)|(1<<ACL_WHERE_VRFY)|
+      (1<<ACL_WHERE_NOTSMTP)|
+      (1<<ACL_WHERE_NOTSMTP_START),
+  },
 #endif
-  TRUE,    /* udpsend */
-  TRUE     /* verify */
+  { US"udpsend",       TRUE, TRUE,     0 },
+
+  /* Certain types of verify are always allowed, so we let it through
+  always and check in the verify function itself */
+  { US"verify",                TRUE, FALSE,
+    0
+  },
 };
 
-/* Flags to identify the modifiers */
 
-static uschar cond_modifiers[] = {
-  FALSE,   /* acl */
-  TRUE,    /* add_header */
-  FALSE,   /* authenticated */
+
+/* Return values from decode_control(); used as index so keep in step
+with the controls_list table that follows! */
+
+enum {
+  CONTROL_AUTH_UNADVERTISED,
 #ifdef EXPERIMENTAL_BRIGHTMAIL
-  TRUE,    /* bmi_optin */
-#endif
-  FALSE,   /* condition */
-  TRUE,    /* continue */
-  TRUE,    /* control */
-#ifdef EXPERIMENTAL_DCC
-  FALSE,   /* dcc */
-#endif
-#ifdef WITH_CONTENT_SCAN
-  FALSE,   /* decode */
-#endif
-  TRUE,    /* delay */
-#ifdef WITH_OLD_DEMIME
-  FALSE,   /* demime */
+  CONTROL_BMI_RUN,
 #endif
+  CONTROL_CASEFUL_LOCAL_PART,
+  CONTROL_CASELOWER_LOCAL_PART,
+  CONTROL_CUTTHROUGH_DELIVERY,
+  CONTROL_DEBUG,
 #ifndef DISABLE_DKIM
-  FALSE,   /* dkim_signers */
-  FALSE,   /* dkim_status */
+  CONTROL_DKIM_VERIFY,
 #endif
 #ifdef EXPERIMENTAL_DMARC
-  FALSE,   /* dmarc_status */
-#endif
-  FALSE,   /* dnslists */
-  FALSE,   /* domains */
-  FALSE,   /* encrypted */
-  TRUE,    /* endpass */
-  FALSE,   /* hosts */
-  FALSE,   /* local_parts */
-  TRUE,    /* log_message */
-  TRUE,    /* log_reject_target */
-  TRUE,    /* logwrite */
-#ifdef WITH_CONTENT_SCAN
-  FALSE,   /* malware */
-#endif
-  TRUE,    /* message */
-#ifdef WITH_CONTENT_SCAN
-  FALSE,   /* mime_regex */
-#endif
-  FALSE,   /* ratelimit */
-  FALSE,   /* recipients */
-#ifdef WITH_CONTENT_SCAN
-  FALSE,   /* regex */
+  CONTROL_DMARC_VERIFY,
+  CONTROL_DMARC_FORENSIC,
 #endif
-  TRUE,    /* remove_header */
-  FALSE,   /* sender_domains */
-  FALSE,   /* senders */
-  TRUE,    /* set */
+  CONTROL_DSCP,
+  CONTROL_ENFORCE_SYNC,
+  CONTROL_ERROR,               /* pseudo-value for decode errors */
+  CONTROL_FAKEDEFER,
+  CONTROL_FAKEREJECT,
+  CONTROL_FREEZE,
+
+  CONTROL_NO_CALLOUT_FLUSH,
+  CONTROL_NO_DELAY_FLUSH,
+  CONTROL_NO_ENFORCE_SYNC,
 #ifdef WITH_CONTENT_SCAN
-  FALSE,   /* spam */
-#endif
-#ifdef EXPERIMENTAL_SPF
-  FALSE,   /* spf */
-  FALSE,   /* spf_guess */
+  CONTROL_NO_MBOX_UNSPOOL,
 #endif
-  TRUE,    /* udpsend */
-  FALSE    /* verify */
-};
-
-/* Bit map vector of which conditions and modifiers are not allowed at certain
-times. For each condition and modifier, there's a bitmap of dis-allowed times.
-For some, it is easier to specify the negation of a small number of allowed
-times. */
-
-static unsigned int cond_forbids[] = {
-  0,                                               /* acl */
-
-  (unsigned int)
-  ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|       /* add_header */
-    (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
-  #ifndef DISABLE_PRDR
-    (1<<ACL_WHERE_PRDR)|
-  #endif
-    (1<<ACL_WHERE_MIME)|(1<<ACL_WHERE_NOTSMTP)|
-    (1<<ACL_WHERE_DKIM)|
-    (1<<ACL_WHERE_NOTSMTP_START)),
-
-  (1<<ACL_WHERE_NOTSMTP)|                          /* authenticated */
-    (1<<ACL_WHERE_NOTSMTP_START)|
-    (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO),
-
-  #ifdef EXPERIMENTAL_BRIGHTMAIL
-  (1<<ACL_WHERE_AUTH)|                             /* bmi_optin */
-    (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
-    (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_MIME)|
-  #ifndef DISABLE_PRDR
-    (1<<ACL_WHERE_PRDR)|
-  #endif
-    (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
-    (1<<ACL_WHERE_MAILAUTH)|
-    (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
-    (1<<ACL_WHERE_VRFY)|(1<<ACL_WHERE_PREDATA)|
-    (1<<ACL_WHERE_NOTSMTP_START),
-  #endif
-
-  0,                                               /* condition */
-
-  0,                                               /* continue */
-
-  /* Certain types of control are always allowed, so we let it through
-  always and check in the control processing itself. */
-
-  0,                                               /* control */
-
-  #ifdef EXPERIMENTAL_DCC
-  (unsigned int)
-  ~((1<<ACL_WHERE_DATA)|                           /* dcc */
-  #ifndef DISABLE_PRDR
-    (1<<ACL_WHERE_PRDR)|
-  #endif
-    (1<<ACL_WHERE_NOTSMTP)),
-  #endif
-
-  #ifdef WITH_CONTENT_SCAN
-  (unsigned int)
-  ~(1<<ACL_WHERE_MIME),                            /* decode */
-  #endif
-
-  (1<<ACL_WHERE_NOTQUIT),                          /* delay */
-
-  #ifdef WITH_OLD_DEMIME
-  (unsigned int)
-  ~((1<<ACL_WHERE_DATA)|                           /* demime */
-  #ifndef DISABLE_PRDR
-    (1<<ACL_WHERE_PRDR)|
-  #endif
-    (1<<ACL_WHERE_NOTSMTP)),
-  #endif
-
-  #ifndef DISABLE_DKIM
-  (unsigned int)
-  ~(1<<ACL_WHERE_DKIM),                            /* dkim_signers */
-
-  (unsigned int)
-  ~(1<<ACL_WHERE_DKIM),                            /* dkim_status */
-  #endif
-
-  #ifdef EXPERIMENTAL_DMARC
-  (unsigned int)
-  ~(1<<ACL_WHERE_DATA),                            /* dmarc_status */
-  #endif
-
-  (1<<ACL_WHERE_NOTSMTP)|                          /* dnslists */
-    (1<<ACL_WHERE_NOTSMTP_START),
-
-  (unsigned int)
-  ~((1<<ACL_WHERE_RCPT)                            /* domains */
-  #ifndef DISABLE_PRDR
-    |(1<<ACL_WHERE_PRDR)
-  #endif
-    ),
-
-  (1<<ACL_WHERE_NOTSMTP)|                          /* encrypted */
-    (1<<ACL_WHERE_CONNECT)|
-    (1<<ACL_WHERE_NOTSMTP_START)|
-    (1<<ACL_WHERE_HELO),
-
-  0,                                               /* endpass */
-
-  (1<<ACL_WHERE_NOTSMTP)|                          /* hosts */
-    (1<<ACL_WHERE_NOTSMTP_START),
-
-  (unsigned int)
-  ~((1<<ACL_WHERE_RCPT)                             /* local_parts */
-  #ifndef DISABLE_PRDR
-    |(1<<ACL_WHERE_PRDR)
-  #endif
-    ),
-
-  0,                                               /* log_message */
-
-  0,                                               /* log_reject_target */
-
-  0,                                               /* logwrite */
-
-  #ifdef WITH_CONTENT_SCAN
-  (unsigned int)
-  ~((1<<ACL_WHERE_DATA)|                           /* malware */
-  #ifndef DISABLE_PRDR
-    (1<<ACL_WHERE_PRDR)|
-  #endif
-    (1<<ACL_WHERE_NOTSMTP)),
-  #endif
-
-  0,                                               /* message */
-
-  #ifdef WITH_CONTENT_SCAN
-  (unsigned int)
-  ~(1<<ACL_WHERE_MIME),                            /* mime_regex */
-  #endif
-
-  0,                                               /* ratelimit */
-
-  (unsigned int)
-  ~(1<<ACL_WHERE_RCPT),                            /* recipients */
-
-  #ifdef WITH_CONTENT_SCAN
-  (unsigned int)
-  ~((1<<ACL_WHERE_DATA)|                           /* regex */
-  #ifndef DISABLE_PRDR
-    (1<<ACL_WHERE_PRDR)|
-  #endif
-    (1<<ACL_WHERE_NOTSMTP)|
-    (1<<ACL_WHERE_MIME)),
-  #endif
-
-  (unsigned int)
-  ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|       /* remove_header */
-    (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
-  #ifndef DISABLE_PRDR
-    (1<<ACL_WHERE_PRDR)|
-  #endif
-    (1<<ACL_WHERE_MIME)|(1<<ACL_WHERE_NOTSMTP)|
-    (1<<ACL_WHERE_NOTSMTP_START)),
-
-  (1<<ACL_WHERE_AUTH)|(1<<ACL_WHERE_CONNECT)|      /* sender_domains */
-    (1<<ACL_WHERE_HELO)|
-    (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
-    (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
-    (1<<ACL_WHERE_STARTTLS)|(1<<ACL_WHERE_VRFY),
-
-  (1<<ACL_WHERE_AUTH)|(1<<ACL_WHERE_CONNECT)|      /* senders */
-    (1<<ACL_WHERE_HELO)|
-    (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
-    (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
-    (1<<ACL_WHERE_STARTTLS)|(1<<ACL_WHERE_VRFY),
-
-  0,                                               /* set */
-
-  #ifdef WITH_CONTENT_SCAN
-  (unsigned int)
-  ~((1<<ACL_WHERE_DATA)|                           /* spam */
-  #ifndef DISABLE_PRDR
-    (1<<ACL_WHERE_PRDR)|
-  #endif
-    (1<<ACL_WHERE_NOTSMTP)),
-  #endif
-
-  #ifdef EXPERIMENTAL_SPF
-  (1<<ACL_WHERE_AUTH)|(1<<ACL_WHERE_CONNECT)|      /* spf */
-    (1<<ACL_WHERE_HELO)|
-    (1<<ACL_WHERE_MAILAUTH)|
-    (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
-    (1<<ACL_WHERE_STARTTLS)|(1<<ACL_WHERE_VRFY)|
-    (1<<ACL_WHERE_NOTSMTP)|
-    (1<<ACL_WHERE_NOTSMTP_START),
-
-  (1<<ACL_WHERE_AUTH)|(1<<ACL_WHERE_CONNECT)|      /* spf_guess */
-    (1<<ACL_WHERE_HELO)|
-    (1<<ACL_WHERE_MAILAUTH)|
-    (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
-    (1<<ACL_WHERE_STARTTLS)|(1<<ACL_WHERE_VRFY)|
-    (1<<ACL_WHERE_NOTSMTP)|
-    (1<<ACL_WHERE_NOTSMTP_START),
-  #endif
-
-  0,                                               /* udpsend */
-
-  /* Certain types of verify are always allowed, so we let it through
-  always and check in the verify function itself */
+  CONTROL_NO_MULTILINE,
+  CONTROL_NO_PIPELINING,
 
-  0                                                /* verify */
+  CONTROL_QUEUE_ONLY,
+  CONTROL_SUBMISSION,
+  CONTROL_SUPPRESS_LOCAL_FIXUPS,
+#ifdef SUPPORT_I18N
+  CONTROL_UTF8_DOWNCONVERT,
+#endif
 };
 
 
-/* Bit map vector of which controls are not allowed at certain times. For
-each control, there's a bitmap of dis-allowed times. For some, it is easier to
-specify the negation of a small number of allowed times. */
-
-static unsigned int control_forbids[] = {
-  (unsigned int)
-  ~((1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)),   /* allow_auth_unadvertised */
-
-  #ifdef EXPERIMENTAL_BRIGHTMAIL
-  0,                                               /* bmi_run */
-  #endif
-
-  0,                                               /* debug */
-
-  #ifndef DISABLE_DKIM
-  (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)|      /* dkim_disable_verify */
-  #ifndef DISABLE_PRDR
-    (1<<ACL_WHERE_PRDR)|
-  #endif
-    (1<<ACL_WHERE_NOTSMTP_START),
-  #endif
-
-  #ifdef EXPERIMENTAL_DMARC
-  (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)|      /* dmarc_disable_verify */
-    (1<<ACL_WHERE_NOTSMTP_START),
-  (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)|      /* dmarc_enable_forensic */
-    (1<<ACL_WHERE_NOTSMTP_START),
-  #endif
-
-  (1<<ACL_WHERE_NOTSMTP)|
-    (1<<ACL_WHERE_NOTSMTP_START)|
-    (1<<ACL_WHERE_NOTQUIT),                        /* dscp */
-
-  0,                                               /* error */
-
-  (unsigned int)
-  ~(1<<ACL_WHERE_RCPT),                            /* caseful_local_part */
-
-  (unsigned int)
-  ~(1<<ACL_WHERE_RCPT),                            /* caselower_local_part */
-
-  (unsigned int)
-  0,                                              /* cutthrough_delivery */
-
-  (1<<ACL_WHERE_NOTSMTP)|                          /* enforce_sync */
-    (1<<ACL_WHERE_NOTSMTP_START),
-
-  (1<<ACL_WHERE_NOTSMTP)|                          /* no_enforce_sync */
-    (1<<ACL_WHERE_NOTSMTP_START),
-
-  (unsigned int)
-  ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|       /* freeze */
-    (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
-    // (1<<ACL_WHERE_PRDR)|    /* Not allow one user to freeze for all */
-    (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_MIME)),
-
-  (unsigned int)
-  ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|       /* queue_only */
-    (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
-    // (1<<ACL_WHERE_PRDR)|    /* Not allow one user to freeze for all */
-    (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_MIME)),
-
-  (unsigned int)
-  ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|       /* submission */
-    (1<<ACL_WHERE_PREDATA)),
-
-  (unsigned int)
-  ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|       /* suppress_local_fixups */
-    (1<<ACL_WHERE_PREDATA)|
-    (1<<ACL_WHERE_NOTSMTP_START)),
-
-  #ifdef WITH_CONTENT_SCAN
-  (unsigned int)
-  ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|       /* no_mbox_unspool */
-    (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
-    // (1<<ACL_WHERE_PRDR)|    /* Not allow one user to freeze for all */
-    (1<<ACL_WHERE_MIME)),
-  #endif
-
-  (unsigned int)
-  ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|       /* fakedefer */
-    (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
-  #ifndef DISABLE_PRDR
-    (1<<ACL_WHERE_PRDR)|
-  #endif
-    (1<<ACL_WHERE_MIME)),
-
-  (unsigned int)
-  ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|       /* fakereject */
-    (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
-  #ifndef DISABLE_PRDR
-    (1<<ACL_WHERE_PRDR)|
-  #endif
-    (1<<ACL_WHERE_MIME)),
-
-  (1<<ACL_WHERE_NOTSMTP)|                          /* no_multiline */
-    (1<<ACL_WHERE_NOTSMTP_START),
-
-  (1<<ACL_WHERE_NOTSMTP)|                          /* no_pipelining */
-    (1<<ACL_WHERE_NOTSMTP_START),
-
-  (1<<ACL_WHERE_NOTSMTP)|                          /* no_delay_flush */
-    (1<<ACL_WHERE_NOTSMTP_START),
-
-  (1<<ACL_WHERE_NOTSMTP)|                          /* no_callout_flush */
-    (1<<ACL_WHERE_NOTSMTP_START)
-};
 
-/* Structure listing various control arguments, with their characteristics. */
+/* Structure listing various control arguments, with their characteristics.
+For each control, there's a bitmap of dis-allowed times. For some, it is easier
+to specify the negation of a small number of allowed times. */
 
 typedef struct control_def {
-  uschar *name;
-  int    value;                  /* CONTROL_xxx value */
-  BOOL   has_option;             /* Has /option(s) following */
+  uschar       *name;
+  BOOL         has_option;     /* Has /option(s) following */
+  unsigned     forbids;        /* bitmap of dis-allowed times */
 } control_def;
 
 static control_def controls_list[] = {
-  { US"allow_auth_unadvertised", CONTROL_AUTH_UNADVERTISED, FALSE },
+  { US"allow_auth_unadvertised", FALSE,
+    (unsigned)
+    ~((1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO))
+  },
 #ifdef EXPERIMENTAL_BRIGHTMAIL
-  { US"bmi_run",                 CONTROL_BMI_RUN, FALSE },
+  { US"bmi_run",                 FALSE, 0 },
 #endif
-  { US"debug",                   CONTROL_DEBUG, TRUE },
+  { US"caseful_local_part",      FALSE, (unsigned) ~(1<<ACL_WHERE_RCPT) },
+  { US"caselower_local_part",    FALSE, (unsigned) ~(1<<ACL_WHERE_RCPT) },
+  { US"cutthrough_delivery",     TRUE, 0 },
+  { US"debug",                   TRUE, 0 },
+
 #ifndef DISABLE_DKIM
-  { US"dkim_disable_verify",     CONTROL_DKIM_VERIFY, FALSE },
+  { US"dkim_disable_verify",     FALSE,
+    (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)|
+# ifndef DISABLE_PRDR
+      (1<<ACL_WHERE_PRDR)|
+# endif
+      (1<<ACL_WHERE_NOTSMTP_START)
+  },
 #endif
+
 #ifdef EXPERIMENTAL_DMARC
-  { US"dmarc_disable_verify",    CONTROL_DMARC_VERIFY, FALSE },
-  { US"dmarc_enable_forensic",   CONTROL_DMARC_FORENSIC, FALSE },
+  { US"dmarc_disable_verify",    FALSE,
+    (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_NOTSMTP_START)
+  },
+  { US"dmarc_enable_forensic",   FALSE,
+    (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_NOTSMTP_START)
+  },
 #endif
-  { US"dscp",                    CONTROL_DSCP, TRUE },
-  { US"caseful_local_part",      CONTROL_CASEFUL_LOCAL_PART, FALSE },
-  { US"caselower_local_part",    CONTROL_CASELOWER_LOCAL_PART, FALSE },
-  { US"enforce_sync",            CONTROL_ENFORCE_SYNC, FALSE },
-  { US"freeze",                  CONTROL_FREEZE, TRUE },
-  { US"no_callout_flush",        CONTROL_NO_CALLOUT_FLUSH, FALSE },
-  { US"no_delay_flush",          CONTROL_NO_DELAY_FLUSH, FALSE },
-  { US"no_enforce_sync",         CONTROL_NO_ENFORCE_SYNC, FALSE },
-  { US"no_multiline_responses",  CONTROL_NO_MULTILINE, FALSE },
-  { US"no_pipelining",           CONTROL_NO_PIPELINING, FALSE },
-  { US"queue_only",              CONTROL_QUEUE_ONLY, FALSE },
+
+  { US"dscp",                    TRUE,
+    (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_NOTSMTP_START)|(1<<ACL_WHERE_NOTQUIT)
+  },
+  { US"enforce_sync",            FALSE,
+    (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_NOTSMTP_START)
+  },
+
+  /* Pseudo-value for decode errors */
+  { US"error",                   FALSE, 0 },
+
+  { US"fakedefer",               TRUE,
+    (unsigned)
+    ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|
+      (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+#ifndef DISABLE_PRDR
+      (1<<ACL_WHERE_PRDR)|
+#endif
+      (1<<ACL_WHERE_MIME))
+  },
+  { US"fakereject",              TRUE,
+    (unsigned)
+    ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|
+      (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+#ifndef DISABLE_PRDR
+      (1<<ACL_WHERE_PRDR)|
+#endif
+      (1<<ACL_WHERE_MIME))
+  },
+  { US"freeze",                  TRUE,
+    (unsigned)
+    ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|
+      (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+      // (1<<ACL_WHERE_PRDR)|    /* Not allow one user to freeze for all */
+      (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_MIME))
+  },
+
+  { US"no_callout_flush",        FALSE,
+    (1<<ACL_WHERE_NOTSMTP)| (1<<ACL_WHERE_NOTSMTP_START)
+  },
+  { US"no_delay_flush",          FALSE,
+    (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_NOTSMTP_START)
+  },
+  
+  { US"no_enforce_sync",         FALSE,
+    (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_NOTSMTP_START)
+  },
 #ifdef WITH_CONTENT_SCAN
-  { US"no_mbox_unspool",         CONTROL_NO_MBOX_UNSPOOL, FALSE },
+  { US"no_mbox_unspool",         FALSE,
+    (unsigned)
+    ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|
+      (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+      // (1<<ACL_WHERE_PRDR)|    /* Not allow one user to freeze for all */
+      (1<<ACL_WHERE_MIME))
+  },
 #endif
-  { US"fakedefer",               CONTROL_FAKEDEFER, TRUE },
-  { US"fakereject",              CONTROL_FAKEREJECT, TRUE },
-  { US"submission",              CONTROL_SUBMISSION, TRUE },
-  { US"suppress_local_fixups",   CONTROL_SUPPRESS_LOCAL_FIXUPS, FALSE },
-  { US"cutthrough_delivery",     CONTROL_CUTTHROUGH_DELIVERY, FALSE }
-  };
+  { US"no_multiline_responses",  FALSE,
+    (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_NOTSMTP_START)
+  },
+  { US"no_pipelining",           FALSE,
+    (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_NOTSMTP_START)
+  },
+
+  { US"queue_only",              FALSE,
+    (unsigned)
+    ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|
+      (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+      // (1<<ACL_WHERE_PRDR)|    /* Not allow one user to freeze for all */
+      (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_MIME))
+  },
+  { US"submission",              TRUE,
+    (unsigned)
+    ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|(1<<ACL_WHERE_PREDATA))
+  },
+  { US"suppress_local_fixups",   FALSE,
+    (unsigned)
+    ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|(1<<ACL_WHERE_PREDATA)|
+      (1<<ACL_WHERE_NOTSMTP_START))
+  },
+#ifdef SUPPORT_I18N
+  { US"utf8_downconvert",        TRUE, 0 }
+#endif
+};
 
 /* Support data structures for Client SMTP Authorization. acl_verify_csa()
 caches its result in a tree to avoid repeated DNS queries. The result is an
@@ -802,10 +573,72 @@ static uschar *ratelimit_option_string[] = {
 
 /* Enable recursion between acl_check_internal() and acl_check_condition() */
 
-static int acl_check_wargs(int, address_item *, uschar *, int, uschar **,
+static int acl_check_wargs(int, address_item *, const uschar *, uschar **,
     uschar **);
 
 
+/*************************************************
+*            Find control in list                *
+*************************************************/
+
+/* The lists are always in order, so binary chop can be used.
+
+Arguments:
+  name      the control name to search for
+  ol        the first entry in the control list
+  last      one more than the offset of the last entry in the control list
+
+Returns:    index of a control entry, or -1 if not found
+*/
+
+static int
+find_control(const uschar * name, control_def * ol, int last)
+{
+int first = 0;
+while (last > first)
+  {
+  int middle = (first + last)/2;
+  uschar * s =  ol[middle].name;
+  int c = Ustrncmp(name, s, Ustrlen(s));
+  if (c == 0) return middle;
+  else if (c > 0) first = middle + 1;
+  else last = middle;
+  }
+return -1;
+}
+
+
+
+/*************************************************
+*         Pick out condition from list           *
+*************************************************/
+
+/* Use a binary chop method
+
+Arguments:
+  name        name to find
+  list        list of conditions
+  end         size of list
+
+Returns:      offset in list, or -1 if not found
+*/
+
+static int
+acl_checkcondition(uschar * name, condition_def * list, int end)
+{
+int start = 0;
+while (start < end)
+  {
+  int mid = (start + end)/2;
+  int c = Ustrcmp(name, list[mid].name);
+  if (c == 0) return mid;
+  if (c < 0) end = mid;
+  else start = mid + 1;
+  }
+return -1;
+}
+
+
 /*************************************************
 *         Pick out name from list                *
 *************************************************/
@@ -893,8 +726,7 @@ while ((s = (*func)()) != NULL)
   /* If a verb is unrecognized, it may be another condition or modifier that
   continues the previous verb. */
 
-  v = acl_checkname(name, verbs, sizeof(verbs)/sizeof(char *));
-  if (v < 0)
+  if ((v = acl_checkname(name, verbs, nelem(verbs))) < 0)
     {
     if (this == NULL)
       {
@@ -931,8 +763,7 @@ while ((s = (*func)()) != NULL)
 
   /* Handle a condition or modifier. */
 
-  c = acl_checkname(name, conditions, sizeof(conditions)/sizeof(char *));
-  if (c < 0)
+  if ((c = acl_checkcondition(name, conditions, nelem(conditions))) < 0)
     {
     *error = string_sprintf("unknown ACL condition/modifier in \"%s\"",
       saveline);
@@ -941,10 +772,10 @@ while ((s = (*func)()) != NULL)
 
   /* The modifiers may not be negated */
 
-  if (negated && cond_modifiers[c])
+  if (negated && conditions[c].is_modifier)
     {
     *error = string_sprintf("ACL error: negation is not allowed with "
-      "\"%s\"", conditions[c]);
+      "\"%s\"", conditions[c].name);
     return NULL;
     }
 
@@ -955,7 +786,7 @@ while ((s = (*func)()) != NULL)
       this->verb != ACL_DISCARD)
     {
     *error = string_sprintf("ACL error: \"%s\" is not allowed with \"%s\"",
-      conditions[c], verbs[this->verb]);
+      conditions[c].name, verbs[this->verb]);
     return NULL;
     }
 
@@ -1022,7 +853,7 @@ while ((s = (*func)()) != NULL)
     if (*s++ != '=')
       {
       *error = string_sprintf("\"=\" missing after ACL \"%s\" %s", name,
-        cond_modifiers[c]? US"modifier" : US"condition");
+        conditions[c].is_modifier ? US"modifier" : US"condition");
       return NULL;
       }
     while (isspace(*s)) s++;
@@ -1049,9 +880,9 @@ Returns:    nothing
 */
 
 static void
-setup_header(uschar *hstring)
+setup_header(const uschar *hstring)
 {
-uschar *p, *q;
+const uschar *p, *q;
 int hlen = Ustrlen(hstring);
 
 /* Ignore any leading newlines */
@@ -1059,14 +890,24 @@ while (*hstring == '\n') hstring++, hlen--;
 
 /* An empty string does nothing; ensure exactly one final newline. */
 if (hlen <= 0) return;
-if (hstring[--hlen] != '\n') hstring = string_sprintf("%s\n", hstring);
-else while(hstring[--hlen] == '\n') hstring[hlen+1] = '\0';
+if (hstring[--hlen] != '\n')           /* no newline */
+  q = string_sprintf("%s\n", hstring);
+else if (hstring[hlen-1] == '\n')      /* double newline */
+  {
+  uschar * s = string_copy(hstring);
+  while(s[--hlen] == '\n')
+    s[hlen+1] = '\0';
+  q = s;
+  }
+else
+  q = hstring;
 
 /* Loop for multiple header lines, taking care about continuations */
 
-for (p = q = hstring; *p != 0; )
+for (p = q; *p != 0; )
   {
-  uschar *s;
+  const uschar *s;
+  uschar * hdr;
   int newtype = htype_add_bot;
   header_line **hptr = &acl_added_headers;
 
@@ -1074,7 +915,7 @@ for (p = q = hstring; *p != 0; )
 
   for (;;)
     {
-    q = Ustrchr(q, '\n');
+    q = Ustrchr(q, '\n');              /* we know there was a newline */
     if (*(++q) != ' ' && *q != '\t') break;
     }
 
@@ -1114,14 +955,14 @@ for (p = q = hstring; *p != 0; )
     if (*s == ':' || !isgraph(*s)) break;
     }
 
-  s = string_sprintf("%s%.*s", (*s == ':')? "" : "X-ACL-Warn: ", (int) (q - p), p);
-  hlen = Ustrlen(s);
+  hdr = string_sprintf("%s%.*s", (*s == ':')? "" : "X-ACL-Warn: ", (int) (q - p), p);
+  hlen = Ustrlen(hdr);
 
   /* See if this line has already been added */
 
   while (*hptr != NULL)
     {
-    if (Ustrncmp((*hptr)->text, s, hlen) == 0) break;
+    if (Ustrncmp((*hptr)->text, hdr, hlen) == 0) break;
     hptr = &((*hptr)->next);
     }
 
@@ -1130,7 +971,7 @@ for (p = q = hstring; *p != 0; )
   if (*hptr == NULL)
     {
     header_line *h = store_get(sizeof(header_line));
-    h->text = s;
+    h->text = hdr;
     h->next = NULL;
     h->type = newtype;
     h->slen = hlen;
@@ -1153,11 +994,11 @@ uschar *
 fn_hdrs_added(void)
 {
 uschar * ret = NULL;
+int size = 0;
+int ptr = 0;
 header_line * h = acl_added_headers;
 uschar * s;
 uschar * cp;
-int size = 0;
-int ptr = 0;
 
 if (!h) return NULL;
 
@@ -1169,13 +1010,13 @@ do
     if (cp[1] == '\0') break;
 
     /* contains embedded newline; needs doubling */
-    ret = string_cat(ret, &size, &ptr, s, cp-s+1);
-    ret = string_cat(ret, &size, &ptr, US"\n", 1);
+    ret = string_catn(ret, &size, &ptr, s, cp-s+1);
+    ret = string_catn(ret, &size, &ptr, US"\n", 1);
     s = cp+1;
     }
   /* last bit of header */
 
-  ret = string_cat(ret, &size, &ptr, s, cp-s+1);       /* newline-sep list */
+  ret = string_catn(ret, &size, &ptr, s, cp-s+1);      /* newline-sep list */
   }
 while((h = h->next));
 
@@ -1197,15 +1038,12 @@ Returns:    nothing
 */
 
 static void
-setup_remove_header(uschar *hnames)
+setup_remove_header(const uschar *hnames)
 {
 if (*hnames != 0)
-  {
-  if (acl_removed_headers == NULL)
-    acl_removed_headers = hnames;
-  else
-    acl_removed_headers = string_sprintf("%s : %s", acl_removed_headers, hnames);
-  }
+  acl_removed_headers = acl_removed_headers
+    ? string_sprintf("%s : %s", acl_removed_headers, hnames)
+    : string_copy(hnames);
 }
 
 
@@ -1329,7 +1167,7 @@ if (host_lookup_failed)
 /* Need to do a lookup */
 
 HDEBUG(D_acl)
-  debug_printf("looking up host name to force name/address consistency check\n");
+  debug_printf_indent("looking up host name to force name/address consistency check\n");
 
 if ((rc = host_name_lookup()) != OK)
   {
@@ -1363,7 +1201,7 @@ an odd configuration - why didn't the SRV record have a weight of 1 instead?)
 Arguments:
   dnsa       the DNS answer block
   dnss       a DNS scan block for us to use
-  reset      option specifing what portion to scan, as described above
+  reset      option specifying what portion to scan, as described above
   target     the target hostname to use for matching RR names
 
 Returns:     CSA_OK             successfully authorized
@@ -1389,9 +1227,6 @@ for (rr = dns_next_rr(dnsa, dnss, reset);
   if (rr->type != T_A
     #if HAVE_IPV6
       && rr->type != T_AAAA
-      #ifdef SUPPORT_A6
-        && rr->type != T_A6
-      #endif
     #endif
   ) continue;
 
@@ -1406,7 +1241,7 @@ for (rr = dns_next_rr(dnsa, dnss, reset);
     {
     /* If the client IP address matches the target IP address, it's good! */
 
-    DEBUG(D_acl) debug_printf("CSA target address is %s\n", da->address);
+    DEBUG(D_acl) debug_printf_indent("CSA target address is %s\n", da->address);
 
     if (strcmpic(sender_host_address, da->address) == 0) return CSA_OK;
     }
@@ -1444,10 +1279,10 @@ Returns:    CSA_UNKNOWN    no valid CSA record found
 */
 
 static int
-acl_verify_csa(uschar *domain)
+acl_verify_csa(const uschar *domain)
 {
 tree_node *t;
-uschar *found, *p;
+const uschar *found;
 int priority, weight, port;
 dns_answer dnsa;
 dns_scan dnss;
@@ -1470,7 +1305,7 @@ containing a keyword and a colon before the actual IP address. */
 
 if (domain[0] == '[')
   {
-  uschar *start = Ustrchr(domain, ':');
+  const uschar *start = Ustrchr(domain, ':');
   if (start == NULL) start = domain;
   domain = string_copyn(start + 1, Ustrlen(start) - 2);
   }
@@ -1525,20 +1360,19 @@ switch (dns_special_lookup(&dnsa, domain, T_CSA, &found))
 /* Scan the reply for well-formed CSA SRV records. */
 
 for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
-     rr != NULL;
-     rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
+     rr;
+     rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT)) if (rr->type == T_SRV)
   {
-  if (rr->type != T_SRV) continue;
+  const uschar * p = rr->data;
 
   /* Extract the numerical SRV fields (p is incremented) */
 
-  p = rr->data;
   GETSHORT(priority, p);
   GETSHORT(weight, p);
   GETSHORT(port, p);
 
   DEBUG(D_acl)
-    debug_printf("CSA priority=%d weight=%d port=%d\n", priority, weight, port);
+    debug_printf_indent("CSA priority=%d weight=%d port=%d\n", priority, weight, port);
 
   /* Check the CSA version number */
 
@@ -1550,13 +1384,8 @@ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
   assertion: legitimate SMTP clients are all explicitly authorized with CSA
   SRV records of their own. */
 
-  if (found != domain)
-    {
-    if (port & 1)
-      return t->data.val = CSA_FAIL_EXPLICIT;
-    else
-      return t->data.val = CSA_UNKNOWN;
-    }
+  if (Ustrcmp(found, domain) != 0)
+    return t->data.val = port & 1 ? CSA_FAIL_EXPLICIT : CSA_UNKNOWN;
 
   /* This CSA SRV record refers directly to our domain, so we check the value
   in the weight field to work out the domain's authorization. 0 and 1 are
@@ -1575,7 +1404,7 @@ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
   (void)dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen, p,
     (DN_EXPAND_ARG4_TYPE)target, sizeof(target));
 
-  DEBUG(D_acl) debug_printf("CSA target is %s\n", target);
+  DEBUG(D_acl) debug_printf_indent("CSA target is %s\n", target);
 
   break;
   }
@@ -1610,24 +1439,20 @@ else
   type = T_A;
 
 
-#if HAVE_IPV6 && defined(SUPPORT_A6)
-DNS_LOOKUP_AGAIN:
-#endif
-
 lookup_dnssec_authenticated = NULL;
 switch (dns_lookup(&dnsa, target, type, NULL))
   {
   /* If something bad happened (most commonly DNS_AGAIN), defer. */
 
   default:
-  return t->data.val = CSA_DEFER_ADDR;
+    return t->data.val = CSA_DEFER_ADDR;
 
   /* If the query succeeded, scan the addresses and return the result. */
 
   case DNS_SUCCEED:
-  rc = acl_verify_csa_address(&dnsa, &dnss, RESET_ANSWERS, target);
-  if (rc != CSA_FAIL_NOADDR) return t->data.val = rc;
-  /* else fall through */
+    rc = acl_verify_csa_address(&dnsa, &dnss, RESET_ANSWERS, target);
+    if (rc != CSA_FAIL_NOADDR) return t->data.val = rc;
+    /* else fall through */
 
   /* If the target has no IP addresses, the client cannot have an authorized
   IP address. However, if the target site uses A6 records (not AAAA records)
@@ -1635,12 +1460,7 @@ switch (dns_lookup(&dnsa, target, type, NULL))
 
   case DNS_NOMATCH:
   case DNS_NODATA:
-
-  #if HAVE_IPV6 && defined(SUPPORT_A6)
-  if (type == T_AAAA) { type = T_A6; goto DNS_LOOKUP_AGAIN; }
-  #endif
-
-  return t->data.val = CSA_FAIL_NOADDR;
+    return t->data.val = CSA_FAIL_NOADDR;
   }
 }
 
@@ -1662,7 +1482,7 @@ typedef struct {
   unsigned alt_opt_sep;                /* >0 Non-/ option separator (custom parser) */
   } verify_type_t;
 static verify_type_t verify_type_list[] = {
-    { US"reverse_host_lookup", VERIFY_REV_HOST_LKUP,   ~0,     TRUE, 0 },
+    { US"reverse_host_lookup", VERIFY_REV_HOST_LKUP,   ~0,     FALSE, 0 },
     { US"certificate",         VERIFY_CERT,            ~0,     TRUE, 0 },
     { US"helo",                        VERIFY_HELO,            ~0,     TRUE, 0 },
     { US"csa",                 VERIFY_CSA,             ~0,     FALSE, 0 },
@@ -1726,7 +1546,7 @@ Returns:       OK        verification condition succeeded
 */
 
 static int
-acl_verify(int where, address_item *addr, uschar *arg,
+acl_verify(int where, address_item *addr, const uschar *arg,
   uschar **user_msgptr, uschar **log_msgptr, int *basic_errno)
 {
 int sep = '/';
@@ -1750,7 +1570,7 @@ an error if options are given for items that don't expect them.
 */
 
 uschar *slash = Ustrchr(arg, '/');
-uschar *list = arg;
+const uschar *list = arg;
 uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size);
 verify_type_t * vp;
 
@@ -1783,34 +1603,38 @@ switch(vp->value)
   {
   case VERIFY_REV_HOST_LKUP:
     if (sender_host_address == NULL) return OK;
-    return acl_verify_reverse(user_msgptr, log_msgptr);
+    if ((rc = acl_verify_reverse(user_msgptr, log_msgptr)) == DEFER)
+      while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
+       if (strcmpic(ss, US"defer_ok") == 0)
+         return OK;
+    return rc;
 
   case VERIFY_CERT:
     /* TLS certificate verification is done at STARTTLS time; here we just
     test whether it was successful or not. (This is for optional verification; for
     mandatory verification, the connection doesn't last this long.) */
 
-      if (tls_in.certificate_verified) return OK;
-      *user_msgptr = US"no verified certificate";
-      return FAIL;
+    if (tls_in.certificate_verified) return OK;
+    *user_msgptr = US"no verified certificate";
+    return FAIL;
 
   case VERIFY_HELO:
     /* We can test the result of optional HELO verification that might have
     occurred earlier. If not, we can attempt the verification now. */
 
-      if (!helo_verified && !helo_verify_failed) smtp_verify_helo();
-      return helo_verified? OK : FAIL;
+    if (!helo_verified && !helo_verify_failed) smtp_verify_helo();
+    return helo_verified? OK : FAIL;
 
   case VERIFY_CSA:
     /* Do Client SMTP Authorization checks in a separate function, and turn the
     result code into user-friendly strings. */
 
-      rc = acl_verify_csa(list);
-      *log_msgptr = *user_msgptr = string_sprintf("client SMTP authorization %s",
+    rc = acl_verify_csa(list);
+    *log_msgptr = *user_msgptr = string_sprintf("client SMTP authorization %s",
                                               csa_reason_string[rc]);
-      csa_status = csa_status_string[rc];
-      DEBUG(D_acl) debug_printf("CSA result %s\n", csa_status);
-      return csa_return_code[rc];
+    csa_status = csa_status_string[rc];
+    DEBUG(D_acl) debug_printf_indent("CSA result %s\n", csa_status);
+    return csa_return_code[rc];
 
   case VERIFY_HDR_SYNTAX:
     /* Check that all relevant header lines have the correct syntax. If there is
@@ -1819,8 +1643,11 @@ switch(vp->value)
     always). */
 
     rc = verify_check_headers(log_msgptr);
-    if (rc != OK && smtp_return_error_details && *log_msgptr != NULL)
-      *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
+    if (rc != OK && *log_msgptr)
+      if (smtp_return_error_details)
+       *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
+      else
+       acl_verify_message = *log_msgptr;
     return rc;
 
   case VERIFY_HDR_NAMES_ASCII:
@@ -1911,12 +1738,13 @@ while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))
       while (isspace(*ss)) ss++;
       if (*ss++ == '=')
         {
+       const uschar * sublist = ss;
         int optsep = ',';
         uschar *opt;
         uschar buffer[256];
-        while (isspace(*ss)) ss++;
+        while (isspace(*sublist)) sublist++;
 
-        while ((opt = string_nextinlist(&ss, &optsep, buffer, sizeof(buffer)))
+        while ((opt = string_nextinlist(&sublist, &optsep, buffer, sizeof(buffer)))
               != NULL)
           {
          callout_opt_t * op;
@@ -2005,15 +1833,15 @@ message if giving out verification details. */
 if (verify_header_sender)
   {
   int verrno;
-  rc = verify_check_header_address(user_msgptr, log_msgptr, callout,
+
+  if ((rc = verify_check_header_address(user_msgptr, log_msgptr, callout,
     callout_overall, callout_connect, se_mailfrom, pm_mailfrom, verify_options,
-    &verrno);
-  if (rc != OK)
+    &verrno)) != OK)
     {
     *basic_errno = verrno;
     if (smtp_return_error_details)
       {
-      if (*user_msgptr == NULL && *log_msgptr != NULL)
+      if (!*user_msgptr && *log_msgptr)
         *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
       if (rc == DEFER) acl_temp_details = TRUE;
       }
@@ -2035,10 +1863,9 @@ Therefore, we always do a full sender verify when any kind of callout is
 specified. Caching elsewhere, for instance in the DNS resolver and in the
 callout handling, should ensure that this is not terribly inefficient. */
 
-else if (verify_sender_address != NULL)
+else if (verify_sender_address)
   {
-  if ((verify_options & (vopt_callout_recipsender|vopt_callout_recippmaster))
-       != 0)
+  if ((verify_options & (vopt_callout_recipsender|vopt_callout_recippmaster)))
     {
     *log_msgptr = US"use_sender or use_postmaster cannot be used for a "
       "sender verify callout";
@@ -2054,12 +1881,14 @@ else if (verify_sender_address != NULL)
     callout that was done previously). If the "routed" flag is not set, routing
     must have failed, so we use the saved return code. */
 
-    if (testflag(sender_vaddr, af_verify_routed)) rc = OK; else
+    if (testflag(sender_vaddr, af_verify_routed))
+      rc = OK;
+    else
       {
       rc = sender_vaddr->special_action;
       *basic_errno = sender_vaddr->basic_errno;
       }
-    HDEBUG(D_acl) debug_printf("using cached sender verify result\n");
+    HDEBUG(D_acl) debug_printf_indent("using cached sender verify result\n");
     }
 
   /* Do a new verification, and cache the result. The cache is used to avoid
@@ -2078,6 +1907,13 @@ else if (verify_sender_address != NULL)
     uschar *save_address_data = deliver_address_data;
 
     sender_vaddr = deliver_make_addr(verify_sender_address, TRUE);
+#ifdef SUPPORT_I18N
+    if ((sender_vaddr->prop.utf8_msg = message_smtputf8))
+      {
+      sender_vaddr->prop.utf8_downcvt =       message_utf8_downconvert == 1;
+      sender_vaddr->prop.utf8_downcvt_maybe = message_utf8_downconvert == -1;
+      }
+#endif
     if (no_details) setflag(sender_vaddr, af_sverify_told);
     if (verify_sender_address[0] != 0)
       {
@@ -2099,24 +1935,23 @@ else if (verify_sender_address != NULL)
       rc = verify_address(sender_vaddr, NULL, verify_options, callout,
         callout_overall, callout_connect, se_mailfrom, pm_mailfrom, &routed);
 
-      HDEBUG(D_acl) debug_printf("----------- end verify ------------\n");
+      HDEBUG(D_acl) debug_printf_indent("----------- end verify ------------\n");
 
-      if (rc == OK)
-        {
-        if (Ustrcmp(sender_vaddr->address, verify_sender_address) != 0)
-          {
-          DEBUG(D_acl) debug_printf("sender %s verified ok as %s\n",
-            verify_sender_address, sender_vaddr->address);
-          }
-        else
-          {
-          DEBUG(D_acl) debug_printf("sender %s verified ok\n",
-            verify_sender_address);
-          }
-        }
-      else *basic_errno = sender_vaddr->basic_errno;
+      if (rc != OK)
+        *basic_errno = sender_vaddr->basic_errno;
+      else
+       DEBUG(D_acl)
+         {
+         if (Ustrcmp(sender_vaddr->address, verify_sender_address) != 0)
+           debug_printf_indent("sender %s verified ok as %s\n",
+             verify_sender_address, sender_vaddr->address);
+         else
+           debug_printf_indent("sender %s verified ok\n",
+             verify_sender_address);
+         }
       }
-    else rc = OK;  /* Null sender */
+    else
+      rc = OK;  /* Null sender */
 
     /* Cache the result code */
 
@@ -2134,7 +1969,7 @@ else if (verify_sender_address != NULL)
 
   /* Put the sender address_data value into $sender_address_data */
 
-  sender_address_data = sender_vaddr->p.address_data;
+  sender_address_data = sender_vaddr->prop.address_data;
   }
 
 /* A recipient address just gets a straightforward verify; again we must handle
@@ -2153,7 +1988,7 @@ else
   addr2 = *addr;
   rc = verify_address(&addr2, NULL, verify_options|vopt_is_recipient, callout,
     callout_overall, callout_connect, se_mailfrom, pm_mailfrom, NULL);
-  HDEBUG(D_acl) debug_printf("----------- end verify ------------\n");
+  HDEBUG(D_acl) debug_printf_indent("----------- end verify ------------\n");
 
   *basic_errno = addr2.basic_errno;
   *log_msgptr = addr2.message;
@@ -2164,7 +1999,7 @@ else
   if (testflag((&addr2), af_pass_message)) acl_temp_details = TRUE;
 
   /* Make $address_data visible */
-  deliver_address_data = addr2.p.address_data;
+  deliver_address_data = addr2.prop.address_data;
   }
 
 /* We have a result from the relevant test. Handle defer overrides first. */
@@ -2172,7 +2007,7 @@ else
 if (rc == DEFER && (defer_ok ||
    (callout_defer_ok && *basic_errno == ERRNO_CALLOUTDEFER)))
   {
-  HDEBUG(D_acl) debug_printf("verify defer overridden by %s\n",
+  HDEBUG(D_acl) debug_printf_indent("verify defer overridden by %s\n",
     defer_ok? "defer_ok" : "callout_defer_ok");
   rc = OK;
   }
@@ -2183,13 +2018,9 @@ sender_verified_failed to the address item that actually failed. */
 if (rc != OK && verify_sender_address != NULL)
   {
   if (rc != DEFER)
-    {
     *log_msgptr = *user_msgptr = US"Sender verify failed";
-    }
   else if (*basic_errno != ERRNO_CALLOUTDEFER)
-    {
     *log_msgptr = *user_msgptr = US"Could not complete sender verify";
-    }
   else
     {
     *log_msgptr = US"Could not complete sender verify callout";
@@ -2239,28 +2070,22 @@ Returns:      CONTROL_xxx value
 */
 
 static int
-decode_control(uschar *arg, uschar **pptr, int where, uschar **log_msgptr)
+decode_control(const uschar *arg, const uschar **pptr, int where, uschar **log_msgptr)
 {
-int len;
-control_def *d;
+int idx, len;
+control_def * d;
 
-for (d = controls_list;
-     d < controls_list + sizeof(controls_list)/sizeof(control_def);
-     d++)
-  {
-  len = Ustrlen(d->name);
-  if (Ustrncmp(d->name, arg, len) == 0) break;
-  }
-
-if (d >= controls_list + sizeof(controls_list)/sizeof(control_def) ||
-   (arg[len] != 0 && (!d->has_option || arg[len] != '/')))
+if (  (idx = find_control(arg, controls_list, nelem(controls_list))) < 0
+   || (  arg[len = Ustrlen((d = controls_list+idx)->name)] != 0
+      && (!d->has_option || arg[len] != '/')
+   )  )
   {
   *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg);
   return CONTROL_ERROR;
   }
 
 *pptr = arg + len;
-return d->value;
+return idx;
 }
 
 
@@ -2324,7 +2149,7 @@ Returns:       OK        - Sender's rate is above limit
 */
 
 static int
-acl_ratelimit(uschar *arg, int where, uschar **log_msgptr)
+acl_ratelimit(const uschar *arg, int where, uschar **log_msgptr)
 {
 double limit, period, count;
 uschar *ss;
@@ -2352,14 +2177,13 @@ rate measurement as opposed to rate limiting. */
 
 sender_rate_limit = string_nextinlist(&arg, &sep, NULL, 0);
 if (sender_rate_limit == NULL)
-  limit = -1.0;
-else
-  {
-  limit = Ustrtod(sender_rate_limit, &ss);
-  if (tolower(*ss) == 'k') { limit *= 1024.0; ss++; }
-  else if (tolower(*ss) == 'm') { limit *= 1024.0*1024.0; ss++; }
-  else if (tolower(*ss) == 'g') { limit *= 1024.0*1024.0*1024.0; ss++; }
-  }
+  return ratelimit_error(log_msgptr, "sender rate limit not set");
+
+limit = Ustrtod(sender_rate_limit, &ss);
+if      (tolower(*ss) == 'k') { limit *= 1024.0; ss++; }
+else if (tolower(*ss) == 'm') { limit *= 1024.0*1024.0; ss++; }
+else if (tolower(*ss) == 'g') { limit *= 1024.0*1024.0*1024.0; ss++; }
+
 if (limit < 0.0 || *ss != '\0')
   return ratelimit_error(log_msgptr,
     "\"%s\" is not a positive number", sender_rate_limit);
@@ -2416,7 +2240,7 @@ while ((ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size))
   else if (strcmpic(ss, US"per_byte") == 0)
     {
     /* If we have not yet received the message data and there was no SIZE
-    declaration on the MAIL comand, then it's safe to just use a value of
+    declaration on the MAIL command, then it's safe to just use a value of
     zero and let the recorded rate decay as if nothing happened. */
     RATE_SET(mode, PER_MAIL);
     if (where > ACL_WHERE_NOTSMTP) badacl = TRUE;
@@ -2482,7 +2306,7 @@ key = string_sprintf("%s/%s/%s%s",
   key);
 
 HDEBUG(D_acl)
-  debug_printf("ratelimit condition count=%.0f %.1f/%s\n", count, limit, key);
+  debug_printf_indent("ratelimit condition count=%.0f %.1f/%s\n", count, limit, key);
 
 /* See if we have already computed the rate by looking in the relevant tree.
 For per-connection rate limiting, store tree nodes and dbdata in the permanent
@@ -2525,7 +2349,7 @@ if (t != NULL)
   store_pool = old_pool;
   sender_rate = string_sprintf("%.1f", dbd->rate);
   HDEBUG(D_acl)
-    debug_printf("ratelimit found pre-computed rate %s\n", sender_rate);
+    debug_printf_indent("ratelimit found pre-computed rate %s\n", sender_rate);
   return rc;
   }
 
@@ -2537,7 +2361,7 @@ if (dbm == NULL)
   {
   store_pool = old_pool;
   sender_rate = NULL;
-  HDEBUG(D_acl) debug_printf("ratelimit database not available\n");
+  HDEBUG(D_acl) debug_printf_indent("ratelimit database not available\n");
   *log_msgptr = US"ratelimit database not available";
   return DEFER;
   }
@@ -2549,7 +2373,7 @@ gettimeofday(&tv, NULL);
 if (dbdb != NULL)
   {
   /* Locate the basic ratelimit block inside the DB data. */
-  HDEBUG(D_acl) debug_printf("ratelimit found key in database\n");
+  HDEBUG(D_acl) debug_printf_indent("ratelimit found key in database\n");
   dbd = &dbdb->dbd;
 
   /* Forget the old Bloom filter if it is too old, so that we count each
@@ -2559,7 +2383,7 @@ if (dbdb != NULL)
 
   if(unique != NULL && tv.tv_sec > dbdb->bloom_epoch + period)
     {
-    HDEBUG(D_acl) debug_printf("ratelimit discarding old Bloom filter\n");
+    HDEBUG(D_acl) debug_printf_indent("ratelimit discarding old Bloom filter\n");
     dbdb = NULL;
     }
 
@@ -2567,7 +2391,7 @@ if (dbdb != NULL)
 
   if(unique != NULL && dbdb_size < sizeof(*dbdb))
     {
-    HDEBUG(D_acl) debug_printf("ratelimit discarding undersize Bloom filter\n");
+    HDEBUG(D_acl) debug_printf_indent("ratelimit discarding undersize Bloom filter\n");
     dbdb = NULL;
     }
   }
@@ -2580,14 +2404,14 @@ if (dbdb == NULL)
   if (unique == NULL)
     {
     /* No Bloom filter. This basic ratelimit block is initialized below. */
-    HDEBUG(D_acl) debug_printf("ratelimit creating new rate data block\n");
+    HDEBUG(D_acl) debug_printf_indent("ratelimit creating new rate data block\n");
     dbdb_size = sizeof(*dbd);
     dbdb = store_get(dbdb_size);
     }
   else
     {
     int extra;
-    HDEBUG(D_acl) debug_printf("ratelimit creating new Bloom filter\n");
+    HDEBUG(D_acl) debug_printf_indent("ratelimit creating new Bloom filter\n");
 
     /* See the long comment below for an explanation of the magic number 2.
     The filter has a minimum size in case the rate limit is very small;
@@ -2671,7 +2495,7 @@ if (unique != NULL && !readonly)
   /* Scan the bits corresponding to this event. A zero bit means we have
   not seen it before. Ensure all bits are set to record this event. */
 
-  HDEBUG(D_acl) debug_printf("ratelimit checking uniqueness of %s\n", unique);
+  HDEBUG(D_acl) debug_printf_indent("ratelimit checking uniqueness of %s\n", unique);
 
   seen = TRUE;
   for (n = 0; n < 8; n++, hash += hinc)
@@ -2689,11 +2513,11 @@ if (unique != NULL && !readonly)
 
   if (seen)
     {
-    HDEBUG(D_acl) debug_printf("ratelimit event found in Bloom filter\n");
+    HDEBUG(D_acl) debug_printf_indent("ratelimit event found in Bloom filter\n");
     count = 0.0;
     }
   else
-    HDEBUG(D_acl) debug_printf("ratelimit event added to Bloom filter\n");
+    HDEBUG(D_acl) debug_printf_indent("ratelimit event added to Bloom filter\n");
   }
 
 /* If there was no previous ratelimit data block for this key, initialize
@@ -2702,7 +2526,7 @@ is what would be computed by the code below for an infinite interval. */
 
 if (dbd == NULL)
   {
-  HDEBUG(D_acl) debug_printf("ratelimit initializing new key's rate data\n");
+  HDEBUG(D_acl) debug_printf_indent("ratelimit initializing new key's rate data\n");
   dbd = &dbdb->dbd;
   dbd->time_stamp = tv.tv_sec;
   dbd->time_usec = tv.tv_usec;
@@ -2782,7 +2606,7 @@ else
   size of the event per the period size, ignoring the lack of events outside
   the current period and regardless of where the event falls in the period. So,
   if the interval was so long that the calculated rate is unhelpfully small, we
-  re-intialize the rate. In the absence of higher-rate bursts, the condition
+  re-initialize the rate. In the absence of higher-rate bursts, the condition
   below is true if the interval is greater than the period. */
 
   if (dbd->rate < count) dbd->rate = count;
@@ -2803,11 +2627,11 @@ neither leaky nor strict are set, so we do not do any updates. */
 if ((rc == FAIL && leaky) || strict)
   {
   dbfn_write(dbm, key, dbdb, dbdb_size);
-  HDEBUG(D_acl) debug_printf("ratelimit db updated\n");
+  HDEBUG(D_acl) debug_printf_indent("ratelimit db updated\n");
   }
 else
   {
-  HDEBUG(D_acl) debug_printf("ratelimit db not updated: %s\n",
+  HDEBUG(D_acl) debug_printf_indent("ratelimit db not updated: %s\n",
     readonly? "readonly mode" : "over the limit, but leaky");
   }
 
@@ -2827,7 +2651,7 @@ store_pool = old_pool;
 sender_rate = string_sprintf("%.1f", dbd->rate);
 
 HDEBUG(D_acl)
-  debug_printf("ratelimit computed rate %s\n", sender_rate);
+  debug_printf_indent("ratelimit computed rate %s\n", sender_rate);
 
 return rc;
 }
@@ -2850,7 +2674,7 @@ Returns:       OK        - Completed.
 */
 
 static int
-acl_udpsend(uschar *arg, uschar **log_msgptr)
+acl_udpsend(const uschar *arg, uschar **log_msgptr)
 {
 int sep = 0;
 uschar *hostname;
@@ -2865,17 +2689,17 @@ uschar * errstr;
 hostname = string_nextinlist(&arg, &sep, NULL, 0);
 portstr = string_nextinlist(&arg, &sep, NULL, 0);
 
-if (hostname == NULL)
+if (!hostname)
   {
   *log_msgptr = US"missing destination host in \"udpsend\" modifier";
   return ERROR;
   }
-if (portstr == NULL)
+if (!portstr)
   {
   *log_msgptr = US"missing destination port in \"udpsend\" modifier";
   return ERROR;
   }
-if (arg == NULL)
+if (!arg)
   {
   *log_msgptr = US"missing datagram payload in \"udpsend\" modifier";
   return ERROR;
@@ -2905,7 +2729,7 @@ if (r == HOST_FIND_FAILED || r == HOST_FIND_AGAIN)
   }
 
 HDEBUG(D_acl)
-  debug_printf("udpsend [%s]:%d %s\n", h->address, portnum, arg);
+  debug_printf_indent("udpsend [%s]:%d %s\n", h->address, portnum, arg);
 
 r = s = ip_connectedsocket(SOCK_DGRAM, h->address, portnum, portnum,
                1, NULL, &errstr);
@@ -2927,7 +2751,7 @@ if (r < len)
   }
 
 HDEBUG(D_acl)
-  debug_printf("udpsend %d bytes\n", r);
+  debug_printf_indent("udpsend %d bytes\n", r);
 
 return OK;
 
@@ -2974,17 +2798,14 @@ acl_check_condition(int verb, acl_condition_block *cb, int where,
 {
 uschar *user_message = NULL;
 uschar *log_message = NULL;
-uschar *debug_tag = NULL;
-uschar *debug_opts = NULL;
-uschar *p = NULL;
 int rc = OK;
 #ifdef WITH_CONTENT_SCAN
-int sep = '/';
+int sep = -'/';
 #endif
 
 for (; cb != NULL; cb = cb->next)
   {
-  uschar *arg;
+  const uschar *arg;
   int control_type;
 
   /* The message and log_message items set up messages to be used in
@@ -2992,14 +2813,14 @@ for (; cb != NULL; cb = cb->next)
 
   if (cb->type == ACLC_MESSAGE)
     {
-    HDEBUG(D_acl) debug_printf("  message: %s\n", cb->arg);
+    HDEBUG(D_acl) debug_printf_indent("  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);
+    HDEBUG(D_acl) debug_printf_indent("l_message: %s\n", cb->arg);
     log_message = cb->arg;
     continue;
     }
@@ -3017,27 +2838,24 @@ for (; cb != NULL; cb = cb->next)
   of them, but not for all, because expansion happens down in some lower level
   checking functions in some cases. */
 
-  if (cond_expand_at_top[cb->type])
+  if (!conditions[cb->type].expand_at_top)
+    arg = cb->arg;
+  else if (!(arg = expand_string(cb->arg)))
     {
-    arg = expand_string(cb->arg);
-    if (arg == NULL)
-      {
-      if (expand_string_forcedfail) continue;
-      *log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s",
-        cb->arg, expand_string_message);
-      return search_find_defer? DEFER : ERROR;
-      }
+    if (expand_string_forcedfail) continue;
+    *log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s",
+      cb->arg, expand_string_message);
+    return search_find_defer ? DEFER : ERROR;
     }
-  else arg = cb->arg;
 
   /* Show condition, and expanded condition if it's different */
 
   HDEBUG(D_acl)
     {
     int lhswidth = 0;
-    debug_printf("check %s%s %n",
-      (!cond_modifiers[cb->type] && cb->u.negated)? "!":"",
-      conditions[cb->type], &lhswidth);
+    debug_printf_indent("check %s%s %n",
+      (!conditions[cb->type].is_modifier && cb->u.negated)? "!":"",
+      conditions[cb->type].name, &lhswidth);
 
     if (cb->type == ACLC_SET)
       {
@@ -3054,11 +2872,11 @@ for (; cb != NULL; cb = cb->next)
 
   /* Check that this condition makes sense at this time */
 
-  if ((cond_forbids[cb->type] & (1 << where)) != 0)
+  if ((conditions[cb->type].forbids & (1 << where)) != 0)
     {
     *log_msgptr = string_sprintf("cannot %s %s condition in %s ACL",
-      cond_modifiers[cb->type]? "use" : "test",
-      conditions[cb->type], acl_wherenames[where]);
+      conditions[cb->type].is_modifier ? "use" : "test",
+      conditions[cb->type].name, acl_wherenames[where]);
     return ERROR;
     }
 
@@ -3075,7 +2893,7 @@ for (; cb != NULL; cb = cb->next)
     "discard" verb. */
 
     case ACLC_ACL:
-      rc = acl_check_wargs(where, addr, arg, level+1, user_msgptr, log_msgptr);
+      rc = acl_check_wargs(where, addr, arg, user_msgptr, log_msgptr);
       if (rc == DISCARD && verb != ACL_ACCEPT && verb != ACL_DISCARD)
         {
         *log_msgptr = string_sprintf("nested ACL returned \"discard\" for "
@@ -3123,267 +2941,350 @@ for (; cb != NULL; cb = cb->next)
     break;
 
     case ACLC_CONTROL:
-    control_type = decode_control(arg, &p, where, log_msgptr);
+      {
+      const uschar *p = NULL;
+      control_type = decode_control(arg, &p, where, log_msgptr);
 
-    /* Check if this control makes sense at this time */
+      /* Check if this control makes sense at this time */
 
-    if ((control_forbids[control_type] & (1 << where)) != 0)
-      {
-      *log_msgptr = string_sprintf("cannot use \"control=%s\" in %s ACL",
-        controls[control_type], acl_wherenames[where]);
-      return ERROR;
-      }
+      if (controls_list[control_type].forbids & (1 << where))
+       {
+       *log_msgptr = string_sprintf("cannot use \"control=%s\" in %s ACL",
+         controls_list[control_type].name, acl_wherenames[where]);
+       return ERROR;
+       }
 
-    switch(control_type)
-      {
-      case CONTROL_AUTH_UNADVERTISED:
-      allow_auth_unadvertised = TRUE;
-      break;
+      switch(control_type)
+       {
+       case CONTROL_AUTH_UNADVERTISED:
+       allow_auth_unadvertised = TRUE;
+       break;
 
-      #ifdef EXPERIMENTAL_BRIGHTMAIL
-      case CONTROL_BMI_RUN:
-      bmi_run = 1;
-      break;
-      #endif
-
-      #ifndef DISABLE_DKIM
-      case CONTROL_DKIM_VERIFY:
-      dkim_disable_verify = TRUE;
-      #ifdef EXPERIMENTAL_DMARC
-      /* Since DKIM was blocked, skip DMARC too */
-      dmarc_disable_verify = TRUE;
-      dmarc_enable_forensic = FALSE;
-      #endif
-      break;
-      #endif
+       #ifdef EXPERIMENTAL_BRIGHTMAIL
+       case CONTROL_BMI_RUN:
+       bmi_run = 1;
+       break;
+       #endif
+
+       #ifndef DISABLE_DKIM
+       case CONTROL_DKIM_VERIFY:
+       dkim_disable_verify = TRUE;
+       #ifdef EXPERIMENTAL_DMARC
+       /* Since DKIM was blocked, skip DMARC too */
+       dmarc_disable_verify = TRUE;
+       dmarc_enable_forensic = FALSE;
+       #endif
+       break;
+       #endif
 
-      #ifdef EXPERIMENTAL_DMARC
-      case CONTROL_DMARC_VERIFY:
-      dmarc_disable_verify = TRUE;
-      break;
+       #ifdef EXPERIMENTAL_DMARC
+       case CONTROL_DMARC_VERIFY:
+       dmarc_disable_verify = TRUE;
+       break;
 
-      case CONTROL_DMARC_FORENSIC:
-      dmarc_enable_forensic = TRUE;
-      break;
-      #endif
+       case CONTROL_DMARC_FORENSIC:
+       dmarc_enable_forensic = TRUE;
+       break;
+       #endif
+
+       case CONTROL_DSCP:
+       if (*p == '/')
+         {
+         int fd, af, level, optname, value;
+         /* If we are acting on stdin, the setsockopt may fail if stdin is not
+         a socket; we can accept that, we'll just debug-log failures anyway. */
+         fd = fileno(smtp_in);
+         af = ip_get_address_family(fd);
+         if (af < 0)
+           {
+           HDEBUG(D_acl)
+             debug_printf_indent("smtp input is probably not a socket [%s], not setting DSCP\n",
+                 strerror(errno));
+           break;
+           }
+         if (dscp_lookup(p+1, af, &level, &optname, &value))
+           {
+           if (setsockopt(fd, level, optname, &value, sizeof(value)) < 0)
+             {
+             HDEBUG(D_acl) debug_printf_indent("failed to set input DSCP[%s]: %s\n",
+                 p+1, strerror(errno));
+             }
+           else
+             {
+             HDEBUG(D_acl) debug_printf_indent("set input DSCP to \"%s\"\n", p+1);
+             }
+           }
+         else
+           {
+           *log_msgptr = string_sprintf("unrecognised DSCP value in \"control=%s\"", arg);
+           return ERROR;
+           }
+         }
+       else
+         {
+         *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg);
+         return ERROR;
+         }
+       break;
 
-      case CONTROL_DSCP:
-      if (*p == '/')
-        {
-        int fd, af, level, optname, value;
-        /* If we are acting on stdin, the setsockopt may fail if stdin is not
-        a socket; we can accept that, we'll just debug-log failures anyway. */
-        fd = fileno(smtp_in);
-        af = ip_get_address_family(fd);
-        if (af < 0)
-          {
-          HDEBUG(D_acl)
-            debug_printf("smtp input is probably not a socket [%s], not setting DSCP\n",
-                strerror(errno));
-          break;
-          }
-        if (dscp_lookup(p+1, af, &level, &optname, &value))
-          {
-          if (setsockopt(fd, level, optname, &value, sizeof(value)) < 0)
-            {
-            HDEBUG(D_acl) debug_printf("failed to set input DSCP[%s]: %s\n",
-                p+1, strerror(errno));
-            }
-          else
-            {
-            HDEBUG(D_acl) debug_printf("set input DSCP to \"%s\"\n", p+1);
-            }
-          }
-        else
-          {
-          *log_msgptr = string_sprintf("unrecognised DSCP value in \"control=%s\"", arg);
-          return ERROR;
-          }
-        }
-      else
-        {
-        *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg);
-        return ERROR;
-        }
-      break;
+       case CONTROL_ERROR:
+       return ERROR;
 
-      case CONTROL_ERROR:
-      return ERROR;
+       case CONTROL_CASEFUL_LOCAL_PART:
+       deliver_localpart = addr->cc_local_part;
+       break;
 
-      case CONTROL_CASEFUL_LOCAL_PART:
-      deliver_localpart = addr->cc_local_part;
-      break;
+       case CONTROL_CASELOWER_LOCAL_PART:
+       deliver_localpart = addr->lc_local_part;
+       break;
 
-      case CONTROL_CASELOWER_LOCAL_PART:
-      deliver_localpart = addr->lc_local_part;
-      break;
+       case CONTROL_ENFORCE_SYNC:
+       smtp_enforce_sync = TRUE;
+       break;
 
-      case CONTROL_ENFORCE_SYNC:
-      smtp_enforce_sync = TRUE;
-      break;
+       case CONTROL_NO_ENFORCE_SYNC:
+       smtp_enforce_sync = FALSE;
+       break;
 
-      case CONTROL_NO_ENFORCE_SYNC:
-      smtp_enforce_sync = FALSE;
-      break;
+       #ifdef WITH_CONTENT_SCAN
+       case CONTROL_NO_MBOX_UNSPOOL:
+       no_mbox_unspool = TRUE;
+       break;
+       #endif
 
-      #ifdef WITH_CONTENT_SCAN
-      case CONTROL_NO_MBOX_UNSPOOL:
-      no_mbox_unspool = TRUE;
-      break;
-      #endif
+       case CONTROL_NO_MULTILINE:
+       no_multiline_responses = TRUE;
+       break;
 
-      case CONTROL_NO_MULTILINE:
-      no_multiline_responses = TRUE;
-      break;
+       case CONTROL_NO_PIPELINING:
+       pipelining_enable = FALSE;
+       break;
 
-      case CONTROL_NO_PIPELINING:
-      pipelining_enable = FALSE;
-      break;
+       case CONTROL_NO_DELAY_FLUSH:
+       disable_delay_flush = TRUE;
+       break;
 
-      case CONTROL_NO_DELAY_FLUSH:
-      disable_delay_flush = TRUE;
-      break;
+       case CONTROL_NO_CALLOUT_FLUSH:
+       disable_callout_flush = TRUE;
+       break;
 
-      case CONTROL_NO_CALLOUT_FLUSH:
-      disable_callout_flush = TRUE;
-      break;
+       case CONTROL_FAKEREJECT:
+       cancel_cutthrough_connection("fakereject");
+       case CONTROL_FAKEDEFER:
+       fake_response = (control_type == CONTROL_FAKEDEFER) ? DEFER : FAIL;
+       if (*p == '/')
+         {
+         const uschar *pp = p + 1;
+         while (*pp != 0) pp++;
+         fake_response_text = expand_string(string_copyn(p+1, pp-p-1));
+         p = pp;
+         }
+        else
+         {
+         /* Explicitly reset to default string */
+         fake_response_text = US"Your message has been rejected but is being kept for evaluation.\nIf it was a legitimate message, it may still be delivered to the target recipient(s).";
+         }
+       break;
 
-      case CONTROL_FAKEREJECT:
-      cancel_cutthrough_connection("fakereject");
-      case CONTROL_FAKEDEFER:
-      fake_response = (control_type == CONTROL_FAKEDEFER) ? DEFER : FAIL;
-      if (*p == '/')
-        {
-        uschar *pp = p + 1;
-        while (*pp != 0) pp++;
-        fake_response_text = expand_string(string_copyn(p+1, pp-p-1));
-        p = pp;
-        }
-       else
-        {
-        /* Explicitly reset to default string */
-        fake_response_text = US"Your message has been rejected but is being kept for evaluation.\nIf it was a legitimate message, it may still be delivered to the target recipient(s).";
-        }
-      break;
+       case CONTROL_FREEZE:
+       deliver_freeze = TRUE;
+       deliver_frozen_at = time(NULL);
+       freeze_tell = freeze_tell_config;       /* Reset to configured value */
+       if (Ustrncmp(p, "/no_tell", 8) == 0)
+         {
+         p += 8;
+         freeze_tell = NULL;
+         }
+       if (*p != 0)
+         {
+         *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg);
+         return ERROR;
+         }
+       cancel_cutthrough_connection("item frozen");
+       break;
 
-      case CONTROL_FREEZE:
-      deliver_freeze = TRUE;
-      deliver_frozen_at = time(NULL);
-      freeze_tell = freeze_tell_config;       /* Reset to configured value */
-      if (Ustrncmp(p, "/no_tell", 8) == 0)
-        {
-        p += 8;
-        freeze_tell = NULL;
-        }
-      if (*p != 0)
-        {
-        *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_QUEUE_ONLY:
-      queue_only_policy = TRUE;
-      cancel_cutthrough_connection("queueing forced");
-      break;
+       case CONTROL_SUBMISSION:
+       originator_name = US"";
+       submission_mode = TRUE;
+       while (*p == '/')
+         {
+         if (Ustrncmp(p, "/sender_retain", 14) == 0)
+           {
+           p += 14;
+           active_local_sender_retain = TRUE;
+           active_local_from_check = FALSE;
+           }
+         else if (Ustrncmp(p, "/domain=", 8) == 0)
+           {
+           const uschar *pp = p + 8;
+           while (*pp != 0 && *pp != '/') pp++;
+           submission_domain = string_copyn(p+8, pp-p-8);
+           p = pp;
+           }
+         /* The name= option must be last, because it swallows the rest of
+         the string. */
+         else if (Ustrncmp(p, "/name=", 6) == 0)
+           {
+           const uschar *pp = p + 6;
+           while (*pp != 0) pp++;
+           submission_name = string_copy(parse_fix_phrase(p+6, pp-p-6,
+             big_buffer, big_buffer_size));
+           p = pp;
+           }
+         else break;
+         }
+       if (*p != 0)
+         {
+         *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg);
+         return ERROR;
+         }
+       break;
 
-      case CONTROL_SUBMISSION:
-      originator_name = US"";
-      submission_mode = TRUE;
-      while (*p == '/')
-        {
-        if (Ustrncmp(p, "/sender_retain", 14) == 0)
-          {
-          p += 14;
-          active_local_sender_retain = TRUE;
-          active_local_from_check = FALSE;
-          }
-        else if (Ustrncmp(p, "/domain=", 8) == 0)
-          {
-          uschar *pp = p + 8;
-          while (*pp != 0 && *pp != '/') pp++;
-          submission_domain = string_copyn(p+8, pp-p-8);
-          p = pp;
-          }
-        /* The name= option must be last, because it swallows the rest of
-        the string. */
-        else if (Ustrncmp(p, "/name=", 6) == 0)
-          {
-          uschar *pp = p + 6;
-          while (*pp != 0) pp++;
-          submission_name = string_copy(parse_fix_phrase(p+6, pp-p-6,
-            big_buffer, big_buffer_size));
-          p = pp;
-          }
-        else break;
-        }
-      if (*p != 0)
-        {
-        *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg);
-        return ERROR;
-        }
-      break;
+       case CONTROL_DEBUG:
+         {
+         uschar * debug_tag = NULL;
+         uschar * debug_opts = NULL;
+         BOOL kill = FALSE;
 
-      case CONTROL_DEBUG:
-      while (*p == '/')
-        {
-        if (Ustrncmp(p, "/tag=", 5) == 0)
-          {
-          uschar *pp = p + 5;
-          while (*pp != '\0' && *pp != '/') pp++;
-          debug_tag = string_copyn(p+5, pp-p-5);
-          p = pp;
-          }
-        else if (Ustrncmp(p, "/opts=", 6) == 0)
-          {
-          uschar *pp = p + 6;
-          while (*pp != '\0' && *pp != '/') pp++;
-          debug_opts = string_copyn(p+6, pp-p-6);
-          p = pp;
-          }
-        }
-        debug_logging_activate(debug_tag, debug_opts);
-      break;
+         while (*p == '/')
+           {
+           const uschar * pp = p+1;
+           if (Ustrncmp(pp, "tag=", 4) == 0)
+             {
+             for (pp += 4; *pp && *pp != '/';) pp++;
+             debug_tag = string_copyn(p+5, pp-p-5);
+             }
+           else if (Ustrncmp(pp, "opts=", 5) == 0)
+             {
+             for (pp += 5; *pp && *pp != '/';) pp++;
+             debug_opts = string_copyn(p+6, pp-p-6);
+             }
+           else if (Ustrncmp(pp, "kill", 4) == 0)
+             {
+             for (pp += 4; *pp && *pp != '/';) pp++;
+             kill = TRUE;
+             }
+           else
+             while (*pp && *pp != '/') pp++;
+           p = pp;
+           }
 
-      case CONTROL_SUPPRESS_LOCAL_FIXUPS:
-      suppress_local_fixups = TRUE;
-      break;
+           if (kill)
+             debug_logging_stop();
+           else
+             debug_logging_activate(debug_tag, debug_opts);
+         }
+       break;
 
-      case CONTROL_CUTTHROUGH_DELIVERY:
-      if (deliver_freeze)
-        *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;
+       case CONTROL_SUPPRESS_LOCAL_FIXUPS:
+       suppress_local_fixups = TRUE;
        break;
+
+       case CONTROL_CUTTHROUGH_DELIVERY:
+#ifndef DISABLE_PRDR
+       if (prdr_requested)
+#else
+       if (0)
+#endif
+         /* Too hard to think about for now.  We might in future cutthrough
+         the case where both sides handle prdr and this-node prdr acl
+         is "accept" */
+         *log_msgptr = string_sprintf("PRDR on %s reception\n", arg);
+       else
+         {
+         if (deliver_freeze)
+           *log_msgptr = US"frozen";
+         else if (queue_only_policy)
+           *log_msgptr = US"queue-only";
+         else if (fake_response == FAIL)
+           *log_msgptr = US"fakereject";
+         else
+           {
+           if (rcpt_count == 1)
+             {
+             cutthrough.delivery = TRUE;
+             while (*p == '/')
+               {
+               const uschar * pp = p+1;
+               if (Ustrncmp(pp, "defer=", 6) == 0)
+                 {
+                 pp += 6;
+                 if (Ustrncmp(pp, "pass", 4) == 0) cutthrough.defer_pass = TRUE;
+                 /* else if (Ustrncmp(pp, "spool") == 0) ;     default */
+                 }
+               else
+                 while (*pp && *pp != '/') pp++;
+               p = pp;
+               }
+             }
+           break;
+           }
+         *log_msgptr = string_sprintf("\"control=%s\" on %s item",
+                                       arg, *log_msgptr);
+         }
+       return ERROR;
+
+#ifdef SUPPORT_I18N
+       case CONTROL_UTF8_DOWNCONVERT:
+       if (*p == '/')
+         {
+         if (p[1] == '1')
+           {
+           message_utf8_downconvert = 1;
+           addr->prop.utf8_downcvt = TRUE;
+           addr->prop.utf8_downcvt_maybe = FALSE;
+           p += 2;
+           break;
+           }
+         if (p[1] == '0')
+           {
+           message_utf8_downconvert = 0;
+           addr->prop.utf8_downcvt = FALSE;
+           addr->prop.utf8_downcvt_maybe = FALSE;
+           p += 2;
+           break;
+           }
+         if (p[1] == '-' && p[2] == '1')
+           {
+           message_utf8_downconvert = -1;
+           addr->prop.utf8_downcvt = FALSE;
+           addr->prop.utf8_downcvt_maybe = TRUE;
+           p += 3;
+           break;
+           }
+         *log_msgptr = US"bad option value for control=utf8_downconvert";
+         }
+       else
+         {
+         message_utf8_downconvert = 1;
+         addr->prop.utf8_downcvt = TRUE;
+         addr->prop.utf8_downcvt_maybe = FALSE;
+         break;
+         }
+       return ERROR;
+#endif
+
        }
-      *log_msgptr = string_sprintf("\"control=%s\" on %s item",
-                                   arg, *log_msgptr);
-      return ERROR;
+      break;
       }
-    break;
 
     #ifdef EXPERIMENTAL_DCC
     case ACLC_DCC:
       {
-      /* Seperate the regular expression and any optional parameters. */
-      uschar *ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size);
+      /* Separate the regular expression and any optional parameters. */
+      const uschar * list = arg;
+      uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size);
       /* Run the dcc backend. */
       rc = dcc_process(&ss);
-      /* Modify return code based upon the existance of options. */
-      while ((ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size))
-            != NULL) {
+      /* Modify return code based upon the existence of options. */
+      while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
         if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER)
-          {
-          /* FAIL so that the message is passed to the next ACL */
-          rc = FAIL;
-          }
-        }
+          rc = FAIL;   /* FAIL so that the message is passed to the next ACL */
       }
     break;
     #endif
@@ -3405,14 +3306,42 @@ for (; cb != NULL; cb = cb->next)
         }
       else
         {
-        HDEBUG(D_acl) debug_printf("delay modifier requests %d-second delay\n",
+        HDEBUG(D_acl) debug_printf_indent("delay modifier requests %d-second delay\n",
           delay);
         if (host_checking)
           {
           HDEBUG(D_acl)
-            debug_printf("delay skipped in -bh checking mode\n");
+            debug_printf_indent("delay skipped in -bh checking mode\n");
           }
 
+       /* NOTE 1: Remember that we may be
+        dealing with stdin/stdout here, in addition to TCP/IP connections.
+        Also, delays may be specified for non-SMTP input, where smtp_out and
+        smtp_in will be NULL. Whatever is done must work in all cases.
+
+        NOTE 2: The added feature of flushing the output before a delay must
+        apply only to SMTP input. Hence the test for smtp_out being non-NULL.
+        */
+
+        else
+          {
+          if (smtp_out != NULL && !disable_delay_flush)
+           mac_smtp_fflush();
+
+#if !defined(NO_POLL_H) && defined (POLLRDHUP)
+           {
+           struct pollfd p;
+           nfds_t n = 0;
+           if (smtp_out)
+             {
+             p.fd = fileno(smtp_out);
+             p.events = POLLRDHUP;
+             n = 1;
+             }
+           if (poll(&p, n, delay*1000) > 0)
+             HDEBUG(D_acl) debug_printf_indent("delay cancelled by peer close\n");
+           }
+#else
         /* It appears to be impossible to detect that a TCP/IP connection has
         gone away without reading from it. This means that we cannot shorten
         the delay below if the client goes away, because we cannot discover
@@ -3422,38 +3351,22 @@ for (; cb != NULL; cb = cb->next)
         Exim process is not held up unnecessarily. However, it seems that we
         can't. The poll() function does not do the right thing, and in any case
         it is not always available.
-
-        NOTE 1: If ever this state of affairs changes, remember that we may be
-        dealing with stdin/stdout here, in addition to TCP/IP connections.
-        Also, delays may be specified for non-SMTP input, where smtp_out and
-        smtp_in will be NULL. Whatever is done must work in all cases.
-
-        NOTE 2: The added feature of flushing the output before a delay must
-        apply only to SMTP input. Hence the test for smtp_out being non-NULL.
         */
 
-        else
-          {
-          if (smtp_out != NULL && !disable_delay_flush) mac_smtp_fflush();
           while (delay > 0) delay = sleep(delay);
+#endif
           }
         }
       }
     break;
 
-    #ifdef WITH_OLD_DEMIME
-    case ACLC_DEMIME:
-      rc = demime(&arg);
-    break;
-    #endif
-
     #ifndef DISABLE_DKIM
     case ACLC_DKIM_SIGNER:
     if (dkim_cur_signer != NULL)
       rc = match_isinlist(dkim_cur_signer,
                           &arg,0,NULL,NULL,MCL_STRING,TRUE,NULL);
     else
-       rc = FAIL;
+      rc = FAIL;
     break;
 
     case ACLC_DKIM_STATUS:
@@ -3475,12 +3388,12 @@ for (; cb != NULL; cb = cb->next)
     #endif
 
     case ACLC_DNSLISTS:
-    rc = verify_check_dnsbl(&arg);
+    rc = verify_check_dnsbl(where, &arg, log_msgptr);
     break;
 
     case ACLC_DOMAINS:
     rc = match_isinlist(addr->domain, &arg, 0, &domainlist_anchor,
-      addr->domain_cache, MCL_DOMAIN, TRUE, &deliver_domain_data);
+      addr->domain_cache, MCL_DOMAIN, TRUE, CUSS &deliver_domain_data);
     break;
 
     /* The value in tls_cipher is the full cipher name, for example,
@@ -3513,24 +3426,25 @@ for (; cb != NULL; cb = cb->next)
 
     case ACLC_HOSTS:
     rc = verify_check_this_host(&arg, sender_host_cache, NULL,
-      (sender_host_address == NULL)? US"" : sender_host_address, &host_data);
-    if (host_data != NULL) host_data = string_copy_malloc(host_data);
+      (sender_host_address == NULL)? US"" : sender_host_address,
+      CUSS &host_data);
+    if (rc == DEFER) *log_msgptr = search_error_message;
+    if (host_data) host_data = string_copy_malloc(host_data);
     break;
 
     case ACLC_LOCAL_PARTS:
     rc = match_isinlist(addr->cc_local_part, &arg, 0,
       &localpartlist_anchor, addr->localpart_cache, MCL_LOCALPART, TRUE,
-      &deliver_localpart_data);
+      CUSS &deliver_localpart_data);
     break;
 
     case ACLC_LOG_REJECT_TARGET:
       {
       int logbits = 0;
       int sep = 0;
-      uschar *s = arg;
+      const uschar *s = arg;
       uschar *ss;
-      while ((ss = string_nextinlist(&s, &sep, big_buffer, big_buffer_size))
-              != NULL)
+      while ((ss = string_nextinlist(&s, &sep, big_buffer, big_buffer_size)))
         {
         if (Ustrcmp(ss, "main") == 0) logbits |= LOG_MAIN;
         else if (Ustrcmp(ss, "panic") == 0) logbits |= LOG_PANIC;
@@ -3549,7 +3463,7 @@ for (; cb != NULL; cb = cb->next)
     case ACLC_LOGWRITE:
       {
       int logbits = 0;
-      uschar *s = arg;
+      const uschar *s = arg;
       if (*s == ':')
         {
         s++;
@@ -3580,21 +3494,29 @@ for (; cb != NULL; cb = cb->next)
     break;
 
     #ifdef WITH_CONTENT_SCAN
-    case ACLC_MALWARE:
+    case ACLC_MALWARE:                 /* Run the malware backend. */
       {
       /* Separate the regular expression and any optional parameters. */
-      uschar *ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size);
-      /* Run the malware backend. */
-      rc = malware(&ss);
-      /* Modify return code based upon the existance of options. */
-      while ((ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size))
-            != NULL) {
-        if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER)
-          {
-          /* FAIL so that the message is passed to the next ACL */
-          rc = FAIL;
-          }
-        }
+      const uschar * list = arg;
+      uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size);
+      uschar *opt;
+      BOOL defer_ok = FALSE;
+      int timeout = 0;
+
+      while ((opt = string_nextinlist(&list, &sep, NULL, 0)))
+        if (strcmpic(opt, US"defer_ok") == 0)
+         defer_ok = TRUE;
+       else if (  strncmpic(opt, US"tmo=", 4) == 0
+               && (timeout = readconf_readtime(opt+4, '\0', FALSE)) < 0
+               )
+         {
+         *log_msgptr = string_sprintf("bad timeout value in '%s'", opt);
+         return ERROR;
+         }
+
+      rc = malware(ss, timeout);
+      if (rc == DEFER && defer_ok)
+       rc = FAIL;      /* FAIL so that the message is passed to the next ACL */
       }
     break;
 
@@ -3603,13 +3525,17 @@ for (; cb != NULL; cb = cb->next)
     break;
     #endif
 
+    case ACLC_QUEUE:
+    queue_name = string_copy_malloc(arg);
+    break;
+
     case ACLC_RATELIMIT:
     rc = acl_ratelimit(arg, where, log_msgptr);
     break;
 
     case ACLC_RECIPIENTS:
-    rc = match_address_list(addr->address, TRUE, TRUE, &arg, NULL, -1, 0,
-      &recipient_data);
+    rc = match_address_list((const uschar *)addr->address, TRUE, TRUE, &arg, NULL, -1, 0,
+      CUSS &recipient_data);
     break;
 
     #ifdef WITH_CONTENT_SCAN
@@ -3633,8 +3559,8 @@ for (; cb != NULL; cb = cb->next)
     break;
 
     case ACLC_SENDERS:
-    rc = match_address_list(sender_address, TRUE, TRUE, &arg,
-      sender_address_cache, -1, 0, &sender_data);
+    rc = match_address_list((const uschar *)sender_address, TRUE, TRUE, &arg,
+      sender_address_cache, -1, 0, CUSS &sender_data);
     break;
 
     /* Connection variables must persist forever */
@@ -3642,7 +3568,12 @@ for (; cb != NULL; cb = cb->next)
     case ACLC_SET:
       {
       int old_pool = store_pool;
-      if (cb->u.varname[0] == 'c') store_pool = POOL_PERM;
+      if (  cb->u.varname[0] == 'c'
+#ifndef DISABLE_EVENT
+        || event_name          /* An event is being delivered */
+#endif
+        )
+        store_pool = POOL_PERM;
       acl_var_create(cb->u.varname)->data.ptr = string_copy(arg);
       store_pool = old_pool;
       }
@@ -3651,12 +3582,13 @@ for (; cb != NULL; cb = cb->next)
     #ifdef WITH_CONTENT_SCAN
     case ACLC_SPAM:
       {
-      /* Seperate the regular expression and any optional parameters. */
-      uschar *ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size);
+      /* Separate the regular expression and any optional parameters. */
+      const uschar * list = arg;
+      uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size);
       /* Run the spam backend. */
-      rc = spam(&ss);
-      /* Modify return code based upon the existance of options. */
-      while ((ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size))
+      rc = spam(CUSS &ss);
+      /* Modify return code based upon the existence of options. */
+      while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))
             != NULL) {
         if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER)
           {
@@ -3689,7 +3621,8 @@ for (; cb != NULL; cb = cb->next)
 
     case ACLC_VERIFY:
     rc = acl_verify(where, addr, arg, user_msgptr, log_msgptr, basic_errno);
-    acl_verify_message = *user_msgptr;
+    if (*user_msgptr)
+      acl_verify_message = *user_msgptr;
     if (verb == ACL_WARN) *user_msgptr = NULL;
     break;
 
@@ -3701,11 +3634,9 @@ for (; cb != NULL; cb = cb->next)
 
   /* If a condition was negated, invert OK/FAIL. */
 
-  if (!cond_modifiers[cb->type] && cb->u.negated)
-    {
+  if (!conditions[cb->type].is_modifier && cb->u.negated)
     if (rc == OK) rc = FAIL;
-      else if (rc == FAIL || rc == FAIL_DROP) rc = OK;
-    }
+    else if (rc == FAIL || rc == FAIL_DROP) rc = OK;
 
   if (rc != OK) break;   /* Conditions loop */
   }
@@ -3911,7 +3842,6 @@ Arguments:
   where        where called from
   addr         address item when called from RCPT; otherwise NULL
   s            the input string; NULL is the same as an empty ACL => DENY
-  level        the nesting level
   user_msgptr  where to put a user error (for SMTP response)
   log_msgptr   where to put a logging message (not for SMTP response)
 
@@ -3924,7 +3854,7 @@ Returns:       OK         access is granted
 */
 
 static int
-acl_check_internal(int where, address_item *addr, uschar *s, int level,
+acl_check_internal(int where, address_item *addr, uschar *s,
   uschar **user_msgptr, uschar **log_msgptr)
 {
 int fd = -1;
@@ -3934,25 +3864,24 @@ uschar *ss;
 
 /* Catch configuration loops */
 
-if (level > 20)
+if (acl_level > 20)
   {
   *log_msgptr = US"ACL nested too deep: possible loop";
   return ERROR;
   }
 
-if (s == NULL)
+if (!s)
   {
-  HDEBUG(D_acl) debug_printf("ACL is NULL: implicit DENY\n");
+  HDEBUG(D_acl) debug_printf_indent("ACL is NULL: implicit DENY\n");
   return FAIL;
   }
 
 /* At top level, we expand the incoming string. At lower levels, it has already
 been expanded as part of condition processing. */
 
-if (level == 0)
+if (acl_level == 0)
   {
-  ss = expand_string(s);
-  if (ss == NULL)
+  if (!(ss = expand_string(s)))
     {
     if (expand_string_forcedfail) return OK;
     *log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s", s,
@@ -3983,11 +3912,11 @@ if (Ustrchr(ss, ' ') == NULL)
     acl = (acl_block *)(t->data.ptr);
     if (acl == NULL)
       {
-      HDEBUG(D_acl) debug_printf("ACL \"%s\" is empty: implicit DENY\n", ss);
+      HDEBUG(D_acl) debug_printf_indent("ACL \"%s\" is empty: implicit DENY\n", ss);
       return FAIL;
       }
     acl_name = string_sprintf("ACL \"%s\"", ss);
-    HDEBUG(D_acl) debug_printf("using ACL \"%s\"\n", ss);
+    HDEBUG(D_acl) debug_printf_indent("using ACL \"%s\"\n", ss);
     }
 
   else if (*ss == '/')
@@ -4021,7 +3950,7 @@ if (Ustrchr(ss, ' ') == NULL)
     (void)close(fd);
 
     acl_name = string_sprintf("ACL \"%s\"", ss);
-    HDEBUG(D_acl) debug_printf("read ACL from file %s\n", ss);
+    HDEBUG(D_acl) debug_printf_indent("read ACL from file %s\n", ss);
     }
   }
 
@@ -4052,26 +3981,19 @@ while (acl != NULL)
   int cond;
   int basic_errno = 0;
   BOOL endpass_seen = FALSE;
+  BOOL acl_quit_check = acl_level == 0
+    && (where == ACL_WHERE_QUIT || where == ACL_WHERE_NOTQUIT);
 
   *log_msgptr = *user_msgptr = NULL;
   acl_temp_details = FALSE;
 
-  if ((where == ACL_WHERE_QUIT || where == ACL_WHERE_NOTQUIT) &&
-      acl->verb != ACL_ACCEPT &&
-      acl->verb != ACL_WARN)
-    {
-    *log_msgptr = string_sprintf("\"%s\" is not allowed in a QUIT or not-QUIT ACL",
-      verbs[acl->verb]);
-    return ERROR;
-    }
-
-  HDEBUG(D_acl) debug_printf("processing \"%s\"\n", verbs[acl->verb]);
+  HDEBUG(D_acl) debug_printf_indent("processing \"%s\"\n", verbs[acl->verb]);
 
   /* Clear out any search error message from a previous check before testing
   this condition. */
 
   search_error_message = NULL;
-  cond = acl_check_condition(acl->verb, acl->condition, where, addr, level,
+  cond = acl_check_condition(acl->verb, acl->condition, where, addr, acl_level,
     &endpass_seen, user_msgptr, log_msgptr, &basic_errno);
 
   /* Handle special returns: DEFER causes a return except on a WARN verb;
@@ -4080,7 +4002,7 @@ while (acl != NULL)
   switch (cond)
     {
     case DEFER:
-    HDEBUG(D_acl) debug_printf("%s: condition test deferred in %s\n", verbs[acl->verb], acl_name);
+    HDEBUG(D_acl) debug_printf_indent("%s: condition test deferred in %s\n", verbs[acl->verb], acl_name);
     if (basic_errno != ERRNO_CALLOUTDEFER)
       {
       if (search_error_message != NULL && *search_error_message != 0)
@@ -4096,28 +4018,28 @@ while (acl != NULL)
 
     default:      /* Paranoia */
     case ERROR:
-    HDEBUG(D_acl) debug_printf("%s: condition test error in %s\n", verbs[acl->verb], acl_name);
+    HDEBUG(D_acl) debug_printf_indent("%s: condition test error in %s\n", verbs[acl->verb], acl_name);
     return ERROR;
 
     case OK:
-    HDEBUG(D_acl) debug_printf("%s: condition test succeeded in %s\n",
+    HDEBUG(D_acl) debug_printf_indent("%s: condition test succeeded in %s\n",
       verbs[acl->verb], acl_name);
     break;
 
     case FAIL:
-    HDEBUG(D_acl) debug_printf("%s: condition test failed in %s\n", verbs[acl->verb], acl_name);
+    HDEBUG(D_acl) debug_printf_indent("%s: condition test failed in %s\n", verbs[acl->verb], acl_name);
     break;
 
     /* DISCARD and DROP can happen only from a nested ACL condition, and
     DISCARD can happen only for an "accept" or "discard" verb. */
 
     case DISCARD:
-    HDEBUG(D_acl) debug_printf("%s: condition test yielded \"discard\" in %s\n",
+    HDEBUG(D_acl) debug_printf_indent("%s: condition test yielded \"discard\" in %s\n",
       verbs[acl->verb], acl_name);
     break;
 
     case FAIL_DROP:
-    HDEBUG(D_acl) debug_printf("%s: condition test yielded \"drop\" in %s\n",
+    HDEBUG(D_acl) debug_printf_indent("%s: condition test yielded \"drop\" in %s\n",
       verbs[acl->verb], acl_name);
     break;
     }
@@ -4129,10 +4051,14 @@ while (acl != NULL)
   switch(acl->verb)
     {
     case ACL_ACCEPT:
-    if (cond == OK || cond == DISCARD) return cond;
+    if (cond == OK || cond == DISCARD)
+      {
+      HDEBUG(D_acl) debug_printf_indent("end of %s: ACCEPT\n", acl_name);
+      return cond;
+      }
     if (endpass_seen)
       {
-      HDEBUG(D_acl) debug_printf("accept: endpass encountered - denying access\n");
+      HDEBUG(D_acl) debug_printf_indent("accept: endpass encountered - denying access\n");
       return cond;
       }
     break;
@@ -4140,36 +4066,58 @@ while (acl != NULL)
     case ACL_DEFER:
     if (cond == OK)
       {
+      HDEBUG(D_acl) debug_printf_indent("end of %s: DEFER\n", acl_name);
+      if (acl_quit_check) goto badquit;
       acl_temp_details = TRUE;
       return DEFER;
       }
     break;
 
     case ACL_DENY:
-    if (cond == OK) return FAIL;
+    if (cond == OK)
+      {
+      HDEBUG(D_acl) debug_printf_indent("end of %s: DENY\n", acl_name);
+      if (acl_quit_check) goto badquit;
+      return FAIL;
+      }
     break;
 
     case ACL_DISCARD:
-    if (cond == OK || cond == DISCARD) return DISCARD;
+    if (cond == OK || cond == DISCARD)
+      {
+      HDEBUG(D_acl) debug_printf_indent("end of %s: DISCARD\n", acl_name);
+      if (acl_quit_check) goto badquit;
+      return DISCARD;
+      }
     if (endpass_seen)
       {
-      HDEBUG(D_acl) debug_printf("discard: endpass encountered - denying access\n");
+      HDEBUG(D_acl) debug_printf_indent("discard: endpass encountered - denying access\n");
       return cond;
       }
     break;
 
     case ACL_DROP:
-    if (cond == OK) return FAIL_DROP;
+    if (cond == OK)
+      {
+      HDEBUG(D_acl) debug_printf_indent("end of %s: DROP\n", acl_name);
+      if (acl_quit_check) goto badquit;
+      return FAIL_DROP;
+      }
     break;
 
     case ACL_REQUIRE:
-    if (cond != OK) return cond;
+    if (cond != OK)
+      {
+      HDEBUG(D_acl) debug_printf_indent("end of %s: not OK\n", acl_name);
+      if (acl_quit_check) goto badquit;
+      return cond;
+      }
     break;
 
     case ACL_WARN:
     if (cond == OK)
       acl_warn(where, *user_msgptr, *log_msgptr);
-    else if (cond == DEFER && (log_extra_selector & LX_acl_warn_skipped) != 0)
+    else if (cond == DEFER && LOGGING(acl_warn_skipped))
       log_write(0, LOG_MAIN, "%s Warning: ACL \"warn\" statement skipped: "
         "condition test deferred%s%s", host_and_ident(TRUE),
         (*log_msgptr == NULL)? US"" : US": ",
@@ -4190,8 +4138,13 @@ while (acl != NULL)
 
 /* We have reached the end of the ACL. This is an implicit DENY. */
 
-HDEBUG(D_acl) debug_printf("end of %s: implicit DENY\n", acl_name);
+HDEBUG(D_acl) debug_printf_indent("end of %s: implicit DENY\n", acl_name);
 return FAIL;
+
+badquit:
+  *log_msgptr = string_sprintf("QUIT or not-QUIT toplevel ACL may not fail "
+    "('%s' verb used incorrectly)", verbs[acl->verb]);
+  return ERROR;
 }
 
 
@@ -4201,7 +4154,7 @@ return FAIL;
 the name of an ACL followed optionally by up to 9 space-separated arguments.
 The name and args are separately expanded.  Args go into $acl_arg globals. */
 static int
-acl_check_wargs(int where, address_item *addr, uschar *s, int level,
+acl_check_wargs(int where, address_item *addr, const uschar *s,
   uschar **user_msgptr, uschar **log_msgptr)
 {
 uschar * tmp;
@@ -4239,7 +4192,9 @@ while (i < 9)
   acl_arg[i++] = NULL;
   }
 
-ret = acl_check_internal(where, addr, name, level, user_msgptr, log_msgptr);
+acl_level++;
+ret = acl_check_internal(where, addr, name, user_msgptr, log_msgptr);
+acl_level--;
 
 acl_narg = sav_narg;
 for (i = 0; i < 9; i++) acl_arg[i] = sav_arg[i];
@@ -4264,6 +4219,7 @@ acl_eval(int where, uschar *s, uschar **user_msgptr, uschar **log_msgptr)
 {
 address_item adb;
 address_item *addr = NULL;
+int rc;
 
 *user_msgptr = *log_msgptr = NULL;
 sender_verified_failed = NULL;
@@ -4281,7 +4237,10 @@ if (where == ACL_WHERE_RCPT)
   addr->lc_local_part = deliver_localpart;
   }
 
-return acl_check_internal(where, addr, s, 0, user_msgptr, log_msgptr);
+acl_level++;
+rc = acl_check_internal(where, addr, s, user_msgptr, log_msgptr);
+acl_level--;
+return rc;
 }
 
 
@@ -4320,9 +4279,9 @@ ratelimiters_cmd = NULL;
 log_reject_target = LOG_MAIN|LOG_REJECT;
 
 #ifndef DISABLE_PRDR
-if (where == ACL_WHERE_RCPT || where == ACL_WHERE_PRDR )
+if (where==ACL_WHERE_RCPT || where==ACL_WHERE_VRFY || where==ACL_WHERE_PRDR)
 #else
-if (where == ACL_WHERE_RCPT )
+if (where==ACL_WHERE_RCPT || where==ACL_WHERE_VRFY)
 #endif
   {
   adb = address_defaults;
@@ -4333,12 +4292,21 @@ if (where == ACL_WHERE_RCPT )
     *log_msgptr = US"defer in percent_hack_domains check";
     return DEFER;
     }
+#ifdef SUPPORT_I18N
+  if ((addr->prop.utf8_msg = message_smtputf8))
+    {
+    addr->prop.utf8_downcvt =       message_utf8_downconvert == 1;
+    addr->prop.utf8_downcvt_maybe = message_utf8_downconvert == -1;
+    }
+#endif
   deliver_domain = addr->domain;
   deliver_localpart = addr->local_part;
   }
 
 acl_where = where;
-rc = acl_check_internal(where, addr, s, 0, user_msgptr, log_msgptr);
+acl_level = 0;
+rc = acl_check_internal(where, addr, s, user_msgptr, log_msgptr);
+acl_level = 0;
 acl_where = ACL_WHERE_UNKNOWN;
 
 /* Cutthrough - if requested,
@@ -4346,8 +4314,8 @@ and WHERE_RCPT and not yet opened conn as result of recipient-verify,
 and rcpt acl returned accept,
 and first recipient (cancel on any subsequents)
 open one now and run it up to RCPT acceptance.
-A failed verify should cancel cutthrough request.
-
+A failed verify should cancel cutthrough request,
+and will pass the fail to the originator.
 Initial implementation:  dual-write to spool.
 Assume the rxd datastream is now being copied byte-for-byte to an open cutthrough connection.
 
@@ -4361,32 +4329,50 @@ If temp-reject, close the conn (and keep the spooled copy).
 If conn-failure, no action (and keep the spooled copy).
 */
 switch (where)
-{
-case ACL_WHERE_RCPT:
+  {
+  case ACL_WHERE_RCPT:
 #ifndef DISABLE_PRDR
-case ACL_WHERE_PRDR:
+  case ACL_WHERE_PRDR:
 #endif
-  if( rcpt_count > 1 )
-    cancel_cutthrough_connection("more than one recipient");
-  else if (rc == OK  &&  cutthrough_delivery  &&  cutthrough_fd < 0)
-    open_cutthrough_connection(addr);
-  break;
+    if (host_checking_callout) /* -bhc mode */
+      cancel_cutthrough_connection("host-checking mode");
+
+    else if (  rc == OK
+           && cutthrough.delivery
+           && rcpt_count > cutthrough.nrcpt
+           && (rc = open_cutthrough_connection(addr)) == DEFER
+           )
+      if (cutthrough.defer_pass)
+       {
+       uschar * s = addr->message;
+       /* Horrid kludge to recover target's SMTP message */
+       while (*s) s++;
+       do --s; while (!isdigit(*s));
+       if (*--s && isdigit(*s) && *--s && isdigit(*s)) *user_msgptr = s;
+       acl_temp_details = TRUE;
+       }
+       else
+       {
+       HDEBUG(D_acl) debug_printf_indent("cutthrough defer; will spool\n");
+       rc = OK;
+       }
+    break;
 
-case ACL_WHERE_PREDATA:
-  if( rc == OK )
-    cutthrough_predata();
-  else
-    cancel_cutthrough_connection("predata acl not ok");
-  break;
+  case ACL_WHERE_PREDATA:
+    if (rc == OK)
+      cutthrough_predata();
+    else
+      cancel_cutthrough_connection("predata acl not ok");
+    break;
 
-case ACL_WHERE_QUIT:
-case ACL_WHERE_NOTQUIT:
-  cancel_cutthrough_connection("quit or notquit");
-  break;
+  case ACL_WHERE_QUIT:
+  case ACL_WHERE_NOTQUIT:
+    cancel_cutthrough_connection("quit or notquit");
+    break;
 
-default:
-  break;
-}
+  default:
+    break;
+  }
 
 deliver_domain = deliver_localpart = deliver_address_data =
   sender_address_data = NULL;
index c6ef218..62ce9d0 100644 (file)
@@ -5,11 +5,11 @@
 # after cd'ing to the auths subdirectory. When the relevant AUTH_ macros are
 # defined, the equivalent modules herein is not included in the final binary.
 
-OBJ = auth-spa.o b64decode.o b64encode.o call_pam.o call_pwcheck.o \
+OBJ = auth-spa.o call_pam.o call_pwcheck.o \
       call_radius.o check_serv_cond.o cram_md5.o cyrus_sasl.o dovecot.o \
       get_data.o get_no64_data.o gsasl_exim.o heimdal_gssapi.o \
-      md5.o plaintext.o pwcheck.o sha1.o \
-      spa.o xtextdecode.o xtextencode.o
+      md5.o plaintext.o pwcheck.o \
+      spa.o tls.o xtextdecode.o xtextencode.o
 
 auths.a:         $(OBJ)
                 @$(RM_COMMAND) -f auths.a
@@ -22,8 +22,6 @@ auths.a:         $(OBJ)
                 $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) $*.c
 
 auth-spa.o:         $(HDRS) auth-spa.c
-b64encode.o:        $(HDRS) b64encode.c
-b64decode.o:        $(HDRS) b64decode.c
 call_pam.o:         $(HDRS) call_pam.c
 call_pwcheck.o:     $(HDRS) call_pwcheck.c pwcheck.h
 call_radius.o:      $(HDRS) call_radius.c
@@ -32,7 +30,6 @@ get_data.o:         $(HDRS) get_data.c
 get_no64_data.o:    $(HDRS) get_no64_data.c
 md5.o:              $(HDRS) md5.c
 pwcheck.o:          $(HDRS) pwcheck.c pwcheck.h
-sha1.o:             $(HDRS) sha1.c
 xtextdecode.o:      $(HDRS) xtextdecode.c
 xtextencode.o:      $(HDRS) xtextencode.c
 
@@ -43,5 +40,6 @@ gsasl_exim.o:       $(HDRS) gsasl_exim.c gsasl_exim.h
 heimdal_gssapi.o:   $(HDRS) heimdal_gssapi.c heimdal_gssapi.h
 plaintext.o:        $(HDRS) plaintext.c plaintext.h
 spa.o:              $(HDRS) spa.c spa.h
+tls.o:              $(HDRS) tls.c tls.h
 
 # End
index 7ad5a1d..d1df7f2 100644 (file)
@@ -68,7 +68,7 @@ int main (int argc, char ** argv)
 
        spa_build_auth_request (&request, username, domain);
 
-       spa_bits_to_base64 (msgbuf, (unsigned char*)&request,
+       spa_bits_to_base64 (msgbuf, US &request,
                spa_request_length(&request));
 
        printf ("SPA Login request for username=%s:\n   %s\n",
@@ -91,7 +91,7 @@ int main (int argc, char ** argv)
        }
 
        spa_build_auth_response (&challenge, &response, username, password);
-       spa_bits_to_base64 (msgbuf, (unsigned char*)&response,
+       spa_bits_to_base64 (msgbuf, US &response,
                spa_request_length(&response));
 
        printf ("SPA Response to challenge:\n   %s\n for " \
@@ -153,87 +153,73 @@ int main (int argc, char ** argv)
    up with a different answer to the one above)
 */
 
-#define DEBUG(a,b) ;
+#define DEBUG_X(a,b) ;
 
 extern int DEBUGLEVEL;
 
-#include <sys/types.h>     /* For size_t */
+#include "../exim.h"
 #include "auth-spa.h"
 #include <assert.h>
-#include <ctype.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-#include <unistd.h>
-
-#ifndef _AIX
-typedef unsigned char uchar;
-#endif
-
 
-typedef int BOOL;
-#define False 0
-#define True  1
 
 #ifndef _BYTEORDER_H
-#define _BYTEORDER_H
+# define _BYTEORDER_H
 
-#define RW_PCVAL(read,inbuf,outbuf,len) \
+# define RW_PCVAL(read,inbuf,outbuf,len) \
        { if (read) { PCVAL (inbuf,0,outbuf,len); } \
        else      { PSCVAL(inbuf,0,outbuf,len); } }
 
-#define RW_PIVAL(read,big_endian,inbuf,outbuf,len) \
+# define RW_PIVAL(read,big_endian,inbuf,outbuf,len) \
        { if (read) { if (big_endian) { RPIVAL(inbuf,0,outbuf,len); } else { PIVAL(inbuf,0,outbuf,len); } } \
        else      { if (big_endian) { RPSIVAL(inbuf,0,outbuf,len); } else { PSIVAL(inbuf,0,outbuf,len); } } }
 
-#define RW_PSVAL(read,big_endian,inbuf,outbuf,len) \
+# define RW_PSVAL(read,big_endian,inbuf,outbuf,len) \
        { if (read) { if (big_endian) { RPSVAL(inbuf,0,outbuf,len); } else { PSVAL(inbuf,0,outbuf,len); } } \
        else      { if (big_endian) { RPSSVAL(inbuf,0,outbuf,len); } else { PSSVAL(inbuf,0,outbuf,len); } } }
 
-#define RW_CVAL(read, inbuf, outbuf, offset) \
+# define RW_CVAL(read, inbuf, outbuf, offset) \
        { if (read) { (outbuf) = CVAL (inbuf,offset); } \
        else      { SCVAL(inbuf,offset,outbuf); } }
 
-#define RW_IVAL(read, big_endian, inbuf, outbuf, offset) \
+# define RW_IVAL(read, big_endian, inbuf, outbuf, offset) \
        { if (read) { (outbuf) = ((big_endian) ? RIVAL(inbuf,offset) : IVAL (inbuf,offset)); } \
        else      { if (big_endian) { RSIVAL(inbuf,offset,outbuf); } else { SIVAL(inbuf,offset,outbuf); } } }
 
-#define RW_SVAL(read, big_endian, inbuf, outbuf, offset) \
+# define RW_SVAL(read, big_endian, inbuf, outbuf, offset) \
        { if (read) { (outbuf) = ((big_endian) ? RSVAL(inbuf,offset) : SVAL (inbuf,offset)); } \
        else      { if (big_endian) { RSSVAL(inbuf,offset,outbuf); } else { SSVAL(inbuf,offset,outbuf); } } }
 
-#undef CAREFUL_ALIGNMENT
+# undef CAREFUL_ALIGNMENT
 
 /* we know that the 386 can handle misalignment and has the "right"
    byteorder */
-#ifdef __i386__
-#define CAREFUL_ALIGNMENT 0
-#endif
+# ifdef __i386__
+#  define CAREFUL_ALIGNMENT 0
+# endif
 
-#ifndef CAREFUL_ALIGNMENT
-#define CAREFUL_ALIGNMENT 1
-#endif
+# ifndef CAREFUL_ALIGNMENT
+#  define CAREFUL_ALIGNMENT 1
+# endif
 
-#define CVAL(buf,pos) (((unsigned char *)(buf))[pos])
-#define PVAL(buf,pos) ((unsigned)CVAL(buf,pos))
-#define SCVAL(buf,pos,val) (CVAL(buf,pos) = (val))
+# define CVAL(buf,pos) ((US (buf))[pos])
+# define PVAL(buf,pos) ((unsigned)CVAL(buf,pos))
+# define SCVAL(buf,pos,val) (CVAL(buf,pos) = (val))
 
 
-#if CAREFUL_ALIGNMENT
+# if CAREFUL_ALIGNMENT
 
-#define SVAL(buf,pos) (PVAL(buf,pos)|PVAL(buf,(pos)+1)<<8)
-#define IVAL(buf,pos) (SVAL(buf,pos)|SVAL(buf,(pos)+2)<<16)
-#define SSVALX(buf,pos,val) (CVAL(buf,pos)=(val)&0xFF,CVAL(buf,pos+1)=(val)>>8)
-#define SIVALX(buf,pos,val) (SSVALX(buf,pos,val&0xFFFF),SSVALX(buf,pos+2,val>>16))
-#define SVALS(buf,pos) ((int16x)SVAL(buf,pos))
-#define IVALS(buf,pos) ((int32x)IVAL(buf,pos))
-#define SSVAL(buf,pos,val) SSVALX((buf),(pos),((uint16x)(val)))
-#define SIVAL(buf,pos,val) SIVALX((buf),(pos),((uint32x)(val)))
-#define SSVALS(buf,pos,val) SSVALX((buf),(pos),((int16x)(val)))
-#define SIVALS(buf,pos,val) SIVALX((buf),(pos),((int32x)(val)))
+#  define SVAL(buf,pos) (PVAL(buf,pos)|PVAL(buf,(pos)+1)<<8)
+#  define IVAL(buf,pos) (SVAL(buf,pos)|SVAL(buf,(pos)+2)<<16)
+#  define SSVALX(buf,pos,val) (CVAL(buf,pos)=(val)&0xFF,CVAL(buf,pos+1)=(val)>>8)
+#  define SIVALX(buf,pos,val) (SSVALX(buf,pos,val&0xFFFF),SSVALX(buf,pos+2,val>>16))
+#  define SVALS(buf,pos) ((int16x)SVAL(buf,pos))
+#  define IVALS(buf,pos) ((int32x)IVAL(buf,pos))
+#  define SSVAL(buf,pos,val) SSVALX((buf),(pos),((uint16x)(val)))
+#  define SIVAL(buf,pos,val) SIVALX((buf),(pos),((uint32x)(val)))
+#  define SSVALS(buf,pos,val) SSVALX((buf),(pos),((int16x)(val)))
+#  define SIVALS(buf,pos,val) SIVALX((buf),(pos),((int32x)(val)))
 
-#else /* CAREFUL_ALIGNMENT */
+# else /* CAREFUL_ALIGNMENT */
 
 /* this handles things for architectures like the 386 that can handle
    alignment errors */
@@ -243,116 +229,116 @@ typedef int BOOL;
 */
 
 /* get single value from an SMB buffer */
-#define SVAL(buf,pos) (*(uint16x *)((char *)(buf) + (pos)))
-#define IVAL(buf,pos) (*(uint32x *)((char *)(buf) + (pos)))
-#define SVALS(buf,pos) (*(int16x *)((char *)(buf) + (pos)))
-#define IVALS(buf,pos) (*(int32x *)((char *)(buf) + (pos)))
+#  define SVAL(buf,pos) (*(uint16x *)((char *)(buf) + (pos)))
+#  define IVAL(buf,pos) (*(uint32x *)((char *)(buf) + (pos)))
+#  define SVALS(buf,pos) (*(int16x *)((char *)(buf) + (pos)))
+#  define IVALS(buf,pos) (*(int32x *)((char *)(buf) + (pos)))
 
 /* store single value in an SMB buffer */
-#define SSVAL(buf,pos,val) SVAL(buf,pos)=((uint16x)(val))
-#define SIVAL(buf,pos,val) IVAL(buf,pos)=((uint32x)(val))
-#define SSVALS(buf,pos,val) SVALS(buf,pos)=((int16x)(val))
-#define SIVALS(buf,pos,val) IVALS(buf,pos)=((int32x)(val))
+#  define SSVAL(buf,pos,val) SVAL(buf,pos)=((uint16x)(val))
+#  define SIVAL(buf,pos,val) IVAL(buf,pos)=((uint32x)(val))
+#  define SSVALS(buf,pos,val) SVALS(buf,pos)=((int16x)(val))
+#  define SIVALS(buf,pos,val) IVALS(buf,pos)=((int32x)(val))
 
-#endif /* CAREFUL_ALIGNMENT */
+# endif /* CAREFUL_ALIGNMENT */
 
 /* macros for reading / writing arrays */
 
-#define SMBMACRO(macro,buf,pos,val,len,size) \
+# define SMBMACRO(macro,buf,pos,val,len,size) \
 { int l; for (l = 0; l < (len); l++) (val)[l] = macro((buf), (pos) + (size)*l); }
 
-#define SSMBMACRO(macro,buf,pos,val,len,size) \
+# define SSMBMACRO(macro,buf,pos,val,len,size) \
 { int l; for (l = 0; l < (len); l++) macro((buf), (pos) + (size)*l, (val)[l]); }
 
 /* reads multiple data from an SMB buffer */
-#define PCVAL(buf,pos,val,len) SMBMACRO(CVAL,buf,pos,val,len,1)
-#define PSVAL(buf,pos,val,len) SMBMACRO(SVAL,buf,pos,val,len,2)
-#define PIVAL(buf,pos,val,len) SMBMACRO(IVAL,buf,pos,val,len,4)
-#define PCVALS(buf,pos,val,len) SMBMACRO(CVALS,buf,pos,val,len,1)
-#define PSVALS(buf,pos,val,len) SMBMACRO(SVALS,buf,pos,val,len,2)
-#define PIVALS(buf,pos,val,len) SMBMACRO(IVALS,buf,pos,val,len,4)
+# define PCVAL(buf,pos,val,len) SMBMACRO(CVAL,buf,pos,val,len,1)
+# define PSVAL(buf,pos,val,len) SMBMACRO(SVAL,buf,pos,val,len,2)
+# define PIVAL(buf,pos,val,len) SMBMACRO(IVAL,buf,pos,val,len,4)
+# define PCVALS(buf,pos,val,len) SMBMACRO(CVALS,buf,pos,val,len,1)
+# define PSVALS(buf,pos,val,len) SMBMACRO(SVALS,buf,pos,val,len,2)
+# define PIVALS(buf,pos,val,len) SMBMACRO(IVALS,buf,pos,val,len,4)
 
 /* stores multiple data in an SMB buffer */
-#define PSCVAL(buf,pos,val,len) SSMBMACRO(SCVAL,buf,pos,val,len,1)
-#define PSSVAL(buf,pos,val,len) SSMBMACRO(SSVAL,buf,pos,val,len,2)
-#define PSIVAL(buf,pos,val,len) SSMBMACRO(SIVAL,buf,pos,val,len,4)
-#define PSCVALS(buf,pos,val,len) SSMBMACRO(SCVALS,buf,pos,val,len,1)
-#define PSSVALS(buf,pos,val,len) SSMBMACRO(SSVALS,buf,pos,val,len,2)
-#define PSIVALS(buf,pos,val,len) SSMBMACRO(SIVALS,buf,pos,val,len,4)
+# define PSCVAL(buf,pos,val,len) SSMBMACRO(SCVAL,buf,pos,val,len,1)
+# define PSSVAL(buf,pos,val,len) SSMBMACRO(SSVAL,buf,pos,val,len,2)
+# define PSIVAL(buf,pos,val,len) SSMBMACRO(SIVAL,buf,pos,val,len,4)
+# define PSCVALS(buf,pos,val,len) SSMBMACRO(SCVALS,buf,pos,val,len,1)
+# define PSSVALS(buf,pos,val,len) SSMBMACRO(SSVALS,buf,pos,val,len,2)
+# define PSIVALS(buf,pos,val,len) SSMBMACRO(SIVALS,buf,pos,val,len,4)
 
 
 /* now the reverse routines - these are used in nmb packets (mostly) */
-#define SREV(x) ((((x)&0xFF)<<8) | (((x)>>8)&0xFF))
-#define IREV(x) ((SREV(x)<<16) | (SREV((x)>>16)))
-
-#define RSVAL(buf,pos) SREV(SVAL(buf,pos))
-#define RSVALS(buf,pos) SREV(SVALS(buf,pos))
-#define RIVAL(buf,pos) IREV(IVAL(buf,pos))
-#define RIVALS(buf,pos) IREV(IVALS(buf,pos))
-#define RSSVAL(buf,pos,val) SSVAL(buf,pos,SREV(val))
-#define RSSVALS(buf,pos,val) SSVALS(buf,pos,SREV(val))
-#define RSIVAL(buf,pos,val) SIVAL(buf,pos,IREV(val))
-#define RSIVALS(buf,pos,val) SIVALS(buf,pos,IREV(val))
+# define SREV(x) ((((x)&0xFF)<<8) | (((x)>>8)&0xFF))
+# define IREV(x) ((SREV(x)<<16) | (SREV((x)>>16)))
+
+# define RSVAL(buf,pos) SREV(SVAL(buf,pos))
+# define RSVALS(buf,pos) SREV(SVALS(buf,pos))
+# define RIVAL(buf,pos) IREV(IVAL(buf,pos))
+# define RIVALS(buf,pos) IREV(IVALS(buf,pos))
+# define RSSVAL(buf,pos,val) SSVAL(buf,pos,SREV(val))
+# define RSSVALS(buf,pos,val) SSVALS(buf,pos,SREV(val))
+# define RSIVAL(buf,pos,val) SIVAL(buf,pos,IREV(val))
+# define RSIVALS(buf,pos,val) SIVALS(buf,pos,IREV(val))
 
 /* reads multiple data from an SMB buffer (big-endian) */
-#define RPSVAL(buf,pos,val,len) SMBMACRO(RSVAL,buf,pos,val,len,2)
-#define RPIVAL(buf,pos,val,len) SMBMACRO(RIVAL,buf,pos,val,len,4)
-#define RPSVALS(buf,pos,val,len) SMBMACRO(RSVALS,buf,pos,val,len,2)
-#define RPIVALS(buf,pos,val,len) SMBMACRO(RIVALS,buf,pos,val,len,4)
+# define RPSVAL(buf,pos,val,len) SMBMACRO(RSVAL,buf,pos,val,len,2)
+# define RPIVAL(buf,pos,val,len) SMBMACRO(RIVAL,buf,pos,val,len,4)
+# define RPSVALS(buf,pos,val,len) SMBMACRO(RSVALS,buf,pos,val,len,2)
+# define RPIVALS(buf,pos,val,len) SMBMACRO(RIVALS,buf,pos,val,len,4)
 
 /* stores multiple data in an SMB buffer (big-endian) */
-#define RPSSVAL(buf,pos,val,len) SSMBMACRO(RSSVAL,buf,pos,val,len,2)
-#define RPSIVAL(buf,pos,val,len) SSMBMACRO(RSIVAL,buf,pos,val,len,4)
-#define RPSSVALS(buf,pos,val,len) SSMBMACRO(RSSVALS,buf,pos,val,len,2)
-#define RPSIVALS(buf,pos,val,len) SSMBMACRO(RSIVALS,buf,pos,val,len,4)
+# define RPSSVAL(buf,pos,val,len) SSMBMACRO(RSSVAL,buf,pos,val,len,2)
+# define RPSIVAL(buf,pos,val,len) SSMBMACRO(RSIVAL,buf,pos,val,len,4)
+# define RPSSVALS(buf,pos,val,len) SSMBMACRO(RSSVALS,buf,pos,val,len,2)
+# define RPSIVALS(buf,pos,val,len) SSMBMACRO(RSIVALS,buf,pos,val,len,4)
 
-#define DBG_RW_PCVAL(charmode,string,depth,base,read,inbuf,outbuf,len) \
+# define DBG_RW_PCVAL(charmode,string,depth,base,read,inbuf,outbuf,len) \
        { RW_PCVAL(read,inbuf,outbuf,len) \
-       DEBUG(5,("%s%04x %s: ", \
+       DEBUG_X(5,("%s%04x %s: ", \
              tab_depth(depth), base,string)); \
-    if (charmode) print_asc(5, (unsigned char*)(outbuf), (len)); else \
-       { int idx; for (idx = 0; idx < len; idx++) { DEBUG(5,("%02x ", (outbuf)[idx])); } } \
-       DEBUG(5,("\n")); }
+    if (charmode) print_asc(5, US (outbuf), (len)); else \
+       { int idx; for (idx = 0; idx < len; idx++) { DEBUG_X(5,("%02x ", (outbuf)[idx])); } } \
+       DEBUG_X(5,("\n")); }
 
-#define DBG_RW_PSVAL(charmode,string,depth,base,read,big_endian,inbuf,outbuf,len) \
+# define DBG_RW_PSVAL(charmode,string,depth,base,read,big_endian,inbuf,outbuf,len) \
        { RW_PSVAL(read,big_endian,inbuf,outbuf,len) \
-       DEBUG(5,("%s%04x %s: ", \
+       DEBUG_X(5,("%s%04x %s: ", \
              tab_depth(depth), base,string)); \
-    if (charmode) print_asc(5, (unsigned char*)(outbuf), 2*(len)); else \
-       { int idx; for (idx = 0; idx < len; idx++) { DEBUG(5,("%04x ", (outbuf)[idx])); } } \
-       DEBUG(5,("\n")); }
+    if (charmode) print_asc(5, US (outbuf), 2*(len)); else \
+       { int idx; for (idx = 0; idx < len; idx++) { DEBUG_X(5,("%04x ", (outbuf)[idx])); } } \
+       DEBUG_X(5,("\n")); }
 
-#define DBG_RW_PIVAL(charmode,string,depth,base,read,big_endian,inbuf,outbuf,len) \
+# define DBG_RW_PIVAL(charmode,string,depth,base,read,big_endian,inbuf,outbuf,len) \
        { RW_PIVAL(read,big_endian,inbuf,outbuf,len) \
-       DEBUG(5,("%s%04x %s: ", \
+       DEBUG_X(5,("%s%04x %s: ", \
              tab_depth(depth), base,string)); \
-    if (charmode) print_asc(5, (unsigned char*)(outbuf), 4*(len)); else \
-       { int idx; for (idx = 0; idx < len; idx++) { DEBUG(5,("%08x ", (outbuf)[idx])); } } \
-       DEBUG(5,("\n")); }
+    if (charmode) print_asc(5, US (outbuf), 4*(len)); else \
+       { int idx; for (idx = 0; idx < len; idx++) { DEBUG_X(5,("%08x ", (outbuf)[idx])); } } \
+       DEBUG_X(5,("\n")); }
 
-#define DBG_RW_CVAL(string,depth,base,read,inbuf,outbuf) \
+# define DBG_RW_CVAL(string,depth,base,read,inbuf,outbuf) \
        { RW_CVAL(read,inbuf,outbuf,0) \
-       DEBUG(5,("%s%04x %s: %02x\n", \
+       DEBUG_X(5,("%s%04x %s: %02x\n", \
              tab_depth(depth), base, string, outbuf)); }
 
-#define DBG_RW_SVAL(string,depth,base,read,big_endian,inbuf,outbuf) \
+# define DBG_RW_SVAL(string,depth,base,read,big_endian,inbuf,outbuf) \
        { RW_SVAL(read,big_endian,inbuf,outbuf,0) \
-       DEBUG(5,("%s%04x %s: %04x\n", \
+       DEBUG_X(5,("%s%04x %s: %04x\n", \
              tab_depth(depth), base, string, outbuf)); }
 
-#define DBG_RW_IVAL(string,depth,base,read,big_endian,inbuf,outbuf) \
+# define DBG_RW_IVAL(string,depth,base,read,big_endian,inbuf,outbuf) \
        { RW_IVAL(read,big_endian,inbuf,outbuf,0) \
-       DEBUG(5,("%s%04x %s: %08x\n", \
+       DEBUG_X(5,("%s%04x %s: %08x\n", \
              tab_depth(depth), base, string, outbuf)); }
 
 #endif /* _BYTEORDER_H */
 
-void E_P16 (unsigned char *p14, unsigned char *p16);
-void E_P24 (unsigned char *p21, unsigned char *c8, unsigned char *p24);
-void D_P16 (unsigned char *p14, unsigned char *in, unsigned char *out);
-void SMBOWFencrypt (uchar passwd[16], uchar * c8, uchar p24[24]);
+void E_P16 (uschar *p14, uschar *p16);
+void E_P24 (uschar *p21, uschar *c8, uschar *p24);
+void D_P16 (uschar *p14, uschar *in, uschar *out);
+void SMBOWFencrypt (uschar passwd[16], uschar * c8, uschar p24[24]);
 
-void mdfour (unsigned char *out, unsigned char *in, int n);
+void mdfour (uschar *out, uschar *in, int n);
 
 
 /*
@@ -368,7 +354,7 @@ void mdfour (unsigned char *out, unsigned char *in, int n);
 static const char base64digits[] =
   "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
 
-#define BAD    -1
+#define BAD    (char) -1
 static const char base64val[] = {
   BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD,
     BAD,
@@ -385,7 +371,7 @@ static const char base64val[] = {
 #define DECODE64(c)  (isascii(c) ? base64val[c] : BAD)
 
 void
-spa_bits_to_base64 (unsigned char *out, const unsigned char *in, int inlen)
+spa_bits_to_base64 (uschar *out, const uschar *in, int inlen)
 /* raw bytes in quasi-big-endian order to base 64 string (NUL-terminated) */
 {
   for (; inlen >= 3; inlen -= 3)
@@ -398,7 +384,7 @@ spa_bits_to_base64 (unsigned char *out, const unsigned char *in, int inlen)
     }
   if (inlen > 0)
     {
-      unsigned char fragment;
+      uschar fragment;
 
       *out++ = base64digits[in[0] >> 2];
       fragment = (in[0] << 4) & 0x30;
@@ -419,7 +405,7 @@ spa_base64_to_bits (char *out, int outlength, const char *in)
 /* base 64 to raw bytes in quasi-big-endian order, returning count of bytes */
 {
   int len = 0;
-  register unsigned char digit1, digit2, digit3, digit4;
+  register uschar digit1, digit2, digit3, digit4;
 
   if (in[0] == '+' && in[1] == ' ')
     in += 2;
@@ -467,9 +453,7 @@ spa_base64_to_bits (char *out, int outlength, const char *in)
 }
 
 
-#define uchar unsigned char
-
-static uchar perm1[56] = { 57, 49, 41, 33, 25, 17, 9,
+static uschar perm1[56] = { 57, 49, 41, 33, 25, 17, 9,
   1, 58, 50, 42, 34, 26, 18,
   10, 2, 59, 51, 43, 35, 27,
   19, 11, 3, 60, 52, 44, 36,
@@ -479,7 +463,7 @@ static uchar perm1[56] = { 57, 49, 41, 33, 25, 17, 9,
   21, 13, 5, 28, 20, 12, 4
 };
 
-static uchar perm2[48] = { 14, 17, 11, 24, 1, 5,
+static uschar perm2[48] = { 14, 17, 11, 24, 1, 5,
   3, 28, 15, 6, 21, 10,
   23, 19, 12, 4, 26, 8,
   16, 7, 27, 20, 13, 2,
@@ -489,7 +473,7 @@ static uchar perm2[48] = { 14, 17, 11, 24, 1, 5,
   46, 42, 50, 36, 29, 32
 };
 
-static uchar perm3[64] = { 58, 50, 42, 34, 26, 18, 10, 2,
+static uschar perm3[64] = { 58, 50, 42, 34, 26, 18, 10, 2,
   60, 52, 44, 36, 28, 20, 12, 4,
   62, 54, 46, 38, 30, 22, 14, 6,
   64, 56, 48, 40, 32, 24, 16, 8,
@@ -499,7 +483,7 @@ static uchar perm3[64] = { 58, 50, 42, 34, 26, 18, 10, 2,
   63, 55, 47, 39, 31, 23, 15, 7
 };
 
-static uchar perm4[48] = { 32, 1, 2, 3, 4, 5,
+static uschar perm4[48] = { 32, 1, 2, 3, 4, 5,
   4, 5, 6, 7, 8, 9,
   8, 9, 10, 11, 12, 13,
   12, 13, 14, 15, 16, 17,
@@ -509,7 +493,7 @@ static uchar perm4[48] = { 32, 1, 2, 3, 4, 5,
   28, 29, 30, 31, 32, 1
 };
 
-static uchar perm5[32] = { 16, 7, 20, 21,
+static uschar perm5[32] = { 16, 7, 20, 21,
   29, 12, 28, 17,
   1, 15, 23, 26,
   5, 18, 31, 10,
@@ -520,7 +504,7 @@ static uchar perm5[32] = { 16, 7, 20, 21,
 };
 
 
-static uchar perm6[64] = { 40, 8, 48, 16, 56, 24, 64, 32,
+static uschar perm6[64] = { 40, 8, 48, 16, 56, 24, 64, 32,
   39, 7, 47, 15, 55, 23, 63, 31,
   38, 6, 46, 14, 54, 22, 62, 30,
   37, 5, 45, 13, 53, 21, 61, 29,
@@ -531,9 +515,9 @@ static uchar perm6[64] = { 40, 8, 48, 16, 56, 24, 64, 32,
 };
 
 
-static uchar sc[16] = { 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 };
+static uschar sc[16] = { 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 };
 
-static uchar sbox[8][4][16] = {
+static uschar sbox[8][4][16] = {
   {{14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7},
    {0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8},
    {4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0},
@@ -576,7 +560,7 @@ static uchar sbox[8][4][16] = {
 };
 
 static void
-permute (char *out, char *in, uchar * p, int n)
+permute (char *out, char *in, uschar * p, int n)
 {
   int i;
   for (i = 0; i < n; i++)
@@ -696,7 +680,7 @@ dohash (char *out, char *in, char *key, int forw)
 }
 
 static void
-str_to_key (unsigned char *str, unsigned char *key)
+str_to_key (uschar *str, uschar *key)
 {
   int i;
 
@@ -716,13 +700,13 @@ str_to_key (unsigned char *str, unsigned char *key)
 
 
 static void
-smbhash (unsigned char *out, unsigned char *in, unsigned char *key, int forw)
+smbhash (uschar *out, uschar *in, uschar *key, int forw)
 {
   int i;
   char outb[64];
   char inb[64];
   char keyb[64];
-  unsigned char key2[8];
+  uschar key2[8];
 
   str_to_key (key, key2);
 
@@ -748,15 +732,15 @@ smbhash (unsigned char *out, unsigned char *in, unsigned char *key, int forw)
 }
 
 void
-E_P16 (unsigned char *p14, unsigned char *p16)
+E_P16 (uschar *p14, uschar *p16)
 {
-  unsigned char sp8[8] = { 0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25 };
+  uschar sp8[8] = { 0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25 };
   smbhash (p16, sp8, p14, 1);
   smbhash (p16 + 8, sp8, p14 + 7, 1);
 }
 
 void
-E_P24 (unsigned char *p21, unsigned char *c8, unsigned char *p24)
+E_P24 (uschar *p21, uschar *c8, uschar *p24)
 {
   smbhash (p24, c8, p21, 1);
   smbhash (p24 + 8, c8, p21 + 7, 1);
@@ -764,7 +748,7 @@ E_P24 (unsigned char *p21, unsigned char *c8, unsigned char *p24)
 }
 
 void
-D_P16 (unsigned char *p14, unsigned char *in, unsigned char *out)
+D_P16 (uschar *p14, uschar *in, uschar *out)
 {
   smbhash (out, in, p14, 0);
   smbhash (out + 8, in + 8, p14 + 7, 0);
@@ -814,7 +798,7 @@ safe_strcpy (char *dest, const char *src, size_t maxlength)
 
   if (!dest)
     {
-      DEBUG (0, ("ERROR: NULL dest in safe_strcpy\n"));
+      DEBUG_X (0, ("ERROR: NULL dest in safe_strcpy\n"));
       return NULL;
     }
 
@@ -828,7 +812,7 @@ safe_strcpy (char *dest, const char *src, size_t maxlength)
 
   if (len > maxlength)
     {
-      DEBUG (0, ("ERROR: string overflow by %d in safe_strcpy [%.50s]\n",
+      DEBUG_X (0, ("ERROR: string overflow by %d in safe_strcpy [%.50s]\n",
                 (int) (len - maxlength), src));
       len = maxlength;
     }
@@ -850,7 +834,7 @@ strupper (char *s)
          s += skip;
        else
          {
-           if (islower ((unsigned char)(*s)))
+           if (islower ((uschar)(*s)))
              *s = toupper (*s);
            s++;
          }
@@ -866,9 +850,9 @@ strupper (char *s)
  */
 
 void
-spa_smb_encrypt (uchar * passwd, uchar * c8, uchar * p24)
+spa_smb_encrypt (uschar * passwd, uschar * c8, uschar * p24)
 {
-  uchar p14[15], p21[21];
+  uschar p14[15], p21[21];
 
   memset (p21, '\0', 21);
   memset (p14, '\0', 14);
@@ -880,7 +864,7 @@ spa_smb_encrypt (uchar * passwd, uchar * c8, uchar * p24)
   SMBOWFencrypt (p21, c8, p24);
 
 #ifdef DEBUG_PASSWORD
-  DEBUG (100, ("spa_smb_encrypt: lm#, challenge, response\n"));
+  DEBUG_X (100, ("spa_smb_encrypt: lm#, challenge, response\n"));
   dump_data (100, (char *) p21, 16);
   dump_data (100, (char *) c8, 8);
   dump_data (100, (char *) p24, 24);
@@ -905,7 +889,7 @@ _my_wcslen (int16x * str)
  */
 
 static int
-_my_mbstowcs (int16x * dst, uchar * src, int len)
+_my_mbstowcs (int16x * dst, uschar * src, int len)
 {
   int i;
   int16x val;
@@ -927,7 +911,7 @@ _my_mbstowcs (int16x * dst, uchar * src, int len)
  */
 
 void
-E_md4hash (uchar * passwd, uchar * p16)
+E_md4hash (uschar * passwd, uschar * p16)
 {
   int len;
   int16x wpwd[129];
@@ -942,12 +926,12 @@ E_md4hash (uchar * passwd, uchar * p16)
   /* Calculate length in bytes */
   len = _my_wcslen (wpwd) * sizeof (int16x);
 
-  mdfour (p16, (unsigned char *) wpwd, len);
+  mdfour (p16, US wpwd, len);
 }
 
 /* Does both the NT and LM owfs of a user's password */
 void
-nt_lm_owf_gen (char *pwd, uchar nt_p16[16], uchar p16[16])
+nt_lm_owf_gen (char *pwd, uschar nt_p16[16], uschar p16[16])
 {
   char passwd[130];
 
@@ -956,10 +940,10 @@ nt_lm_owf_gen (char *pwd, uchar nt_p16[16], uchar p16[16])
 
   /* Calculate the MD4 hash (NT compatible) of the password */
   memset (nt_p16, '\0', 16);
-  E_md4hash ((uchar *) passwd, nt_p16);
+  E_md4hash (US passwd, nt_p16);
 
 #ifdef DEBUG_PASSWORD
-  DEBUG (100, ("nt_lm_owf_gen: pwd, nt#\n"));
+  DEBUG_X (100, ("nt_lm_owf_gen: pwd, nt#\n"));
   dump_data (120, passwd, strlen (passwd));
   dump_data (100, (char *) nt_p16, 16);
 #endif
@@ -971,10 +955,10 @@ nt_lm_owf_gen (char *pwd, uchar nt_p16[16], uchar p16[16])
   /* Calculate the SMB (lanman) hash functions of the password */
 
   memset (p16, '\0', 16);
-  E_P16 ((uchar *) passwd, (uchar *) p16);
+  E_P16 (US passwd, US p16);
 
 #ifdef DEBUG_PASSWORD
-  DEBUG (100, ("nt_lm_owf_gen: pwd, lm#\n"));
+  DEBUG_X (100, ("nt_lm_owf_gen: pwd, lm#\n"));
   dump_data (120, passwd, strlen (passwd));
   dump_data (100, (char *) p16, 16);
 #endif
@@ -984,9 +968,9 @@ nt_lm_owf_gen (char *pwd, uchar nt_p16[16], uchar p16[16])
 
 /* Does the des encryption from the NT or LM MD4 hash. */
 void
-SMBOWFencrypt (uchar passwd[16], uchar * c8, uchar p24[24])
+SMBOWFencrypt (uschar passwd[16], uschar * c8, uschar p24[24])
 {
-  uchar p21[21];
+  uschar p21[21];
 
   memset (p21, '\0', 21);
 
@@ -996,9 +980,9 @@ SMBOWFencrypt (uchar passwd[16], uchar * c8, uchar p24[24])
 
 /* Does the des encryption from the FIRST 8 BYTES of the NT or LM MD4 hash. */
 void
-NTLMSSPOWFencrypt (uchar passwd[8], uchar * ntlmchalresp, uchar p24[24])
+NTLMSSPOWFencrypt (uschar passwd[8], uschar * ntlmchalresp, uschar p24[24])
 {
-  uchar p21[21];
+  uschar p21[21];
 
   memset (p21, '\0', 21);
   memcpy (p21, passwd, 8);
@@ -1006,7 +990,7 @@ NTLMSSPOWFencrypt (uchar passwd[8], uchar * ntlmchalresp, uchar p24[24])
 
   E_P24 (p21, ntlmchalresp, p24);
 #ifdef DEBUG_PASSWORD
-  DEBUG (100, ("NTLMSSPOWFencrypt: p21, c8, p24\n"));
+  DEBUG_X (100, ("NTLMSSPOWFencrypt: p21, c8, p24\n"));
   dump_data (100, (char *) p21, 21);
   dump_data (100, (char *) ntlmchalresp, 8);
   dump_data (100, (char *) p24, 24);
@@ -1017,9 +1001,9 @@ NTLMSSPOWFencrypt (uchar passwd[8], uchar * ntlmchalresp, uchar p24[24])
 /* Does the NT MD4 hash then des encryption. */
 
 void
-spa_smb_nt_encrypt (uchar * passwd, uchar * c8, uchar * p24)
+spa_smb_nt_encrypt (uschar * passwd, uschar * c8, uschar * p24)
 {
-  uchar p21[21];
+  uschar p21[21];
 
   memset (p21, '\0', 21);
 
@@ -1027,7 +1011,7 @@ spa_smb_nt_encrypt (uchar * passwd, uchar * c8, uchar * p24)
   SMBOWFencrypt (p21, c8, p24);
 
 #ifdef DEBUG_PASSWORD
-  DEBUG (100, ("spa_smb_nt_encrypt: nt#, challenge, response\n"));
+  DEBUG_X (100, ("spa_smb_nt_encrypt: nt#, challenge, response\n"));
   dump_data (100, (char *) p21, 16);
   dump_data (100, (char *) c8, 8);
   dump_data (100, (char *) p24, 24);
@@ -1147,7 +1131,7 @@ spa_mdfour64 (uint32x * M)
 }
 
 static void
-copy64 (uint32x * M, unsigned char *in)
+copy64 (uint32x * M, uschar *in)
 {
   int i;
 
@@ -1157,7 +1141,7 @@ copy64 (uint32x * M, unsigned char *in)
 }
 
 static void
-copy4 (unsigned char *out, uint32x x)
+copy4 (uschar *out, uint32x x)
 {
   out[0] = x & 0xFF;
   out[1] = (x >> 8) & 0xFF;
@@ -1167,9 +1151,9 @@ copy4 (unsigned char *out, uint32x x)
 
 /* produce a md4 message digest from data of length n bytes */
 void
-mdfour (unsigned char *out, unsigned char *in, int n)
+mdfour (uschar *out, uschar *in, int n)
 {
-  unsigned char buf[128];
+  uschar buf[128];
   uint32x M[16];
   uint32x b = n * 8;
   int i;
@@ -1257,13 +1241,13 @@ else \
 char *p = string; \
 int len = 0; \
 if (p) len = strlen(p); \
-spa_bytes_add(ptr, header, ((unsigned char*)p), len); \
+spa_bytes_add(ptr, header, (US p), len); \
 }
 
 #define spa_unicode_add_string(ptr, header, string) \
 { \
 char *p = string; \
-unsigned char *b = NULL; \
+uschar *b = NULL; \
 int len = 0; \
 if (p) \
   { \
@@ -1278,12 +1262,15 @@ spa_bytes_add(ptr, header, b, len*2); \
 unicodeToString(((char*)structPtr) + IVAL(&structPtr->header.offset,0) , SVAL(&structPtr->header.len,0)/2)
 #define GetString(structPtr, header) \
 toString((((char *)structPtr) + IVAL(&structPtr->header.offset,0)), SVAL(&structPtr->header.len,0))
+
+#ifdef notdef
+
 #define DumpBuffer(fp, structPtr, header) \
-dumpRaw(fp,((unsigned char*)structPtr)+IVAL(&structPtr->header.offset,0),SVAL(&structPtr->header.len,0))
+dumpRaw(fp,(US structPtr)+IVAL(&structPtr->header.offset,0),SVAL(&structPtr->header.len,0))
 
 
 static void
-dumpRaw (FILE * fp, unsigned char *buf, size_t len)
+dumpRaw (FILE * fp, uschar *buf, size_t len)
 {
   int i;
 
@@ -1293,6 +1280,8 @@ dumpRaw (FILE * fp, unsigned char *buf, size_t len)
   fprintf (fp, "\n");
 }
 
+#endif
+
 char *
 unicodeToString (char *p, size_t len)
 {
@@ -1311,10 +1300,10 @@ unicodeToString (char *p, size_t len)
   return buf;
 }
 
-static unsigned char *
+static uschar *
 strToUnicode (char *p)
 {
-  static unsigned char buf[1024];
+  static uschar buf[1024];
   size_t l = strlen (p);
   int i = 0;
 
@@ -1329,10 +1318,10 @@ strToUnicode (char *p)
   return buf;
 }
 
-static unsigned char *
+static uschar *
 toString (char *p, size_t len)
 {
-  static unsigned char buf[1024];
+  static uschar buf[1024];
 
   assert (len + 1 < sizeof buf);
 
@@ -1341,6 +1330,8 @@ toString (char *p, size_t len)
   return buf;
 }
 
+#ifdef notdef
+
 void
 dumpSmbNtlmAuthRequest (FILE * fp, SPAAuthRequest * request)
 {
@@ -1381,6 +1372,7 @@ dumpSmbNtlmAuthResponse (FILE * fp, SPAAuthResponse * response)
   DumpBuffer (fp, response, sessionKey);
   fprintf (fp, "      Flags = %08x\n", IVAL (&response->flags, 0));
 }
+#endif
 
 void
 spa_build_auth_request (SPAAuthRequest * request, char *user, char *domain)
@@ -1432,7 +1424,7 @@ spa_build_auth_challenge (SPAAuthRequest * request, SPAAuthChallenge * challenge
   /* generate eight pseudo random bytes (method ripped from host.c) */
 
   for(i=0;i<8;i++) {
-    chalstr[i] = (unsigned char)(random_seed >> 16) % 256;
+    chalstr[i] = (uschar)(random_seed >> 16) % 256;
     random_seed = (1103515245 - (chalstr[i])) * random_seed + 12345;
   };
 
@@ -1467,8 +1459,8 @@ spa_build_auth_response (SPAAuthChallenge * challenge,
       *p = '\0';
     }
 
-  spa_smb_encrypt ((uchar *)password, challenge->challengeData, lmRespData);
-  spa_smb_nt_encrypt ((uchar *)password, challenge->challengeData, ntRespData);
+  spa_smb_encrypt (US password, challenge->challengeData, lmRespData);
+  spa_smb_nt_encrypt (US password, challenge->challengeData, ntRespData);
 
   response->bufIndex = 0;
   memcpy (response->ident, "NTLMSSP\0\0\0", 8);
@@ -1514,8 +1506,8 @@ spa_build_auth_response (SPAAuthChallenge * challenge,
     (const char *)GetUnicodeString(challenge, uDomain) :
     (const char *)GetString(challenge, uDomain));
 
-  spa_smb_encrypt ((uchar *)password, challenge->challengeData, lmRespData);
-  spa_smb_nt_encrypt ((uchar *)password, challenge->challengeData, ntRespData);
+  spa_smb_encrypt (US password, challenge->challengeData, lmRespData);
+  spa_smb_nt_encrypt (US password, challenge->challengeData, ntRespData);
 
   response->bufIndex = 0;
   memcpy (response->ident, "NTLMSSP\0\0\0", 8);
diff --git a/src/auths/b64decode.c b/src/auths/b64decode.c
deleted file mode 100644 (file)
index 0a3c2ec..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-/*************************************************
-*     Exim - an Internet mail transport agent    *
-*************************************************/
-
-/* Copyright (c) University of Cambridge 1995 - 2009 */
-/* See the file NOTICE for conditions of use and distribution. */
-
-#include "../exim.h"
-
-
-/*************************************************
-*          Decode byte-string in base 64         *
-*************************************************/
-
-/* This function decodes a string in base 64 format as defined in RFC 2045
-(MIME) and required by the SMTP AUTH extension (RFC 2554). The decoding
-algorithm is written out in a straightforward way. Turning it into some kind of
-compact loop is messy and would probably run more slowly.
-
-Arguments:
-  code        points to the coded string, zero-terminated
-  ptr         where to put the pointer to the result, which is in
-              dynamic store, and zero-terminated
-
-Returns:      the number of bytes in the result,
-              or -1 if the input was malformed
-
-A zero is added on to the end to make it easy in cases where the result is to
-be interpreted as text. This is not included in the count. */
-
-static uschar dec64table[] = {
-  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, /*  0-15 */
-  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, /* 16-31 */
-  255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63, /* 32-47 */
-   52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255,255,255,255, /* 48-63 */
-  255,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, /* 64-79 */
-   15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255, /* 80-95 */
-  255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 96-111 */
-   41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255  /* 112-127*/
-};
-
-int
-auth_b64decode(uschar *code, uschar **ptr)
-{
-register int x, y;
-uschar *result = store_get(3*(Ustrlen(code)/4) + 1);
-
-*ptr = result;
-
-/* Each cycle of the loop handles a quantum of 4 input bytes. For the last
-quantum this may decode to 1, 2, or 3 output bytes. */
-
-while ((x = (*code++)) != 0)
-  {
-  if (x > 127 || (x = dec64table[x]) == 255) return -1;
-  if ((y = (*code++)) == 0 || (y = dec64table[y]) == 255)
-    return -1;
-  *result++ = (x << 2) | (y >> 4);
-
-  if ((x = (*code++)) == '=')
-    {
-    if (*code++ != '=' || *code != 0) return -1;
-    }
-  else
-    {
-    if (x > 127 || (x = dec64table[x]) == 255) return -1;
-    *result++ = (y << 4) | (x >> 2);
-    if ((y = (*code++)) == '=')
-      {
-      if (*code != 0) return -1;
-      }
-    else
-      {
-      if (y > 127 || (y = dec64table[y]) == 255) return -1;
-      *result++ = (x << 6) | y;
-      }
-    }
-  }
-
-*result = 0;
-return result - *ptr;
-}
-
-/* End of b64decode.c */
diff --git a/src/auths/b64encode.c b/src/auths/b64encode.c
deleted file mode 100644 (file)
index 509590c..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-/*************************************************
-*     Exim - an Internet mail transport agent    *
-*************************************************/
-
-/* Copyright (c) University of Cambridge 1995 - 2009 */
-/* See the file NOTICE for conditions of use and distribution. */
-
-#include "../exim.h"
-
-
-/*************************************************
-*          Encode byte-string in base 64         *
-*************************************************/
-
-/* This function encodes a string of bytes, containing any values whatsoever,
-in base 64 as defined in RFC 2045 (MIME) and required by the SMTP AUTH
-extension (RFC 2554). The encoding algorithm is written out in a
-straightforward way. Turning it into some kind of compact loop is messy and
-would probably run more slowly.
-
-Arguments:
-  clear       points to the clear text bytes
-  len         the number of bytes to encode
-
-Returns:      a pointer to the zero-terminated base 64 string, which
-              is in working store
-*/
-
-static uschar *enc64table =
-  US"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-
-uschar *
-auth_b64encode(uschar *clear, int len)
-{
-uschar *code = store_get(4*((len+2)/3) + 1);
-uschar *p = code;
-
-while (len-- >0)
-  {
-  register int x, y;
-
-  x = *clear++;
-  *p++ = enc64table[(x >> 2) & 63];
-
-  if (len-- <= 0)
-    {
-    *p++ = enc64table[(x << 4) & 63];
-    *p++ = '=';
-    *p++ = '=';
-    break;
-    }
-
-  y = *clear++;
-  *p++ = enc64table[((x << 4) | ((y >> 4) & 15)) & 63];
-
-  if (len-- <= 0)
-    {
-    *p++ = enc64table[(y << 2) & 63];
-    *p++ = '=';
-    break;
-    }
-
-  x = *clear++;
-  *p++ = enc64table[((y << 2) | ((x >> 6) & 3)) & 63];
-
-  *p++ = enc64table[x & 63];
-  }
-
-*p = 0;
-
-return code;
-}
-
-/* End of b64encode.c */
index 710de7d..b4677ec 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -36,7 +36,7 @@ data pointer passed to the conversation function. However, I was unable to get
 this to work on Solaris 2.6, so static variables are used instead. */
 
 static int pam_conv_had_error;
-static uschar *pam_args;
+static const uschar *pam_args;
 static BOOL pam_arg_ended;
 
 
@@ -129,7 +129,7 @@ Returns:   OK if authentication succeeded
 */
 
 int
-auth_call_pam(uschar *s, uschar **errptr)
+auth_call_pam(const uschar *s, uschar **errptr)
 {
 pam_handle_t *pamh = NULL;
 struct pam_conv pamc;
index a4567c1..089f501 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* This module contains interface functions to the two Cyrus authentication
@@ -88,8 +88,8 @@ Returns:   OK if authentication succeeded
 */
 
 int
-auth_call_saslauthd(uschar *username, uschar *password, uschar *service,
-  uschar *realm, uschar **errptr)
+auth_call_saslauthd(const uschar *username, const uschar *password,
+  const uschar *service, const uschar *realm, uschar **errptr)
 {
 uschar *reply = NULL;
 
index 2064ed2..c363743 100644 (file)
@@ -2,12 +2,22 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* This file was originally supplied by Ian Kirk. The libradius support came
 from Alex Kiernan. */
 
+/* ugly hack to work around redefinition of ENV by radiusclient.h and
+ * db.h: define _DB_H_ so the db.h include thinks it's already included,
+ * we can get away with it like this, since this file doesn't use any db
+ * functions. */
+#ifndef _DB_H_
+# define _DB_H_ 1
+# define _DB_EXT_PROT_IN_ 1
+# define DB void
+#endif
+
 #include "../exim.h"
 
 /* This module contains functions that call the Radius authentication
@@ -36,9 +46,14 @@ using its original API. At release 0.4.0 the API changed. */
   #include <radlib.h>
 #else
   #if !defined(RADIUS_LIB_RADIUSCLIENT) && !defined(RADIUS_LIB_RADIUSCLIENTNEW)
-  #define RADIUS_LIB_RADIUSCLIENT
+  # define RADIUS_LIB_RADIUSCLIENT
+  #endif
+
+  #ifdef RADIUS_LIB_RADIUSCLIENTNEW
+  # include <freeradius-client.h>
+  #else
+  # include <radiusclient.h>
   #endif
-  #include <radiusclient.h>
 #endif
 
 
@@ -60,10 +75,10 @@ Returns:   OK if authentication succeeded
 */
 
 int
-auth_call_radius(uschar *s, uschar **errptr)
+auth_call_radius(const uschar *s, uschar **errptr)
 {
 uschar *user;
-uschar *radius_args = s;
+const uschar *radius_args = s;
 int result;
 int sep = 0;
 
@@ -150,6 +165,7 @@ switch (result)
   case OK_RC:
   return OK;
 
+  case REJECT_RC:
   case ERROR_RC:
   return FAIL;
 
index f744a89..1ae38a9 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -38,7 +38,7 @@ address can appear in the tables drtables.c. */
 int auth_cram_md5_options_count =
   sizeof(auth_cram_md5_options)/sizeof(optionlist);
 
-/* Default private options block for the contidion authentication method. */
+/* Default private options block for the condition authentication method. */
 
 auth_cram_md5_options_block auth_cram_md5_option_defaults = {
   NULL,             /* server_secret */
@@ -73,7 +73,7 @@ if (ob->client_secret != NULL)
 
 
 /*************************************************
-*      Peform the CRAM-MD5 algorithm             *
+*      Perform the CRAM-MD5 algorithm            *
 *************************************************/
 
 /* The CRAM-MD5 algorithm is described in RFC 2195. It computes
@@ -172,7 +172,7 @@ if (*data != 0) return UNEXPECTED;
 /* Send the challenge, read the return */
 
 if ((rc = auth_get_data(&data, challenge, Ustrlen(challenge))) != OK) return rc;
-if ((len = auth_b64decode(data, &clear)) < 0) return BAD64;
+if ((len = b64decode(data, &clear)) < 0) return BAD64;
 
 /* The return consists of a user name, space-separated from the CRAM-MD5
 digest, expressed in hex. Extract the user name and put it in $auth1 and $1.
@@ -261,9 +261,9 @@ int i;
 uschar digest[16];
 
 /* If expansion of either the secret or the user name failed, return CANCELLED
-or ERROR, as approriate. */
+or ERROR, as appropriate. */
 
-if (secret == NULL || name == NULL)
+if (!secret || !name)
   {
   if (expand_string_forcedfail)
     {
@@ -272,7 +272,7 @@ if (secret == NULL || name == NULL)
     }
   string_format(buffer, buffsize, "expansion of \"%s\" failed in "
     "%s authenticator: %s",
-    (secret == NULL)? ob->client_secret : ob->client_name,
+    !secret ? ob->client_secret : ob->client_name,
     ablock->name, expand_string_message);
   return ERROR;
   }
@@ -282,10 +282,10 @@ in base 64. */
 
 if (smtp_write_command(outblock, FALSE, "AUTH %s\r\n", ablock->public_name) < 0)
   return FAIL_SEND;
-if (smtp_read_response(inblock, (uschar *)buffer, buffsize, '3', timeout) < 0)
+if (!smtp_read_response(inblock, buffer, buffsize, '3', timeout))
   return FAIL;
 
-if (auth_b64decode(buffer + 4, &challenge) < 0)
+if (b64decode(buffer + 4, &challenge) < 0)
   {
   string_format(buffer, buffsize, "bad base 64 string in challenge: %s",
     big_buffer + 4);
@@ -299,8 +299,7 @@ compute_cram_md5(secret, challenge, digest);
 /* Create the response from the user name plus the CRAM-MD5 digest */
 
 string_format(big_buffer, big_buffer_size - 36, "%s", name);
-p = big_buffer;
-while (*p != 0) p++;
+for (p = big_buffer; *p; ) p++;
 *p++ = ' ';
 
 for (i = 0; i < 16; i++)
@@ -310,15 +309,15 @@ for (i = 0; i < 16; i++)
   }
 
 /* Send the response, in base 64, and check the result. The response is
-in big_buffer, but auth_b64encode() returns its result in working store,
+in big_buffer, but b64encode() returns its result in working store,
 so calling smtp_write_command(), which uses big_buffer, is OK. */
 
 buffer[0] = 0;
-if (smtp_write_command(outblock, FALSE, "%s\r\n", auth_b64encode(big_buffer,
+if (smtp_write_command(outblock, FALSE, "%s\r\n", b64encode(big_buffer,
   p - big_buffer)) < 0) return FAIL_SEND;
 
-return smtp_read_response(inblock, (uschar *)buffer, buffsize, '2', timeout)?
-  OK : FAIL;
+return smtp_read_response(inblock, (uschar *)buffer, buffsize, '2', timeout)
+  OK : FAIL;
 }
 #endif  /* STAND_ALONE */
 
index c7fb593..bab2be3 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* This code was originally contributed by Matthew Byng-Maddick */
@@ -97,7 +97,7 @@ auth_cyrus_sasl_init(auth_instance *ablock)
 {
 auth_cyrus_sasl_options_block *ob =
   (auth_cyrus_sasl_options_block *)(ablock->options_block);
-uschar *list, *listptr, *buffer;
+const uschar *list, *listptr, *buffer;
 int rc, i;
 unsigned int len;
 uschar *rs_point, *expanded_hostname;
@@ -146,7 +146,7 @@ if( rc != SASL_OK )
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
       "couldn't initialise Cyrus SASL server connection.", ablock->name);
 
-rc=sasl_listmech(conn, NULL, "", ":", "", (const char **)(&list), &len, &i);
+rc=sasl_listmech(conn, NULL, "", ":", "", (const char **)&list, &len, &i);
 if( rc != SASL_OK )
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
       "couldn't get Cyrus SASL mechanism list.", ablock->name);
@@ -228,7 +228,7 @@ if((hname == NULL) ||
 
 if(inlen)
   {
-  clen=auth_b64decode(input, &clear);
+  clen = b64decode(input, &clear);
   if(clen < 0)
     {
     return BAD64;
@@ -363,7 +363,7 @@ while(rc==SASL_CONTINUE)
     HDEBUG(D_auth) debug=string_copy(input);
     if(inlen)
       {
-      clen=auth_b64decode(input, &clear);
+      clen = b64decode(input, &clear);
       if(clen < 0)
        {
         sasl_dispose(&conn);
index 1874f32..5bf7b9c 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (c) 2004 Andrey Panin <pazke@donpac.ru>
- * Copyright (c) 2006-2014 The Exim Maintainers
+ * Copyright (c) 2006-2016 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
@@ -228,232 +228,270 @@ return s;
 *              Server entry point                *
 *************************************************/
 
-int auth_dovecot_server(auth_instance *ablock, uschar *data)
+int
+auth_dovecot_server(auth_instance * ablock, uschar * data)
 {
-       auth_dovecot_options_block *ob =
-               (auth_dovecot_options_block *)(ablock->options_block);
-       struct sockaddr_un sa;
-       uschar buffer[DOVECOT_AUTH_MAXLINELEN];
-       uschar *args[DOVECOT_AUTH_MAXFIELDCOUNT];
-       uschar *auth_command;
-       uschar *auth_extra_data = US"";
-       uschar *p;
-       int nargs, tmp;
-       int crequid = 1, cont = 1, fd, ret = DEFER;
-       BOOL found = FALSE;
-
-       HDEBUG(D_auth) debug_printf("dovecot authentication\n");
-
-       memset(&sa, 0, sizeof(sa));
-       sa.sun_family = AF_UNIX;
-
-       /* This was the original code here: it is nonsense because strncpy()
-       does not return an integer. I have converted this to use the function
-       that formats and checks length. PH */
-
-       /*
-       if (strncpy(sa.sun_path, ob->server_socket, sizeof(sa.sun_path)) < 0) {
-       */
-
-       if (!string_format(US sa.sun_path, sizeof(sa.sun_path), "%s",
-                          ob->server_socket)) {
-               auth_defer_msg = US"authentication socket path too long";
-               return DEFER;
-       }
-
-       auth_defer_msg = US"authentication socket connection error";
-
-       fd = socket(PF_UNIX, SOCK_STREAM, 0);
-       if (fd < 0)
-               return DEFER;
-
-       if (connect(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0)
-               goto out;
+auth_dovecot_options_block *ob =
+       (auth_dovecot_options_block *) ablock->options_block;
+struct sockaddr_un sa;
+uschar buffer[DOVECOT_AUTH_MAXLINELEN];
+uschar *args[DOVECOT_AUTH_MAXFIELDCOUNT];
+uschar *auth_command;
+uschar *auth_extra_data = US"";
+uschar *p;
+int nargs, tmp;
+int crequid = 1, cont = 1, fd = -1, ret = DEFER;
+BOOL found = FALSE, have_mech_line = FALSE;
+
+HDEBUG(D_auth) debug_printf("dovecot authentication\n");
+
+if (!data)
+  {
+  ret = FAIL;
+  goto out;
+  }
 
-       auth_defer_msg = US"authentication socket protocol error";
+memset(&sa, 0, sizeof(sa));
+sa.sun_family = AF_UNIX;
 
-       socket_buffer_left = 0;  /* Global, used to read more than a line but return by line */
-       while (cont) {
-               if (dc_gets(buffer, sizeof(buffer), fd) == NULL)
-                       OUT("authentication socket read error or premature eof");
-               p = buffer + Ustrlen(buffer) - 1;
-               if (*p != '\n') {
-                       OUT("authentication socket protocol line too long");
-               }
-               *p = '\0';
-               HDEBUG(D_auth) debug_printf("received: %s\n", buffer);
-               nargs = strcut(buffer, args, sizeof(args) / sizeof(args[0]));
-               /* HDEBUG(D_auth) debug_strcut(args, nargs, sizeof(args) / sizeof(args[0])); */
-
-               /* Code below rewritten by Kirill Miazine (km@krot.org). Only check commands that
-                  Exim will need. Original code also failed if Dovecot server sent unknown
-                  command. E.g. COOKIE in version 1.1 of the protocol would cause troubles. */
-               /* pdp: note that CUID is a per-connection identifier sent by the server,
-                  which increments at server discretion.
-                  By contrast, the "id" field of the protocol is a connection-specific request
-                  identifier, which needs to be unique per request from the client and is not
-                  connected to the CUID value, so we ignore CUID from server.  It's purely for
-                  diagnostics. */
-               if (Ustrcmp(args[0], US"VERSION") == 0) {
-                       CHECK_COMMAND("VERSION", 2, 2);
-                       if (Uatoi(args[1]) != VERSION_MAJOR)
-                               OUT("authentication socket protocol version mismatch");
-               } else if (Ustrcmp(args[0], US"MECH") == 0) {
-                       CHECK_COMMAND("MECH", 1, INT_MAX);
-                       if (strcmpic(US args[1], ablock->public_name) == 0)
-                               found = TRUE;
-               } else if (Ustrcmp(args[0], US"DONE") == 0) {
-                       CHECK_COMMAND("DONE", 0, 0);
-                       cont = 0;
-               }
-       }
+/* This was the original code here: it is nonsense because strncpy()
+does not return an integer. I have converted this to use the function
+that formats and checks length. PH */
 
-       if (!found) {
-               auth_defer_msg = string_sprintf("Dovecot did not advertise mechanism \"%s\" to us", ablock->public_name);
-               goto out;
-       }
+/*
+if (strncpy(sa.sun_path, ob->server_socket, sizeof(sa.sun_path)) < 0) {
+}
+*/
 
-       /* Added by PH: data must not contain tab (as it is
-       b64 it shouldn't, but check for safety). */
+if (!string_format(US sa.sun_path, sizeof(sa.sun_path), "%s",
+                 ob->server_socket))
+  {
+  auth_defer_msg = US"authentication socket path too long";
+  return DEFER;
+  }
 
-       if (Ustrchr(data, '\t') != NULL) {
-               ret = FAIL;
-               goto out;
-       }
+auth_defer_msg = US"authentication socket connection error";
 
-       /* Added by PH: extra fields when TLS is in use or if the TCP/IP
-       connection is local. */
+if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
+  return DEFER;
 
-       if (tls_in.cipher != NULL)
-               auth_extra_data = string_sprintf("secured\t%s%s",
-                   tls_in.certificate_verified? "valid-client-cert" : "",
-                   tls_in.certificate_verified? "\t" : "");
-       else if (interface_address != NULL &&
-                Ustrcmp(sender_host_address, interface_address) == 0)
-               auth_extra_data = US"secured\t";
+if (connect(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0)
+  goto out;
 
+auth_defer_msg = US"authentication socket protocol error";
 
-/****************************************************************************
-   The code below was the original code here. It didn't work. A reading of the
-   file auth-protocol.txt.gz that came with Dovecot 1.0_beta8 indicated that
-   this was not right. Maybe something changed. I changed it to move the
-   service indication into the AUTH command, and it seems to be better. PH
-
-       fprintf(f, "VERSION\t%d\t%d\r\nSERVICE\tSMTP\r\nCPID\t%d\r\n"
-               "AUTH\t%d\t%s\trip=%s\tlip=%s\tresp=%s\r\n",
-               VERSION_MAJOR, VERSION_MINOR, getpid(), cuid,
-               ablock->public_name, sender_host_address, interface_address,
-               data ? (char *) data : "");
-
-   Subsequently, the command was modified to add "secured" and "valid-client-
-   cert" when relevant.
-****************************************************************************/
+socket_buffer_left = 0;  /* Global, used to read more than a line but return by line */
+while (cont)
+  {
+  if (dc_gets(buffer, sizeof(buffer), fd) == NULL)
+    OUT("authentication socket read error or premature eof");
+  p = buffer + Ustrlen(buffer) - 1;
+  if (*p != '\n')
+    OUT("authentication socket protocol line too long");
+
+  *p = '\0';
+  HDEBUG(D_auth) debug_printf("received: %s\n", buffer);
+
+  nargs = strcut(buffer, args, sizeof(args) / sizeof(args[0]));
+
+  /* HDEBUG(D_auth) debug_strcut(args, nargs, sizeof(args) / sizeof(args[0])); */
+
+  /* Code below rewritten by Kirill Miazine (km@krot.org). Only check commands that
+    Exim will need. Original code also failed if Dovecot server sent unknown
+    command. E.g. COOKIE in version 1.1 of the protocol would cause troubles. */
+  /* pdp: note that CUID is a per-connection identifier sent by the server,
+    which increments at server discretion.
+    By contrast, the "id" field of the protocol is a connection-specific request
+    identifier, which needs to be unique per request from the client and is not
+    connected to the CUID value, so we ignore CUID from server.  It's purely for
+    diagnostics. */
+
+  if (Ustrcmp(args[0], US"VERSION") == 0)
+    {
+    CHECK_COMMAND("VERSION", 2, 2);
+    if (Uatoi(args[1]) != VERSION_MAJOR)
+      OUT("authentication socket protocol version mismatch");
+    }
+  else if (Ustrcmp(args[0], US"MECH") == 0)
+    {
+    CHECK_COMMAND("MECH", 1, INT_MAX);
+    have_mech_line = TRUE;
+    if (strcmpic(US args[1], ablock->public_name) == 0)
+      found = TRUE;
+    }
+  else if (Ustrcmp(args[0], US"SPID") == 0)
+    {
+    /* Unfortunately the auth protocol handshake wasn't designed well
+    to differentiate between auth-client/userdb/master. auth-userdb
+    and auth-master send VERSION + SPID lines only and nothing
+    afterwards, while auth-client sends VERSION + MECH + SPID +
+    CUID + more. The simplest way that we can determine if we've
+    connected to the correct socket is to see if MECH line exists or
+    not (alternatively we'd have to have a small timeout after SPID
+    to see if CUID is sent or not). */
+
+    if (!have_mech_line)
+      OUT("authentication socket type mismatch"
+       " (connected to auth-master instead of auth-client)");
+    }
+  else if (Ustrcmp(args[0], US"DONE") == 0)
+    {
+    CHECK_COMMAND("DONE", 0, 0);
+    cont = 0;
+    }
+  }
 
-       auth_command = string_sprintf("VERSION\t%d\t%d\nCPID\t%d\n"
-               "AUTH\t%d\t%s\tservice=smtp\t%srip=%s\tlip=%s\tnologin\tresp=%s\n",
-               VERSION_MAJOR, VERSION_MINOR, getpid(), crequid,
-               ablock->public_name, auth_extra_data, sender_host_address,
-               interface_address, data ? (char *) data : "");
+if (!found)
+  {
+  auth_defer_msg = string_sprintf(
+    "Dovecot did not advertise mechanism \"%s\" to us", ablock->public_name);
+  goto out;
+  }
 
-       if (write(fd, auth_command, Ustrlen(auth_command)) < 0)
-              HDEBUG(D_auth) debug_printf("error sending auth_command: %s\n",
-                strerror(errno));
+/* Added by PH: data must not contain tab (as it is
+b64 it shouldn't, but check for safety). */
 
-       HDEBUG(D_auth) debug_printf("sent: %s", auth_command);
+if (Ustrchr(data, '\t') != NULL)
+  {
+  ret = FAIL;
+  goto out;
+  }
 
-       while (1) {
-               uschar *temp;
-               uschar *auth_id_pre = NULL;
-               int i;
+/* Added by PH: extra fields when TLS is in use or if the TCP/IP
+connection is local. */
 
-               if (dc_gets(buffer, sizeof(buffer), fd) == NULL) {
-                       auth_defer_msg = US"authentication socket read error or premature eof";
-                       goto out;
-               }
+if (tls_in.cipher != NULL)
+  auth_extra_data = string_sprintf("secured\t%s%s",
+     tls_in.certificate_verified? "valid-client-cert" : "",
+     tls_in.certificate_verified? "\t" : "");
 
-               buffer[Ustrlen(buffer) - 1] = 0;
-               HDEBUG(D_auth) debug_printf("received: %s\n", buffer);
-               nargs = strcut(buffer, args, sizeof(args) / sizeof(args[0]));
+else if (  interface_address != NULL
+        && Ustrcmp(sender_host_address, interface_address) == 0)
+  auth_extra_data = US"secured\t";
 
-               if (Uatoi(args[1]) != crequid)
-                       OUT("authentication socket connection id mismatch");
 
-               switch (toupper(*args[0])) {
-               case 'C':
-                       CHECK_COMMAND("CONT", 1, 2);
+/****************************************************************************
+The code below was the original code here. It didn't work. A reading of the
+file auth-protocol.txt.gz that came with Dovecot 1.0_beta8 indicated that
+this was not right. Maybe something changed. I changed it to move the
+service indication into the AUTH command, and it seems to be better. PH
+
+fprintf(f, "VERSION\t%d\t%d\r\nSERVICE\tSMTP\r\nCPID\t%d\r\n"
+       "AUTH\t%d\t%s\trip=%s\tlip=%s\tresp=%s\r\n",
+       VERSION_MAJOR, VERSION_MINOR, getpid(), cuid,
+       ablock->public_name, sender_host_address, interface_address,
+       data ? (char *) data : "");
+
+Subsequently, the command was modified to add "secured" and "valid-client-
+cert" when relevant.
+****************************************************************************/
 
-                       tmp = auth_get_no64_data(&data, US args[2]);
-                       if (tmp != OK) {
-                               ret = tmp;
-                               goto out;
-                       }
+auth_command = string_sprintf("VERSION\t%d\t%d\nCPID\t%d\n"
+       "AUTH\t%d\t%s\tservice=smtp\t%srip=%s\tlip=%s\tnologin\tresp=%s\n",
+       VERSION_MAJOR, VERSION_MINOR, getpid(), crequid,
+       ablock->public_name, auth_extra_data, sender_host_address,
+       interface_address, data);
 
-                       /* Added by PH: data must not contain tab (as it is
-                       b64 it shouldn't, but check for safety). */
+if (write(fd, auth_command, Ustrlen(auth_command)) < 0)
+  HDEBUG(D_auth) debug_printf("error sending auth_command: %s\n",
+    strerror(errno));
 
-                       if (Ustrchr(data, '\t') != NULL) {
-                               ret = FAIL;
-                               goto out;
-                       }
+HDEBUG(D_auth) debug_printf("sent: %s", auth_command);
 
-                       temp = string_sprintf("CONT\t%d\t%s\n", crequid, data);
-                       if (write(fd, temp, Ustrlen(temp)) < 0)
-                               OUT("authentication socket write error");
-                       break;
-
-               case 'F':
-                       CHECK_COMMAND("FAIL", 1, -1);
-
-                       for (i=2; (i<nargs) && (auth_id_pre == NULL); i++)
-                       {
-                               if ( Ustrncmp(args[i], US"user=", 5) == 0 )
-                               {
-                                       auth_id_pre = args[i]+5;
-                                       expand_nstring[1] = auth_vars[0] =
-                                               string_copy(auth_id_pre); /* PH */
-                                       expand_nlength[1] = Ustrlen(auth_id_pre);
-                                       expand_nmax = 1;
-                               }
-                       }
+while (1)
+  {
+  uschar *temp;
+  uschar *auth_id_pre = NULL;
+  int i;
 
-                       ret = FAIL;
-                       goto out;
-
-               case 'O':
-                       CHECK_COMMAND("OK", 2, -1);
-
-                       /*
-                        * Search for the "user=$USER" string in the args array
-                        * and return the proper value.
-                        */
-                       for (i=2; (i<nargs) && (auth_id_pre == NULL); i++)
-                       {
-                               if ( Ustrncmp(args[i], US"user=", 5) == 0 )
-                               {
-                                       auth_id_pre = args[i]+5;
-                                       expand_nstring[1] = auth_vars[0] =
-                                               string_copy(auth_id_pre); /* PH */
-                                       expand_nlength[1] = Ustrlen(auth_id_pre);
-                                       expand_nmax = 1;
-                               }
-                       }
+  if (dc_gets(buffer, sizeof(buffer), fd) == NULL)
+    {
+    auth_defer_msg = US"authentication socket read error or premature eof";
+    goto out;
+    }
 
-                       if (auth_id_pre == NULL)
-                               OUT("authentication socket protocol error, username missing");
+  buffer[Ustrlen(buffer) - 1] = 0;
+  HDEBUG(D_auth) debug_printf("received: %s\n", buffer);
+  nargs = strcut(buffer, args, sizeof(args) / sizeof(args[0]));
 
-                       ret = OK;
-                       /* fallthrough */
+  if (Uatoi(args[1]) != crequid)
+    OUT("authentication socket connection id mismatch");
 
-               default:
-                       goto out;
-               }
-       }
+  switch (toupper(*args[0]))
+    {
+    case 'C':
+      CHECK_COMMAND("CONT", 1, 2);
+
+      if ((tmp = auth_get_no64_data(&data, US args[2])) != OK)
+       {
+       ret = tmp;
+       goto out;
+       }
+
+      /* Added by PH: data must not contain tab (as it is
+      b64 it shouldn't, but check for safety). */
+
+      if (Ustrchr(data, '\t') != NULL)
+        {
+       ret = FAIL;
+       goto out;
+       }
+
+      temp = string_sprintf("CONT\t%d\t%s\n", crequid, data);
+      if (write(fd, temp, Ustrlen(temp)) < 0)
+       OUT("authentication socket write error");
+      break;
+
+    case 'F':
+      CHECK_COMMAND("FAIL", 1, -1);
+
+      for (i=2; (i<nargs) && (auth_id_pre == NULL); i++)
+       {
+       if ( Ustrncmp(args[i], US"user=", 5) == 0 )
+         {
+         auth_id_pre = args[i]+5;
+         expand_nstring[1] = auth_vars[0] = string_copy(auth_id_pre); /* PH */
+         expand_nlength[1] = Ustrlen(auth_id_pre);
+         expand_nmax = 1;
+         }
+       }
+
+      ret = FAIL;
+      goto out;
+
+    case 'O':
+      CHECK_COMMAND("OK", 2, -1);
+
+      /* Search for the "user=$USER" string in the args array
+      and return the proper value.  */
+
+      for (i=2; (i<nargs) && (auth_id_pre == NULL); i++)
+       {
+       if ( Ustrncmp(args[i], US"user=", 5) == 0 )
+         {
+         auth_id_pre = args[i]+5;
+         expand_nstring[1] = auth_vars[0] = string_copy(auth_id_pre); /* PH */
+         expand_nlength[1] = Ustrlen(auth_id_pre);
+         expand_nmax = 1;
+         }
+       }
+
+      if (auth_id_pre == NULL)
+        OUT("authentication socket protocol error, username missing");
+
+      ret = OK;
+      /* fallthrough */
+
+    default:
+      goto out;
+    }
+  }
 
 out:
-       /* close the socket used by dovecot */
-       if (fd >= 0)
-              close(fd);
+/* close the socket used by dovecot */
+if (fd >= 0)
+  close(fd);
 
-       /* Expand server_condition as an authorization check */
-       return (ret == OK)? auth_check_serv_cond(ablock) : ret;
+/* Expand server_condition as an authorization check */
+return ret == OK ? auth_check_serv_cond(ablock) : ret;
 }
index a121bde..11bc581 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -30,8 +30,8 @@ auth_get_data(uschar **aptr, uschar *challenge, int challen)
 {
 int c;
 int p = 0;
-smtp_printf("334 %s\r\n", auth_b64encode(challenge, challen));
-while ((c = receive_getc()) != '\n' && c != EOF)
+smtp_printf("334 %s\r\n", b64encode(challenge, challen));
+while ((c = receive_getc(GETC_BUFFER_UNLIMITED)) != '\n' && c != EOF)
   {
   if (p >= big_buffer_size - 1) return BAD64;
   big_buffer[p++] = c;
index d3ffe08..71e7139 100644 (file)
@@ -32,7 +32,7 @@ auth_get_no64_data(uschar **aptr, uschar *challenge)
 int c;
 int p = 0;
 smtp_printf("334 %s\r\n", challenge);
-while ((c = receive_getc()) != '\n' && c != EOF)
+while ((c = receive_getc(GETC_BUFFER_UNLIMITED)) != '\n' && c != EOF)
   {
   if (p >= big_buffer_size - 1) return BAD64;
   big_buffer[p++] = c;
index 87be9b5..77db2e7 100644 (file)
@@ -275,7 +275,7 @@ auth_gsasl_server(auth_instance *ablock, uschar *initial_data)
     /* Some auth mechanisms can ensure that both sides are talking withing the
     same security context; for TLS, this means that even if a bad certificate
     has been accepted, they remain MitM-proof because both sides must be within
-    the same negotiated session; if someone is terminating one sesson and
+    the same negotiated session; if someone is terminating one session and
     proxying data on within a second, authentication will fail.
 
     We might not have this available, depending upon TLS implementation,
index 21ed75b..732a673 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Copyright (c) Twitter Inc 2012
@@ -320,7 +320,7 @@ auth_heimdal_gssapi_server(auth_instance *ablock, uschar *initial_data)
         break;
 
       case 1:
-        gbufdesc_in.length = auth_b64decode(from_client, USS &gbufdesc_in.value);
+        gbufdesc_in.length = b64decode(from_client, USS &gbufdesc_in.value);
         if (gclient) {
           maj_stat = gss_release_name(&min_stat, &gclient);
           gclient = GSS_C_NO_NAME;
@@ -400,7 +400,7 @@ auth_heimdal_gssapi_server(auth_instance *ablock, uschar *initial_data)
         break;
 
       case 3:
-        gbufdesc_in.length = auth_b64decode(from_client, USS &gbufdesc_in.value);
+        gbufdesc_in.length = b64decode(from_client, USS &gbufdesc_in.value);
         maj_stat = gss_unwrap(&min_stat,
             gcontext,
             &gbufdesc_in,       /* data from client */
index ff449e5..161aab6 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -66,7 +66,7 @@ auth_plaintext_server(auth_instance *ablock, uschar *data)
 {
 auth_plaintext_options_block *ob =
   (auth_plaintext_options_block *)(ablock->options_block);
-uschar *prompts = ob->server_prompts;
+const uschar *prompts = ob->server_prompts;
 uschar *clear, *end, *s;
 int number = 1;
 int len, rc;
@@ -76,7 +76,7 @@ int sep = 0;
 
 if (prompts != NULL)
   {
-  prompts = expand_string(prompts);
+  prompts = expand_cstring(prompts);
   if (prompts == NULL)
     {
     auth_defer_msg = expand_string_message;
@@ -99,7 +99,7 @@ if (*data != 0)
     }
   else
     {
-    if ((len = auth_b64decode(data, &clear)) < 0) return BAD64;
+    if ((len = b64decode(data, &clear)) < 0) return BAD64;
     end = clear + len;
     while (clear < end && expand_nmax < EXPAND_MAXN)
       {
@@ -121,7 +121,7 @@ while ((s = string_nextinlist(&prompts, &sep, big_buffer, big_buffer_size))
   {
   if (number++ <= expand_nmax) continue;
   if ((rc = auth_get_data(&data, s, Ustrlen(s))) != OK) return rc;
-  if ((len = auth_b64decode(data, &clear)) < 0) return BAD64;
+  if ((len = b64decode(data, &clear)) < 0) return BAD64;
   end = clear + len;
 
   /* This loop must run at least once, in case the length is zero */
@@ -163,7 +163,7 @@ auth_plaintext_client(
 {
 auth_plaintext_options_block *ob =
   (auth_plaintext_options_block *)(ablock->options_block);
-uschar *text = ob->client_send;
+const uschar *text = ob->client_send;
 uschar *s;
 BOOL first = TRUE;
 int sep = 0;
@@ -221,20 +221,20 @@ while ((s = string_nextinlist(&text, &sep, big_buffer, big_buffer_size)) != NULL
     }
 
   /* The first string is attached to the AUTH command; others are sent
-  unembelished. */
+  unembellished. */
 
   if (first)
     {
     first = FALSE;
     if (smtp_write_command(outblock, FALSE, "AUTH %s%s%s\r\n",
          ablock->public_name, (len == 0)? "" : " ",
-         auth_b64encode(ss, len)) < 0)
+         b64encode(ss, len)) < 0)
       return FAIL_SEND;
     }
   else
     {
     if (smtp_write_command(outblock, FALSE, "%s\r\n",
-          auth_b64encode(ss, len)) < 0)
+          b64encode(ss, len)) < 0)
       return FAIL_SEND;
     }
 
@@ -265,7 +265,7 @@ while ((s = string_nextinlist(&text, &sep, big_buffer, big_buffer_size)) != NULL
   /* Now that we know we'll continue, we put the received data into $auth<n>,
   if possible. First, decode it: buffer+4 skips over the SMTP status code. */
 
-  clear_len = auth_b64decode(buffer+4, &clear);
+  clear_len = b64decode(buffer+4, &clear);
 
   /* If decoding failed, the default is to terminate the authentication, and
   return FAIL, with the SMTP response still in the buffer. However, if client_
index 0bf7b04..4d435a4 100644 (file)
@@ -61,7 +61,7 @@ address can appear in the tables drtables.c. */
 int auth_spa_options_count =
   sizeof(auth_spa_options)/sizeof(optionlist);
 
-/* Default private options block for the contidion authentication method. */
+/* Default private options block for the condition authentication method. */
 
 auth_spa_options_block auth_spa_option_defaults = {
   NULL,              /* spa_password */
diff --git a/src/auths/tls.c b/src/auths/tls.c
new file mode 100644 (file)
index 0000000..99c7563
--- /dev/null
@@ -0,0 +1,80 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Jeremy Harris 2016 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* This file provides an Exim authenticator driver for
+a server to verify a client SSL certificate
+*/
+
+
+#include "../exim.h"
+#include "tls.h"
+
+/* Options specific to the tls authentication mechanism. */
+
+optionlist auth_tls_options[] = {
+  { "server_param",     opt_stringptr,
+      (void *)(offsetof(auth_tls_options_block, server_param1)) },
+  { "server_param1",    opt_stringptr,
+      (void *)(offsetof(auth_tls_options_block, server_param1)) },
+  { "server_param2",    opt_stringptr,
+      (void *)(offsetof(auth_tls_options_block, server_param2)) },
+  { "server_param3",    opt_stringptr,
+      (void *)(offsetof(auth_tls_options_block, server_param3)) },
+};
+
+/* Size of the options list. An extern variable has to be used so that its
+address can appear in the tables drtables.c. */
+
+int auth_tls_options_count = nelem(auth_tls_options);
+
+/* Default private options block for the authentication method. */
+
+auth_tls_options_block auth_tls_option_defaults = {
+    NULL,      /* server_param1 */
+    NULL,      /* server_param2 */
+    NULL,      /* server_param3 */
+};
+
+
+/*************************************************
+*          Initialization entry point            *
+*************************************************/
+
+/* Called for each instance, after its options have been read, to
+enable consistency checks to be done, or anything else that needs
+to be set up. */
+
+void
+auth_tls_init(auth_instance *ablock)
+{
+ablock->public_name = ablock->name;    /* needed for core code */
+}
+
+
+
+/*************************************************
+*             Server entry point                 *
+*************************************************/
+
+/* For interface, see auths/README */
+
+int
+auth_tls_server(auth_instance *ablock, uschar *data)
+{
+auth_tls_options_block * ob = (auth_tls_options_block *)ablock->options_block;
+
+if (ob->server_param1)
+  auth_vars[expand_nmax++] = expand_string(ob->server_param1);
+if (ob->server_param2)
+  auth_vars[expand_nmax++] = expand_string(ob->server_param2);
+if (ob->server_param3)
+  auth_vars[expand_nmax++] = expand_string(ob->server_param3);
+return auth_check_serv_cond(ablock);
+}
+
+
+/* End of tls.c */
diff --git a/src/auths/tls.h b/src/auths/tls.h
new file mode 100644 (file)
index 0000000..bf2a2a1
--- /dev/null
@@ -0,0 +1,30 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Jeremy Harris 2015 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* Private structure for the private options. */
+
+typedef struct {
+  uschar * server_param1;
+  uschar * server_param2;
+  uschar * server_param3;
+} auth_tls_options_block;
+
+/* Data for reading the private options. */
+
+extern optionlist auth_tls_options[];
+extern int auth_tls_options_count;
+
+/* Block containing default values. */
+
+extern auth_tls_options_block auth_tls_option_defaults;
+
+/* The entry points for the mechanism */
+
+extern void auth_tls_init(auth_instance *);
+extern int auth_tls_server(auth_instance *, uschar *);
+
+/* End of sa.h */
diff --git a/src/base64.c b/src/base64.c
new file mode 100644 (file)
index 0000000..cee77c3
--- /dev/null
@@ -0,0 +1,285 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004, 2015 */
+/* License: GPL */
+
+/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+
+#include "exim.h"
+#ifdef WITH_CONTENT_SCAN       /* file-IO specific decode function */
+# include "mime.h"
+
+/* BASE64 decoder matrix */
+static unsigned char mime_b64[256]={
+/*   0 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
+/*  16 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
+/*  32 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,   62,  128,  128,  128,   63,
+/*  48 */   52,   53,   54,   55,   56,   57,   58,   59,   60,   61,  128,  128,  128,  255,  128,  128,
+/*  64 */  128,    0,    1,    2,    3,    4,    5,    6,    7,    8,    9,   10,   11,   12,   13,   14,
+/*  80 */   15,   16,   17,   18,   19,   20,   21,   22,   23,   24,   25,  128,  128,  128,  128,  128,
+/*  96 */  128,   26,   27,   28,   29,   30,   31,   32,   33,   34,   35,   36,   37,   38,   39,   40,
+/* 112 */   41,   42,   43,   44,   45,   46,   47,   48,   49,   50,   51,  128,  128,  128,  128,  128,
+/* 128 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
+/* 144 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
+/* 160 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
+/* 176 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
+/* 192 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
+/* 208 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
+/* 224 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
+/* 240 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128
+};
+
+/* decode base64 MIME part */
+ssize_t
+mime_decode_base64(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;
+int bytestate = 0;
+
+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
+     )
+    break;
+
+  for (ipos = ibuf ; *ipos != '\r' && *ipos != '\n' && *ipos != 0; ++ipos)
+    if (*ipos == '=')                  /* skip padding */
+      ++bytestate;
+
+    else if (mime_b64[*ipos] == 128)   /* skip bad characters */
+      mime_set_anomaly(MIME_ANOMALY_BROKEN_BASE64);
+
+    /* simple state-machine */
+    else switch((bytestate++) & 3)
+      {
+      case 0:
+       *opos = mime_b64[*ipos] << 2; break;
+      case 1:
+       *opos++ |= mime_b64[*ipos] >> 4;
+       *opos = mime_b64[*ipos] << 4; break;
+      case 2:
+       *opos++ |= mime_b64[*ipos] >> 2;
+       *opos = mime_b64[*ipos] << 6; break;
+      case 3:
+       *opos++ |= mime_b64[*ipos]; break;
+      }
+
+  /* something to write? */
+  len = opos - obuf;
+  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 */
+
+/* write out last byte if it was incomplete */
+if (bytestate & 3)
+  {
+  if (fwrite(obuf, 1, 1, out) != 1) return -1;
+  ++size;
+  }
+
+return size;
+}
+
+#endif /*WITH_CONTENT_SCAN*/
+
+/*************************************************
+ *************************************************
+ *************************************************
+ *************************************************
+ *************************************************
+ *************************************************
+ *************************************************
+ *************************************************
+ *************************************************
+ *************************************************
+ *************************************************
+ *************************************************
+ *************************************************
+ *************************************************
+ *************************************************
+ *************************************************/
+
+
+/*************************************************
+*          Decode byte-string in base 64         *
+*************************************************/
+
+/* This function decodes a string in base 64 format as defined in RFC 2045
+(MIME) and required by the SMTP AUTH extension (RFC 2554). The decoding
+algorithm is written out in a straightforward way. Turning it into some kind of
+compact loop is messy and would probably run more slowly.
+
+Arguments:
+  code        points to the coded string, zero-terminated
+  ptr         where to put the pointer to the result, which is in
+              allocated store, and zero-terminated
+
+Returns:      the number of bytes in the result,
+              or -1 if the input was malformed
+
+Whitespace in the input is ignored.
+A zero is added on to the end to make it easy in cases where the result is to
+be interpreted as text. This is not included in the count. */
+
+static uschar dec64table[] = {
+  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, /*  0-15 */
+  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, /* 16-31 */
+  255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63, /* 32-47 */
+   52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255,255,255,255, /* 48-63 */
+  255,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, /* 64-79 */
+   15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255, /* 80-95 */
+  255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 96-111 */
+   41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255  /* 112-127*/
+};
+
+int
+b64decode(uschar *code, uschar **ptr)
+{
+int x, y;
+uschar *result = store_get(3*(Ustrlen(code)/4) + 1);
+
+*ptr = result;
+
+/* Each cycle of the loop handles a quantum of 4 input bytes. For the last
+quantum this may decode to 1, 2, or 3 output bytes. */
+
+while ((x = *code++) != 0)
+  {
+  if (isspace(x)) continue;
+  /* debug_printf("b64d: '%c'\n", x); */
+
+  if (x > 127 || (x = dec64table[x]) == 255) return -1;
+
+  while (isspace(y = *code++)) ;
+  /* debug_printf("b64d: '%c'\n", y); */
+  if (y == 0 || (y = dec64table[y]) == 255)
+    return -1;
+
+  *result++ = (x << 2) | (y >> 4);
+  /* debug_printf("b64d:      -> %02x\n", result[-1]); */
+
+  while (isspace(x = *code++)) ;
+  /* debug_printf("b64d: '%c'\n", x); */
+  if (x == '=')                /* endmarker, but there should be another */
+    {
+    while (isspace(x = *code++)) ;
+    /* debug_printf("b64d: '%c'\n", x); */
+    if (x != '=') return -1;
+    while (isspace(y = *code++)) ;
+    if (y != 0) return -1;
+    /* debug_printf("b64d: DONE\n"); */
+    break;
+    }
+  else
+    {
+    if (x > 127 || (x = dec64table[x]) == 255) return -1;
+    *result++ = (y << 4) | (x >> 2);
+    /* debug_printf("b64d:      -> %02x\n", result[-1]); */
+
+    while (isspace(y = *code++)) ;
+    /* debug_printf("b64d: '%c'\n", y); */
+    if (y == '=')
+      {
+      while (isspace(y = *code++)) ;
+      if (y != 0) return -1;
+      /* debug_printf("b64d: DONE\n"); */
+      break;
+      }
+    else
+      {
+      if (y > 127 || (y = dec64table[y]) == 255) return -1;
+      *result++ = (x << 6) | y;
+      /* debug_printf("b64d:      -> %02x\n", result[-1]); */
+      }
+    }
+  }
+
+*result = 0;
+return result - *ptr;
+}
+
+
+/*************************************************
+*          Encode byte-string in base 64         *
+*************************************************/
+
+/* This function encodes a string of bytes, containing any values whatsoever,
+in base 64 as defined in RFC 2045 (MIME) and required by the SMTP AUTH
+extension (RFC 2554). The encoding algorithm is written out in a
+straightforward way. Turning it into some kind of compact loop is messy and
+would probably run more slowly.
+
+Arguments:
+  clear       points to the clear text bytes
+  len         the number of bytes to encode
+
+Returns:      a pointer to the zero-terminated base 64 string, which
+              is in working store
+*/
+
+static uschar *enc64table =
+  US"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+uschar *
+b64encode(uschar *clear, int len)
+{
+uschar *code = store_get(4*((len+2)/3) + 1);
+uschar *p = code;
+
+while (len-- >0)
+  {
+  register int x, y;
+
+  x = *clear++;
+  *p++ = enc64table[(x >> 2) & 63];
+
+  if (len-- <= 0)
+    {
+    *p++ = enc64table[(x << 4) & 63];
+    *p++ = '=';
+    *p++ = '=';
+    break;
+    }
+
+  y = *clear++;
+  *p++ = enc64table[((x << 4) | ((y >> 4) & 15)) & 63];
+
+  if (len-- <= 0)
+    {
+    *p++ = enc64table[(y << 2) & 63];
+    *p++ = '=';
+    break;
+    }
+
+  x = *clear++;
+  *p++ = enc64table[((y << 2) | ((x >> 6) & 3)) & 63];
+
+  *p++ = enc64table[x & 63];
+  }
+
+*p = 0;
+
+return code;
+}
+
+
+/* End of base64.c */
+/* vi: sw ai sw=2
+*/
diff --git a/src/blob.h b/src/blob.h
new file mode 100644 (file)
index 0000000..a3f1e24
--- /dev/null
@@ -0,0 +1,15 @@
+/*
+ *  Blob - a general pointer/size item for a memory chunk
+ *
+ *  Copyright (C) 2016  Exim maintainers
+ */
+
+#ifndef BLOB_H /* entire file */
+#define BLOB_H
+
+typedef struct {
+  uschar * data;
+  size_t   len;
+} blob;
+
+#endif
index f3390cb..4ed2874 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -105,8 +105,10 @@ time_t test_time_t = 0;
 size_t test_size_t = 0;
 ssize_t test_ssize_t = 0;
 unsigned long test_ulong_t = 0L;
+unsigned int test_uint_t = 0;
 #endif
 long test_long_t = 0;
+int test_int_t = 0;
 FILE *base;
 FILE *new;
 int last_initial = 'A';
@@ -190,12 +192,17 @@ fprintf(new, "#define SSIZE_T_FMT  \"%%zd\"\n");
 #else
 if (sizeof(test_size_t) > sizeof (test_ulong_t))
   fprintf(new, "#define SIZE_T_FMT  \"%%llu\"\n");
-else
+else if (sizeof(test_size_t) > sizeof (test_uint_t))
   fprintf(new, "#define SIZE_T_FMT  \"%%lu\"\n");
+else
+  fprintf(new, "#define SIZE_T_FMT  \"%%u\"\n");
+
 if (sizeof(test_ssize_t) > sizeof(test_long_t))
   fprintf(new, "#define SSIZE_T_FMT  \"%%lld\"\n");
-else
+else if (sizeof(test_ssize_t) > sizeof(test_int_t))
   fprintf(new, "#define SSIZE_T_FMT  \"%%ld\"\n");
+else
+  fprintf(new, "#define SSIZE_T_FMT  \"%%d\"\n");
 #endif
 
 /* Now search the makefile for certain settings */
@@ -720,17 +727,31 @@ else if (isgroup)
     continue;
     }
 
-  /* WITH_CONTENT_SCAN is another special case: it must be set if either it or
-  WITH_OLD_DEMIME is set. */
+  /* WITH_CONTENT_SCAN is another special case: it must be set if it or
+  EXPERIMENTAL_DCC is set. */
 
   if (strcmp(name, "WITH_CONTENT_SCAN") == 0)
     {
     char *wcs = getenv("WITH_CONTENT_SCAN");
-    char *wod = getenv("WITH_OLD_DEMIME");
     char *dcc = getenv("EXPERIMENTAL_DCC");
-    if (wcs != NULL || wod != NULL || dcc != NULL)
-      fprintf(new, "#define WITH_CONTENT_SCAN     yes\n");
-    else fprintf(new, "/* WITH_CONTENT_SCAN not set */\n");
+    fprintf(new, wcs || dcc
+      ? "#define WITH_CONTENT_SCAN     yes\n"
+      : "/* WITH_CONTENT_SCAN not set */\n");
+    continue;
+    }
+
+  /* DISABLE_DKIM is special; must be forced if no SUPPORT_TLS */
+  if (strcmp(name, "DISABLE_DKIM") == 0)
+    {
+    char *d_dkim = getenv("DISABLE_DKIM");
+    char *tls = getenv("SUPPORT_TLS");
+
+    if (d_dkim)
+      fprintf(new, "#define DISABLE_DKIM          yes\n");
+    else if (!tls)
+      fprintf(new, "#define DISABLE_DKIM          yes /* forced by lack of TLS */\n");
+    else
+      fprintf(new, "/* DISABLE_DKIM not set */\n");
     continue;
     }
 
@@ -804,7 +825,11 @@ else if (isgroup)
             strncpy(buffer, ss, sss-ss);
             buffer[sss-ss] = 0;  /* For empty case */
             }
-          else strcpy(buffer, ss);
+          else
+           {
+                   strncpy(buffer, ss, sizeof(buffer));
+           buffer[sizeof(buffer)-1] = 0;
+           }
           pp = buffer + (int)strlen(buffer);
           while (pp > buffer && isspace((unsigned char)pp[-1])) pp--;
           *pp = 0;
index 20083b4..de12c44 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -72,9 +72,9 @@ child_exec_exim(int exec_type, BOOL kill_v, int *pcount, BOOL minimal,
 {
 int first_special = -1;
 int n = 0;
-int extra = (pcount != NULL)? *pcount : 0;
+int extra = pcount ? *pcount : 0;
 uschar **argv =
-  store_get((extra + acount + MAX_CLMACROS + 16) * sizeof(char *));
+  store_get((extra + acount + MAX_CLMACROS + 18) * sizeof(char *));
 
 /* In all case, the list starts out with the path, any macros, and a changed
 config file. */
@@ -113,6 +113,11 @@ if (!minimal)
   if (synchronous_delivery) argv[n++] = US"-odi";
   if (connection_max_messages >= 0)
     argv[n++] = string_sprintf("-oB%d", connection_max_messages);
+  if (*queue_name)
+    {
+    argv[n++] = US"-MCG";
+    argv[n++] = queue_name;
+    }
   }
 
 /* Now add in any others that are in the call. Remember which they were,
@@ -141,7 +146,7 @@ if (exec_type == CEE_RETURN_ARGV)
 failure. We know that there will always be at least one extra option in the
 call when exec() is done here, so it can be used to add to the panic data. */
 
-DEBUG(D_exec) debug_print_argv(argv);
+DEBUG(D_exec) debug_print_argv(CUSS argv);
 exim_nullstd();                            /* Make sure std{in,out,err} exist */
 execv(CS argv[0], (char *const *)argv);
 
@@ -217,7 +222,7 @@ pid = fork();
 
 /* Child process: make the reading end of the pipe into the standard input and
 close the writing end. If debugging, pass debug_fd as stderr. Then re-exec
-Exim with appropriat options. In the test harness, use -odi unless queue_only
+Exim with appropriate options. In the test harness, use -odi unless queue_only
 is set, so that the bounce is fully delivered before returning. Failure is
 signalled with EX_EXECFAILED (specified by CEE_EXEC_EXIT), but this shouldn't
 occur. */
@@ -307,8 +312,9 @@ Returns:      the pid of the created process or -1 if anything has gone wrong
 */
 
 pid_t
-child_open_uid(uschar **argv, uschar **envp, int newumask, uid_t *newuid,
-  gid_t *newgid, int *infdptr, int *outfdptr, uschar *wd, BOOL make_leader)
+child_open_uid(const uschar **argv, const uschar **envp, int newumask,
+  uid_t *newuid, gid_t *newgid, int *infdptr, int *outfdptr, uschar *wd,
+  BOOL make_leader)
 {
 int save_errno;
 int inpfd[2], outpfd[2];
@@ -387,7 +393,7 @@ if (pid == 0)
   /* Now do the exec */
 
   if (envp == NULL) execv(CS argv[0], (char *const *)argv);
-    else execve(CS argv[0], (char *const *)argv, (char *const *)envp);
+  else execve(CS argv[0], (char *const *)argv, (char *const *)envp);
 
   /* Failed to execv. Signal this failure using EX_EXECFAILED. We are
   losing the actual errno we got back, because there is no way to return
@@ -450,8 +456,8 @@ pid_t
 child_open(uschar **argv, uschar **envp, int newumask, int *infdptr,
   int *outfdptr, BOOL make_leader)
 {
-return child_open_uid(argv, envp, newumask, NULL, NULL, infdptr, outfdptr,
-  NULL, make_leader);
+return child_open_uid(CUSS argv, CUSS envp, newumask, NULL, NULL,
+  infdptr, outfdptr, NULL, make_leader);
 }
 
 
diff --git a/src/cnumber.h b/src/cnumber.h
deleted file mode 100644 (file)
index d00491f..0000000
+++ /dev/null
@@ -1 +0,0 @@
-1
index ba4615c..58e1813 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* The default settings for Exim configuration variables. A #define without
@@ -24,6 +24,7 @@ it's a default value. */
 #define AUTH_HEIMDAL_GSSAPI
 #define AUTH_PLAINTEXT
 #define AUTH_SPA
+#define AUTH_TLS
 
 #define AUTH_VARS                     3
 
@@ -40,10 +41,11 @@ it's a default value. */
 #define DEFAULT_CRYPT              crypt
 #define DELIVER_IN_BUFFER_SIZE     8192
 #define DELIVER_OUT_BUFFER_SIZE    8192
+#define DISABLE_DNSSEC
 #define DISABLE_DKIM
+#define DISABLE_EVENT
 #define DISABLE_PRDR
 #define DISABLE_OCSP
-#define DISABLE_DNSSEC
 #define DISABLE_D_OPTION
 
 #define ENABLE_DISABLE_FSYNC
@@ -93,6 +95,7 @@ it's a default value. */
 #define LOOKUP_ORACLE
 #define LOOKUP_PASSWD
 #define LOOKUP_PGSQL
+#define LOOKUP_REDIS
 #define LOOKUP_SQLITE
 #define LOOKUP_TESTDB
 #define LOOKUP_WHOSON
@@ -115,6 +118,8 @@ it's a default value. */
 #define RADIUS_CONFIG_FILE
 #define RADIUS_LIB_TYPE
 
+#define REGEX_VARS                 9
+
 #define ROUTER_ACCEPT
 #define ROUTER_DNSLOOKUP
 #define ROUTER_IPLITERAL
@@ -128,13 +133,16 @@ it's a default value. */
 #define SPOOL_MODE                 0640
 #define STRING_SPRINTF_BUFFER_SIZE (8192 * 4)
 
-#define SUPPORT_A6
 #define SUPPORT_CRYPTEQ
+#define SUPPORT_I18N
+#define SUPPORT_I18N_2008
 #define SUPPORT_MAILDIR
 #define SUPPORT_MAILSTORE
 #define SUPPORT_MBX
 #define SUPPORT_MOVE_FROZEN_MESSAGES
 #define SUPPORT_PAM
+#define SUPPORT_PROXY
+#define SUPPORT_SOCKS
 #define SUPPORT_TLS
 #define SUPPORT_TRANSLATE_IP_ADDRESS
 
@@ -143,7 +151,7 @@ it's a default value. */
 
 #define TCP_WRAPPERS_DAEMON_NAME "exim"
 #define TIMEZONE_DEFAULT
-#define TMPDIR
+#define EXIM_TMPDIR
 
 #define TRANSPORT_APPENDFILE
 #define TRANSPORT_AUTOREPLY
@@ -162,20 +170,18 @@ it's a default value. */
 #define WHITELIST_D_MACROS
 
 #define WITH_CONTENT_SCAN
-#define WITH_OLD_DEMIME
 #define WITH_OLD_CLAMAV_STREAM
 
 /* EXPERIMENTAL features */
 #define EXPERIMENTAL_BRIGHTMAIL
-#define EXPERIMENTAL_CERTNAMES
+#define EXPERIMENTAL_DANE
 #define EXPERIMENTAL_DCC
+#define EXPERIMENTAL_DSN_INFO
 #define EXPERIMENTAL_DMARC
-#define EXPERIMENTAL_DSN
-#define EXPERIMENTAL_PROXY
-#define EXPERIMENTAL_REDIS
+#define EXPERIMENTAL_LMDB
+#define EXPERIMENTAL_QUEUEFILE
 #define EXPERIMENTAL_SPF
 #define EXPERIMENTAL_SRS
-#define EXPERIMENTAL_TPDA
 
 /* For developers */
 #define WANT_DEEPER_PRINTF_CHECKS
@@ -191,7 +197,7 @@ just in case. */
 /* Sizes for integer arithmetic.
 Go for 64bit; can be overridden in OS/Makefile-FOO
 If you make it a different number of bits, provide a definition
-for EXIM_64B_MAX and _MIN in OS/oh.h-FOO */
+for EXIM_ARITH_MAX and _MIN in OS/oh.h-FOO */
 #define int_eximarith_t int64_t
 #define PR_EXIM_ARITH "%" PRId64               /* C99 standard, printf %lld */
 #define SC_EXIM_ARITH "%" SCNi64               /* scanf incl. 0x prefix */
index 1274349..a294dc3 100644 (file)
@@ -40,6 +40,7 @@
 ######################################################################
 #                    MAIN CONFIGURATION SETTINGS                     #
 ######################################################################
+#
 
 # Specify your host's canonical name here. This should normally be the fully
 # qualified "official" name of your host. If this option is not set, the
@@ -221,18 +222,26 @@ never_users = root
 host_lookup = *
 
 
-# The settings below, which are actually the same as the defaults in the
-# code, cause Exim to make RFC 1413 (ident) callbacks for all incoming SMTP
-# calls. You can limit the hosts to which these calls are made, and/or change
-# the timeout that is used. If you set the timeout to zero, all RFC 1413 calls
-# are disabled. RFC 1413 calls are cheap and can provide useful information
-# for tracing problem messages, but some hosts and firewalls have problems
-# with them. This can result in a timeout instead of an immediate refused
-# connection, leading to delays on starting up SMTP sessions. (The default was
-# reduced from 30s to 5s for release 4.61.)
+# The settings below cause Exim to make RFC 1413 (ident) callbacks
+# for all incoming SMTP calls. You can limit the hosts to which these
+# calls are made, and/or change the timeout that is used. If you set
+# the timeout to zero, all RFC 1413 calls are disabled. RFC 1413 calls
+# are cheap and can provide useful information for tracing problem
+# messages, but some hosts and firewalls have problems with them.
+# This can result in a timeout instead of an immediate refused
+# connection, leading to delays on starting up SMTP sessions.
+# (The default was reduced from 30s to 5s for release 4.61. and to
+# disabled for release 4.86)
+#
+#rfc1413_hosts = *
+#rfc1413_query_timeout = 5s
+
 
-rfc1413_hosts = *
-rfc1413_query_timeout = 5s
+# Enable an efficiency feature.  We advertise the feature; clients
+# may request to use it.  For multi-recipient mails we then can
+# reject or accept per-user after the message is received.
+#
+prdr_enable = true
 
 
 # By default, Exim expects all envelope addresses to be fully qualified, that
@@ -248,6 +257,13 @@ rfc1413_query_timeout = 5s
 # and/or qualify_recipient (see above).
 
 
+# Unless you run a high-volume site you probably want more logging
+# detail than the default.  Adjust to suit.
+
+log_selector = +smtp_protocol_error +smtp_syntax_error \
+       +tls_certificate_verified
+
+
 # If you want Exim to support the "percent hack" for certain domains,
 # uncomment the following line and provide a list of domains. The "percent
 # hack" is the feature by which mail addressed to x%y@z (where z is one of
@@ -314,6 +330,18 @@ timeout_frozen_after = 7d
 # accept_8bitmime = false
 
 
+# Exim does not make use of environment variables itself. However,
+# libraries that Exim uses (e.g. LDAP) depend on specific environment settings.
+# There are two lists: keep_environment for the variables we trust, and
+# add_environment for variables we want to set to a specific value.
+# Note that TZ is handled separately by the timezone runtime option
+# and TIMEZONE_DEFAULT buildtime option.
+
+# keep_environment = ^LDAP
+# add_environment = PATH=/usr/bin::/bin
+
+
+
 ######################################################################
 #                       ACL CONFIGURATION                            #
 #         Specifies access control lists for incoming SMTP mail      #
@@ -421,6 +449,11 @@ acl_check_rcpt:
           control       = submission
           control       = dkim_disable_verify
 
+  # Insist that a HELO/EHLO was accepted.
+
+  require message      = nice hosts say HELO first
+          condition    = ${if def:sender_helo_name}
+
   # Insist that any other recipient address that we accept is either in one of
   # our local domains, or is in a domain for which we explicitly allow
   # relaying. Any other domain is rejected as being unacceptable for relaying.
@@ -477,6 +510,13 @@ acl_check_rcpt:
 
 acl_check_data:
 
+  # Deny if the message contains an overlong line.  Per the standards
+  # we should never receive one such via SMTP.
+  #
+  deny    message    = maximum allowed line length is 998 octets, \
+                       got $max_received_linelength
+          condition  = ${if > {$max_received_linelength}{998}}
+
   # Deny if the message contains a virus. Before enabling this check, you
   # must install a virus scanner and set the av_scanner option above.
   #
@@ -669,9 +709,13 @@ begin transports
 
 
 # This transport is used for delivering messages over SMTP connections.
+# Refuse to send any message with over-long lines, which could have
+# been received other than via SMTP. The use of message_size_limit to
+# enforce this is a red herring.
 
 remote_smtp:
   driver = smtp
+  message_size_limit = ${if > {$max_received_linelength}{998} {1}{0}}
 
 
 # This transport is used for local delivery to user mailboxes in traditional
index 0ccaf6c..632eb70 100755 (executable)
@@ -1,4 +1,4 @@
-#! PERL_COMMAND -w
+#! PERL_COMMAND
 
 # This is a Perl script that reads an Exim run-time configuration file and
 # checks for settings that were valid prior to release 3.00 but which were
@@ -7,6 +7,8 @@
 
 # It is assumed that the input is a valid Exim configuration file.
 
+use warnings;
+BEGIN { pop @INC if $INC[-1] eq '.' };
 
 ##################################################
 #             Analyse one line                   #
index 4455fb7..fff4e47 100755 (executable)
@@ -1,4 +1,4 @@
-#! PERL_COMMAND -w
+#! PERL_COMMAND
 
 # This is a Perl script that reads an Exim run-time configuration file for
 # Exim 3. It makes what changes it can for Exim 4, and also output commentary
@@ -6,6 +6,8 @@
 
 # It is assumed that the input is a valid Exim 3 configuration file.
 
+use warnings;
+BEGIN { pop @INC if $INC[-1] eq '.' };
 
 # These are lists of main options which are abolished in Exim 4.
 # The first contains options that are used to construct new options.
index e8a4fe8..56353c3 100644 (file)
@@ -44,31 +44,33 @@ static void dummy(int x) { dummy(x-1); }
 #include <crypt.h>
 #endif
 
-char *crypt16(char *key, char *salt)
+char *
+crypt16(char *key, char *salt)
 {
-       static char res[25];
-       static char s2[3];
-       char *p;
+static char res[25];   /* Not threadsafe; like crypt() */
+static char s2[3];
+char *p;
 
-       /* Clear the string of any previous data */
-       memset (res, 0, sizeof (res));
+/* Clear the string of any previous data */
+memset (res, 0, sizeof (res));
 
-       /* crypt the first part */
-       p = crypt (key, salt);
-       strncpy (res, p, 13);
+/* crypt the first part */
+if (!(p = crypt (key, salt))) return NULL;
+strncpy (res, p, 13);
 
-       if (strlen (key) > 8)
-       {
-               /* crypt the rest
-                * the first two characters of the first block (not counting
-                * the salt) make up the new salt */
-               strncpy (s2, &(res[2]), 2);
-               p = crypt (&(key[8]), s2);
-               strncpy (&(res[13]), &(p[2]), 11);
-               memset (s2, 0, sizeof (s2));
-       }
+if (strlen (key) > 8)
+  {
+  /* crypt the rest
+   * the first two characters of the first block (not counting
+   * the salt) make up the new salt */
 
-       return (res);
+  strncpy (s2, res+2, 2);
+  p = crypt (key+8, s2);
+  strncpy (res+13, p+2, 11);
+  memset (s2, 0, sizeof(s2));
+  }
+
+return (res);
 }
 #endif
 
index 5c64205..2935d0a 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2017 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions concerned with running Exim as a daemon */
@@ -19,7 +19,7 @@ typedef struct smtp_slot {
 } smtp_slot;
 
 /* An empty slot for initializing (Standard C does not allow constructor
-expressions in assigments except as initializers in declarations). */
+expressions in assignments except as initializers in declarations). */
 
 static smtp_slot empty_smtp_slot = { 0, NULL };
 
@@ -145,7 +145,7 @@ int dup_accept_socket = -1;
 int max_for_this_host = 0;
 int wfsize = 0;
 int wfptr = 0;
-int use_log_write_selector = log_write_selector;
+int save_log_selector = *log_selector;
 uschar *whofrom = NULL;
 
 void *reset_point = store_get(0);
@@ -161,23 +161,20 @@ DEBUG(D_any) debug_printf("Connection request from %s port %d\n",
 input stream. These operations fail only the exceptional circumstances. Note
 that never_error() won't use smtp_out if it is NULL. */
 
-smtp_out = fdopen(accept_socket, "wb");
-if (smtp_out == NULL)
+if (!(smtp_out = fdopen(accept_socket, "wb")))
   {
   never_error(US"daemon: fdopen() for smtp_out failed", US"", errno);
   goto ERROR_RETURN;
   }
 
-dup_accept_socket = dup(accept_socket);
-if (dup_accept_socket < 0)
+if ((dup_accept_socket = dup(accept_socket)) < 0)
   {
   never_error(US"daemon: couldn't dup socket descriptor",
     US"Connection setup failed", errno);
   goto ERROR_RETURN;
   }
 
-smtp_in = fdopen(dup_accept_socket, "rb");
-if (smtp_in == NULL)
+if (!(smtp_in = fdopen(dup_accept_socket, "rb")))
   {
   never_error(US"daemon: fdopen() for smtp_in failed",
     US"Connection setup failed", errno);
@@ -206,11 +203,11 @@ memory is reclaimed. */
 
 whofrom = string_append(whofrom, &wfsize, &wfptr, 3, "[", sender_host_address, "]");
 
-if ((log_extra_selector & LX_incoming_port) != 0)
+if (LOGGING(incoming_port))
   whofrom = string_append(whofrom, &wfsize, &wfptr, 2, ":", string_sprintf("%d",
     sender_host_port));
 
-if ((log_extra_selector & LX_incoming_interface) != 0)
+if (LOGGING(incoming_interface))
   whofrom = string_append(whofrom, &wfsize, &wfptr, 4, " I=[",
     interface_address, "]:", string_sprintf("%d", interface_port));
 
@@ -293,8 +290,7 @@ if ((max_for_this_host > 0) &&
   int other_host_count = 0;    /* keep a count of non matches to optimise */
 
   for (i = 0; i < smtp_accept_max; ++i)
-    {
-    if (smtp_slots[i].host_address != NULL)
+    if (smtp_slots[i].host_address)
       {
       if (Ustrcmp(sender_host_address, smtp_slots[i].host_address) == 0)
        host_accept_count++;
@@ -309,7 +305,6 @@ if ((max_for_this_host > 0) &&
          ((smtp_accept_count - other_host_count) < max_for_this_host))
        break;
       }
-    }
 
   if (host_accept_count >= max_for_this_host)
     {
@@ -338,11 +333,12 @@ the generalized logging code each time when the selector is false. If the
 selector is set, check whether the host is on the list for logging. If not,
 arrange to unset the selector in the subprocess. */
 
-if ((log_write_selector & L_smtp_connection) != 0)
+if (LOGGING(smtp_connection))
   {
   uschar *list = hosts_connection_nolog;
+  memset(sender_host_cache, 0, sizeof(sender_host_cache));
   if (list != NULL && verify_check_host(&list) == OK)
-    use_log_write_selector &= ~L_smtp_connection;
+    save_log_selector &= ~L_smtp_connection;
   else
     log_write(L_smtp_connection, LOG_MAIN, "SMTP connection from %s "
       "(TCP/IP connection count = %d)", whofrom, smtp_accept_count + 1);
@@ -372,7 +368,7 @@ if (pid == 0)
 
   /* May have been modified for the subprocess */
 
-  log_write_selector = use_log_write_selector;
+  *log_selector = save_log_selector;
 
   /* Get the local interface address into permanent store */
 
@@ -389,10 +385,10 @@ if (pid == 0)
   likely what it depends on.) */
 
   smtp_active_hostname = primary_hostname;
-  if (raw_active_hostname != NULL)
+  if (raw_active_hostname)
     {
-    uschar *nah = expand_string(raw_active_hostname);
-    if (nah == NULL)
+    uschar * nah = expand_string(raw_active_hostname);
+    if (!nah)
       {
       if (!expand_string_forcedfail)
         {
@@ -406,7 +402,7 @@ if (pid == 0)
         _exit(EXIT_FAILURE);
         }
       }
-    else if (nah[0] != 0) smtp_active_hostname = nah;
+    else if (*nah) smtp_active_hostname = nah;
     }
 
   /* Initialize the queueing flags */
@@ -522,10 +518,23 @@ if (pid == 0)
       }
     else
       {
-      mac_smtp_fflush();
+      if (smtp_out)
+       {
+       int i, fd = fileno(smtp_in);
+       uschar buf[128];
+
+       mac_smtp_fflush();
+       /* drain socket, for clean TCP FINs */
+       if (fcntl(fd, F_SETFL, O_NONBLOCK) == 0)
+         for(i = 16; read(fd, buf, sizeof(buf)) > 0 && i > 0; ) i--;
+       }
       search_tidyup();
       smtp_log_no_mail();                 /* Log no mail if configured */
-      _exit((rc == 0)? EXIT_SUCCESS : EXIT_FAILURE);
+
+      /*XXX should we pause briefly, hoping that the client will be the
+      active TCP closer hence get the TCP_WAIT endpoint? */
+      DEBUG(D_receive) debug_printf("SMTP>>(close on process exit)\n");
+      _exit(rc ? EXIT_FAILURE : EXIT_SUCCESS);
       }
 
     /* Show the recipients when debugging */
@@ -555,6 +564,17 @@ if (pid == 0)
 
     /* Reclaim up the store used in accepting this message */
 
+    return_path = sender_address = NULL;
+    authenticated_sender = NULL;
+    sending_ip_address = NULL;
+    deliver_host_address = deliver_host =
+    deliver_domain_orig = deliver_localpart_orig = NULL;
+    dnslist_domain = dnslist_matched = NULL;
+    callout_address = NULL;
+#ifndef DISABLE_DKIM
+    dkim_cur_signer = NULL;
+#endif
+    acl_var_m = NULL;
     store_reset(reset_point);
 
     /* If queue_only is set or if there are too many incoming connections in
@@ -581,15 +601,13 @@ if (pid == 0)
     very long-lived connections from scanning appliances where this is not the
     best strategy. In such cases, queue_only_load_latch should be set false. */
 
-    local_queue_only = session_local_queue_only;
-    if (!local_queue_only && queue_only_load >= 0)
+    if (  !(local_queue_only = session_local_queue_only)
+       && queue_only_load >= 0
+       && (local_queue_only = (load_average = OS_GETLOADAVG()) > queue_only_load)
+       )
       {
-      local_queue_only = (load_average = OS_GETLOADAVG()) > queue_only_load;
-      if (local_queue_only)
-        {
-        queue_only_reason = 3;
-        if (queue_only_load_latch) session_local_queue_only = TRUE;
-        }
+      queue_only_reason = 3;
+      if (queue_only_load_latch) session_local_queue_only = TRUE;
       }
 
     /* Log the queueing here, when it will get a message id attached, but
@@ -597,23 +615,20 @@ if (pid == 0)
 
     if (local_queue_only) switch(queue_only_reason)
       {
-      case 1:
-      log_write(L_delay_delivery,
+      case 1: log_write(L_delay_delivery,
                 LOG_MAIN, "no immediate delivery: too many connections "
                 "(%d, max %d)", smtp_accept_count, smtp_accept_queue);
-      break;
+             break;
 
-      case 2:
-      log_write(L_delay_delivery,
+      case 2: log_write(L_delay_delivery,
                 LOG_MAIN, "no immediate delivery: more than %d messages "
                 "received in one connection", smtp_accept_queue_per_connection);
-      break;
+             break;
 
-      case 3:
-      log_write(L_delay_delivery,
+      case 3: log_write(L_delay_delivery,
                 LOG_MAIN, "no immediate delivery: load average %.2f",
                 (double)load_average/1000.0);
-      break;
+             break;
       }
 
     /* If a delivery attempt is required, spin off a new process to handle it.
@@ -650,8 +665,8 @@ if (pid == 0)
         if (geteuid() != root_uid && !deliver_drop_privilege)
           {
           signal(SIGALRM, SIG_DFL);
-          (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, FALSE, 2, US"-Mc",
-            message_id);
+          (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, FALSE,
+           2, US"-Mc", message_id);
           /* Control does not return here. */
           }
 
@@ -667,10 +682,8 @@ if (pid == 0)
         DEBUG(D_any) debug_printf("forked delivery process %d\n", (int)dpid);
         }
       else
-        {
         log_write(0, LOG_MAIN|LOG_PANIC, "daemon: delivery process fork "
           "failed: %s", strerror(errno));
-        }
       }
     }
   }
@@ -681,14 +694,11 @@ failed. Otherwise, keep count of the number of accepting processes and
 remember the pid for ticking off when the child completes. */
 
 if (pid < 0)
-  {
   never_error(US"daemon: accept process fork failed", US"Fork failed", errno);
-  }
 else
   {
   int i;
   for (i = 0; i < smtp_accept_max; ++i)
-    {
     if (smtp_slots[i].pid <= 0)
       {
       smtp_slots[i].pid = pid;
@@ -697,7 +707,6 @@ else
       smtp_accept_count++;
       break;
       }
-    }
   DEBUG(D_any) debug_printf("%d SMTP accept process%s running\n",
     smtp_accept_count, (smtp_accept_count == 1)? "" : "es");
   }
@@ -714,7 +723,7 @@ manifest itself as a broken pipe, so drop that one too. If the streams don't
 exist, something went wrong while setting things up. Make sure the socket
 descriptors are closed, in order to drop the connection. */
 
-if (smtp_out != NULL)
+if (smtp_out)
   {
   if (fclose(smtp_out) != 0 && errno != ECONNRESET && errno != EPIPE)
     log_write(0, LOG_MAIN|LOG_PANIC, "daemon: fclose(smtp_out) failed: %s",
@@ -723,7 +732,7 @@ if (smtp_out != NULL)
   }
 else (void)close(accept_socket);
 
-if (smtp_in != NULL)
+if (smtp_in)
   {
   if (fclose(smtp_in) != 0 && errno != ECONNRESET && errno != EPIPE)
     log_write(0, LOG_MAIN|LOG_PANIC, "daemon: fclose(smtp_in) failed: %s",
@@ -735,6 +744,9 @@ else (void)close(dup_accept_socket);
 /* Release any store used in this process, including the store used for holding
 the incoming host address and an expanded active_hostname. */
 
+log_close_all();
+interface_address =
+sender_host_address = NULL;
 store_reset(reset_point);
 sender_host_address = NULL;
 }
@@ -843,13 +855,12 @@ while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
   /* If it's a listening daemon for which we are keeping track of individual
   subprocesses, deal with an accepting process that has terminated. */
 
-  if (smtp_slots != NULL)
+  if (smtp_slots)
     {
     for (i = 0; i < smtp_accept_max; i++)
-      {
       if (smtp_slots[i].pid == pid)
         {
-        if (smtp_slots[i].host_address != NULL)
+        if (smtp_slots[i].host_address)
           store_free(smtp_slots[i].host_address);
         smtp_slots[i] = empty_smtp_slot;
         if (--smtp_accept_count < 0) smtp_accept_count = 0;
@@ -857,17 +868,16 @@ while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
           smtp_accept_count, (smtp_accept_count == 1)? "" : "es");
         break;
         }
-      }
     if (i < smtp_accept_max) continue;  /* Found an accepting process */
     }
 
   /* If it wasn't an accepting process, see if it was a queue-runner
   process that we are tracking. */
 
-  if (queue_pid_slots != NULL)
+  if (queue_pid_slots)
     {
-    for (i = 0; i < queue_run_max; i++)
-      {
+    int max = atoi(CS expand_string(queue_run_max));
+    for (i = 0; i < max; i++)
       if (queue_pid_slots[i] == pid)
         {
         queue_pid_slots[i] = 0;
@@ -876,7 +886,6 @@ while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
           queue_run_count, (queue_run_count == 1)? "" : "es");
         break;
         }
-      }
     }
   }
 }
@@ -914,6 +923,7 @@ int *listen_sockets = NULL;
 int listen_socket_count = 0;
 ip_address_item *addresses = NULL;
 time_t last_connection_time = (time_t)0;
+int local_queue_run_max = atoi(CS expand_string(queue_run_max));
 
 /* If any debugging options are set, turn on the D_pid bit so that all
 debugging lines get the pid added. */
@@ -922,16 +932,13 @@ DEBUG(D_any|D_v) debug_selector |= D_pid;
 
 if (inetd_wait_mode)
   {
-  int on = 1;
-
   listen_socket_count = 1;
-  listen_sockets = store_get(sizeof(int *));
+  listen_sockets = store_get(sizeof(int));
   (void) close(3);
   if (dup2(0, 3) == -1)
-    {
     log_write(0, LOG_MAIN|LOG_PANIC_DIE,
         "failed to dup inetd socket safely away: %s", strerror(errno));
-    }
+
   listen_sockets[0] = 3;
   (void) close(0);
   (void) close(1);
@@ -955,8 +962,10 @@ if (inetd_wait_mode)
   /* As per below, when creating sockets ourselves, we handle tcp_nodelay for
   our own buffering; we assume though that inetd set the socket REUSEADDR. */
 
-  if (tcp_nodelay) setsockopt(3, IPPROTO_TCP, TCP_NODELAY,
-    (uschar *)(&on), sizeof(on));
+  if (tcp_nodelay)
+    if (setsockopt(3, IPPROTO_TCP, TCP_NODELAY, US &on, sizeof(on)))
+      log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to set socket NODELAY: %s",
+       strerror(errno));
   }
 
 
@@ -1048,7 +1057,7 @@ if (daemon_listen && !inetd_wait_mode)
   int sep;
   int pct = 0;
   uschar *s;
-  uschar *list;
+  const uschar * list;
   uschar *local_iface_source = US"local_interfaces";
   ip_address_item *ipa;
   ip_address_item **pipa;
@@ -1071,8 +1080,7 @@ if (daemon_listen && !inetd_wait_mode)
 
     list = override_local_interfaces;
     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)))
       {
       uschar joinstr[4];
       uschar **ptr;
@@ -1096,11 +1104,11 @@ if (daemon_listen && !inetd_wait_mode)
         {
         joinstr[0] = sep;
         joinstr[1] = ' ';
-        *ptr = string_cat(*ptr, sizeptr, ptrptr, US"<", 1);
+        *ptr = string_catn(*ptr, sizeptr, ptrptr, US"<", 1);
         }
 
-      *ptr = string_cat(*ptr, sizeptr, ptrptr, joinstr, 2);
-      *ptr = string_cat(*ptr, sizeptr, ptrptr, s, Ustrlen(s));
+      *ptr = string_catn(*ptr, sizeptr, ptrptr, joinstr, 2);
+      *ptr = string_cat (*ptr, sizeptr, ptrptr, s);
       }
 
     if (new_smtp_port != NULL)
@@ -1122,18 +1130,18 @@ if (daemon_listen && !inetd_wait_mode)
     }
 
   /* Create a list of default SMTP ports, to be used if local_interfaces
-  contains entries without explict ports. First count the number of ports, then
+  contains entries without explicit ports. First count the number of ports, then
   build a translated list in a vector. */
 
   list = daemon_smtp_port;
   sep = 0;
-  while ((s = string_nextinlist(&list,&sep,big_buffer,big_buffer_size)))
+  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));
+       (s = string_nextinlist(&list, &sep, big_buffer, big_buffer_size));
        pct++)
     {
     if (isdigit(*s))
@@ -1275,7 +1283,7 @@ if (daemon_listen && !inetd_wait_mode)
 
   for (ipa = addresses; ipa != NULL; ipa = ipa->next)
     listen_socket_count++;
-  listen_sockets = store_get(sizeof(int *) * listen_socket_count);
+  listen_sockets = store_get(sizeof(int) * listen_socket_count);
 
   } /* daemon_listen but not inetd_wait_mode */
 
@@ -1354,7 +1362,6 @@ the listening sockets if required. */
 if (daemon_listen && !inetd_wait_mode)
   {
   int sk;
-  int on = 1;
   ip_address_item *ipa;
 
   /* For each IP address, create a socket, bind it to the appropriate port, and
@@ -1380,8 +1387,7 @@ if (daemon_listen && !inetd_wait_mode)
       wildcard = ipa->address[0] == 0;
       }
 
-    listen_sockets[sk] = ip_socket(SOCK_STREAM, af);
-    if (listen_sockets[sk] < 0)
+    if ((listen_sockets[sk] = ip_socket(SOCK_STREAM, af)) < 0)
       {
       if (check_special_case(0, addresses, ipa, FALSE))
         {
@@ -1430,6 +1436,9 @@ if (daemon_listen && !inetd_wait_mode)
     necessary for (some release of) USAGI Linux; other IP stacks fail at the
     listen() stage instead. */
 
+#ifdef TCP_FASTOPEN
+    tcp_fastopen_ok = TRUE;
+#endif
     for(;;)
       {
       uschar *msg, *addr;
@@ -1457,13 +1466,20 @@ if (daemon_listen && !inetd_wait_mode)
       }
 
     DEBUG(D_any)
-      {
       if (wildcard)
         debug_printf("listening on all interfaces (IPv%c) port %d\n",
-          (af == AF_INET6)? '6' : '4', ipa->port);
+          af == AF_INET6 ? '6' : '4', ipa->port);
       else
         debug_printf("listening on %s port %d\n", ipa->address, ipa->port);
+
+#ifdef TCP_FASTOPEN
+    if (setsockopt(listen_sockets[sk], IPPROTO_TCP, TCP_FASTOPEN,
+                   &smtp_connect_backlog, sizeof(smtp_connect_backlog)))
+      {
+      DEBUG(D_any) debug_printf("setsockopt FASTOPEN: %s\n", strerror(errno));
+      tcp_fastopen_ok = FALSE;
       }
+#endif
 
     /* Start listening on the bound socket, establishing the maximum backlog of
     connections that is allowed. On success, continue to the next address. */
@@ -1478,8 +1494,8 @@ if (daemon_listen && !inetd_wait_mode)
 
     if (!check_special_case(errno, addresses, ipa, TRUE))
       log_write(0, LOG_PANIC_DIE, "listen() failed on interface %s: %s",
-        wildcard? ((af == AF_INET6)? US"(any IPv6)" : US"(any IPv4)") :
-        ipa->address,
+        wildcard
+       ? af == AF_INET6 ? US"(any IPv6)" : US"(any IPv4)" : ipa->address,
         strerror(errno));
 
     DEBUG(D_any) debug_printf("wildcard IPv4 listen() failed after IPv6 "
@@ -1571,11 +1587,11 @@ originator_login = ((pw = getpwuid(exim_uid)) != NULL)?
 /* Get somewhere to keep the list of queue-runner pids if we are keeping track
 of them (and also if we are doing queue runs). */
 
-if (queue_interval > 0 && queue_run_max > 0)
+if (queue_interval > 0 && local_queue_run_max > 0)
   {
   int i;
-  queue_pid_slots = store_get(queue_run_max * sizeof(pid_t));
-  for (i = 0; i < queue_run_max; i++) queue_pid_slots[i] = 0;
+  queue_pid_slots = store_get(local_queue_run_max * sizeof(pid_t));
+  for (i = 0; i < local_queue_run_max; i++) queue_pid_slots[i] = 0;
   }
 
 /* Set up the handler for termination of child processes. */
@@ -1603,7 +1619,7 @@ if (inetd_wait_mode)
   log_write(0, LOG_MAIN,
     "exim %s daemon started: pid=%d, launched with listening socket, %s",
     version_string, getpid(), big_buffer);
-  set_process_info("daemon: pre-listening socket");
+  set_process_info("daemon(%s): pre-listening socket", version_string);
 
   /* set up the timeout logic */
   sigalrm_seen = 1;
@@ -1614,12 +1630,11 @@ else if (daemon_listen)
   int i, j;
   int smtp_ports = 0;
   int smtps_ports = 0;
-  ip_address_item *ipa;
-  uschar *p = big_buffer;
-  uschar *qinfo = (queue_interval > 0)?
-    string_sprintf("-q%s", readconf_printtime(queue_interval))
-    :
-    US"no queue runs";
+  ip_address_item * ipa;
+  uschar * p = big_buffer;
+  uschar * qinfo = queue_interval > 0
+    ? string_sprintf("-q%s", readconf_printtime(queue_interval))
+    : US"no queue runs";
 
   /* Build a list of listening addresses in big_buffer, but limit it to 10
   items. The style is for backwards compatibility.
@@ -1630,30 +1645,30 @@ else if (daemon_listen)
 
   for (j = 0; j < 2; j++)
     {
-    for (i = 0, ipa = addresses; i < 10 && ipa != NULL; i++, ipa = ipa->next)
+    for (i = 0, ipa = addresses; i < 10 && ipa; i++, ipa = ipa->next)
        {
        /* First time round, look for SMTP ports; second time round, look for
        SMTPS ports. For the first one of each, insert leading text. */
 
        if (host_is_tls_on_connect_port(ipa->port) == (j > 0))
          {
-         if (j == 0)
-           {
-           if (smtp_ports++ == 0)
+        if (j == 0)
+          {
+          if (smtp_ports++ == 0)
              {
              memcpy(p, "SMTP on", 8);
              p += 7;
              }
-           }
-         else
-           {
-           if (smtps_ports++ == 0)
+          }
+        else
+          {
+          if (smtps_ports++ == 0)
              {
              (void)sprintf(CS p, "%sSMTPS on",
-               (smtp_ports == 0)? "":" and for ");
-             while (*p != 0) p++;
+               smtp_ports == 0 ? "" : " and for ");
+             while (*p) p++;
              }
-           }
+          }
 
          /* Now the information about the port (and sometimes interface) */
 
@@ -1678,7 +1693,7 @@ else if (daemon_listen)
          }
        }
 
-    if (ipa != NULL)
+    if (ipa)
       {
       memcpy(p, " ...", 5);
       p += 4;
@@ -1688,18 +1703,29 @@ else if (daemon_listen)
   log_write(0, LOG_MAIN,
     "exim %s daemon started: pid=%d, %s, listening for %s",
     version_string, getpid(), qinfo, big_buffer);
-  set_process_info("daemon: %s, listening for %s", qinfo, big_buffer);
+  set_process_info("daemon(%s): %s, listening for %s",
+    version_string, qinfo, big_buffer);
   }
 
 else
   {
+  uschar * s = *queue_name
+    ? string_sprintf("-qG%s/%s", queue_name, readconf_printtime(queue_interval))
+    : string_sprintf("-q%s", readconf_printtime(queue_interval));
   log_write(0, LOG_MAIN,
-    "exim %s daemon started: pid=%d, -q%s, not listening for SMTP",
-    version_string, getpid(), readconf_printtime(queue_interval));
-  set_process_info("daemon: -q%s, not listening",
-    readconf_printtime(queue_interval));
+    "exim %s daemon started: pid=%d, %s, not listening for SMTP",
+    version_string, getpid(), s);
+  set_process_info("daemon(%s): %s, not listening", version_string, s);
   }
 
+/* Do any work it might be useful to amortize over our children
+(eg: compile regex) */
+
+dns_pattern_init();
+
+#ifdef WITH_CONTENT_SCAN
+malware_init();
+#endif
 
 /* Close the log so it can be renamed and moved. In the few cases below where
 this long-running process writes to the log (always exceptional conditions), it
@@ -1781,7 +1807,7 @@ for (;;)
       re-exec is required. */
 
       if (queue_interval > 0 &&
-         (queue_run_max <= 0 || queue_run_count < queue_run_max))
+         (local_queue_run_max <= 0 || queue_run_count < local_queue_run_max))
         {
         if ((pid = fork()) == 0)
           {
@@ -1825,21 +1851,22 @@ for (;;)
             if (deliver_force_thaw) *p++ = 'f';
             if (queue_run_local) *p++ = 'l';
             *p = 0;
-            extra[0] = opt;
+           extra[0] = queue_name
+             ? string_sprintf("%sG%s", opt, queue_name) : opt;
 
             /* If -R or -S were on the original command line, ensure they get
             passed on. */
 
-            if (deliver_selectstring != NULL)
+            if (deliver_selectstring)
               {
-              extra[extracount++] = deliver_selectstring_regex? US"-Rr" : US"-R";
+              extra[extracount++] = deliver_selectstring_regex ? US"-Rr" : US"-R";
               extra[extracount++] = deliver_selectstring;
               }
 
-            if (deliver_selectstring_sender != NULL)
+            if (deliver_selectstring_sender)
               {
-              extra[extracount++] = deliver_selectstring_sender_regex?
-                US"-Sr" : US"-S";
+              extra[extracount++] = deliver_selectstring_sender_regex
+               ? US"-Sr" : US"-S";
               extra[extracount++] = deliver_selectstring_sender;
               }
 
@@ -1866,15 +1893,13 @@ for (;;)
         else
           {
           int i;
-          for (i = 0; i < queue_run_max; ++i)
-            {
+          for (i = 0; i < local_queue_run_max; ++i)
             if (queue_pid_slots[i] <= 0)
               {
               queue_pid_slots[i] = pid;
               queue_run_count++;
               break;
               }
-            }
           DEBUG(D_any) debug_printf("%d queue-runner process%s running\n",
             queue_run_count, (queue_run_count == 1)? "" : "es");
           }
diff --git a/src/dane-gnu.c b/src/dane-gnu.c
new file mode 100644 (file)
index 0000000..b98bffa
--- /dev/null
@@ -0,0 +1,21 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2013 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* This file (will) provide DANE support for Exim using the GnuTLS library,
+but is not yet an available supported implementation.  This file is #included
+into dane.c when USE_GNUTLS has been set.  */
+
+/* As of March 2014, the reference implementation for DANE that we are
+using was written by Viktor Dukhovny and it supports OpenSSL only.  At
+some point we will add GnuTLS support, but for right now just abort the
+build and explain why. */
+
+
+#error No support for DANE using GnuTLS yet.
+
+
+/* End of dane-gnu.c */
diff --git a/src/dane-openssl.c b/src/dane-openssl.c
new file mode 100644 (file)
index 0000000..97acccb
--- /dev/null
@@ -0,0 +1,1719 @@
+/*
+ *  Author: Viktor Dukhovni
+ *  License: THIS CODE IS IN THE PUBLIC DOMAIN.
+ *
+ * Copyright (c) The Exim Maintainers 2014 - 2016
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+
+#include <openssl/opensslv.h>
+#include <openssl/err.h>
+#include <openssl/crypto.h>
+#include <openssl/safestack.h>
+#include <openssl/objects.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+#include <openssl/evp.h>
+#include <openssl/bn.h>
+
+#if OPENSSL_VERSION_NUMBER < 0x1000000fL
+# error "OpenSSL 1.0.0 or higher required"
+#endif
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
+# define X509_up_ref(x) CRYPTO_add(&((x)->references), 1, CRYPTO_LOCK_X509)
+#endif
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
+# define EXIM_HAVE_ASN1_MACROS
+# define EXIM_OPAQUE_X509
+#else
+# define X509_STORE_CTX_get_verify(ctx)                (ctx)->verify
+# define X509_STORE_CTX_get_verify_cb(ctx)     (ctx)->verify_cb
+# define X509_STORE_CTX_get0_cert(ctx)         (ctx)->cert
+# define X509_STORE_CTX_get0_chain(ctx)                (ctx)->chain
+# define X509_STORE_CTX_get0_untrusted(ctx)    (ctx)->untrusted
+
+# define X509_STORE_CTX_set_verify(ctx, verify_chain)  (ctx)->verify = (verify_chain)
+# define X509_STORE_CTX_set0_verified_chain(ctx, sk)   (ctx)->chain = (sk)
+# define X509_STORE_CTX_set_error_depth(ctx, val)      (ctx)->error_depth = (val)
+# define X509_STORE_CTX_set_current_cert(ctx, cert)    (ctx)->current_cert = (cert)
+
+# define ASN1_STRING_get0_data ASN1_STRING_data
+# define X509_getm_notBefore   X509_get_notBefore
+# define X509_getm_notAfter    X509_get_notAfter
+
+# define CRYPTO_ONCE_STATIC_INIT 0
+# define CRYPTO_THREAD_run_once         run_once
+typedef int CRYPTO_ONCE;
+#endif
+
+
+#include "danessl.h"
+
+#define DANESSL_F_ADD_SKID             100
+#define DANESSL_F_ADD_TLSA             101
+#define DANESSL_F_CHECK_END_ENTITY     102
+#define DANESSL_F_CTX_INIT             103
+#define DANESSL_F_GROW_CHAIN           104
+#define DANESSL_F_INIT                 105
+#define DANESSL_F_LIBRARY_INIT         106
+#define DANESSL_F_LIST_ALLOC           107
+#define DANESSL_F_MATCH                        108
+#define DANESSL_F_PUSH_EXT             109
+#define DANESSL_F_SET_TRUST_ANCHOR     110
+#define DANESSL_F_VERIFY_CERT          111
+#define DANESSL_F_WRAP_CERT            112
+#define DANESSL_F_DANESSL_VERIFY_CHAIN 113
+
+#define DANESSL_R_BAD_CERT             100
+#define DANESSL_R_BAD_CERT_PKEY                101
+#define DANESSL_R_BAD_DATA_LENGTH      102
+#define DANESSL_R_BAD_DIGEST           103
+#define DANESSL_R_BAD_NULL_DATA                104
+#define DANESSL_R_BAD_PKEY             105
+#define DANESSL_R_BAD_SELECTOR         106
+#define DANESSL_R_BAD_USAGE            107
+#define DANESSL_R_INIT                 108
+#define DANESSL_R_LIBRARY_INIT         109
+#define DANESSL_R_NOSIGN_KEY           110
+#define DANESSL_R_SCTX_INIT            111
+#define DANESSL_R_SUPPORT              112
+
+#ifndef OPENSSL_NO_ERR
+#define        DANESSL_F_PLACEHOLDER           0               /* FIRST! Value TBD */
+static ERR_STRING_DATA dane_str_functs[] = {
+    {DANESSL_F_PLACEHOLDER,            "DANE library"},        /* FIRST!!! */
+    {DANESSL_F_ADD_SKID,               "add_skid"},
+    {DANESSL_F_ADD_TLSA,               "DANESSL_add_tlsa"},
+    {DANESSL_F_CHECK_END_ENTITY,       "check_end_entity"},
+    {DANESSL_F_CTX_INIT,               "DANESSL_CTX_init"},
+    {DANESSL_F_GROW_CHAIN,             "grow_chain"},
+    {DANESSL_F_INIT,                   "DANESSL_init"},
+    {DANESSL_F_LIBRARY_INIT,           "DANESSL_library_init"},
+    {DANESSL_F_LIST_ALLOC,             "list_alloc"},
+    {DANESSL_F_MATCH,                  "match"},
+    {DANESSL_F_PUSH_EXT,               "push_ext"},
+    {DANESSL_F_SET_TRUST_ANCHOR,       "set_trust_anchor"},
+    {DANESSL_F_VERIFY_CERT,            "verify_cert"},
+    {DANESSL_F_WRAP_CERT,              "wrap_cert"},
+    {0,                                        NULL}
+};
+static ERR_STRING_DATA dane_str_reasons[] = {
+    {DANESSL_R_BAD_CERT,       "Bad TLSA record certificate"},
+    {DANESSL_R_BAD_CERT_PKEY,  "Bad TLSA record certificate public key"},
+    {DANESSL_R_BAD_DATA_LENGTH,        "Bad TLSA record digest length"},
+    {DANESSL_R_BAD_DIGEST,     "Bad TLSA record digest"},
+    {DANESSL_R_BAD_NULL_DATA,  "Bad TLSA record null data"},
+    {DANESSL_R_BAD_PKEY,       "Bad TLSA record public key"},
+    {DANESSL_R_BAD_SELECTOR,   "Bad TLSA record selector"},
+    {DANESSL_R_BAD_USAGE,      "Bad TLSA record usage"},
+    {DANESSL_R_INIT,           "DANESSL_init() required"},
+    {DANESSL_R_LIBRARY_INIT,   "DANESSL_library_init() required"},
+    {DANESSL_R_NOSIGN_KEY,     "Certificate usage 2 requires EC support"},
+    {DANESSL_R_SCTX_INIT,      "DANESSL_CTX_init() required"},
+    {DANESSL_R_SUPPORT,                "DANE library features not supported"},
+    {0,                                NULL}
+};
+#endif
+
+#define DANEerr(f, r) ERR_PUT_error(err_lib_dane, (f), (r), __FILE__, __LINE__)
+
+static int err_lib_dane = -1;
+static int dane_idx = -1;
+
+#ifdef X509_V_FLAG_PARTIAL_CHAIN       /* OpenSSL >= 1.0.2 */
+static int wrap_to_root = 0;
+#else
+static int wrap_to_root = 1;
+#endif
+
+static void (*cert_free)(void *) = (void (*)(void *)) X509_free;
+static void (*pkey_free)(void *) = (void (*)(void *)) EVP_PKEY_free;
+
+typedef struct dane_list
+{
+    struct dane_list *next;
+    void *value;
+} *dane_list;
+
+#define LINSERT(h, e) do { (e)->next = (h); (h) = (e); } while (0)
+
+typedef struct dane_host_list
+{
+    struct dane_host_list *next;
+    char *value;
+} *dane_host_list;
+
+typedef struct dane_data
+{
+    size_t datalen;
+    unsigned char data[0];
+} *dane_data;
+
+typedef struct dane_data_list
+{
+    struct dane_data_list *next;
+    dane_data value;
+} *dane_data_list;
+
+typedef struct dane_mtype
+{
+    int mdlen;
+    const EVP_MD *md;
+    dane_data_list data;
+} *dane_mtype;
+
+typedef struct dane_mtype_list
+{
+    struct dane_mtype_list *next;
+    dane_mtype value;
+} *dane_mtype_list;
+
+typedef struct dane_selector
+{
+    uint8_t selector;
+    dane_mtype_list mtype;
+} *dane_selector;
+
+typedef struct dane_selector_list
+{
+    struct dane_selector_list *next;
+    dane_selector value;
+} *dane_selector_list;
+
+typedef struct dane_pkey_list
+{
+    struct dane_pkey_list *next;
+    EVP_PKEY *value;
+} *dane_pkey_list;
+
+typedef struct dane_cert_list
+{
+    struct dane_cert_list *next;
+    X509 *value;
+} *dane_cert_list;
+
+typedef struct ssl_dane
+{
+    int            (*verify)(X509_STORE_CTX *);
+    STACK_OF(X509) *roots;
+    STACK_OF(X509) *chain;
+    X509           *match;             /* Matched cert */
+    const char     *thost;             /* TLSA base domain */
+    char          *mhost;              /* Matched peer name */
+    dane_pkey_list pkeys;
+    dane_cert_list certs;
+    dane_host_list hosts;
+    dane_selector_list selectors[DANESSL_USAGE_LAST + 1];
+    int            depth;
+    int                   mdpth;               /* Depth of matched cert */
+    int                   multi;               /* Multi-label wildcards? */
+    int                   count;               /* Number of TLSA records */
+} ssl_dane;
+
+#ifndef X509_V_ERR_HOSTNAME_MISMATCH
+# define X509_V_ERR_HOSTNAME_MISMATCH X509_V_ERR_APPLICATION_VERIFICATION
+#endif
+
+
+
+static int
+match(dane_selector_list slist, X509 *cert, int depth)
+{
+int matched;
+
+/*
+ * Note, set_trust_anchor() needs to know whether the match was for a
+ * pkey digest or a certificate digest.  We return MATCHED_PKEY or
+ * MATCHED_CERT accordingly.
+ */
+#define MATCHED_CERT (DANESSL_SELECTOR_CERT + 1)
+#define MATCHED_PKEY (DANESSL_SELECTOR_SPKI + 1)
+
+/*
+ * Loop over each selector, mtype, and associated data element looking
+ * for a match.
+ */
+for (matched = 0; !matched && slist; slist = slist->next)
+  {
+  dane_mtype_list m;
+  unsigned char mdbuf[EVP_MAX_MD_SIZE];
+  unsigned char *buf = NULL;
+  unsigned char *buf2;
+  unsigned int len = 0;
+
+  /*
+   * Extract ASN.1 DER form of certificate or public key.
+   */
+  switch(slist->value->selector)
+    {
+    case DANESSL_SELECTOR_CERT:
+      len = i2d_X509(cert, NULL);
+      buf2 = buf = (unsigned char *) OPENSSL_malloc(len);
+      if(buf) i2d_X509(cert, &buf2);
+      break;
+    case DANESSL_SELECTOR_SPKI:
+      len = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), NULL);
+      buf2 = buf = (unsigned char *) OPENSSL_malloc(len);
+      if(buf) i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), &buf2);
+      break;
+    }
+
+  if (!buf)
+    {
+    DANEerr(DANESSL_F_MATCH, ERR_R_MALLOC_FAILURE);
+    return 0;
+    }
+  OPENSSL_assert(buf2 - buf == len);
+
+  /*
+   * Loop over each mtype and data element
+   */
+  for (m = slist->value->mtype; !matched && m; m = m->next)
+    {
+    dane_data_list d;
+    unsigned char *cmpbuf = buf;
+    unsigned int cmplen = len;
+
+    /*
+     * If it is a digest, compute the corresponding digest of the
+     * DER data for comparison, otherwise, use the full object.
+     */
+    if (m->value->md)
+      {
+      cmpbuf = mdbuf;
+      if (!EVP_Digest(buf, len, cmpbuf, &cmplen, m->value->md, 0))
+         matched = -1;
+      }
+    for (d = m->value->data; !matched && d; d = d->next)
+       if (  cmplen == d->value->datalen
+          && memcmp(cmpbuf, d->value->data, cmplen) == 0)
+           matched = slist->value->selector + 1;
+    }
+
+  OPENSSL_free(buf);
+  }
+
+return matched;
+}
+
+static int
+push_ext(X509 *cert, X509_EXTENSION *ext)
+{
+if (ext)
+  {
+  if (X509_add_ext(cert, ext, -1))
+      return 1;
+  X509_EXTENSION_free(ext);
+  }
+DANEerr(DANESSL_F_PUSH_EXT, ERR_R_MALLOC_FAILURE);
+return 0;
+}
+
+static int
+add_ext(X509 *issuer, X509 *subject, int ext_nid, char *ext_val)
+{
+X509V3_CTX v3ctx;
+
+X509V3_set_ctx(&v3ctx, issuer, subject, 0, 0, 0);
+return push_ext(subject, X509V3_EXT_conf_nid(0, &v3ctx, ext_nid, ext_val));
+}
+
+static int
+set_serial(X509 *cert, AUTHORITY_KEYID *akid, X509 *subject)
+{
+int ret = 0;
+BIGNUM *bn;
+
+if (akid && akid->serial)
+  return (X509_set_serialNumber(cert, akid->serial));
+
+/*
+ * Add one to subject's serial to avoid collisions between TA serial and
+ * serial of signing root.
+ */
+if (  (bn = ASN1_INTEGER_to_BN(X509_get_serialNumber(subject), 0)) != 0
+   && BN_add_word(bn, 1)
+   && BN_to_ASN1_INTEGER(bn, X509_get_serialNumber(cert)))
+  ret = 1;
+
+if (bn)
+  BN_free(bn);
+return ret;
+}
+
+static int
+add_akid(X509 *cert, AUTHORITY_KEYID *akid)
+{
+int nid = NID_authority_key_identifier;
+ASN1_OCTET_STRING *id;
+unsigned char c = 0;
+int ret = 0;
+
+/*
+ * 0 will never be our subject keyid from a SHA-1 hash, but it could be
+ * our subject keyid if forced from child's akid.  If so, set our
+ * authority keyid to 1.  This way we are never self-signed, and thus
+ * exempt from any potential (off by default for now in OpenSSL)
+ * self-signature checks!
+ */
+id =  akid && akid->keyid ? akid->keyid : 0;
+if (id && ASN1_STRING_length(id) == 1 && *ASN1_STRING_get0_data(id) == c)
+  c = 1;
+
+if (  (akid = AUTHORITY_KEYID_new()) != 0
+   && (akid->keyid = ASN1_OCTET_STRING_new()) != 0
+#ifdef EXIM_HAVE_ASN1_MACROS
+   && ASN1_OCTET_STRING_set(akid->keyid, (void *) &c, 1)
+#else
+   && M_ASN1_OCTET_STRING_set(akid->keyid, (void *) &c, 1)
+#endif
+   && X509_add1_ext_i2d(cert, nid, akid, 0, X509V3_ADD_APPEND))
+  ret = 1;
+if (akid)
+  AUTHORITY_KEYID_free(akid);
+return ret;
+}
+
+static int
+add_skid(X509 *cert, AUTHORITY_KEYID *akid)
+{
+int nid = NID_subject_key_identifier;
+
+if (!akid || !akid->keyid)
+  return add_ext(0, cert, nid, "hash");
+return X509_add1_ext_i2d(cert, nid, akid->keyid, 0, X509V3_ADD_APPEND) > 0;
+}
+
+static X509_NAME *
+akid_issuer_name(AUTHORITY_KEYID *akid)
+{
+if (akid && akid->issuer)
+  {
+  int     i;
+  GENERAL_NAMES *gens = akid->issuer;
+
+  for (i = 0; i < sk_GENERAL_NAME_num(gens); ++i)
+    {
+    GENERAL_NAME *gn = sk_GENERAL_NAME_value(gens, i);
+
+    if (gn->type == GEN_DIRNAME)
+      return (gn->d.dirn);
+    }
+  }
+return 0;
+}
+
+static int
+set_issuer_name(X509 *cert, AUTHORITY_KEYID *akid)
+{
+X509_NAME *name = akid_issuer_name(akid);
+
+/*
+ * If subject's akid specifies an authority key identifer issuer name, we
+ * must use that.
+ */
+return X509_set_issuer_name(cert,
+                           name ? name : X509_get_subject_name(cert));
+}
+
+static int
+grow_chain(ssl_dane *dane, int trusted, X509 *cert)
+{
+STACK_OF(X509) **xs = trusted ? &dane->roots : &dane->chain;
+static ASN1_OBJECT *serverAuth = 0;
+
+#define UNTRUSTED 0
+#define TRUSTED 1
+
+if (  trusted && !serverAuth
+   && !(serverAuth = OBJ_nid2obj(NID_server_auth)))
+  {
+  DANEerr(DANESSL_F_GROW_CHAIN, ERR_R_MALLOC_FAILURE);
+  return 0;
+  }
+if (!*xs && !(*xs = sk_X509_new_null()))
+  {
+  DANEerr(DANESSL_F_GROW_CHAIN, ERR_R_MALLOC_FAILURE);
+  return 0;
+  }
+
+if (cert)
+  {
+  if (trusted && !X509_add1_trust_object(cert, serverAuth))
+    return 0;
+#ifdef EXIM_OPAQUE_X509
+  X509_up_ref(cert);
+#else
+  CRYPTO_add(&cert->references, 1, CRYPTO_LOCK_X509);
+#endif
+  if (!sk_X509_push(*xs, cert))
+    {
+    X509_free(cert);
+    DANEerr(DANESSL_F_GROW_CHAIN, ERR_R_MALLOC_FAILURE);
+    return 0;
+    }
+  }
+return 1;
+}
+
+static int
+wrap_issuer(ssl_dane *dane, EVP_PKEY *key, X509 *subject, int depth, int top)
+{
+int ret = 1;
+X509 *cert = 0;
+AUTHORITY_KEYID *akid;
+X509_NAME *name = X509_get_issuer_name(subject);
+EVP_PKEY *newkey = key ? key : X509_get_pubkey(subject);
+
+#define WRAP_MID 0              /* Ensure intermediate. */
+#define WRAP_TOP 1              /* Ensure self-signed. */
+
+if (!name || !newkey || !(cert = X509_new()))
+  return 0;
+
+/*
+ * Record the depth of the trust-anchor certificate.
+ */
+if (dane->depth < 0)
+  dane->depth = depth + 1;
+
+/*
+ * XXX: Uncaught error condition:
+ *
+ * The return value is NULL both when the extension is missing, and when
+ * OpenSSL rans out of memory while parsing the extension.
+ */
+ERR_clear_error();
+akid = X509_get_ext_d2i(subject, NID_authority_key_identifier, 0, 0);
+/* XXX: Should we peek at the error stack here??? */
+
+/*
+ * If top is true generate a self-issued root CA, otherwise an
+ * intermediate CA and possibly its self-signed issuer.
+ *
+ * CA cert valid for +/- 30 days
+ */
+if (  !X509_set_version(cert, 2)
+   || !set_serial(cert, akid, subject)
+   || !set_issuer_name(cert, akid)
+   || !X509_gmtime_adj(X509_getm_notBefore(cert), -30 * 86400L)
+   || !X509_gmtime_adj(X509_getm_notAfter(cert), 30 * 86400L)
+   || !X509_set_subject_name(cert, name)
+   || !X509_set_pubkey(cert, newkey)
+   || !add_ext(0, cert, NID_basic_constraints, "CA:TRUE")
+   || (!top && !add_akid(cert, akid))
+   || !add_skid(cert, akid)
+   || (  !top && wrap_to_root
+      && !wrap_issuer(dane, newkey, cert, depth, WRAP_TOP)))
+  ret = 0;
+
+if (akid)
+  AUTHORITY_KEYID_free(akid);
+if (!key)
+  EVP_PKEY_free(newkey);
+if (ret)
+  ret = grow_chain(dane, !top && wrap_to_root ? UNTRUSTED : TRUSTED, cert);
+if (cert)
+  X509_free(cert);
+return ret;
+}
+
+static int
+wrap_cert(ssl_dane *dane, X509 *tacert, int depth)
+{
+if (dane->depth < 0)
+  dane->depth = depth + 1;
+
+/*
+ * If the TA certificate is self-issued, or need not be, use it directly.
+ * Otherwise, synthesize requisite ancestors.
+ */
+if (  !wrap_to_root
+   || X509_check_issued(tacert, tacert) == X509_V_OK)
+  return grow_chain(dane, TRUSTED, tacert);
+
+if (wrap_issuer(dane, 0, tacert, depth, WRAP_MID))
+  return grow_chain(dane, UNTRUSTED, tacert);
+return 0;
+}
+
+static int
+ta_signed(ssl_dane *dane, X509 *cert, int depth)
+{
+dane_cert_list x;
+dane_pkey_list k;
+EVP_PKEY *pk;
+int done = 0;
+
+/*
+ * First check whether issued and signed by a TA cert, this is cheaper
+ * than the bare-public key checks below, since we can determine whether
+ * the candidate TA certificate issued the certificate to be checked
+ * first (name comparisons), before we bother with signature checks
+ * (public key operations).
+ */
+for (x = dane->certs; !done && x; x = x->next)
+  {
+  if (X509_check_issued(x->value, cert) == X509_V_OK)
+    {
+    if (!(pk = X509_get_pubkey(x->value)))
+      {
+      /*
+       * The cert originally contained a valid pkey, which does
+       * not just vanish, so this is most likely a memory error.
+       */
+      done = -1;
+      break;
+      }
+    /* Check signature, since some other TA may work if not this. */
+    if (X509_verify(cert, pk) > 0)
+      done = wrap_cert(dane, x->value, depth) ? 1 : -1;
+    EVP_PKEY_free(pk);
+    }
+  }
+
+/*
+ * With bare TA public keys, we can't check whether the trust chain is
+ * issued by the key, but we can determine whether it is signed by the
+ * key, so we go with that.
+ *
+ * Ideally, the corresponding certificate was presented in the chain, and we
+ * matched it by its public key digest one level up.  This code is here
+ * to handle adverse conditions imposed by sloppy administrators of
+ * receiving systems with poorly constructed chains.
+ *
+ * We'd like to optimize out keys that should not match when the cert's
+ * authority key id does not match the key id of this key computed via
+ * the RFC keyid algorithm (SHA-1 digest of public key bit-string sans
+ * ASN1 tag and length thus also excluding the unused bits field that is
+ * logically part of the length).  However, some CAs have a non-standard
+ * authority keyid, so we lose.  Too bad.
+ *
+ * This may push errors onto the stack when the certificate signature is
+ * not of the right type or length, throw these away,
+ */
+for (k = dane->pkeys; !done && k; k = k->next)
+  if (X509_verify(cert, k->value) > 0)
+    done = wrap_issuer(dane, k->value, cert, depth, WRAP_MID) ? 1 : -1;
+  else
+    ERR_clear_error();
+
+return done;
+}
+
+static int
+set_trust_anchor(X509_STORE_CTX *ctx, ssl_dane *dane, X509 *cert)
+{
+int matched = 0;
+int n;
+int i;
+int depth = 0;
+EVP_PKEY *takey;
+X509 *ca;
+STACK_OF(X509) *in = X509_STORE_CTX_get0_untrusted(ctx);
+
+if (!grow_chain(dane, UNTRUSTED, 0))
+  return -1;
+
+/*
+ * Accept a degenerate case: depth 0 self-signed trust-anchor.
+ */
+if (X509_check_issued(cert, cert) == X509_V_OK)
+  {
+  dane->depth = 0;
+  matched = match(dane->selectors[DANESSL_USAGE_DANE_TA], cert, 0);
+  if (matched > 0 && !grow_chain(dane, TRUSTED, cert))
+    matched = -1;
+  return matched;
+  }
+
+/* Make a shallow copy of the input untrusted chain. */
+if (!(in = sk_X509_dup(in)))
+  {
+  DANEerr(DANESSL_F_SET_TRUST_ANCHOR, ERR_R_MALLOC_FAILURE);
+  return -1;
+  }
+
+/*
+ * At each iteration we consume the issuer of the current cert.  This
+ * reduces the length of the "in" chain by one.  If no issuer is found,
+ * we are done.  We also stop when a certificate matches a TA in the
+ * peer's TLSA RRset.
+ *
+ * Caller ensures that the initial certificate is not self-signed.
+ */
+for (n = sk_X509_num(in); n > 0; --n, ++depth)
+  {
+  for (i = 0; i < n; ++i)
+    if (X509_check_issued(sk_X509_value(in, i), cert) == X509_V_OK)
+      break;
+
+  /*
+   * Final untrusted element with no issuer in the peer's chain, it may
+   * however be signed by a pkey or cert obtained via a TLSA RR.
+   */
+  if (i == n)
+    break;
+
+  /* Peer's chain contains an issuer ca. */
+  ca = sk_X509_delete(in, i);
+
+  /* If not a trust anchor, record untrusted ca and continue. */
+  if ((matched = match(dane->selectors[DANESSL_USAGE_DANE_TA], ca,
+                    depth + 1)) == 0)
+    {
+    if (grow_chain(dane, UNTRUSTED, ca))
+      {
+      if (X509_check_issued(ca, ca) != X509_V_OK)
+       {
+       /* Restart with issuer as subject */
+       cert = ca;
+       continue;
+       }
+      /* Final self-signed element, skip ta_signed() check. */
+      cert = 0;
+      }
+    else
+      matched = -1;
+    }
+  else if(matched == MATCHED_CERT)
+    {
+    if(!wrap_cert(dane, ca, depth))
+      matched = -1;
+    }
+  else if(matched == MATCHED_PKEY)
+    {
+    if (  !(takey = X509_get_pubkey(ca))
+       || !wrap_issuer(dane, takey, cert, depth, WRAP_MID))
+      {
+      if (takey)
+       EVP_PKEY_free(takey);
+      else
+       DANEerr(DANESSL_F_SET_TRUST_ANCHOR, ERR_R_MALLOC_FAILURE);
+      matched = -1;
+      }
+    }
+  break;
+  }
+
+/* Shallow free the duplicated input untrusted chain. */
+sk_X509_free(in);
+
+/*
+ * When the loop exits, if "cert" is set, it is not self-signed and has
+ * no issuer in the chain, we check for a possible signature via a DNS
+ * obtained TA cert or public key.
+ */
+if (matched == 0 && cert)
+  matched = ta_signed(dane, cert, depth);
+
+return matched;
+}
+
+static int
+check_end_entity(X509_STORE_CTX *ctx, ssl_dane *dane, X509 *cert)
+{
+int matched;
+
+matched = match(dane->selectors[DANESSL_USAGE_DANE_EE], cert, 0);
+if (matched > 0)
+  {
+  dane->mdpth = 0;
+  dane->match = cert;
+  X509_up_ref(cert);
+  if(!X509_STORE_CTX_get0_chain(ctx))
+    {
+    STACK_OF(X509) * sk = sk_X509_new_null();
+    if (sk && sk_X509_push(sk, cert))
+      {
+      X509_up_ref(cert);
+      X509_STORE_CTX_set0_verified_chain(ctx, sk);
+      }
+    else
+      {
+      if (sk) sk_X509_free(sk);
+      DANEerr(DANESSL_F_CHECK_END_ENTITY, ERR_R_MALLOC_FAILURE);
+      return -1;
+      }
+    }
+  }
+return matched;
+}
+
+static int
+match_name(const char *certid, ssl_dane *dane)
+{
+int multi = dane->multi;
+dane_host_list hosts;
+
+for (hosts = dane->hosts; hosts; hosts = hosts->next)
+  {
+  int match_subdomain = 0;
+  const char *domain = hosts->value;
+  const char *parent;
+  int idlen;
+  int domlen;
+
+  if (*domain == '.' && domain[1] != '\0')
+    {
+    ++domain;
+    match_subdomain = 1;
+    }
+
+  /*
+   * Sub-domain match: certid is any sub-domain of hostname.
+   */
+  if(match_subdomain)
+    {
+    if (  (idlen = strlen(certid)) > (domlen = strlen(domain)) + 1
+       && certid[idlen - domlen - 1] == '.'
+       && !strcasecmp(certid + (idlen - domlen), domain))
+      return 1;
+    else
+      continue;
+    }
+
+  /*
+   * Exact match and initial "*" match. The initial "*" in a certid
+   * matches one (if multi is false) or more hostname components under
+   * the condition that the certid contains multiple hostname components.
+   */
+  if (  !strcasecmp(certid, domain)
+     || (  certid[0] == '*' && certid[1] == '.' && certid[2] != 0
+        && (parent = strchr(domain, '.')) != 0
+        && (idlen = strlen(certid + 1)) <= (domlen = strlen(parent))
+        && strcasecmp(multi ? parent + domlen - idlen : parent, certid+1) == 0))
+    return 1;
+  }
+return 0;
+}
+
+static const char *
+check_name(const char *name, int len)
+{
+const char *cp = name + len;
+
+while (len > 0 && !*--cp)
+  --len;                          /* Ignore trailing NULs */
+if (len <= 0)
+  return 0;
+for (cp = name; *cp; cp++)
+  {
+  char c = *cp;
+  if (!((c >= 'a' && c <= 'z') ||
+       (c >= '0' && c <= '9') ||
+       (c >= 'A' && c <= 'Z') ||
+       (c == '.' || c == '-') ||
+       (c == '*')))
+    return 0;                   /* Only LDH, '.' and '*' */
+  }
+if (cp - name != len)               /* Guard against internal NULs */
+  return 0;
+return name;
+}
+
+static const char *
+parse_dns_name(const GENERAL_NAME *gn)
+{
+if (gn->type != GEN_DNS)
+  return 0;
+if (ASN1_STRING_type(gn->d.ia5) != V_ASN1_IA5STRING)
+  return 0;
+return check_name((const char *) ASN1_STRING_get0_data(gn->d.ia5),
+                 ASN1_STRING_length(gn->d.ia5));
+}
+
+static char *
+parse_subject_name(X509 *cert)
+{
+X509_NAME *name = X509_get_subject_name(cert);
+X509_NAME_ENTRY *entry;
+ASN1_STRING *entry_str;
+unsigned char *namebuf;
+int nid = NID_commonName;
+int len;
+int i;
+
+if (!name || (i = X509_NAME_get_index_by_NID(name, nid, -1)) < 0)
+  return 0;
+if (!(entry = X509_NAME_get_entry(name, i)))
+  return 0;
+if (!(entry_str = X509_NAME_ENTRY_get_data(entry)))
+  return 0;
+
+if ((len = ASN1_STRING_to_UTF8(&namebuf, entry_str)) < 0)
+  return 0;
+if (len <= 0 || check_name((char *) namebuf, len) == 0)
+  {
+  OPENSSL_free(namebuf);
+  return 0;
+  }
+return (char *) namebuf;
+}
+
+static int
+name_check(ssl_dane *dane, X509 *cert)
+{
+int matched = 0;
+BOOL got_altname = FALSE;
+GENERAL_NAMES *gens;
+
+gens = X509_get_ext_d2i(cert, NID_subject_alt_name, 0, 0);
+if (gens)
+  {
+  int n = sk_GENERAL_NAME_num(gens);
+  int i;
+
+  for (i = 0; i < n; ++i)
+    {
+    const GENERAL_NAME *gn = sk_GENERAL_NAME_value(gens, i);
+    const char *certid;
+
+    if (gn->type != GEN_DNS)
+       continue;
+    got_altname = TRUE;
+    certid = parse_dns_name(gn);
+    if (certid && *certid)
+      {
+      if ((matched = match_name(certid, dane)) == 0)
+       continue;
+      if (!(dane->mhost = OPENSSL_strdup(certid)))
+       matched = -1;
+      DEBUG(D_tls) debug_printf("Dane name_check: matched SAN %s\n", certid);
+      break;
+      }
+    }
+  GENERAL_NAMES_free(gens);
+  }
+
+/*
+ * XXX: Should the subjectName be skipped when *any* altnames are present,
+ * or only when DNS altnames are present?
+ */
+if (!got_altname)
+  {
+  char *certid = parse_subject_name(cert);
+  if (certid != 0 && *certid && (matched = match_name(certid, dane)) != 0)
+    {
+    DEBUG(D_tls) debug_printf("Dane name_check: matched SN %s\n", certid);
+    dane->mhost = OPENSSL_strdup(certid);
+    }
+  if (certid)
+    OPENSSL_free(certid);
+  }
+return matched;
+}
+
+static int
+verify_chain(X509_STORE_CTX *ctx)
+{
+int (*cb)(int, X509_STORE_CTX *) = X509_STORE_CTX_get_verify_cb(ctx);
+X509 *cert = X509_STORE_CTX_get0_cert(ctx);
+STACK_OF(X509) * chain = X509_STORE_CTX_get0_chain(ctx);
+int chain_length = sk_X509_num(chain);
+int ssl_idx = SSL_get_ex_data_X509_STORE_CTX_idx();
+SSL *ssl = X509_STORE_CTX_get_ex_data(ctx, ssl_idx);
+ssl_dane *dane = SSL_get_ex_data(ssl, dane_idx);
+dane_selector_list issuer_rrs = dane->selectors[DANESSL_USAGE_PKIX_TA];
+dane_selector_list leaf_rrs = dane->selectors[DANESSL_USAGE_PKIX_EE];
+int matched = 0;
+
+DEBUG(D_tls) debug_printf("Dane verify_chain\n");
+
+/* Restore OpenSSL's internal_verify() as the signature check function */
+X509_STORE_CTX_set_verify(ctx, dane->verify);
+
+if ((matched = name_check(dane, cert)) < 0)
+  {
+  X509_STORE_CTX_set_error(ctx, X509_V_ERR_OUT_OF_MEM);
+  return 0;
+  }
+
+if (!matched)
+  {
+  X509_STORE_CTX_set_error_depth(ctx, 0);
+  X509_STORE_CTX_set_current_cert(ctx, cert);
+  X509_STORE_CTX_set_error(ctx, X509_V_ERR_HOSTNAME_MISMATCH);
+  if (!cb(0, ctx))
+    return 0;
+  }
+matched = 0;
+
+/*
+ * Satisfy at least one usage 0 or 1 constraint, unless we've already
+ * matched a usage 2 trust anchor.
+ *
+ * XXX: internal_verify() doesn't callback with top certs that are not
+ * self-issued.  This is fixed in OpenSSL 1.1.0.
+ */
+if (dane->roots && sk_X509_num(dane->roots))
+  {
+  X509 *top = sk_X509_value(chain, dane->depth);
+
+  dane->mdpth = dane->depth;
+  dane->match = top;
+  X509_up_ref(top);
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+  if (X509_check_issued(top, top) != X509_V_OK)
+    {
+    X509_STORE_CTX_set_error_depth(ctx, dane->depth);
+    X509_STORE_CTX_set_current_cert(ctx, top);
+    if (!cb(1, ctx))
+      return 0;
+    }
+#endif
+  /* Pop synthetic trust-anchor ancestors off the chain! */
+  while (--chain_length > dane->depth)
+      X509_free(sk_X509_pop(chain));
+  }
+else
+  {
+  int n = 0;
+  X509 *xn = cert;
+
+  /*
+   * Check for an EE match, then a CA match at depths > 0, and
+   * finally, if the EE cert is self-issued, for a depth 0 CA match.
+   */
+  if (leaf_rrs)
+    matched = match(leaf_rrs, xn, 0);
+  if (matched) DEBUG(D_tls) debug_printf("Dane verify_chain: matched EE\n");
+
+  if (!matched && issuer_rrs)
+    for (n = chain_length-1; !matched && n >= 0; --n)
+      {
+      xn = sk_X509_value(chain, n);
+      if (n > 0 || X509_check_issued(xn, xn) == X509_V_OK)
+       matched = match(issuer_rrs, xn, n);
+      }
+  if (matched) DEBUG(D_tls) debug_printf("Dane verify_chain: matched %s\n",
+    n>0 ? "CA" : "selfisssued EE");
+
+  if (!matched)
+    {
+    X509_STORE_CTX_set_error_depth(ctx, 0);
+    X509_STORE_CTX_set_current_cert(ctx, cert);
+    X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_UNTRUSTED);
+    if (!cb(0, ctx))
+      return 0;
+    }
+  else
+    {
+    dane->mdpth = n;
+    dane->match = xn;
+    X509_up_ref(xn);
+    }
+  }
+
+/* Tail recurse into OpenSSL's internal_verify */
+return dane->verify(ctx);
+}
+
+static void
+dane_reset(ssl_dane *dane)
+{
+dane->depth = -1;
+if (dane->mhost)
+  {
+  OPENSSL_free(dane->mhost);
+  dane->mhost = 0;
+  }
+if (dane->roots)
+  {
+  sk_X509_pop_free(dane->roots, X509_free);
+  dane->roots = 0;
+  }
+if (dane->chain)
+  {
+  sk_X509_pop_free(dane->chain, X509_free);
+  dane->chain = 0;
+  }
+if (dane->match)
+  {
+  X509_free(dane->match);
+  dane->match = 0;
+  }
+dane->mdpth = -1;
+}
+
+static int
+verify_cert(X509_STORE_CTX *ctx, void *unused_ctx)
+{
+static int ssl_idx = -1;
+SSL *ssl;
+ssl_dane *dane;
+int (*cb)(int, X509_STORE_CTX *) = X509_STORE_CTX_get_verify_cb(ctx);
+X509 *cert = X509_STORE_CTX_get0_cert(ctx);
+int matched;
+
+DEBUG(D_tls) debug_printf("Dane verify_cert\n");
+
+if (ssl_idx < 0)
+  ssl_idx = SSL_get_ex_data_X509_STORE_CTX_idx();
+if (dane_idx < 0)
+  {
+  DANEerr(DANESSL_F_VERIFY_CERT, ERR_R_MALLOC_FAILURE);
+  return -1;
+  }
+
+ssl = X509_STORE_CTX_get_ex_data(ctx, ssl_idx);
+if (!(dane = SSL_get_ex_data(ssl, dane_idx)) || !cert)
+  return X509_verify_cert(ctx);
+
+/* Reset for verification of a new chain, perhaps a renegotiation. */
+dane_reset(dane);
+
+if (dane->selectors[DANESSL_USAGE_DANE_EE])
+  {
+  if ((matched = check_end_entity(ctx, dane, cert)) > 0)
+    {
+    X509_STORE_CTX_set_error_depth(ctx, 0);
+    X509_STORE_CTX_set_current_cert(ctx, cert);
+    return cb(1, ctx);
+    }
+  if (matched < 0)
+    {
+    X509_STORE_CTX_set_error(ctx, X509_V_ERR_OUT_OF_MEM);
+    return -1;
+    }
+  }
+
+  if (dane->selectors[DANESSL_USAGE_DANE_TA])
+    {
+    if ((matched = set_trust_anchor(ctx, dane, cert)) < 0)
+      {
+      X509_STORE_CTX_set_error(ctx, X509_V_ERR_OUT_OF_MEM);
+      return -1;
+      }
+    if (matched)
+      {
+      /*
+       * Check that setting the untrusted chain updates the expected
+       * structure member at the expected offset.
+       */
+      X509_STORE_CTX_trusted_stack(ctx, dane->roots);
+      X509_STORE_CTX_set_chain(ctx, dane->chain);
+      OPENSSL_assert(dane->chain == X509_STORE_CTX_get0_untrusted(ctx));
+      }
+    }
+
+  /*
+   * Name checks and usage 0/1 constraint enforcement are delayed until
+   * X509_verify_cert() builds the full chain and calls our verify_chain()
+   * wrapper.
+   */
+  dane->verify = X509_STORE_CTX_get_verify(ctx);
+  X509_STORE_CTX_set_verify(ctx, verify_chain);
+
+  if (X509_verify_cert(ctx))
+    return 1;
+
+  /*
+   * If the chain is invalid, clear any matching cert or hostname, to
+   * protect callers that might erroneously rely on these alone without
+   * checking the validation status.
+   */
+  if (dane->match)
+    {
+    X509_free(dane->match);
+    dane->match = 0;
+    }
+  if (dane->mhost)
+    {
+    OPENSSL_free(dane->mhost);
+    dane->mhost = 0;
+    }
+   return 0;
+}
+
+static dane_list
+list_alloc(size_t vsize)
+{
+void *value = (void *) OPENSSL_malloc(vsize);
+dane_list l;
+
+if (!value)
+  {
+  DANEerr(DANESSL_F_LIST_ALLOC, ERR_R_MALLOC_FAILURE);
+  return 0;
+  }
+if (!(l = (dane_list) OPENSSL_malloc(sizeof(*l))))
+  {
+  OPENSSL_free(value);
+  DANEerr(DANESSL_F_LIST_ALLOC, ERR_R_MALLOC_FAILURE);
+  return 0;
+  }
+l->next = 0;
+l->value = value;
+return l;
+}
+
+static void
+list_free(void *list, void (*f)(void *))
+{
+dane_list head;
+dane_list next;
+
+for (head = (dane_list) list; head; head = next)
+  {
+  next = head->next;
+  if (f && head->value)
+      f(head->value);
+  OPENSSL_free(head);
+  }
+}
+
+static void
+ossl_free(void * p)
+{
+OPENSSL_free(p);
+}
+
+static void
+dane_mtype_free(void *p)
+{
+list_free(((dane_mtype) p)->data, ossl_free);
+OPENSSL_free(p);
+}
+
+static void
+dane_selector_free(void *p)
+{
+list_free(((dane_selector) p)->mtype, dane_mtype_free);
+OPENSSL_free(p);
+}
+
+
+
+/*
+
+Tidy up once the connection is finished with.
+
+Arguments
+  ssl          The ssl connection handle
+
+=> Before calling SSL_free()
+tls_close() and tls_getc() [the error path] are the obvious places.
+Could we do it earlier - right after verification?  In tls_client_start()
+right after SSL_connect() returns, in that case.
+
+*/
+
+void
+DANESSL_cleanup(SSL *ssl)
+{
+ssl_dane *dane;
+int u;
+
+DEBUG(D_tls) debug_printf("Dane lib-cleanup\n");
+
+if (dane_idx < 0 || !(dane = SSL_get_ex_data(ssl, dane_idx)))
+  return;
+(void) SSL_set_ex_data(ssl, dane_idx, 0);
+
+dane_reset(dane);
+if (dane->hosts)
+  list_free(dane->hosts, ossl_free);
+for (u = 0; u <= DANESSL_USAGE_LAST; ++u)
+  if (dane->selectors[u])
+    list_free(dane->selectors[u], dane_selector_free);
+if (dane->pkeys)
+  list_free(dane->pkeys, pkey_free);
+if (dane->certs)
+  list_free(dane->certs, cert_free);
+OPENSSL_free(dane);
+}
+
+static dane_host_list
+host_list_init(const char **src)
+{
+dane_host_list head = NULL;
+
+while (*src)
+  {
+  dane_host_list elem = (dane_host_list) OPENSSL_malloc(sizeof(*elem));
+  if (elem == 0)
+    {
+    list_free(head, ossl_free);
+    return 0;
+    }
+  elem->value = OPENSSL_strdup(*src++);
+  LINSERT(head, elem);
+  }
+return head;
+}
+
+
+int
+DANESSL_get_match_cert(SSL *ssl, X509 **match, const char **mhost, int *depth)
+{
+ssl_dane *dane;
+
+if (dane_idx < 0 || (dane = SSL_get_ex_data(ssl, dane_idx)) == 0)
+  {
+  DANEerr(DANESSL_F_ADD_TLSA, DANESSL_R_INIT);
+  return -1;
+  }
+
+if (dane->match)
+  {
+  if (match)
+    *match = dane->match;
+  if (mhost)
+    *mhost = dane->mhost;
+  if (depth)
+    *depth = dane->mdpth;
+  }
+
+  return (dane->match != 0);
+}
+
+
+#ifdef never_called
+int
+DANESSL_verify_chain(SSL *ssl, STACK_OF(X509) *chain)
+{
+int ret;
+X509 *cert;
+X509_STORE_CTX * store_ctx;
+SSL_CTX *ssl_ctx = SSL_get_SSL_CTX(ssl);
+X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx);
+int store_ctx_idx = SSL_get_ex_data_X509_STORE_CTX_idx();
+
+cert = sk_X509_value(chain, 0);
+if (!(store_ctx = X509_STORE_CTX_new()))
+  {
+  DANEerr(DANESSL_F_DANESSL_VERIFY_CHAIN, ERR_R_MALLOC_FAILURE);
+  return 0;
+  }
+if (!X509_STORE_CTX_init(store_ctx, store, cert, chain))
+  {
+  X509_STORE_CTX_free(store_ctx);
+  return 0;
+  }
+X509_STORE_CTX_set_ex_data(store_ctx, store_ctx_idx, ssl);
+
+X509_STORE_CTX_set_default(store_ctx,
+            SSL_is_server(ssl) ? "ssl_client" : "ssl_server");
+X509_VERIFY_PARAM_set1(X509_STORE_CTX_get0_param(store_ctx),
+            SSL_get0_param(ssl));
+
+if (SSL_get_verify_callback(ssl))
+  X509_STORE_CTX_set_verify_cb(store_ctx, SSL_get_verify_callback(ssl));
+
+ret = verify_cert(store_ctx, NULL);
+
+SSL_set_verify_result(ssl, X509_STORE_CTX_get_error(store_ctx));
+X509_STORE_CTX_cleanup(store_ctx);
+
+return (ret);
+}
+#endif
+
+
+
+
+/*
+
+Call this for each TLSA record found for the target, after the
+DANE setup has been done on the ssl connection handle.
+
+Arguments:
+  ssl          Connection handle
+  usage                TLSA record field
+  selector     TLSA record field
+  mdname       ??? message digest name?
+  data         ??? TLSA record megalump?
+  dlen         length of data
+
+Return
+  -1 on error
+  0  action not taken
+  1  record accepted
+*/
+
+int
+DANESSL_add_tlsa(SSL *ssl, uint8_t usage, uint8_t selector, const char *mdname,
+        unsigned const char *data, size_t dlen)
+{
+ssl_dane *dane;
+dane_selector_list s = 0;
+dane_mtype_list m = 0;
+dane_data_list d = 0;
+dane_cert_list xlist = 0;
+dane_pkey_list klist = 0;
+const EVP_MD *md = 0;
+
+DEBUG(D_tls) debug_printf("Dane add-tlsa: usage %u sel %u mdname \"%s\"\n",
+                         usage, selector, mdname);
+
+if(dane_idx < 0 || !(dane = SSL_get_ex_data(ssl, dane_idx)))
+  {
+  DANEerr(DANESSL_F_ADD_TLSA, DANESSL_R_INIT);
+  return -1;
+  }
+
+if (usage > DANESSL_USAGE_LAST)
+  {
+  DANEerr(DANESSL_F_ADD_TLSA, DANESSL_R_BAD_USAGE);
+  return 0;
+  }
+if (selector > DANESSL_SELECTOR_LAST)
+  {
+  DANEerr(DANESSL_F_ADD_TLSA, DANESSL_R_BAD_SELECTOR);
+  return 0;
+  }
+
+    /* Support built-in standard one-digit mtypes */
+  if (mdname && *mdname && mdname[1] == '\0')
+    switch (*mdname - '0')
+    {
+    case DANESSL_MATCHING_FULL: mdname = 0;       break;
+    case DANESSL_MATCHING_2256: mdname = "sha256"; break;
+    case DANESSL_MATCHING_2512: mdname = "sha512"; break;
+    }
+  if (mdname && *mdname && (md = EVP_get_digestbyname(mdname)) == 0)
+    {
+    DANEerr(DANESSL_F_ADD_TLSA, DANESSL_R_BAD_DIGEST);
+    return 0;
+    }
+  if (mdname && *mdname && dlen != EVP_MD_size(md))
+    {
+    DANEerr(DANESSL_F_ADD_TLSA, DANESSL_R_BAD_DATA_LENGTH);
+    return 0;
+    }
+  if (!data)
+    {
+    DANEerr(DANESSL_F_ADD_TLSA, DANESSL_R_BAD_NULL_DATA);
+    return 0;
+    }
+
+  /*
+   * Full Certificate or Public Key when NULL or empty digest name
+   */
+  if (!mdname || !*mdname)
+    {
+    X509 *x = 0;
+    EVP_PKEY *k = 0;
+    const unsigned char *p = data;
+
+#define xklistinit(lvar, ltype, var, freeFunc) do { \
+         (lvar) = (ltype) OPENSSL_malloc(sizeof(*(lvar))); \
+         if ((lvar) == 0) { \
+             DANEerr(DANESSL_F_ADD_TLSA, ERR_R_MALLOC_FAILURE); \
+             freeFunc((var)); \
+             return 0; \
+         } \
+         (lvar)->next = 0; \
+         lvar->value = var; \
+      } while (0)
+#define xkfreeret(ret) do { \
+         if (xlist) list_free(xlist, cert_free); \
+         if (klist) list_free(klist, pkey_free); \
+         return (ret); \
+      } while (0)
+
+  switch (selector)
+    {
+    case DANESSL_SELECTOR_CERT:
+      if (!d2i_X509(&x, &p, dlen) || dlen != p - data)
+       {
+       if (x)
+         X509_free(x);
+       DANEerr(DANESSL_F_ADD_TLSA, DANESSL_R_BAD_CERT);
+       return 0;
+       }
+      k = X509_get_pubkey(x);
+      EVP_PKEY_free(k);
+      if (k == 0)
+       {
+       X509_free(x);
+       DANEerr(DANESSL_F_ADD_TLSA, DANESSL_R_BAD_CERT_PKEY);
+       return 0;
+       }
+      if (usage == DANESSL_USAGE_DANE_TA)
+       xklistinit(xlist, dane_cert_list, x, X509_free);
+      break;
+
+    case DANESSL_SELECTOR_SPKI:
+      if (!d2i_PUBKEY(&k, &p, dlen) || dlen != p - data)
+       {
+       if (k)
+         EVP_PKEY_free(k);
+      DANEerr(DANESSL_F_ADD_TLSA, DANESSL_R_BAD_PKEY);
+      return 0;
+       }
+      if (usage == DANESSL_USAGE_DANE_TA)
+       xklistinit(klist, dane_pkey_list, k, EVP_PKEY_free);
+      break;
+    }
+  }
+
+/* Find insertion point and don't add duplicate elements. */
+for (s = dane->selectors[usage]; s; s = s->next)
+  if (s->value->selector == selector)
+    {
+    for (m = s->value->mtype; m; m = m->next)
+      if (m->value->md == md)
+       {
+       for (d = m->value->data; d; d = d->next)
+         if (  d->value->datalen == dlen
+            && memcmp(d->value->data, data, dlen) == 0)
+           xkfreeret(1);
+       break;
+       }
+    break;
+    }
+
+if ((d = (dane_data_list) list_alloc(sizeof(*d->value) + dlen)) == 0)
+  xkfreeret(0);
+d->value->datalen = dlen;
+memcpy(d->value->data, data, dlen);
+if (!m)
+  {
+  if ((m = (dane_mtype_list) list_alloc(sizeof(*m->value))) == 0)
+    {
+    list_free(d, ossl_free);
+    xkfreeret(0);
+    }
+  m->value->data = 0;
+  if ((m->value->md = md) != 0)
+    m->value->mdlen = dlen;
+  if (!s)
+    {
+    if ((s = (dane_selector_list) list_alloc(sizeof(*s->value))) == 0)
+      {
+      list_free(m, dane_mtype_free);
+      xkfreeret(0);
+      }
+    s->value->mtype = 0;
+    s->value->selector = selector;
+    LINSERT(dane->selectors[usage], s);
+    }
+  LINSERT(s->value->mtype, m);
+  }
+LINSERT(m->value->data, d);
+
+if (xlist)
+  LINSERT(dane->certs, xlist);
+else if (klist)
+  LINSERT(dane->pkeys, klist);
+++dane->count;
+return 1;
+}
+
+
+
+
+/*
+Call this once we have an ssl connection handle but before
+making the TLS connection.
+
+=> In tls_client_start() after the call to SSL_new()
+and before the call to SSL_connect().  Exactly where
+probably does not matter.
+We probably want to keep our existing SNI handling;
+call this with NULL.
+
+Arguments:
+  ssl          Connection handle
+  sni_domain   Optional peer server name
+  hostnames    list of names to chack against peer cert
+
+Return
+  -1 on fatal error
+  0  nonfatal error
+  1  success
+*/
+
+int
+DANESSL_init(SSL *ssl, const char *sni_domain, const char **hostnames)
+{
+ssl_dane *dane;
+int i;
+
+DEBUG(D_tls) debug_printf("Dane ssl_init\n");
+if (dane_idx < 0)
+  {
+  DANEerr(DANESSL_F_INIT, DANESSL_R_LIBRARY_INIT);
+  return -1;
+  }
+
+if (sni_domain && !SSL_set_tlsext_host_name(ssl, sni_domain))
+  return 0;
+
+if ((dane = (ssl_dane *) OPENSSL_malloc(sizeof(ssl_dane))) == 0)
+  {
+  DANEerr(DANESSL_F_INIT, ERR_R_MALLOC_FAILURE);
+  return 0;
+  }
+if (!SSL_set_ex_data(ssl, dane_idx, dane))
+  {
+  DANEerr(DANESSL_F_INIT, ERR_R_MALLOC_FAILURE);
+  OPENSSL_free(dane);
+  return 0;
+  }
+
+dane->verify = 0;
+dane->hosts = 0;
+dane->thost = 0;
+dane->pkeys = 0;
+dane->certs = 0;
+dane->chain = 0;
+dane->match = 0;
+dane->roots = 0;
+dane->depth = -1;
+dane->mhost = 0;                       /* Future SSL control interface */
+dane->mdpth = 0;                       /* Future SSL control interface */
+dane->multi = 0;                       /* Future SSL control interface */
+dane->count = 0;
+dane->hosts = 0;
+
+for (i = 0; i <= DANESSL_USAGE_LAST; ++i)
+  dane->selectors[i] = 0;
+
+if (hostnames && (dane->hosts = host_list_init(hostnames)) == 0)
+  {
+  DANEerr(DANESSL_F_INIT, ERR_R_MALLOC_FAILURE);
+  DANESSL_cleanup(ssl);
+  return 0;
+  }
+
+return 1;
+}
+
+
+/*
+
+Call this once we have a context to work with, but
+before DANESSL_init()
+
+=> in tls_client_start(), after tls_init() call gives us the ctx,
+if we decide we want to (policy) and can (TLSA records available)
+replacing (? what about fallback) everything from testing tls_verify_hosts
+down to just before calling SSL_new() for the conn handle.
+
+Arguments
+  ctx          SSL context
+
+Return
+  -1   Error
+  1    Success
+*/
+
+int
+DANESSL_CTX_init(SSL_CTX *ctx)
+{
+DEBUG(D_tls) debug_printf("Dane ctx-init\n");
+if (dane_idx >= 0)
+  {
+  SSL_CTX_set_cert_verify_callback(ctx, verify_cert, 0);
+  return 1;
+  }
+DANEerr(DANESSL_F_CTX_INIT, DANESSL_R_LIBRARY_INIT);
+return -1;
+}
+
+static void
+dane_init(void)
+{
+/*
+ * Store library id in zeroth function slot, used to locate the library
+ * name.  This must be done before we load the error strings.
+ */
+err_lib_dane = ERR_get_next_error_library();
+
+#ifndef OPENSSL_NO_ERR
+if (err_lib_dane > 0)
+  {
+  dane_str_functs[0].error |= ERR_PACK(err_lib_dane, 0, 0);
+  ERR_load_strings(err_lib_dane, dane_str_functs);
+  ERR_load_strings(err_lib_dane, dane_str_reasons);
+  }
+#endif
+
+/*
+ * Register SHA-2 digests, if implemented and not already registered.
+ */
+#if defined(LN_sha256) && defined(NID_sha256) && !defined(OPENSSL_NO_SHA256)
+if (!EVP_get_digestbyname(LN_sha224)) EVP_add_digest(EVP_sha224());
+if (!EVP_get_digestbyname(LN_sha256)) EVP_add_digest(EVP_sha256());
+#endif
+#if defined(LN_sha512) && defined(NID_sha512) && !defined(OPENSSL_NO_SHA512)
+if (!EVP_get_digestbyname(LN_sha384)) EVP_add_digest(EVP_sha384());
+if (!EVP_get_digestbyname(LN_sha512)) EVP_add_digest(EVP_sha512());
+#endif
+
+/*
+ * Register an SSL index for the connection-specific ssl_dane structure.
+ * Using a separate index makes it possible to add DANE support to
+ * existing OpenSSL releases that don't have a suitable pointer in the
+ * SSL structure.
+ */
+dane_idx = SSL_get_ex_new_index(0, 0, 0, 0, 0);
+}
+
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
+static void
+run_once(volatile int * once, void (*init)(void))
+{
+int wlock = 0;
+
+CRYPTO_r_lock(CRYPTO_LOCK_SSL_CTX);
+if (!*once)
+  {
+  CRYPTO_r_unlock(CRYPTO_LOCK_SSL_CTX);
+  CRYPTO_w_lock(CRYPTO_LOCK_SSL_CTX);
+  wlock = 1;
+  if (!*once)
+    {
+    *once = 1;
+    init();
+    }
+  }
+if (wlock)
+  CRYPTO_w_unlock(CRYPTO_LOCK_SSL_CTX);
+else
+  CRYPTO_r_unlock(CRYPTO_LOCK_SSL_CTX);
+}
+#endif
+
+
+
+/*
+
+Call this once.  Probably early in startup will do; may need
+to be after SSL library init.
+
+=> put after call to tls_init() for now
+
+Return
+  1    Success
+  0    Fail
+*/
+
+int
+DANESSL_library_init(void)
+{
+static CRYPTO_ONCE once = CRYPTO_ONCE_STATIC_INIT;
+
+DEBUG(D_tls) debug_printf("Dane lib-init\n");
+(void) CRYPTO_THREAD_run_once(&once, dane_init);
+
+#if defined(LN_sha256)
+/* No DANE without SHA256 support */
+if (dane_idx >= 0 && EVP_get_digestbyname(LN_sha256) != 0)
+  return 1;
+#endif
+DANEerr(DANESSL_F_LIBRARY_INIT, DANESSL_R_SUPPORT);
+return 0;
+}
+
+
+/* vi: aw ai sw=2
+*/
diff --git a/src/dane.c b/src/dane.c
new file mode 100644 (file)
index 0000000..137c754
--- /dev/null
@@ -0,0 +1,50 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2012, 2014 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* This module provides DANE (RFC6659) support for Exim.  See also
+the draft RFC for DANE-over-SMTP, "SMTP security via opportunistic DANE TLS"
+(V. Dukhovni, W. Hardaker) - version 10, dated May 25, 2014.
+
+The code for DANE support with Openssl was provided by V.Dukhovni.
+
+No cryptographic code is included in Exim. All this module does is to call
+functions from the OpenSSL or GNU TLS libraries. */
+
+
+#include "exim.h"
+
+/* This module is compiled only when it is specifically requested in the
+build-time configuration. However, some compilers don't like compiling empty
+modules, so keep them happy with a dummy when skipping the rest. Make it
+reference itself to stop picky compilers complaining that it is unused, and put
+in a dummy argument to stop even pickier compilers complaining about infinite
+loops. */
+
+#ifndef EXPERIMENTAL_DANE
+static void dummy(int x) { dummy(x-1); }
+#else
+
+/* Enabling DANE without enabling TLS cannot work. Abort the compilation. */
+# ifndef SUPPORT_TLS
+#  error DANE support requires that TLS support must be enabled. Abort build.
+# endif
+
+/* DNSSEC support is also required */
+# ifndef RES_USE_DNSSEC
+#  error DANE support requires that the DNS resolver library supports DNSSEC
+# endif
+
+# ifdef USE_GNUTLS
+#  include "dane-gnu.c"
+# else
+#  include "dane-openssl.c"
+# endif
+
+
+#endif  /* EXPERIMENTAL_DANE */
+
+/* End of dane.c */
diff --git a/src/danessl.h b/src/danessl.h
new file mode 100644 (file)
index 0000000..1d6439e
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ *  Author: Viktor Dukhovni
+ *  License: THIS CODE IS IN THE PUBLIC DOMAIN.
+ */
+#ifndef HEADER_DANESSL_H
+#define HEADER_DANESSL_H
+
+#include <stdint.h>
+#include <openssl/ssl.h>
+
+/*-
+ * Certificate usages:
+ * https://tools.ietf.org/html/rfc6698#section-2.1.1
+ */
+#define DANESSL_USAGE_PKIX_TA  0
+#define DANESSL_USAGE_PKIX_EE  1
+#define DANESSL_USAGE_DANE_TA  2
+#define DANESSL_USAGE_DANE_EE  3
+#define DANESSL_USAGE_LAST             DANESSL_USAGE_DANE_EE
+
+/*-
+ * Selectors:
+ * https://tools.ietf.org/html/rfc6698#section-2.1.2
+ */
+#define DANESSL_SELECTOR_CERT          0
+#define DANESSL_SELECTOR_SPKI          1
+#define DANESSL_SELECTOR_LAST          DANESSL_SELECTOR_SPKI
+
+/*-
+ * Matching types:
+ * https://tools.ietf.org/html/rfc6698#section-2.1.3
+ */
+#define DANESSL_MATCHING_FULL          0
+#define DANESSL_MATCHING_2256          1
+#define DANESSL_MATCHING_2512          2
+#define DANESSL_MATCHING_LAST          DANESSL_MATCHING_2512
+
+extern int DANESSL_library_init(void);
+extern int DANESSL_CTX_init(SSL_CTX *);
+extern int DANESSL_init(SSL *, const char *, const char **);
+extern void DANESSL_cleanup(SSL *);
+extern int DANESSL_add_tlsa(SSL *, uint8_t, uint8_t, const char *,
+                           unsigned const char *, size_t);
+extern int DANESSL_get_match_cert(SSL *, X509 **, const char **, int *);
+extern int DANESSL_verify_chain(SSL *, STACK_OF(X509) *);
+
+#endif
index 4a1c20f..c9c6fb7 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -141,14 +141,14 @@ if (sigalrm_seen) errno = ETIMEDOUT;
 if (rc < 0)
   {
   log_write(0, LOG_MAIN|LOG_PANIC, "Failed to get %s lock for %s: %s",
-    read_only? "read" : "write", buffer,
-    (errno == ETIMEDOUT)? "timed out" : strerror(errno));
+    read_only ? "read" : "write", buffer,
+    errno == ETIMEDOUT ? "timed out" : strerror(errno));
   (void)close(dbblock->lockfd);
   errno = 0;       /* Indicates locking failure */
   return NULL;
   }
 
-DEBUG(D_hints_lookup) debug_printf("locked %s\n", buffer);
+DEBUG(D_hints_lookup) debug_printf("locked  %s\n", buffer);
 
 /* At this point we have an opened and locked separate lock file, that is,
 exclusive access to the database, so we can go ahead and open it. If we are
@@ -164,7 +164,7 @@ DEBUG(D_hints_lookup) debug_printf("EXIM_DBOPEN(%s)\n", buffer);
 EXIM_DBOPEN(buffer, flags, EXIMDB_MODE, &(dbblock->dbptr));
 DEBUG(D_hints_lookup) debug_printf("returned from EXIM_DBOPEN\n");
 
-if (dbblock->dbptr == NULL && errno == ENOENT && flags == O_RDWR)
+if (!dbblock->dbptr && errno == ENOENT && flags == O_RDWR)
   {
   DEBUG(D_hints_lookup)
     debug_printf("%s appears not to exist: trying to create\n", buffer);
@@ -199,8 +199,7 @@ if (created && geteuid() == root_uid)
   *lastname = 0;
   dd = opendir(CS buffer);
 
-  while ((ent = readdir(dd)) != NULL)
-    {
+  while ((ent = readdir(dd)))
     if (Ustrncmp(ent->d_name, name, namelen) == 0)
       {
       struct stat statbuf;
@@ -212,7 +211,6 @@ if (created && geteuid() == root_uid)
           DEBUG(D_hints_lookup) debug_printf("failed setting %s to owned by exim\n", buffer);
         }
       }
-    }
 
   closedir(dd);
   }
@@ -220,10 +218,9 @@ if (created && geteuid() == root_uid)
 /* If the open has failed, return NULL, leaving errno set. If lof is TRUE,
 log the event - also for debugging - but not if the file just doesn't exist. */
 
-if (dbblock->dbptr == NULL)
+if (!dbblock->dbptr)
   {
   if (save_errno != ENOENT)
-    {
     if (lof)
       log_write(0, LOG_MAIN, "%s", string_open_failed(save_errno, "DB file %s",
         buffer));
@@ -231,7 +228,6 @@ if (dbblock->dbptr == NULL)
       DEBUG(D_hints_lookup)
         debug_printf("%s", CS string_open_failed(save_errno, "DB file %s\n",
           buffer));
-    }
   (void)close(dbblock->lockfd);
   errno = save_errno;
   return NULL;
@@ -239,8 +235,10 @@ if (dbblock->dbptr == NULL)
 
 DEBUG(D_hints_lookup)
   debug_printf("opened hints database %s: flags=%s\n", buffer,
-    (flags == O_RDONLY)? "O_RDONLY" : (flags == O_RDWR)? "O_RDWR" :
-    (flags == (O_RDWR|O_CREAT))? "O_RDWR|O_CREAT" : "??");
+    flags == O_RDONLY ? "O_RDONLY"
+    : flags == O_RDWR ? "O_RDWR"
+    : flags == (O_RDWR|O_CREAT) ? "O_RDWR|O_CREAT"
+    : "??");
 
 /* Pass back the block containing the opened database handle and the open fd
 for the lock. */
@@ -267,6 +265,7 @@ dbfn_close(open_db *dbblock)
 {
 EXIM_DBCLOSE(dbblock->dbptr);
 (void)close(dbblock->lockfd);
+DEBUG(D_hints_lookup) debug_printf("closed hints database and lockfile\n");
 }
 
 
@@ -294,17 +293,21 @@ Returns: a pointer to the retrieved record, or
 */
 
 void *
-dbfn_read_with_length(open_db *dbblock, uschar *key, int *length)
+dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
 {
 void *yield;
 EXIM_DATUM key_datum, result_datum;
+int klen = Ustrlen(key) + 1;
+uschar * key_copy = store_get(klen);
+
+memcpy(key_copy, key, klen);
 
 DEBUG(D_hints_lookup) debug_printf("dbfn_read: key=%s\n", key);
 
 EXIM_DATUM_INIT(key_datum);         /* Some DBM libraries require the datum */
 EXIM_DATUM_INIT(result_datum);      /* to be cleared before use. */
-EXIM_DATUM_DATA(key_datum) = CS key;
-EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
+EXIM_DATUM_DATA(key_datum) = CS key_copy;
+EXIM_DATUM_SIZE(key_datum) = klen;
 
 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
 
@@ -334,18 +337,22 @@ Returns:    the yield of the underlying dbm or db "write" function. If this
 */
 
 int
-dbfn_write(open_db *dbblock, uschar *key, void *ptr, int length)
+dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
 {
 EXIM_DATUM key_datum, value_datum;
 dbdata_generic *gptr = (dbdata_generic *)ptr;
+int klen = Ustrlen(key) + 1;
+uschar * key_copy = store_get(klen);
+
+memcpy(key_copy, key, klen);
 gptr->time_stamp = time(NULL);
 
 DEBUG(D_hints_lookup) debug_printf("dbfn_write: key=%s\n", key);
 
 EXIM_DATUM_INIT(key_datum);         /* Some DBM libraries require the datum */
 EXIM_DATUM_INIT(value_datum);       /* to be cleared before use. */
-EXIM_DATUM_DATA(key_datum) = CS key;
-EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
+EXIM_DATUM_DATA(key_datum) = CS key_copy;
+EXIM_DATUM_SIZE(key_datum) = klen;
 EXIM_DATUM_DATA(value_datum) = CS ptr;
 EXIM_DATUM_SIZE(value_datum) = length;
 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
@@ -366,12 +373,16 @@ Returns: the yield of the underlying dbm or db "delete" function.
 */
 
 int
-dbfn_delete(open_db *dbblock, uschar *key)
+dbfn_delete(open_db *dbblock, const uschar *key)
 {
+int klen = Ustrlen(key) + 1;
+uschar * key_copy = store_get(klen);
+
+memcpy(key_copy, key, klen);
 EXIM_DATUM key_datum;
 EXIM_DATUM_INIT(key_datum);         /* Some DBM libraries require clearing */
-EXIM_DATUM_DATA(key_datum) = CS key;
-EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
+EXIM_DATUM_DATA(key_datum) = CS key_copy;
+EXIM_DATUM_SIZE(key_datum) = klen;
 return EXIM_DBDEL(dbblock->dbptr, key_datum);
 }
 
index 1963fa9..93d12ef 100644 (file)
@@ -2,18 +2,18 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* Functions for reading/writing exim database files */
 
 void     dbfn_close(open_db *);
-int      dbfn_delete(open_db *, uschar *);
+int      dbfn_delete(open_db *, const uschar *);
 open_db *dbfn_open(uschar *, int, open_db *, BOOL);
-void    *dbfn_read_with_length(open_db *, uschar *, int *);
+void    *dbfn_read_with_length(open_db *, const uschar *, int *);
 uschar  *dbfn_scan(open_db *, BOOL, EXIM_CURSOR **);
-int      dbfn_write(open_db *, uschar *, void *, int);
+int      dbfn_write(open_db *, const uschar *, void *, int);
 
 /* Macro for the common call to read without wanting to know the length. */
 
index ce81f1e..576941b 100644 (file)
@@ -64,7 +64,7 @@ tdb_traverse to be called) */
 
 /* EXIM_DBCREATE_CURSOR - initialize for scanning operation */
 #define EXIM_DBCREATE_CURSOR(db, cursor) { \
-   *(cursor) = malloc(sizeof(TDB_DATA)); (*(cursor))->dptr = NULL; }
+   *(cursor) = store_malloc(sizeof(TDB_DATA)); (*(cursor))->dptr = NULL; }
 
 /* EXIM_DBSCAN - This is complicated because we have to free the last datum
 free() must not die when passed NULL */
@@ -582,7 +582,7 @@ done.
 Originally, there was only one structure, used for both types. However, it got
 expanded for domain records, so it got split. To make it possible for Exim to
 handle the old type of record, we retain the old definition. The different
-kinds of record can be distinguised by their different lengths. */
+kinds of record can be distinguished by their different lengths. */
 
 typedef struct {
   time_t time_stamp;
index 44c0c00..fcdc5a8 100644 (file)
--- a/src/dcc.c
+++ b/src/dcc.c
@@ -2,10 +2,12 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) Wolfgang Breyha 2005-2013
+/* Copyright (c) Wolfgang Breyha 2005 - 2015
  * Vienna University Computer Center
  * wbreyha@gmx.net
  * See the file NOTICE for conditions of use and distribution.
+ *
+ * Copyright (c) The Exim Maintainers 2015 - 2016
  */
 
 /* This patch is based on code from Tom Kistners exiscan (ACL integration) and
@@ -45,9 +47,11 @@ int flushbuffer (int socket, uschar *buffer)
   return retval;
 }
 
-int dcc_process(uschar **listptr) {
+int
+dcc_process(uschar **listptr)
+{
   int sep = 0;
-  uschar *list = *listptr;
+  const uschar *list = *listptr;
   FILE *data_file;
   uschar *dcc_default_ip_option = US"127.0.0.1";
   uschar *dcc_helo_option = US"localhost";
@@ -68,7 +72,6 @@ int dcc_process(uschar **listptr) {
   uschar sendbuf[4096];
   uschar recvbuf[4096];
   uschar dcc_return_text[1024];
-  uschar mbox_path[1024];
   uschar message_subdir[2];
   struct header_line *dcchdr;
   uschar *dcc_acl_options;
@@ -77,50 +80,42 @@ int dcc_process(uschar **listptr) {
 
   /* grep 1st option */
   if ((dcc_acl_options = string_nextinlist(&list, &sep,
-                                           dcc_acl_options_buffer,
-                                           sizeof(dcc_acl_options_buffer))) != NULL)
-  {
+                  dcc_acl_options_buffer, sizeof(dcc_acl_options_buffer))))
+    {
     /* parse 1st option */
-    if ( (strcmpic(dcc_acl_options,US"false") == 0) ||
-         (Ustrcmp(dcc_acl_options,"0") == 0) ) {
-      /* explicitly no matching */
-      return FAIL;
-    };
-
-    /* special cases (match anything except empty) */
-    if ( (strcmpic(dcc_acl_options,US"true") == 0) ||
-         (Ustrcmp(dcc_acl_options,"*") == 0) ||
-         (Ustrcmp(dcc_acl_options,"1") == 0) ) {
-      dcc_acl_options = dcc_acl_options;
-    };
-  }
-  else {
-    /* empty means "don't match anything" */
-    return FAIL;
-  };
+    if (  strcmpic(dcc_acl_options, US"false") == 0
+       || Ustrcmp(dcc_acl_options, "0") == 0
+       )
+      return FAIL;     /* explicitly no matching */
+    }
+  else
+    return FAIL;       /* empty means "don't match anything" */
 
   sep = 0;
 
   /* if we scanned this message last time, just return */
-  if ( dcc_ok )
-      return dcc_rc;
+  if (dcc_ok)
+    return dcc_rc;
 
   /* open the spooled body */
   message_subdir[1] = '\0';
-  for (i = 0; i < 2; i++) {
-    message_subdir[0] = (split_spool_directory == (i == 0))? message_id[5] : 0;
-    sprintf(CS mbox_path, "%s/input/%s/%s-D", spool_directory, message_subdir, message_id);
-    data_file = Ufopen(mbox_path,"rb");
-    if (data_file != NULL)
+  for (i = 0; i < 2; i++)
+    {
+    message_subdir[0] = split_spool_directory == (i == 0) ? message_id[5] : 0;
+
+    if ((data_file = Ufopen(
+           spool_fname(US"input", message_subdir, message_id, US"-D"),
+           "rb")))
       break;
-  };
+    }
 
-  if (data_file == NULL) {
+  if (!data_file)
+    {
     /* error while spooling */
     log_write(0, LOG_MAIN|LOG_PANIC,
            "dcc acl condition: error while opening spool file");
     return DEFER;
-  };
+    }
 
   /* Initialize the variables */
 
@@ -142,18 +137,18 @@ int dcc_process(uschar **listptr) {
   bzero(opts,sizeof(opts));
   Ustrncpy(opts, dccifd_options, sizeof(opts)-1);
   /* if $acl_m_dcc_override_client_ip is set use it */
-  if (((override_client_ip = expand_string(US"$acl_m_dcc_override_client_ip")) != NULL) && 
+  if (((override_client_ip = expand_string(US"$acl_m_dcc_override_client_ip")) != NULL) &&
        (override_client_ip[0] != '\0')) {
     Ustrncpy(client_ip, override_client_ip, sizeof(client_ip)-1);
     DEBUG(D_acl)
       debug_printf("DCC: Client IP (overridden): %s\n", client_ip);
-  } 
+  }
   else if(sender_host_address) {
   /* else if $sender_host_address is available use that? */
     Ustrncpy(client_ip, sender_host_address, sizeof(client_ip)-1);
     DEBUG(D_acl)
       debug_printf("DCC: Client IP (sender_host_address): %s\n", client_ip);
-  } 
+  }
   else {
     /* sender_host_address is NULL which means it comes from localhost */
     Ustrncpy(client_ip, dcc_default_ip_option, sizeof(client_ip)-1);
@@ -214,9 +209,9 @@ int dcc_process(uschar **listptr) {
     }
   } else {
     /* connecting to the dccifd UNIX socket */
-    bzero((char *)&serv_addr,sizeof(serv_addr));
+    bzero(&serv_addr, sizeof(serv_addr));
     serv_addr.sun_family = AF_UNIX;
-    Ustrcpy(serv_addr.sun_path, sockpath);
+    Ustrncpy(serv_addr.sun_path, sockpath, sizeof(serv_addr.sun_path));
     if ((sockfd = socket(AF_UNIX, SOCK_STREAM,0)) < 0){
       DEBUG(D_acl)
         debug_printf("DCC: Creating UNIX socket connection failed: %s\n", strerror(errno));
@@ -302,7 +297,7 @@ int dcc_process(uschar **listptr) {
     }
   }
 
-  /* a blank line seperates header from body */
+  /* a blank line separates header from body */
   Ustrncat(sendbuf, "\n", sizeof(sendbuf)-Ustrlen(sendbuf)-1);
   flushbuffer(sockfd, sendbuf);
   DEBUG(D_acl)
@@ -432,11 +427,11 @@ int dcc_process(uschar **listptr) {
             }
           }
           else {
-          /* We're on the first line but not on the first character,
-           * there must be something wrong. */
-            DEBUG(D_acl)
-              debug_printf("DCC: Line = %d but i = %d != 0  character is %c - This is wrong!\n", line, i, recvbuf[i]);
-              log_write(0,LOG_MAIN,"Wrong header from DCC, output is %s\n", recvbuf);
+            /* We're on the first line but not on the first character,
+             * there must be something wrong. */
+            DEBUG(D_acl) debug_printf("DCC: Line = %d but i = %d != 0"
+               "  character is %c - This is wrong!\n", line, i, recvbuf[i]);
+            log_write(0,LOG_MAIN,"Wrong header from DCC, output is %s\n", recvbuf);
           }
         }
         else if(line == 2) {
@@ -449,13 +444,13 @@ int dcc_process(uschar **listptr) {
           /* The third and following lines are the X-DCC header,
            * so we store it in dcc_header_str. */
           /* check if we don't get more than we can handle */
-          if(k < sizeof(dcc_header_str)) { 
+          if(k < sizeof(dcc_header_str)) {
             dcc_header_str[k] = recvbuf[i];
             k++;
           }
           else {
-            DEBUG(D_acl)
-              debug_printf("DCC: We got more output than we can store in the X-DCC header. Truncating at 120 characters.\n");
+            DEBUG(D_acl) debug_printf("DCC: We got more output than we can store"
+               " in the X-DCC header. Truncating at 120 characters.\n");
           }
         }
         else {
index ebd932f..3cd6d0c 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -30,7 +30,7 @@ static uschar tree_printline[tree_printlinesize];
 
 Arguments:
   p          tree node
-  pos        amount of indenting & vertical bars to pring
+  pos        amount of indenting & vertical bars to print
   barswitch  if TRUE print | at the pos value
 
 Returns:     nothing
@@ -75,7 +75,7 @@ Returns:     nothing
 */
 
 void
-debug_print_argv(uschar **argv)
+debug_print_argv(const uschar ** argv)
 {
 debug_printf("exec");
 while (*argv != NULL) debug_printf(" %.256s", *argv++);
@@ -137,13 +137,36 @@ debug_printf("%s uid=%ld gid=%ld euid=%ld egid=%ld\n", s,
 *************************************************/
 
 /* There are two entries, one for use when being called directly from a
-function with a variable argument list.
+function with a variable argument list, one for prepending an indent.
 
 If debug_pid is nonzero, print the pid at the start of each line. This is for
 tidier output when running parallel remote deliveries with debugging turned on.
 Must do the whole thing with a single printf and flush, as otherwise output may
 get interleaved. Since some calls to debug_printf() don't end with newline,
-we save up the text until we do get the newline. */
+we save up the text until we do get the newline.
+Take care to not disturb errno. */
+
+
+/* Debug printf indented by ACL nest depth */
+void
+debug_printf_indent(const char * format, ...)
+{
+va_list ap;
+unsigned depth = acl_level + expand_level, i;
+
+if (!debug_file) return;
+if (depth > 0)
+  {
+  for (i = depth >> 2; i > 0; i--)
+    fprintf(debug_file, "   .");
+  fprintf(debug_file, "%*s", depth & 3, "");
+  }
+
+va_start(ap, format);
+debug_vprintf(format, ap);
+va_end(ap);
+}
+
 
 void
 debug_printf(const char *format, ...)
@@ -157,7 +180,9 @@ va_end(ap);
 void
 debug_vprintf(const char *format, va_list ap)
 {
-if (debug_file == NULL) return;
+int save_errno = errno;
+
+if (!debug_file) return;
 
 /* Various things can be inserted at the start of a line. Don't use the
 tod_stamp() function for the timestamp, because that will overwrite the
@@ -235,6 +260,7 @@ if (debug_ptr[-1] == '\n')
   debug_ptr = debug_buffer;
   debug_prefix_length = 0;
   }
+errno = save_errno;
 }
 
 /* End of debug.c */
index 87b54d8..0a1ea19 100644 (file)
@@ -2,13 +2,14 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* The main code for delivering a message. */
 
 
 #include "exim.h"
+#include <assert.h>
 
 
 /* Data block for keeping track of subprocesses for parallel remote
@@ -63,10 +64,8 @@ static address_item *addr_new = NULL;
 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;
@@ -77,8 +76,6 @@ static int  return_count;
 static uschar *frozen_info = US"";
 static uschar *used_return_path = NULL;
 
-static uschar spoolname[PATH_MAX];
-
 
 
 /*************************************************
@@ -127,10 +124,10 @@ Returns:        nothing
 void
 deliver_set_expansions(address_item *addr)
 {
-if (addr == NULL)
+if (!addr)
   {
-  uschar ***p = address_expansions;
-  while (*p != NULL) **p++ = NULL;
+  const uschar ***p = address_expansions;
+  while (*p) **p++ = NULL;
   return;
   }
 
@@ -138,20 +135,22 @@ if (addr == NULL)
 what they contain. These first ones are always set, taking their values from
 the first address. */
 
-if (addr->host_list == NULL)
+if (!addr->host_list)
   {
   deliver_host = deliver_host_address = US"";
+  deliver_host_port = 0;
   }
 else
   {
   deliver_host = addr->host_list->name;
   deliver_host_address = addr->host_list->address;
+  deliver_host_port = addr->host_list->port;
   }
 
 deliver_recipients = addr;
-deliver_address_data = addr->p.address_data;
-deliver_domain_data = addr->p.domain_data;
-deliver_localpart_data = addr->p.localpart_data;
+deliver_address_data = addr->prop.address_data;
+deliver_domain_data = addr->prop.domain_data;
+deliver_localpart_data = addr->prop.localpart_data;
 
 /* These may be unset for multiple addresses */
 
@@ -167,7 +166,7 @@ bmi_base64_tracker_verdict = NULL;
 
 /* If there's only one address we can set everything. */
 
-if (addr->next == NULL)
+if (!addr->next)
   {
   address_item *addr_orig;
 
@@ -175,8 +174,7 @@ if (addr->next == NULL)
   deliver_localpart_prefix = addr->prefix;
   deliver_localpart_suffix = addr->suffix;
 
-  for (addr_orig = addr; addr_orig->parent != NULL;
-    addr_orig = addr_orig->parent);
+  for (addr_orig = addr; addr_orig->parent; addr_orig = addr_orig->parent) ;
   deliver_domain_orig = addr_orig->domain;
 
   /* Re-instate any prefix and suffix in the original local part. In all
@@ -185,30 +183,33 @@ if (addr->next == NULL)
   filter sets up a pipe, file, or autoreply delivery, no router is involved.
   In this case, though, there won't be any prefix or suffix to worry about. */
 
-  deliver_localpart_orig = (addr_orig->router == NULL)? addr_orig->local_part :
-    addr_orig->router->caseful_local_part?
-      addr_orig->cc_local_part : addr_orig->lc_local_part;
+  deliver_localpart_orig = !addr_orig->router
+    ? addr_orig->local_part
+    : addr_orig->router->caseful_local_part
+    ? addr_orig->cc_local_part
+    : addr_orig->lc_local_part;
 
   /* If there's a parent, make its domain and local part available, and if
   delivering to a pipe or file, or sending an autoreply, get the local
   part from the parent. For pipes and files, put the pipe or file string
   into address_pipe and address_file. */
 
-  if (addr->parent != NULL)
+  if (addr->parent)
     {
     deliver_domain_parent = addr->parent->domain;
-    deliver_localpart_parent = (addr->parent->router == NULL)?
-      addr->parent->local_part :
-        addr->parent->router->caseful_local_part?
-          addr->parent->cc_local_part : addr->parent->lc_local_part;
+    deliver_localpart_parent = !addr->parent->router
+      ? addr->parent->local_part
+      : addr->parent->router->caseful_local_part
+      ? addr->parent->cc_local_part
+      : addr->parent->lc_local_part;
 
     /* File deliveries have their own flag because they need to be picked out
     as special more often. */
 
     if (testflag(addr, af_pfr))
       {
-      if (testflag(addr, af_file)) address_file = addr->local_part;
-        else if (deliver_localpart[0] == '|') address_pipe = addr->local_part;
+      if (testflag(addr, af_file))         address_file = addr->local_part;
+      else if (deliver_localpart[0] == '|') address_pipe = addr->local_part;
       deliver_localpart = addr->parent->local_part;
       deliver_localpart_prefix = addr->parent->prefix;
       deliver_localpart_suffix = addr->parent->suffix;
@@ -222,9 +223,8 @@ if (addr->next == NULL)
     /* get message delivery status (0 - don't deliver | 1 - deliver) */
     bmi_deliver = bmi_get_delivery_status(bmi_base64_verdict);
     /* if message is to be delivered, get eventual alternate location */
-    if (bmi_deliver == 1) {
+    if (bmi_deliver == 1)
       bmi_alt_location = bmi_get_alt_location(bmi_base64_verdict);
-    };
 #endif
 
   }
@@ -239,18 +239,19 @@ else
   address_item *addr2;
   if (testflag(addr, af_pfr))
     {
-    if (testflag(addr, af_file)) address_file = addr->local_part;
-      else if (addr->local_part[0] == '|') address_pipe = addr->local_part;
+    if (testflag(addr, af_file))        address_file = addr->local_part;
+    else if (addr->local_part[0] == '|') address_pipe = addr->local_part;
     }
-  for (addr2 = addr->next; addr2 != NULL; addr2 = addr2->next)
+  for (addr2 = addr->next; addr2; addr2 = addr2->next)
     {
-    if (deliver_domain != NULL &&
-        Ustrcmp(deliver_domain, addr2->domain) != 0)
+    if (deliver_domain && Ustrcmp(deliver_domain, addr2->domain) != 0)
       deliver_domain = NULL;
-    if (self_hostname != NULL && (addr2->self_hostname == NULL ||
-        Ustrcmp(self_hostname, addr2->self_hostname) != 0))
+    if (  self_hostname
+       && (  !addr2->self_hostname
+          || Ustrcmp(self_hostname, addr2->self_hostname) != 0
+       )  )
       self_hostname = NULL;
-    if (deliver_domain == NULL && self_hostname == NULL) break;
+    if (!deliver_domain && !self_hostname) break;
     }
   }
 }
@@ -267,6 +268,8 @@ msglog directory that are used to catch output from pipes. Try to create the
 directory if it does not exist. From release 4.21, normal message logs should
 be created when the message is received.
 
+Called from deliver_message(), can be operating as root.
+
 Argument:
   filename  the file name
   mode      the mode required
@@ -278,38 +281,49 @@ Returns:    a file descriptor, or -1 (with errno set)
 static int
 open_msglog_file(uschar *filename, int mode, uschar **error)
 {
-int fd = Uopen(filename, O_WRONLY|O_APPEND|O_CREAT, mode);
+int fd, i;
 
-if (fd < 0 && errno == ENOENT)
+for (i = 2; i > 0; i--)
   {
-  uschar temp[16];
-  sprintf(CS temp, "msglog/%s", message_subdir);
-  if (message_subdir[0] == 0) temp[6] = 0;
-  (void)directory_make(spool_directory, temp, MSGLOG_DIRECTORY_MODE, TRUE);
-  fd = Uopen(filename, O_WRONLY|O_APPEND|O_CREAT, mode);
-  }
-
-/* Set the close-on-exec flag and change the owner to the exim uid/gid (this
-function is called as root). Double check the mode, because the group setting
-doesn't always get set automatically. */
-
-if (fd >= 0)
-  {
-  (void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
-  if (fchown(fd, exim_uid, exim_gid) < 0)
-    {
-    *error = US"chown";
-    return -1;
-    }
-  if (fchmod(fd, mode) < 0)
+  fd = Uopen(filename,
+#ifdef O_CLOEXEC
+    O_CLOEXEC |
+#endif
+#ifdef O_NOFOLLOW
+    O_NOFOLLOW |
+#endif
+               O_WRONLY|O_APPEND|O_CREAT, mode);
+  if (fd >= 0)
     {
-    *error = US"chmod";
-    return -1;
+    /* Set the close-on-exec flag and change the owner to the exim uid/gid (this
+    function is called as root). Double check the mode, because the group setting
+    doesn't always get set automatically. */
+
+#ifndef O_CLOEXEC
+    (void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
+#endif
+    if (fchown(fd, exim_uid, exim_gid) < 0)
+      {
+      *error = US"chown";
+      return -1;
+      }
+    if (fchmod(fd, mode) < 0)
+      {
+      *error = US"chmod";
+      return -1;
+      }
+    return fd;
     }
+  if (errno != ENOENT)
+    break;
+
+  (void)directory_make(spool_directory,
+                       spool_sname(US"msglog", message_subdir),
+                       MSGLOG_DIRECTORY_MODE, TRUE);
   }
-else *error = US"create";
 
-return fd;
+*error = US"create";
+return -1;
 }
 
 
@@ -361,15 +375,15 @@ static void
 replicate_status(address_item *addr)
 {
 address_item *addr2;
-for (addr2 = addr->next; addr2 != NULL; addr2 = addr2->next)
+for (addr2 = addr->next; addr2; addr2 = addr2->next)
   {
-  addr2->transport = addr->transport;
+  addr2->transport =       addr->transport;
   addr2->transport_return = addr->transport_return;
-  addr2->basic_errno = addr->basic_errno;
-  addr2->more_errno = addr->more_errno;
-  addr2->special_action = addr->special_action;
-  addr2->message = addr->message;
-  addr2->user_message = addr->user_message;
+  addr2->basic_errno =     addr->basic_errno;
+  addr2->more_errno =      addr->more_errno;
+  addr2->special_action =   addr->special_action;
+  addr2->message =         addr->message;
+  addr2->user_message =            addr->user_message;
   }
 }
 
@@ -402,7 +416,7 @@ Returns:    TRUE if the lists refer to the same host set
 static BOOL
 same_hosts(host_item *one, host_item *two)
 {
-while (one != NULL && two != NULL)
+while (one && two)
   {
   if (Ustrcmp(one->name, two->name) != 0)
     {
@@ -416,8 +430,8 @@ while (one != NULL && two != NULL)
 
     /* Find the ends of the shortest sequence of identical MX values */
 
-    while (end_one->next != NULL && end_one->next->mx == mx &&
-           end_two->next != NULL && end_two->next->mx == mx)
+    while (  end_one->next && end_one->next->mx == mx
+          && end_two->next && end_two->next->mx == mx)
       {
       end_one = end_one->next;
       end_two = end_two->next;
@@ -446,6 +460,10 @@ while (one != NULL && two != NULL)
     two = end_two;
     }
 
+  /* if the names matched but ports do not, mismatch */
+  else if (one->port != two->port)
+    return FALSE;
+
   /* Hosts matched */
 
   one = one->next;
@@ -476,13 +494,11 @@ Returns:    TRUE if the lists refer to the same header set
 static BOOL
 same_headers(header_line *one, header_line *two)
 {
-for (;;)
+for (;; one = one->next, two = two->next)
   {
   if (one == two) return TRUE;   /* Includes the case where both NULL */
-  if (one == NULL || two == NULL) return FALSE;
+  if (!one || !two) return FALSE;
   if (Ustrcmp(one->text, two->text) != 0) return FALSE;
-  one = one->next;
-  two = two->next;
   }
 }
 
@@ -506,7 +522,7 @@ static BOOL
 same_strings(uschar *one, uschar *two)
 {
 if (one == two) return TRUE;   /* Includes the case where both NULL */
-if (one == NULL || two == NULL) return FALSE;
+if (!one || !two) return FALSE;
 return (Ustrcmp(one, two) == 0);
 }
 
@@ -531,21 +547,21 @@ Returns:        TRUE or FALSE
 static BOOL
 same_ugid(transport_instance *tp, address_item *addr1, address_item *addr2)
 {
-if (!tp->uid_set && tp->expand_uid == NULL && !tp->deliver_as_creator)
-  {
-  if (testflag(addr1, af_uid_set) != testflag(addr2, af_gid_set) ||
-       (testflag(addr1, af_uid_set) &&
-         (addr1->uid != addr2->uid ||
-          testflag(addr1, af_initgroups) != testflag(addr2, af_initgroups))))
-    return FALSE;
-  }
+if (  !tp->uid_set && !tp->expand_uid
+   && !tp->deliver_as_creator
+   && (  testflag(addr1, af_uid_set) != testflag(addr2, af_gid_set)
+      || (  testflag(addr1, af_uid_set)
+         && (  addr1->uid != addr2->uid
+           || testflag(addr1, af_initgroups) != testflag(addr2, af_initgroups)
+   )  )  )  )
+  return FALSE;
 
-if (!tp->gid_set && tp->expand_gid == NULL)
-  {
-  if (testflag(addr1, af_gid_set) != testflag(addr2, af_gid_set) ||
-     (testflag(addr1, af_gid_set) && addr1->gid != addr2->gid))
-    return FALSE;
-  }
+if (  !tp->gid_set && !tp->expand_gid
+   && (  testflag(addr1, af_gid_set) != testflag(addr2, af_gid_set)
+      || (  testflag(addr1, af_gid_set)
+         && addr1->gid != addr2->gid
+   )  )  )
+  return FALSE;
 
 return TRUE;
 }
@@ -598,7 +614,7 @@ update_spool = TRUE;        /* Ensure spool gets updated */
 
 /* Top-level address */
 
-if (addr->parent == NULL)
+if (!addr->parent)
   {
   tree_add_nonrecipient(addr->unique);
   tree_add_nonrecipient(addr->address);
@@ -608,11 +624,9 @@ if (addr->parent == NULL)
 
 else if (testflag(addr, af_homonym))
   {
-  if (addr->transport != NULL)
-    {
+  if (addr->transport)
     tree_add_nonrecipient(
       string_sprintf("%s/%s", addr->unique + 3, addr->transport->name));
-    }
   }
 
 /* Non-homonymous child address */
@@ -622,14 +636,12 @@ else tree_add_nonrecipient(addr->unique);
 /* Check the list of duplicate addresses and ensure they are now marked
 done as well. */
 
-for (dup = addr_duplicate; dup != NULL; dup = dup->next)
-  {
+for (dup = addr_duplicate; dup; dup = dup->next)
   if (Ustrcmp(addr->unique, dup->unique) == 0)
     {
     tree_add_nonrecipient(dup->unique);
     child_done(dup, now);
     }
-  }
 }
 
 
@@ -656,18 +668,18 @@ static void
 child_done(address_item *addr, uschar *now)
 {
 address_item *aa;
-while (addr->parent != NULL)
+while (addr->parent)
   {
   addr = addr->parent;
-  if ((addr->child_count -= 1) > 0) return;   /* Incomplete parent */
+  if (--addr->child_count > 0) return;   /* Incomplete parent */
   address_done(addr, now);
 
   /* Log the completion of all descendents only when there is no ancestor with
   the same original address. */
 
-  for (aa = addr->parent; aa != NULL; aa = aa->parent)
+  for (aa = addr->parent; aa; aa = aa->parent)
     if (Ustrcmp(aa->address, addr->address) == 0) break;
-  if (aa != NULL) continue;
+  if (aa) continue;
 
   deliver_msglog("%s %s: children all complete\n", now, addr->address);
   DEBUG(D_deliver) debug_printf("%s: children all complete\n", addr->address);
@@ -676,34 +688,349 @@ while (addr->parent != NULL)
 
 
 
+/*************************************************
+*      Delivery logging support functions        *
+*************************************************/
+
+/* The LOGGING() checks in d_log_interface() are complicated for backwards
+compatibility. When outgoing interface logging was originally added, it was
+conditional on just incoming_interface (which is off by default). The
+outgoing_interface option is on by default to preserve this behaviour, but
+you can enable incoming_interface and disable outgoing_interface to get I=
+fields on incoming lines only.
+
+Arguments:
+  s         The log line buffer
+  sizep     Pointer to the buffer size
+  ptrp      Pointer to current index into buffer
+  addr      The address to be logged
+
+Returns:    New value for s
+*/
 
 static uschar *
-d_hostlog(uschar * s, int * sizep, int * ptrp, address_item * addr)
+d_log_interface(uschar *s, int *sizep, int *ptrp)
 {
-  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;
+if (LOGGING(incoming_interface) && LOGGING(outgoing_interface)
+    && sending_ip_address)
+  {
+  s = string_append(s, sizep, ptrp, 2, US" I=[", sending_ip_address);
+  s = LOGGING(outgoing_port)
+    ? string_append(s, sizep, ptrp, 2, US"]:",
+       string_sprintf("%d", sending_port))
+    : string_catn(s, sizep, ptrp, US"]", 1);
+  }
+return s;
 }
 
+
+
+static uschar *
+d_hostlog(uschar * s, int * sp, int * pp, address_item * addr)
+{
+host_item * h = addr->host_used;
+
+s = string_append(s, sp, pp, 2, US" H=", h->name);
+
+if (LOGGING(dnssec) && h->dnssec == DS_YES)
+  s = string_catn(s, sp, pp, US" DS", 3);
+
+s = string_append(s, sp, pp, 3, US" [", h->address, US"]");
+
+if (LOGGING(outgoing_port))
+  s = string_append(s, sp, pp, 2, US":", string_sprintf("%d", h->port));
+
+#ifdef SUPPORT_SOCKS
+if (LOGGING(proxy) && proxy_local_address)
+  {
+  s = string_append(s, sp, pp, 3, US" PRX=[", proxy_local_address, US"]");
+  if (LOGGING(outgoing_port))
+    s = string_append(s, sp, pp, 2, US":", string_sprintf("%d",
+      proxy_local_port));
+  }
+#endif
+
+return d_log_interface(s, sp, pp);
+}
+
+
+
+
+
 #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;
+if (LOGGING(tls_cipher) && addr->cipher)
+  s = string_append(s, sizep, ptrp, 2, US" X=", addr->cipher);
+if (LOGGING(tls_certificate_verified) && addr->cipher)
+  s = string_append(s, sizep, ptrp, 2, US" CV=",
+    testflag(addr, af_cert_verified)
+    ?
+#ifdef EXPERIMENTAL_DANE
+      testflag(addr, af_dane_verified)
+    ? "dane"
+    :
+#endif
+      "yes"
+    : "no");
+if (LOGGING(tls_peerdn) && addr->peerdn)
+  s = string_append(s, sizep, ptrp, 3, US" DN=\"",
+    string_printing(addr->peerdn), US"\"");
+return s;
+}
+#endif
+
+
+
+
+#ifndef DISABLE_EVENT
+uschar *
+event_raise(uschar * action, const uschar * event, uschar * ev_data)
+{
+uschar * s;
+if (action)
+  {
+  DEBUG(D_deliver)
+    debug_printf("Event(%s): event_action=|%s| delivery_IP=%s\n",
+      event,
+      action, deliver_host_address);
+
+  event_name = event;
+  event_data = ev_data;
+
+  if (!(s = expand_string(action)) && *expand_string_message)
+    log_write(0, LOG_MAIN|LOG_PANIC,
+      "failed to expand event_action %s in %s: %s\n",
+      event, transport_name, expand_string_message);
+
+  event_name = event_data = NULL;
+
+  /* If the expansion returns anything but an empty string, flag for
+  the caller to modify his normal processing
+  */
+  if (s && *s)
+    {
+    DEBUG(D_deliver)
+      debug_printf("Event(%s): event_action returned \"%s\"\n", event, s);
+    return s;
+    }
+  }
+return NULL;
+}
+
+void
+msg_event_raise(const uschar * event, const address_item * addr)
+{
+const uschar * save_domain = deliver_domain;
+uschar * save_local =  deliver_localpart;
+const uschar * save_host = deliver_host;
+const uschar * save_address = deliver_host_address;
+const int      save_port =   deliver_host_port;
+
+if (!addr->transport)
+  return;
+
+router_name =    addr->router ? addr->router->name : NULL;
+transport_name = addr->transport->name;
+deliver_domain = addr->domain;
+deliver_localpart = addr->local_part;
+deliver_host =   addr->host_used ? addr->host_used->name : NULL;
+
+(void) event_raise(addr->transport->event_action, event,
+         addr->host_used
+          || Ustrcmp(addr->transport->driver_name, "smtp") == 0
+         || Ustrcmp(addr->transport->driver_name, "lmtp") == 0
+        ? addr->message : NULL); 
+
+deliver_host_port =    save_port;
+deliver_host_address = save_address;
+deliver_host =      save_host;
+deliver_localpart = save_local;
+deliver_domain =    save_domain;
+router_name = transport_name = NULL;
 }
+#endif /*DISABLE_EVENT*/
+
+
+
+/******************************************************************************/
+
+
+/*************************************************
+*        Generate local prt for logging          *
+*************************************************/
+
+/* This function is a subroutine for use in string_log_address() below.
+
+Arguments:
+  addr        the address being logged
+  yield       the current dynamic buffer pointer
+  sizeptr     points to current size
+  ptrptr      points to current insert pointer
+
+Returns:      the new value of the buffer pointer
+*/
+
+static uschar *
+string_get_localpart(address_item *addr, uschar *yield, int *sizeptr,
+  int *ptrptr)
+{
+uschar * s;
+
+s = addr->prefix;
+if (testflag(addr, af_include_affixes) && s)
+  {
+#ifdef SUPPORT_I18N
+  if (testflag(addr, af_utf8_downcvt))
+    s = string_localpart_utf8_to_alabel(s, NULL);
 #endif
+  yield = string_cat(yield, sizeptr, ptrptr, s);
+  }
+
+s = addr->local_part;
+#ifdef SUPPORT_I18N
+if (testflag(addr, af_utf8_downcvt))
+  s = string_localpart_utf8_to_alabel(s, NULL);
+#endif
+yield = string_cat(yield, sizeptr, ptrptr, s);
+
+s = addr->suffix;
+if (testflag(addr, af_include_affixes) && s)
+  {
+#ifdef SUPPORT_I18N
+  if (testflag(addr, af_utf8_downcvt))
+    s = string_localpart_utf8_to_alabel(s, NULL);
+#endif
+  yield = string_cat(yield, sizeptr, ptrptr, s);
+  }
+
+return yield;
+}
+
+
+/*************************************************
+*          Generate log address list             *
+*************************************************/
+
+/* This function generates a list consisting of an address and its parents, for
+use in logging lines. For saved onetime aliased addresses, the onetime parent
+field is used. If the address was delivered by a transport with rcpt_include_
+affixes set, the af_include_affixes bit will be set in the address. In that
+case, we include the affixes here too.
+
+Arguments:
+  str           points to start of growing string, or NULL
+  size          points to current allocation for string
+  ptr           points to offset for append point; updated on exit
+  addr          bottom (ultimate) address
+  all_parents   if TRUE, include all parents
+  success       TRUE for successful delivery
+
+Returns:        a growable string in dynamic store
+*/
+
+static uschar *
+string_log_address(uschar * str, int * size, int * ptr,
+  address_item *addr, BOOL all_parents, BOOL success)
+{
+BOOL add_topaddr = TRUE;
+address_item *topaddr;
+
+/* Find the ultimate parent */
+
+for (topaddr = addr; topaddr->parent; topaddr = topaddr->parent) ;
+
+/* We start with just the local part for pipe, file, and reply deliveries, and
+for successful local deliveries from routers that have the log_as_local flag
+set. File deliveries from filters can be specified as non-absolute paths in
+cases where the transport is going to complete the path. If there is an error
+before this happens (expansion failure) the local part will not be updated, and
+so won't necessarily look like a path. Add extra text for this case. */
+
+if (  testflag(addr, af_pfr)
+   || (  success
+      && addr->router && addr->router->log_as_local
+      && addr->transport && addr->transport->info->local
+   )  )
+  {
+  if (testflag(addr, af_file) && addr->local_part[0] != '/')
+    str = string_catn(str, size, ptr, CUS"save ", 5);
+  str = string_get_localpart(addr, str, size, ptr);
+  }
+
+/* Other deliveries start with the full address. It we have split it into local
+part and domain, use those fields. Some early failures can happen before the
+splitting is done; in those cases use the original field. */
+
+else
+  {
+  uschar * cmp = str + *ptr;
+
+  if (addr->local_part)
+    {
+    const uschar * s;
+    str = string_get_localpart(addr, str, size, ptr);
+    str = string_catn(str, size, ptr, US"@", 1);
+    s = addr->domain;
+#ifdef SUPPORT_I18N
+    if (testflag(addr, af_utf8_downcvt))
+      s = string_localpart_utf8_to_alabel(s, NULL);
+#endif
+    str = string_cat(str, size, ptr, s);
+    }
+  else
+    str = string_cat(str, size, ptr, addr->address);
+
+  /* If the address we are going to print is the same as the top address,
+  and all parents are not being included, don't add on the top address. First
+  of all, do a caseless comparison; if this succeeds, do a caseful comparison
+  on the local parts. */
+
+  str[*ptr] = 0;
+  if (  strcmpic(cmp, topaddr->address) == 0
+     && Ustrncmp(cmp, topaddr->address, Ustrchr(cmp, '@') - cmp) == 0
+     && !addr->onetime_parent
+     && (!all_parents || !addr->parent || addr->parent == topaddr)
+     )
+    add_topaddr = FALSE;
+  }
+
+/* If all parents are requested, or this is a local pipe/file/reply, and
+there is at least one intermediate parent, show it in brackets, and continue
+with all of them if all are wanted. */
+
+if (  (all_parents || testflag(addr, af_pfr))
+   && addr->parent
+   && addr->parent != topaddr)
+  {
+  uschar *s = US" (";
+  address_item *addr2;
+  for (addr2 = addr->parent; addr2 != topaddr; addr2 = addr2->parent)
+    {
+    str = string_catn(str, size, ptr, s, 2);
+    str = string_cat (str, size, ptr, addr2->address);
+    if (!all_parents) break;
+    s = US", ";
+    }
+  str = string_catn(str, size, ptr, US")", 1);
+  }
+
+/* Add the top address if it is required */
+
+if (add_topaddr)
+  str = string_append(str, size, ptr, 3,
+    US" <",
+    addr->onetime_parent ? addr->onetime_parent : topaddr->address,
+    US">");
+
+return str;
+}
+
+
+/******************************************************************************/
+
+
 
 /* If msg is NULL this is a delivery log and logchar is used. Otherwise
 this is a nonstandard call; no two-character delivery flag is written
@@ -715,45 +1042,48 @@ Arguments:
 void
 delivery_log(int flags, address_item * addr, int logchar, uschar * msg)
 {
-uschar *log_address;
 int size = 256;         /* Used for a temporary, */
 int ptr = 0;            /* expanding buffer, for */
-uschar *s;              /* building log lines;   */
-void *reset_point;      /* released afterwards.  */
-
+uschar * s;             /* building log lines;   */
+void * reset_point;     /* released afterwards.  */
 
 /* Log the delivery on the main log. We use an extensible string to build up
 the log line, and reset the store afterwards. Remote deliveries should always
 have a pointer to the host item that succeeded; local deliveries can have a
 pointer to a single host item in their host list, for use by the transport. */
 
-#ifdef EXPERIMENTAL_TPDA
-  tpda_delivery_ip = NULL;     /* presume no successful remote delivery */
-  tpda_delivery_port = 0;
-  tpda_delivery_fqdn = NULL;
-  tpda_delivery_local_part = NULL;
-  tpda_delivery_domain = NULL;
-  tpda_delivery_confirmation = NULL;
+#ifndef DISABLE_EVENT
+  /* presume no successful remote delivery */
   lookup_dnssec_authenticated = NULL;
 #endif
 
 s = reset_point = store_get(size);
 
-log_address = string_log_address(addr, (log_write_selector & L_all_parents) != 0, TRUE);
 if (msg)
-  s = string_append(s, &size, &ptr, 3, host_and_ident(TRUE), US" ", log_address);
+  s = string_append(s, &size, &ptr, 2, host_and_ident(TRUE), US" ");
 else
   {
   s[ptr++] = logchar;
-  s = string_append(s, &size, &ptr, 2, US"> ", log_address);
+  s = string_catn(s, &size, &ptr, US"> ", 2);
   }
+s = string_log_address(s, &size, &ptr, addr, LOGGING(all_parents), TRUE);
+
+if (LOGGING(sender_on_delivery) || msg)
+  s = string_append(s, &size, &ptr, 3, US" F=<",
+#ifdef SUPPORT_I18N
+    testflag(addr, af_utf8_downcvt)
+    ? string_address_utf8_to_alabel(sender_address, NULL)
+    :
+#endif
+      sender_address,
+  US">");
 
-if ((log_extra_selector & LX_sender_on_delivery) != 0  ||  msg)
-  s = string_append(s, &size, &ptr, 3, US" F=<", sender_address, US">");
+if (*queue_name)
+  s = string_append(s, &size, &ptr, 2, US" Q=", queue_name);
 
 #ifdef EXPERIMENTAL_SRS
-if(addr->p.srs_sender)
-  s = string_append(s, &size, &ptr, 3, US" SRS=<", addr->p.srs_sender, US">");
+if(addr->prop.srs_sender)
+  s = string_append(s, &size, &ptr, 3, US" SRS=<", addr->prop.srs_sender, US">");
 #endif
 
 /* You might think that the return path must always be set for a successful
@@ -761,20 +1091,19 @@ delivery; indeed, I did for some time, until this statement crashed. The case
 when it is not set is for a delivery to /dev/null which is optimised by not
 being run at all. */
 
-if (used_return_path != NULL &&
-      (log_extra_selector & LX_return_path_on_delivery) != 0)
+if (used_return_path && LOGGING(return_path_on_delivery))
   s = string_append(s, &size, &ptr, 3, US" P=<", used_return_path, US">");
 
 if (msg)
   s = string_append(s, &size, &ptr, 2, US" ", msg);
 
 /* For a delivery from a system filter, there may not be a router */
-if (addr->router != NULL)
+if (addr->router)
   s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name);
 
 s = string_append(s, &size, &ptr, 2, US" T=", addr->transport->name);
 
-if ((log_extra_selector & LX_delivery_size) != 0)
+if (LOGGING(delivery_size))
   s = string_append(s, &size, &ptr, 2, US" S=",
     string_sprintf("%d", transport_count));
 
@@ -782,16 +1111,11 @@ if ((log_extra_selector & LX_delivery_size) != 0)
 
 if (addr->transport->info->local)
   {
-  if (addr->host_list != NULL)
-    {
+  if (addr->host_list)
     s = string_append(s, &size, &ptr, 2, US" H=", addr->host_list->name);
-    #ifdef EXPERIMENTAL_TPDA
-      tpda_delivery_fqdn = addr->host_list->name;
-    #endif
-    }
-  if (addr->shadow_message != NULL)
-    s = string_cat(s, &size, &ptr, addr->shadow_message,
-      Ustrlen(addr->shadow_message));
+  s = d_log_interface(s, &size, &ptr);
+  if (addr->shadow_message)
+    s = string_cat(s, &size, &ptr, addr->shadow_message);
   }
 
 /* Remote delivery */
@@ -802,26 +1126,23 @@ else
     {
     s = d_hostlog(s, &size, &ptr, addr);
     if (continue_sequence > 1)
-      s = string_cat(s, &size, &ptr, US"*", 1);
+      s = string_catn(s, &size, &ptr, US"*", 1);
 
-    #ifdef EXPERIMENTAL_TPDA
-    tpda_delivery_ip =           addr->host_used->address;
-    tpda_delivery_port =         addr->host_used->port;
-    tpda_delivery_fqdn =         addr->host_used->name;
-    tpda_delivery_local_part =   addr->local_part;
-    tpda_delivery_domain =       addr->domain;
-    tpda_delivery_confirmation = addr->message;
+#ifndef DISABLE_EVENT
+    deliver_host_address = addr->host_used->address;
+    deliver_host_port =    addr->host_used->port;
+    deliver_host =         addr->host_used->name;
 
     /* DNS lookup status */
     lookup_dnssec_authenticated = addr->host_used->dnssec==DS_YES ? US"yes"
                              : addr->host_used->dnssec==DS_NO ? US"no"
                              : NULL;
-    #endif
+#endif
     }
 
-  #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
   s = d_tlslog(s, &size, &ptr, addr);
-  #endif
+#endif
 
   if (addr->authenticator)
     {
@@ -829,28 +1150,33 @@ else
     if (addr->auth_id)
       {
       s = string_append(s, &size, &ptr, 2, US":", addr->auth_id);
-      if (log_extra_selector & LX_smtp_mailauth  &&  addr->auth_sndr)
+      if (LOGGING(smtp_mailauth) && addr->auth_sndr)
         s = string_append(s, &size, &ptr, 2, US":", addr->auth_sndr);
       }
     }
 
-  #ifndef DISABLE_PRDR
+#ifndef DISABLE_PRDR
   if (addr->flags & af_prdr_used)
-    s = string_append(s, &size, &ptr, 1, US" PRDR");
-  #endif
+    s = string_catn(s, &size, &ptr, US" PRDR", 5);
+#endif
+
+  if (addr->flags & af_chunking_used)
+    s = string_catn(s, &size, &ptr, US" K", 2);
   }
 
 /* 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))
+if (  LOGGING(smtp_confirmation)
+   && addr->message
+   && (addr->host_used || Ustrcmp(addr->transport->driver_name, "lmtp") == 0)
+   )
   {
-  int i;
+  unsigned i;
+  unsigned lim = big_buffer_size < 1024 ? big_buffer_size : 1024;
   uschar *p = big_buffer;
   uschar *ss = addr->message;
   *p++ = '\"';
-  for (i = 0; i < 256 && ss[i] != 0; i++)      /* limit logged amount */
+  for (i = 0; i < lim && ss[i] != 0; i++)      /* limit logged amount */
     {
     if (ss[i] == '\"' || ss[i] == '\\') *p++ = '\\'; /* quote \ and " */
     *p++ = ss[i];
@@ -862,11 +1188,11 @@ if (log_extra_selector & LX_smtp_confirmation &&
 
 /* Time on queue and actual time taken to deliver */
 
-if ((log_extra_selector & LX_queue_time) != 0)
+if (LOGGING(queue_time))
   s = string_append(s, &size, &ptr, 2, US" QT=",
     readconf_printtime( (int) ((long)time(NULL) - (long)received_time)) );
 
-if ((log_extra_selector & LX_deliver_time) != 0)
+if (LOGGING(deliver_time))
   s = string_append(s, &size, &ptr, 2, US" DT=",
     readconf_printtime(addr->more_errno));
 
@@ -876,22 +1202,167 @@ store we used to build the line after writing it. */
 s[ptr] = 0;
 log_write(0, flags, "%s", s);
 
-#ifdef EXPERIMENTAL_TPDA
-if (addr->transport->tpda_delivery_action)
+#ifndef DISABLE_EVENT
+if (!msg) msg_event_raise(US"msg:delivery", addr);
+#endif
+
+store_reset(reset_point);
+return;
+}
+
+
+
+static void
+deferral_log(address_item * addr, uschar * now,
+  int logflags, uschar * driver_name, uschar * driver_kind)
+{
+int size = 256;         /* Used for a temporary, */
+int ptr = 0;            /* expanding buffer, for */
+uschar * s;             /* building log lines;   */
+void * reset_point;     /* released afterwards.  */
+
+uschar ss[32];
+
+/* Build up the line that is used for both the message log and the main
+log. */
+
+s = reset_point = store_get(size);
+
+/* Create the address string for logging. Must not do this earlier, because
+an OK result may be changed to FAIL when a pipe returns text. */
+
+s = string_log_address(s, &size, &ptr, addr, LOGGING(all_parents), FALSE);
+
+if (*queue_name)
+  s = string_append(s, &size, &ptr, 2, US" Q=", queue_name);
+
+/* Either driver_name contains something and driver_kind contains
+" router" or " transport" (note the leading space), or driver_name is
+a null string and driver_kind contains "routing" without the leading
+space, if all routing has been deferred. When a domain has been held,
+so nothing has been done at all, both variables contain null strings. */
+
+if (driver_name)
   {
-  DEBUG(D_deliver)
-    debug_printf("  TPDA(Delivery): tpda_deliver_action=|%s| tpda_delivery_IP=%s\n",
-      addr->transport->tpda_delivery_action, tpda_delivery_ip);
-
-  router_name =    addr->router->name;
-  transport_name = addr->transport->name;
-  if (!expand_string(addr->transport->tpda_delivery_action) && *expand_string_message)
-    log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand tpda_deliver_action in %s: %s\n",
-      transport_name, expand_string_message);
-  router_name = NULL;
-  transport_name = NULL;
+  if (driver_kind[1] == 't' && addr->router)
+    s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name);
+  Ustrcpy(ss, " ?=");
+  ss[1] = toupper(driver_kind[1]);
+  s = string_append(s, &size, &ptr, 2, ss, driver_name);
   }
+else if (driver_kind)
+  s = string_append(s, &size, &ptr, 2, US" ", driver_kind);
+
+/*XXX need an s+s+p sprintf */
+sprintf(CS ss, " defer (%d)", addr->basic_errno);
+s = string_cat(s, &size, &ptr, ss);
+
+if (addr->basic_errno > 0)
+  s = string_append(s, &size, &ptr, 2, US": ",
+    US strerror(addr->basic_errno));
+
+if (addr->host_used)
+  {
+  s = string_append(s, &size, &ptr, 5,
+                   US" H=", addr->host_used->name,
+                   US" [",  addr->host_used->address, US"]");
+  if (LOGGING(outgoing_port))
+    {
+    int port = addr->host_used->port;
+    s = string_append(s, &size, &ptr, 2,
+         US":", port == PORT_NONE ? US"25" : string_sprintf("%d", port));
+    }
+  }
+
+if (addr->message)
+  s = string_append(s, &size, &ptr, 2, US": ", addr->message);
+
+s[ptr] = 0;
+
+/* Log the deferment in the message log, but don't clutter it
+up with retry-time defers after the first delivery attempt. */
+
+if (deliver_firsttime || addr->basic_errno > ERRNO_RETRY_BASE)
+  deliver_msglog("%s %s\n", now, s);
+
+/* Write the main log and reset the store.
+For errors of the type "retry time not reached" (also remotes skipped
+on queue run), logging is controlled by L_retry_defer. Note that this kind
+of error number is negative, and all the retry ones are less than any
+others. */
+
+
+log_write(addr->basic_errno <= ERRNO_RETRY_BASE ? L_retry_defer : 0, logflags,
+  "== %s", s);
+
+store_reset(reset_point);
+return;
+}
+
+
+
+static void
+failure_log(address_item * addr, uschar * driver_kind, uschar * now)
+{
+int size = 256;         /* Used for a temporary, */
+int ptr = 0;            /* expanding buffer, for */
+uschar * s;             /* building log lines;   */
+void * reset_point;     /* released afterwards.  */
+
+/* Build up the log line for the message and main logs */
+
+s = reset_point = store_get(size);
+
+/* Create the address string for logging. Must not do this earlier, because
+an OK result may be changed to FAIL when a pipe returns text. */
+
+s = string_log_address(s, &size, &ptr, addr, LOGGING(all_parents), FALSE);
+
+if (LOGGING(sender_on_delivery))
+  s = string_append(s, &size, &ptr, 3, US" F=<", sender_address, US">");
+
+if (*queue_name)
+  s = string_append(s, &size, &ptr, 2, US" Q=", queue_name);
+
+/* Return path may not be set if no delivery actually happened */
+
+if (used_return_path && LOGGING(return_path_on_delivery))
+  s = string_append(s, &size, &ptr, 3, US" P=<", used_return_path, US">");
+
+if (addr->router)
+  s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name);
+if (addr->transport)
+  s = string_append(s, &size, &ptr, 2, US" T=", addr->transport->name);
+
+if (addr->host_used)
+  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": ", US strerror(addr->basic_errno));
+
+if (addr->message)
+  s = string_append(s, &size, &ptr, 2, US": ", addr->message);
+
+s[ptr] = 0;
+
+/* Do the logging. For the message log, "routing failed" for those cases,
+just to make it clearer. */
+
+if (driver_kind)
+  deliver_msglog("%s %s failed for %s\n", now, driver_kind, s);
+else
+  deliver_msglog("%s %s\n", now, s);
+
+log_write(0, LOG_MAIN, "** %s", s);
+
+#ifndef DISABLE_EVENT
+msg_event_raise(US"msg:fail:delivery", addr);
+#endif
+
 store_reset(reset_point);
 return;
 }
@@ -923,13 +1394,6 @@ post_process_one(address_item *addr, int result, int logflags, int driver_type,
 uschar *now = tod_stamp(tod_log);
 uschar *driver_kind = NULL;
 uschar *driver_name = NULL;
-uschar *log_address;
-
-int size = 256;         /* Used for a temporary, */
-int ptr = 0;            /* expanding buffer, for */
-uschar *s;              /* building log lines;   */
-void *reset_point;      /* released afterwards.  */
-
 
 DEBUG(D_deliver) debug_printf("post-process %s (%d)\n", addr->address, result);
 
@@ -938,7 +1402,7 @@ transport has disabled it. */
 
 if (driver_type == DTYPE_TRANSPORT)
   {
-  if (addr->transport != NULL)
+  if (addr->transport)
     {
     driver_name = addr->transport->name;
     driver_kind = US" transport";
@@ -948,7 +1412,7 @@ if (driver_type == DTYPE_TRANSPORT)
   }
 else if (driver_type == DTYPE_ROUTER)
   {
-  if (addr->router != NULL)
+  if (addr->router)
     {
     driver_name = addr->router->name;
     driver_kind = US" router";
@@ -964,22 +1428,12 @@ expansion item that has a password setting, and flatten the password. This is a
 fudge, but I don't know a cleaner way of doing this. (If the item is badly
 malformed, it won't ever have gone near LDAP.) */
 
-if (addr->message != NULL)
+if (addr->message)
   {
-  addr->message = string_printing(addr->message);
-  if (((Ustrstr(addr->message, "failed to expand") != NULL) || (Ustrstr(addr->message, "expansion of ") != NULL)) &&
-      (Ustrstr(addr->message, "mysql") != NULL ||
-       Ustrstr(addr->message, "pgsql") != NULL ||
-#ifdef EXPERIMENTAL_REDIS
-       Ustrstr(addr->message, "redis") != NULL ||
-#endif
-       Ustrstr(addr->message, "sqlite") != NULL ||
-       Ustrstr(addr->message, "ldap:") != NULL ||
-       Ustrstr(addr->message, "ldapdn:") != NULL ||
-       Ustrstr(addr->message, "ldapm:") != NULL))
-    {
-      addr->message = string_sprintf("Temporary internal error");
-    }
+  const uschar * s = string_printing(addr->message);
+
+  /* deconst cast ok as string_printing known to have alloc'n'copied */
+  addr->message = expand_hide_passwords(US s);
   }
 
 /* If we used a transport that has one of the "return_output" options set, and
@@ -994,7 +1448,7 @@ on a non-empty file.
 In any case, we close the message file, because we cannot afford to leave a
 file-descriptor for one address while processing (maybe very many) others. */
 
-if (addr->return_file >= 0 && addr->return_filename != NULL)
+if (addr->return_file >= 0 && addr->return_filename)
   {
   BOOL return_output = FALSE;
   struct stat statbuf;
@@ -1008,46 +1462,44 @@ if (addr->return_file >= 0 && addr->return_filename != NULL)
 
     /* Handle logging options */
 
-    if (tb->log_output || (result == FAIL && tb->log_fail_output) ||
-                          (result == DEFER && tb->log_defer_output))
+    if (  tb->log_output
+       || result == FAIL  && tb->log_fail_output
+       || result == DEFER && tb->log_defer_output
+       )
       {
       uschar *s;
       FILE *f = Ufopen(addr->return_filename, "rb");
-      if (f == NULL)
+      if (!f)
         log_write(0, LOG_MAIN|LOG_PANIC, "failed to open %s to log output "
           "from %s transport: %s", addr->return_filename, tb->name,
           strerror(errno));
       else
-        {
-        s = US Ufgets(big_buffer, big_buffer_size, f);
-        if (s != NULL)
+        if ((s = US Ufgets(big_buffer, big_buffer_size, f)))
           {
           uschar *p = big_buffer + Ustrlen(big_buffer);
+         const uschar * sp;
           while (p > big_buffer && isspace(p[-1])) p--;
           *p = 0;
-          s = string_printing(big_buffer);
+          sp = string_printing(big_buffer);
           log_write(0, LOG_MAIN, "<%s>: %s transport output: %s",
-            addr->address, tb->name, s);
+            addr->address, tb->name, sp);
           }
         (void)fclose(f);
-        }
       }
 
     /* Handle returning options, but only if there is an address to return
     the text to. */
 
-    if (sender_address[0] != 0 || addr->p.errors_address != NULL)
-      {
+    if (sender_address[0] != 0 || addr->prop.errors_address)
       if (tb->return_output)
         {
         addr->transport_return = result = FAIL;
-        if (addr->basic_errno == 0 && addr->message == NULL)
+        if (addr->basic_errno == 0 && !addr->message)
           addr->message = US"return message generated";
         return_output = TRUE;
         }
       else
         if (tb->return_fail_output && result == FAIL) return_output = TRUE;
-      }
     }
 
   /* Get rid of the file unless it might be returned, but close it in
@@ -1078,7 +1530,7 @@ if (result == OK)
   address_done(addr, now);
   DEBUG(D_deliver) debug_printf("%s delivered\n", addr->address);
 
-  if (addr->parent == NULL)
+  if (!addr->parent)
     deliver_msglog("%s %s: %s%s succeeded\n", now, addr->address,
       driver_name, driver_kind);
   else
@@ -1088,8 +1540,8 @@ if (result == OK)
     child_done(addr, now);
     }
 
-  /* Certificates for logging (via TPDA) */
-  #ifdef SUPPORT_TLS
+  /* Certificates for logging (via events) */
+#ifdef SUPPORT_TLS
   tls_out.ourcert = addr->ourcert;
   addr->ourcert = NULL;
   tls_out.peercert = addr->peercert;
@@ -1098,25 +1550,23 @@ if (result == OK)
   tls_out.cipher = addr->cipher;
   tls_out.peerdn = addr->peerdn;
   tls_out.ocsp = addr->ocsp;
-  #endif
+# ifdef EXPERIMENTAL_DANE
+  tls_out.dane_verified = testflag(addr, af_dane_verified);
+# endif
+#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;
-    }
+#ifdef SUPPORT_TLS
+  tls_free_cert(&tls_out.ourcert);
+  tls_free_cert(&tls_out.peercert);
   tls_out.cipher = NULL;
   tls_out.peerdn = NULL;
   tls_out.ocsp = OCSP_NOT_REQ;
-  #endif
+# ifdef EXPERIMENTAL_DANE
+  tls_out.dane_verified = FALSE;
+# endif
+#endif
   }
 
 
@@ -1149,73 +1599,7 @@ else if (result == DEFER || result == PANIC)
   log or the main log for SMTP defers. */
 
   if (!queue_2stage || addr->basic_errno != 0)
-    {
-    uschar ss[32];
-
-    /* For errors of the type "retry time not reached" (also remotes skipped
-    on queue run), logging is controlled by L_retry_defer. Note that this kind
-    of error number is negative, and all the retry ones are less than any
-    others. */
-
-    unsigned int use_log_selector = (addr->basic_errno <= ERRNO_RETRY_BASE)?
-      L_retry_defer : 0;
-
-    /* Build up the line that is used for both the message log and the main
-    log. */
-
-    s = reset_point = store_get(size);
-
-    /* Create the address string for logging. Must not do this earlier, because
-    an OK result may be changed to FAIL when a pipe returns text. */
-
-    log_address = string_log_address(addr,
-      (log_write_selector & L_all_parents) != 0, result == OK);
-
-    s = string_cat(s, &size, &ptr, log_address, Ustrlen(log_address));
-
-    /* Either driver_name contains something and driver_kind contains
-    " router" or " transport" (note the leading space), or driver_name is
-    a null string and driver_kind contains "routing" without the leading
-    space, if all routing has been deferred. When a domain has been held,
-    so nothing has been done at all, both variables contain null strings. */
-
-    if (driver_name == NULL)
-      {
-      if (driver_kind != NULL)
-        s = string_append(s, &size, &ptr, 2, US" ", driver_kind);
-      }
-     else
-      {
-      if (driver_kind[1] == 't' && addr->router != NULL)
-        s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name);
-      Ustrcpy(ss, " ?=");
-      ss[1] = toupper(driver_kind[1]);
-      s = string_append(s, &size, &ptr, 2, ss, driver_name);
-      }
-
-    sprintf(CS ss, " defer (%d)", addr->basic_errno);
-    s = string_cat(s, &size, &ptr, ss, Ustrlen(ss));
-
-    if (addr->basic_errno > 0)
-      s = string_append(s, &size, &ptr, 2, US": ",
-        US strerror(addr->basic_errno));
-
-    if (addr->message != NULL)
-      s = string_append(s, &size, &ptr, 2, US": ", addr->message);
-
-    s[ptr] = 0;
-
-    /* Log the deferment in the message log, but don't clutter it
-    up with retry-time defers after the first delivery attempt. */
-
-    if (deliver_firsttime || addr->basic_errno > ERRNO_RETRY_BASE)
-      deliver_msglog("%s %s\n", now, s);
-
-    /* Write the main log and reset the store */
-
-    log_write(use_log_selector, logflags, "== %s", s);
-    store_reset(reset_point);
-    }
+    deferral_log(addr, now, logflags, driver_name, driver_kind);
   }
 
 
@@ -1231,7 +1615,7 @@ else
   force the af_ignore_error flag. This will cause the address to be discarded
   later (with a log entry). */
 
-  if (sender_address[0] == 0 && message_age >= ignore_bounce_errors_after)
+  if (!*sender_address && message_age >= ignore_bounce_errors_after)
     setflag(addr, af_ignore_error);
 
   /* Freeze the message if requested, or if this is a bounce message (or other
@@ -1240,14 +1624,16 @@ else
   to ignore occurs later, instead of sending a message. Logging of freezing
   occurs later, just before writing the -H file. */
 
-  if (!testflag(addr, af_ignore_error) &&
-      (addr->special_action == SPECIAL_FREEZE ||
-        (sender_address[0] == 0 && addr->p.errors_address == NULL)
-      ))
+  if (  !testflag(addr, af_ignore_error)
+     && (  addr->special_action == SPECIAL_FREEZE
+        || (sender_address[0] == 0 && !addr->prop.errors_address)
+     )  )
     {
-    frozen_info = (addr->special_action == SPECIAL_FREEZE)? US"" :
-      (sender_local && !local_error_message)?
-        US" (message created with -f <>)" : US" (delivery error message)";
+    frozen_info = addr->special_action == SPECIAL_FREEZE
+      ? US""
+      : sender_local && !local_error_message
+      ? US" (message created with -f <>)"
+      : US" (delivery error message)";
     deliver_freeze = TRUE;
     deliver_frozen_at = time(NULL);
     update_spool = TRUE;
@@ -1268,58 +1654,7 @@ else
     addr_failed = addr;
     }
 
-  /* Build up the log line for the message and main logs */
-
-  s = reset_point = store_get(size);
-
-  /* Create the address string for logging. Must not do this earlier, because
-  an OK result may be changed to FAIL when a pipe returns text. */
-
-  log_address = string_log_address(addr,
-    (log_write_selector & L_all_parents) != 0, result == OK);
-
-  s = string_cat(s, &size, &ptr, log_address, Ustrlen(log_address));
-
-  if ((log_extra_selector & LX_sender_on_delivery) != 0)
-    s = string_append(s, &size, &ptr, 3, US" F=<", sender_address, US">");
-
-  /* Return path may not be set if no delivery actually happened */
-
-  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);
-  if (addr->transport != NULL)
-    s = string_append(s, &size, &ptr, 2, US" T=", addr->transport->name);
-
-  if (addr->host_used != NULL)
-    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": ",
-      US strerror(addr->basic_errno));
-
-  if (addr->message != NULL)
-    s = string_append(s, &size, &ptr, 2, US": ", addr->message);
-
-  s[ptr] = 0;
-
-  /* Do the logging. For the message log, "routing failed" for those cases,
-  just to make it clearer. */
-
-  if (driver_name == NULL)
-    deliver_msglog("%s %s failed for %s\n", now, driver_kind, s);
-  else
-    deliver_msglog("%s %s\n", now, s);
-
-  log_write(0, LOG_MAIN, "** %s", s);
-  store_reset(reset_point);
+  failure_log(addr, driver_name ? NULL : driver_kind, now);
   }
 
 /* Ensure logging is turned on again in all cases */
@@ -1356,7 +1691,7 @@ common_error(BOOL logit, address_item *addr, int code, uschar *format, ...)
 address_item *addr2;
 addr->basic_errno = code;
 
-if (format != NULL)
+if (format)
   {
   va_list ap;
   uschar buffer[512];
@@ -1368,7 +1703,7 @@ if (format != NULL)
   addr->message = string_copy(buffer);
   }
 
-for (addr2 = addr->next; addr2 != NULL; addr2 = addr2->next)
+for (addr2 = addr->next; addr2; addr2 = addr2->next)
   {
   addr2->basic_errno = code;
   addr2->message = addr->message;
@@ -1399,7 +1734,7 @@ static BOOL
 check_never_users(uid_t uid, uid_t *nusers)
 {
 int i;
-if (nusers == NULL) return FALSE;
+if (!nusers) return FALSE;
 for (i = 1; i <= (int)(nusers[0]); i++) if (nusers[i] == uid) return TRUE;
 return FALSE;
 }
@@ -1432,7 +1767,7 @@ static BOOL
 findugid(address_item *addr, transport_instance *tp, uid_t *uidp, gid_t *gidp,
   BOOL *igfp)
 {
-uschar *nuname = NULL;
+uschar *nuname;
 BOOL gid_set = FALSE;
 
 /* Default initgroups flag comes from the transport */
@@ -1447,15 +1782,15 @@ if (tp->gid_set)
   *gidp = tp->gid;
   gid_set = TRUE;
   }
-else if (tp->expand_gid != NULL)
+else if (tp->expand_gid)
   {
-  if (route_find_expanded_group(tp->expand_gid, tp->name, US"transport", gidp,
-    &(addr->message))) gid_set = TRUE;
-  else
+  if (!route_find_expanded_group(tp->expand_gid, tp->name, US"transport", gidp,
+    &(addr->message)))
     {
     common_error(FALSE, addr, ERRNO_GIDFAIL, NULL);
     return FALSE;
     }
+  gid_set = TRUE;
   }
 
 /* If the transport did not set a group, see if the router did. */
@@ -1473,7 +1808,7 @@ if (tp->uid_set) *uidp = tp->uid;
 /* Otherwise, try for an expandable uid field. If it ends up as a numeric id,
 it does not provide a passwd value from which a gid can be taken. */
 
-else if (tp->expand_uid != NULL)
+else if (tp->expand_uid)
   {
   struct passwd *pw;
   if (!route_find_expanded_user(tp->expand_uid, tp->name, US"transport", &pw,
@@ -1482,7 +1817,7 @@ else if (tp->expand_uid != NULL)
     common_error(FALSE, addr, ERRNO_UIDFAIL, NULL);
     return FALSE;
     }
-  if (!gid_set && pw != NULL)
+  if (!gid_set && pw)
     {
     *gidp = pw->pw_gid;
     gid_set = TRUE;
@@ -1537,12 +1872,12 @@ if (!gid_set)
 /* Check that the uid is not on the lists of banned uids that may not be used
 for delivery processes. */
 
-if (check_never_users(*uidp, never_users))
-  nuname = US"never_users";
-else if (check_never_users(*uidp, fixed_never_users))
-  nuname = US"fixed_never_users";
-
-if (nuname != NULL)
+nuname = check_never_users(*uidp, never_users)
+  ? US"never_users"
+  : check_never_users(*uidp, fixed_never_users)
+  ? US"fixed_never_users"
+  : NULL;
+if (nuname)
   {
   common_error(TRUE, addr, ERRNO_UIDFAIL, US"User %ld set for %s transport "
     "is on the %s list", (long int)(*uidp), tp->name, nuname);
@@ -1583,14 +1918,13 @@ deliver_set_expansions(addr);
 size_limit = expand_string_integer(tp->message_size_limit, TRUE);
 deliver_set_expansions(NULL);
 
-if (expand_string_message != NULL)
+if (expand_string_message)
   {
   rc = DEFER;
-  if (size_limit == -1)
-    addr->message = string_sprintf("failed to expand message_size_limit "
-      "in %s transport: %s", tp->name, expand_string_message);
-  else
-    addr->message = string_sprintf("invalid message_size_limit "
+  addr->message = size_limit == -1
+    ? string_sprintf("failed to expand message_size_limit "
+      "in %s transport: %s", tp->name, expand_string_message)
+    : string_sprintf("invalid message_size_limit "
       "in %s transport: %s", tp->name, expand_string_message);
   }
 else if (size_limit > 0 && message_size > size_limit)
@@ -1737,19 +2071,19 @@ transport_instance *tp = addr->transport;
 /* Set up the return path from the errors or sender address. If the transport
 has its own return path setting, expand it and replace the existing value. */
 
-if(addr->p.errors_address != NULL)
-  return_path = addr->p.errors_address;
+if(addr->prop.errors_address)
+  return_path = addr->prop.errors_address;
 #ifdef EXPERIMENTAL_SRS
-else if(addr->p.srs_sender != NULL)
-  return_path = addr->p.srs_sender;
+else if (addr->prop.srs_sender)
+  return_path = addr->prop.srs_sender;
 #endif
 else
   return_path = sender_address;
 
-if (tp->return_path != NULL)
+if (tp->return_path)
   {
   uschar *new_return_path = expand_string(tp->return_path);
-  if (new_return_path == NULL)
+  if (!new_return_path)
     {
     if (!expand_string_forcedfail)
       {
@@ -1777,14 +2111,14 @@ if (!findugid(addr, tp, &uid, &gid, &use_initgroups)) return;
 home directory set in the address may already be expanded; a flag is set to
 indicate that. In other cases we must expand it. */
 
-if ((deliver_home = tp->home_dir) != NULL ||       /* Set in transport, or */
-     ((deliver_home = addr->home_dir) != NULL &&   /* Set in address and */
-       !testflag(addr, af_home_expanded)))         /*   not expanded */
+if (  (deliver_home = tp->home_dir)            /* Set in transport, or */
+   || (  (deliver_home = addr->home_dir)       /* Set in address and */
+      && !testflag(addr, af_home_expanded)     /*   not expanded */
+   )  )
   {
   uschar *rawhome = deliver_home;
   deliver_home = NULL;                      /* in case it contains $home */
-  deliver_home = expand_string(rawhome);
-  if (deliver_home == NULL)
+  if (!(deliver_home = expand_string(rawhome)))
     {
     common_error(TRUE, addr, ERRNO_EXPANDFAIL, US"home directory \"%s\" failed "
       "to expand for %s transport: %s", rawhome, tp->name,
@@ -1806,14 +2140,11 @@ all users have access. It is necessary to be in a visible directory for some
 operating systems when running pipes, as some commands (e.g. "rm" under Solaris
 2.5) require this. */
 
-working_directory = (tp->current_dir != NULL)?
-  tp->current_dir : addr->current_dir;
-
-if (working_directory != NULL)
+working_directory = tp->current_dir ? tp->current_dir : addr->current_dir;
+if (working_directory)
   {
   uschar *raw = working_directory;
-  working_directory = expand_string(raw);
-  if (working_directory == NULL)
+  if (!(working_directory = expand_string(raw)))
     {
     common_error(TRUE, addr, ERRNO_EXPANDFAIL, US"current directory \"%s\" "
       "failed to expand for %s transport: %s", raw, tp->name,
@@ -1827,22 +2158,25 @@ if (working_directory != NULL)
     return;
     }
   }
-else working_directory = (deliver_home == NULL)? US"/" : deliver_home;
+else working_directory = deliver_home ? deliver_home : US"/";
 
 /* If one of the return_output flags is set on the transport, create and open a
 file in the message log directory for the transport to write its output onto.
 This is mainly used by pipe transports. The file needs to be unique to the
 address. This feature is not available for shadow transports. */
 
-if (!shadowing && (tp->return_output || tp->return_fail_output ||
-    tp->log_output || tp->log_fail_output))
+if (  !shadowing
+   && (  tp->return_output || tp->return_fail_output
+      || tp->log_output || tp->log_fail_output || tp->log_defer_output
+   )  )
   {
-  uschar *error;
+  uschar * error;
+
   addr->return_filename =
-    string_sprintf("%s/msglog/%s/%s-%d-%d", spool_directory, message_subdir,
-      message_id, getpid(), return_count++);
-  addr->return_file = open_msglog_file(addr->return_filename, 0400, &error);
-  if (addr->return_file < 0)
+    spool_fname(US"msglog", message_subdir, message_id,
+      string_sprintf("-%d-%d", getpid(), return_count++));
+  
+  if ((addr->return_file = open_msglog_file(addr->return_filename, 0400, &error)) < 0)
     {
     common_error(TRUE, addr, errno, US"Unable to %s file for %s transport "
       "to return message: %s", error, tp->name, strerror(errno));
@@ -1888,19 +2222,19 @@ if ((pid = fork()) == 0)
   diagnosis that it's reasonable to make them something that has to be explicitly requested.
   */
 
-  #ifdef RLIMIT_CORE
+#ifdef RLIMIT_CORE
   struct rlimit rl;
   rl.rlim_cur = 0;
   rl.rlim_max = 0;
   if (setrlimit(RLIMIT_CORE, &rl) < 0)
     {
-    #ifdef SETRLIMIT_NOT_SUPPORTED
+ifdef SETRLIMIT_NOT_SUPPORTED
     if (errno != ENOSYS && errno != ENOTSUP)
-    #endif
+endif
       log_write(0, LOG_MAIN|LOG_PANIC, "setrlimit(RLIMIT_CORE) failed: %s",
         strerror(errno));
     }
-  #endif
+#endif
 
   /* Reset the random number generator, so different processes don't all
   have the same sequence. */
@@ -1911,20 +2245,18 @@ if ((pid = fork()) == 0)
   privileged. (Appendfile uses this to expand quota, for example, while
   able to read private files.) */
 
-  if (addr->transport->setup != NULL)
-    {
+  if (addr->transport->setup)
     switch((addr->transport->setup)(addr->transport, addr, NULL, uid, gid,
            &(addr->message)))
       {
       case DEFER:
-      addr->transport_return = DEFER;
-      goto PASS_BACK;
+       addr->transport_return = DEFER;
+       goto PASS_BACK;
 
       case FAIL:
-      addr->transport_return = PANIC;
-      goto PASS_BACK;
+       addr->transport_return = PANIC;
+       goto PASS_BACK;
       }
-    }
 
   /* Ignore SIGINT and SIGTERM during delivery. Also ignore SIGUSR1, as
   when the process becomes unprivileged, it won't be able to write to the
@@ -1950,7 +2282,7 @@ if ((pid = fork()) == 0)
     {
     address_item *batched;
     debug_printf("  home=%s current=%s\n", deliver_home, working_directory);
-    for (batched = addr->next; batched != NULL; batched = batched->next)
+    for (batched = addr->next; batched; batched = batched->next)
       debug_printf("additional batched address: %s\n", batched->address);
     }
 
@@ -1977,7 +2309,7 @@ if ((pid = fork()) == 0)
     /* If a transport filter has been specified, set up its argument list.
     Any errors will get put into the address, and FALSE yielded. */
 
-    if (addr->transport->filter_command != NULL)
+    if (addr->transport->filter_command)
       {
       ok = transport_set_up_command(&transport_filter_argv,
         addr->transport->filter_command,
@@ -2002,20 +2334,20 @@ if ((pid = fork()) == 0)
   PASS_BACK:
 
   if (replicate) replicate_status(addr);
-  for (addr2 = addr; addr2 != NULL; addr2 = addr2->next)
+  for (addr2 = addr; addr2; addr2 = addr2->next)
     {
     int i;
     int local_part_length = Ustrlen(addr2->local_part);
     uschar *s;
     int ret;
 
-    if(  (ret = write(pfd[pipe_write], (void *)&(addr2->transport_return), sizeof(int))) != sizeof(int)
-      || (ret = write(pfd[pipe_write], (void *)&transport_count, sizeof(transport_count))) != sizeof(transport_count)
-      || (ret = write(pfd[pipe_write], (void *)&(addr2->flags), sizeof(addr2->flags))) != sizeof(addr2->flags)
-      || (ret = write(pfd[pipe_write], (void *)&(addr2->basic_errno), sizeof(int))) != sizeof(int)
-      || (ret = write(pfd[pipe_write], (void *)&(addr2->more_errno), sizeof(int))) != sizeof(int)
-      || (ret = write(pfd[pipe_write], (void *)&(addr2->special_action), sizeof(int))) != sizeof(int)
-      || (ret = write(pfd[pipe_write], (void *)&(addr2->transport),
+    if(  (ret = write(pfd[pipe_write], &addr2->transport_return, sizeof(int))) != sizeof(int)
+      || (ret = write(pfd[pipe_write], &transport_count, sizeof(transport_count))) != sizeof(transport_count)
+      || (ret = write(pfd[pipe_write], &addr2->flags, sizeof(addr2->flags))) != sizeof(addr2->flags)
+      || (ret = write(pfd[pipe_write], &addr2->basic_errno,    sizeof(int))) != sizeof(int)
+      || (ret = write(pfd[pipe_write], &addr2->more_errno,     sizeof(int))) != sizeof(int)
+      || (ret = write(pfd[pipe_write], &addr2->special_action, sizeof(int))) != sizeof(int)
+      || (ret = write(pfd[pipe_write], &addr2->transport,
         sizeof(transport_instance *))) != sizeof(transport_instance *)
 
     /* For a file delivery, pass back the local part, in case the original
@@ -2023,23 +2355,23 @@ if ((pid = fork()) == 0)
     logging. */
 
       || (testflag(addr2, af_file)
-          && (  (ret = write(pfd[pipe_write], (void *)&local_part_length, sizeof(int))) != sizeof(int)
+          && (  (ret = write(pfd[pipe_write], &local_part_length, sizeof(int))) != sizeof(int)
              || (ret = write(pfd[pipe_write], addr2->local_part, local_part_length)) != local_part_length
             )
         )
       )
-      log_write(0, LOG_MAIN|LOG_PANIC, "Failed writing transport results to pipe: %s\n",
+      log_write(0, LOG_MAIN|LOG_PANIC, "Failed writing transport results to pipe: %s",
        ret == -1 ? strerror(errno) : "short write");
 
     /* Now any messages */
 
     for (i = 0, s = addr2->message; i < 2; i++, s = addr2->user_message)
       {
-      int message_length = (s == NULL)? 0 : Ustrlen(s) + 1;
-      if(  (ret = write(pfd[pipe_write], (void *)&message_length, sizeof(int))) != sizeof(int)
-        || (message_length > 0  && (ret = write(pfd[pipe_write], s, message_length)) != message_length)
+      int message_length = s ? Ustrlen(s) + 1 : 0;
+      if(  (ret = write(pfd[pipe_write], &message_length, sizeof(int))) != sizeof(int)
+        || message_length > 0  && (ret = write(pfd[pipe_write], s, message_length)) != message_length
        )
-        log_write(0, LOG_MAIN|LOG_PANIC, "Failed writing transport results to pipe: %s\n",
+        log_write(0, LOG_MAIN|LOG_PANIC, "Failed writing transport results to pipe: %s",
          ret == -1 ? strerror(errno) : "short write");
       }
     }
@@ -2068,41 +2400,54 @@ will remain. Afterwards, close the reading end. */
 
 (void)close(pfd[pipe_write]);
 
-for (addr2 = addr; addr2 != NULL; addr2 = addr2->next)
+for (addr2 = addr; addr2; addr2 = addr2->next)
   {
-  len = read(pfd[pipe_read], (void *)&status, sizeof(int));
-  if (len > 0)
+  if ((len = read(pfd[pipe_read], &status, sizeof(int))) > 0)
     {
     int i;
     uschar **sptr;
 
     addr2->transport_return = status;
-    len = read(pfd[pipe_read], (void *)&transport_count,
+    len = read(pfd[pipe_read], &transport_count,
       sizeof(transport_count));
-    len = read(pfd[pipe_read], (void *)&(addr2->flags), sizeof(addr2->flags));
-    len = read(pfd[pipe_read], (void *)&(addr2->basic_errno), sizeof(int));
-    len = read(pfd[pipe_read], (void *)&(addr2->more_errno), sizeof(int));
-    len = read(pfd[pipe_read], (void *)&(addr2->special_action), sizeof(int));
-    len = read(pfd[pipe_read], (void *)&(addr2->transport),
+    len = read(pfd[pipe_read], &addr2->flags, sizeof(addr2->flags));
+    len = read(pfd[pipe_read], &addr2->basic_errno,    sizeof(int));
+    len = read(pfd[pipe_read], &addr2->more_errno,     sizeof(int));
+    len = read(pfd[pipe_read], &addr2->special_action, sizeof(int));
+    len = read(pfd[pipe_read], &addr2->transport,
       sizeof(transport_instance *));
 
     if (testflag(addr2, af_file))
       {
-      int local_part_length;
-      len = read(pfd[pipe_read], (void *)&local_part_length, sizeof(int));
-      len = read(pfd[pipe_read], (void *)big_buffer, local_part_length);
-      big_buffer[local_part_length] = 0;
+      int llen;
+      if (  read(pfd[pipe_read], &llen, sizeof(int)) != sizeof(int)
+        || llen > 64*4 /* limit from rfc 5821, times I18N factor */
+         )
+       {
+       log_write(0, LOG_MAIN|LOG_PANIC, "bad local_part length read"
+         " from delivery subprocess");
+       break;
+       }
+      /* sanity-checked llen so disable the Coverity error */
+      /* coverity[tainted_data] */
+      if (read(pfd[pipe_read], big_buffer, llen) != llen)
+       {
+       log_write(0, LOG_MAIN|LOG_PANIC, "bad local_part read"
+         " from delivery subprocess");
+       break;
+       }
+      big_buffer[llen] = 0;
       addr2->local_part = string_copy(big_buffer);
       }
 
-    for (i = 0, sptr = &(addr2->message); i < 2;
-         i++, sptr = &(addr2->user_message))
+    for (i = 0, sptr = &addr2->message; i < 2; i++, sptr = &addr2->user_message)
       {
       int message_length;
-      len = read(pfd[pipe_read], (void *)&message_length, sizeof(int));
+      len = read(pfd[pipe_read], &message_length, sizeof(int));
       if (message_length > 0)
         {
-        len = read(pfd[pipe_read], (void *)big_buffer, message_length);
+        len = read(pfd[pipe_read], big_buffer, message_length);
+       big_buffer[big_buffer_size-1] = '\0';           /* guard byte */
         if (len > 0) *sptr = string_copy(big_buffer);
         }
       }
@@ -2126,26 +2471,25 @@ in order to record the delivery. */
 
 if (!shadowing)
   {
-  for (addr2 = addr; addr2 != NULL; addr2 = addr2->next)
-    {
-    if (addr2->transport_return != OK) continue;
-
-    if (testflag(addr2, af_homonym))
-      sprintf(CS big_buffer, "%.500s/%s\n", addr2->unique + 3, tp->name);
-    else
-      sprintf(CS big_buffer, "%.500s\n", addr2->unique);
+  for (addr2 = addr; addr2; addr2 = addr2->next)
+    if (addr2->transport_return == OK)
+      {
+      if (testflag(addr2, af_homonym))
+       sprintf(CS big_buffer, "%.500s/%s\n", addr2->unique + 3, tp->name);
+      else
+       sprintf(CS big_buffer, "%.500s\n", addr2->unique);
 
-    /* In the test harness, wait just a bit to let the subprocess finish off
-    any debug output etc first. */
+      /* In the test harness, wait just a bit to let the subprocess finish off
+      any debug output etc first. */
 
-    if (running_in_test_harness) millisleep(300);
+      if (running_in_test_harness) millisleep(300);
 
-    DEBUG(D_deliver) debug_printf("journalling %s", big_buffer);
-    len = Ustrlen(big_buffer);
-    if (write(journal_fd, big_buffer, len) != len)
-      log_write(0, LOG_MAIN|LOG_PANIC, "failed to update journal for %s: %s",
-        big_buffer, strerror(errno));
-    }
+      DEBUG(D_deliver) debug_printf("journalling %s", big_buffer);
+      len = Ustrlen(big_buffer);
+      if (write(journal_fd, big_buffer, len) != len)
+       log_write(0, LOG_MAIN|LOG_PANIC, "failed to update journal for %s: %s",
+         big_buffer, strerror(errno));
+      }
 
   /* Ensure the journal file is pushed out to disk. */
 
@@ -2163,7 +2507,6 @@ happens, wait() doesn't recognize the termination of child processes. Exim now
 resets SIGCHLD to SIG_DFL, but this code should still be robust. */
 
 while ((rc = wait(&status)) != pid)
-  {
   if (rc < 0 && errno == ECHILD)      /* Process has vanished */
     {
     log_write(0, LOG_MAIN, "%s transport process vanished unexpectedly",
@@ -2171,7 +2514,6 @@ while ((rc = wait(&status)) != pid)
     status = 0;
     break;
     }
-  }
 
 if ((status & 0xffff) != 0)
   {
@@ -2184,43 +2526,39 @@ if ((status & 0xffff) != 0)
     "status 0x%04x: %s %d",
     addr->transport->driver_name,
     status,
-    (msb == 0)? "terminated by signal" : "exit code",
+    msb == 0 ? "terminated by signal" : "exit code",
     code);
   }
 
 /* If SPECIAL_WARN is set in the top address, send a warning message. */
 
-if (addr->special_action == SPECIAL_WARN &&
-    addr->transport->warn_message != NULL)
+if (addr->special_action == SPECIAL_WARN && addr->transport->warn_message)
   {
   int fd;
   uschar *warn_message;
+  pid_t pid;
 
   DEBUG(D_deliver) debug_printf("Warning message requested by transport\n");
 
-  warn_message = expand_string(addr->transport->warn_message);
-  if (warn_message == NULL)
+  if (!(warn_message = expand_string(addr->transport->warn_message)))
     log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand \"%s\" (warning "
       "message for %s transport): %s", addr->transport->warn_message,
       addr->transport->name, expand_string_message);
-  else
+
+  else if ((pid = child_open_exim(&fd)) > 0)
     {
-    pid_t pid = child_open_exim(&fd);
-    if (pid > 0)
-      {
-      FILE *f = fdopen(fd, "wb");
-      if (errors_reply_to != NULL &&
-          !contains_header(US"Reply-To", warn_message))
-        fprintf(f, "Reply-To: %s\n", errors_reply_to);
-      fprintf(f, "Auto-Submitted: auto-replied\n");
-      if (!contains_header(US"From", warn_message)) moan_write_from(f);
-      fprintf(f, "%s", CS warn_message);
+    FILE *f = fdopen(fd, "wb");
+    if (errors_reply_to && !contains_header(US"Reply-To", warn_message))
+      fprintf(f, "Reply-To: %s\n", errors_reply_to);
+    fprintf(f, "Auto-Submitted: auto-replied\n");
+    if (!contains_header(US"From", warn_message))
+      moan_write_from(f);
+    fprintf(f, "%s", CS warn_message);
 
-      /* Close and wait for child process to complete, without a timeout. */
+    /* Close and wait for child process to complete, without a timeout. */
 
-      (void)fclose(f);
-      (void)child_close(pid, 0);
-      }
+    (void)fclose(f);
+    (void)child_close(pid, 0);
     }
 
   addr->special_action = SPECIAL_NONE;
@@ -2229,6 +2567,52 @@ if (addr->special_action == SPECIAL_WARN &&
 
 
 
+
+/* Check transport for the given concurrency limit.  Return TRUE if over
+the limit (or an expansion failure), else FALSE and if there was a limit,
+the key for the hints database used for the concurrency count. */
+
+static BOOL
+tpt_parallel_check(transport_instance * tp, address_item * addr, uschar ** key)
+{
+unsigned max_parallel;
+
+if (!tp->max_parallel) return FALSE;
+
+max_parallel = (unsigned) expand_string_integer(tp->max_parallel, TRUE);
+if (expand_string_message)
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand max_parallel option "
+       "in %s transport (%s): %s", tp->name, addr->address,
+       expand_string_message);
+  return TRUE;
+  }
+
+if (max_parallel > 0)
+  {
+  uschar * serialize_key = string_sprintf("tpt-serialize-%s", tp->name);
+  if (!enq_start(serialize_key, max_parallel))
+    {
+    address_item * next;
+    DEBUG(D_transport)
+      debug_printf("skipping tpt %s because concurrency limit %u reached\n",
+                 tp->name, max_parallel);
+    do
+      {
+      next = addr->next;
+      addr->message = US"concurrency limit reached for transport";
+      addr->basic_errno = ERRNO_TRETRY;
+      post_process_one(addr, DEFER, LOG_MAIN, DTYPE_TRANSPORT, 0);
+      } while ((addr = next));
+    return TRUE;
+    }
+  *key = serialize_key;
+  }
+return FALSE;
+}
+
+
+
 /*************************************************
 *              Do local deliveries               *
 *************************************************/
@@ -2252,7 +2636,7 @@ time_t now = time(NULL);
 
 /* Loop until we have exhausted the supply of local deliveries */
 
-while (addr_local != NULL)
+while (addr_local)
   {
   time_t delivery_start;
   int deliver_time;
@@ -2260,6 +2644,7 @@ while (addr_local != NULL)
   int logflags = LOG_MAIN;
   int logchar = dont_deliver? '*' : '=';
   transport_instance *tp;
+  uschar * serialize_key = NULL;
 
   /* Pick the first undelivered address off the chain */
 
@@ -2272,15 +2657,13 @@ while (addr_local != NULL)
 
   /* An internal disaster if there is no transport. Should not occur! */
 
-  if ((tp = addr->transport) == NULL)
+  if (!(tp = addr->transport))
     {
     logflags |= LOG_PANIC;
     disable_logging = FALSE;  /* Jic */
-    addr->message =
-      (addr->router != NULL)?
-        string_sprintf("No transport set by %s router", addr->router->name)
-        :
-        string_sprintf("No transport set by system filter");
+    addr->message = addr->router
+      ? string_sprintf("No transport set by %s router", addr->router->name)
+      : string_sprintf("No transport set by system filter");
     post_process_one(addr, DEFER, logflags, DTYPE_TRANSPORT, 0);
     continue;
     }
@@ -2301,13 +2684,14 @@ while (addr_local != NULL)
   if either batch_max <= 1 or there aren't any other addresses for local
   delivery. */
 
-  if (tp->batch_max > 1 && addr_local != NULL)
+  if (tp->batch_max > 1 && addr_local)
     {
     int batch_count = 1;
     BOOL uses_dom = readconf_depends((driver_instance *)tp, US"domain");
-    BOOL uses_lp = (testflag(addr, af_pfr) &&
-      (testflag(addr, af_file) || addr->local_part[0] == '|')) ||
-      readconf_depends((driver_instance *)tp, US"local_part");
+    BOOL uses_lp = (  testflag(addr, af_pfr)
+                  && (testflag(addr, af_file) || addr->local_part[0] == '|')
+                  )
+               || readconf_depends((driver_instance *)tp, US"local_part");
     uschar *batch_id = NULL;
     address_item **anchor = &addr_local;
     address_item *last = addr;
@@ -2316,12 +2700,12 @@ while (addr_local != NULL)
     /* Expand the batch_id string for comparison with other addresses.
     Expansion failure suppresses batching. */
 
-    if (tp->batch_id != NULL)
+    if (tp->batch_id)
       {
       deliver_set_expansions(addr);
       batch_id = expand_string(tp->batch_id);
       deliver_set_expansions(NULL);
-      if (batch_id == NULL)
+      if (!batch_id)
         {
         log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand batch_id option "
           "in %s transport (%s): %s", tp->name, addr->address,
@@ -2345,27 +2729,29 @@ while (addr_local != NULL)
       same first host if a host list is set
     */
 
-    while ((next = *anchor) != NULL && batch_count < tp->batch_max)
+    while ((next = *anchor) && batch_count < tp->batch_max)
       {
       BOOL ok =
-        tp == next->transport &&
-        !previously_transported(next, TRUE) &&
-        (addr->flags & (af_pfr|af_file)) == (next->flags & (af_pfr|af_file)) &&
-        (!uses_lp  || Ustrcmp(next->local_part, addr->local_part) == 0) &&
-        (!uses_dom || Ustrcmp(next->domain, addr->domain) == 0) &&
-        same_strings(next->p.errors_address, addr->p.errors_address) &&
-        same_headers(next->p.extra_headers, addr->p.extra_headers) &&
-        same_strings(next->p.remove_headers, addr->p.remove_headers) &&
-        same_ugid(tp, addr, next) &&
-        ((addr->host_list == NULL && next->host_list == NULL) ||
-         (addr->host_list != NULL && next->host_list != NULL &&
-          Ustrcmp(addr->host_list->name, next->host_list->name) == 0));
+           tp == next->transport
+       && !previously_transported(next, TRUE)
+       && (addr->flags & (af_pfr|af_file)) == (next->flags & (af_pfr|af_file))
+       && (!uses_lp  || Ustrcmp(next->local_part, addr->local_part) == 0)
+       && (!uses_dom || Ustrcmp(next->domain, addr->domain) == 0)
+       && same_strings(next->prop.errors_address, addr->prop.errors_address)
+       && same_headers(next->prop.extra_headers, addr->prop.extra_headers)
+       && same_strings(next->prop.remove_headers, addr->prop.remove_headers)
+       && same_ugid(tp, addr, next)
+       && (  !addr->host_list && !next->host_list
+          ||    addr->host_list
+             && next->host_list
+             && Ustrcmp(addr->host_list->name, next->host_list->name) == 0
+          );
 
       /* If the transport has a batch_id setting, batch_id will be non-NULL
       from the expansion outside the loop. Expand for this address and compare.
       Expansion failure makes this address ineligible for batching. */
 
-      if (ok && batch_id != NULL)
+      if (ok && batch_id)
         {
         uschar *bid;
         address_item *save_nextnext = next->next;
@@ -2374,7 +2760,7 @@ while (addr_local != NULL)
         next->next = save_nextnext;
         bid = expand_string(tp->batch_id);
         deliver_set_expansions(NULL);
-        if (bid == NULL)
+        if (!bid)
           {
           log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand batch_id option "
             "in %s transport (%s): %s", tp->name, next->address,
@@ -2394,7 +2780,7 @@ while (addr_local != NULL)
         last = next;
         batch_count++;
         }
-      else anchor = &(next->next);      /* Skip the address */
+      else anchor = &next->next;        /* Skip the address */
       }
     }
 
@@ -2403,13 +2789,13 @@ while (addr_local != NULL)
   fail them all forthwith. If the expansion fails, or does not yield an
   integer, defer delivery. */
 
-  if (tp->message_size_limit != NULL)
+  if (tp->message_size_limit)
     {
     int rc = check_message_size(tp, addr);
     if (rc != OK)
       {
       replicate_status(addr);
-      while (addr != NULL)
+      while (addr)
         {
         addr2 = addr->next;
         post_process_one(addr, rc, logflags, DTYPE_TRANSPORT, 0);
@@ -2427,8 +2813,7 @@ while (addr_local != NULL)
   of these checks, rather than for all local deliveries, because some local
   deliveries (e.g. to pipes) can take a substantial time. */
 
-  dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE);
-  if (dbm_file == NULL)
+  if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE)))
     {
     DEBUG(D_deliver|D_retry|D_hints_lookup)
       debug_printf("no retry data available\n");
@@ -2436,7 +2821,7 @@ while (addr_local != NULL)
 
   addr2 = addr;
   addr3 = NULL;
-  while (addr2 != NULL)
+  while (addr2)
     {
     BOOL ok = TRUE;   /* to deliver this address */
     uschar *retry_key;
@@ -2447,20 +2832,20 @@ while (addr_local != NULL)
     a routing delay. */
 
     retry_key = string_copy(
-      (tp->retry_use_local_part)? addr2->address_retry_key :
+      tp->retry_use_local_part ? addr2->address_retry_key :
         addr2->domain_retry_key);
     *retry_key = 'T';
 
     /* Inspect the retry data. If there is no hints file, delivery happens. */
 
-    if (dbm_file != NULL)
+    if (dbm_file)
       {
       dbdata_retry *retry_record = dbfn_read(dbm_file, retry_key);
 
       /* If there is no retry record, delivery happens. If there is,
       remember it exists so it can be deleted after a successful delivery. */
 
-      if (retry_record != NULL)
+      if (retry_record)
         {
         setflag(addr2, af_lt_retry_exists);
 
@@ -2481,9 +2866,9 @@ while (addr_local != NULL)
 
         if (queue_running && !deliver_force)
           {
-          ok = (now - retry_record->time_stamp > retry_data_expire) ||
-               (now >= retry_record->next_try) ||
-               retry_record->expired;
+          ok = (now - retry_record->time_stamp > retry_data_expire)
+           || (now >= retry_record->next_try)
+           || retry_record->expired;
 
           /* If we haven't reached the retry time, there is one more check
           to do, which is for the ultimate address timeout. */
@@ -2513,18 +2898,37 @@ while (addr_local != NULL)
       address_item *this = addr2;
       this->message = US"Retry time not yet reached";
       this->basic_errno = ERRNO_LRETRY;
-      if (addr3 == NULL) addr2 = addr = addr2->next;
-        else addr2 = addr3->next = addr2->next;
+      addr2 = addr3 ? (addr3->next = addr2->next)
+                   : (addr = addr2->next);
       post_process_one(this, DEFER, logflags, DTYPE_TRANSPORT, 0);
       }
     }
 
-  if (dbm_file != NULL) dbfn_close(dbm_file);
+  if (dbm_file) dbfn_close(dbm_file);
 
   /* If there are no addresses left on the chain, they all deferred. Loop
   for the next set of addresses. */
 
-  if (addr == NULL) continue;
+  if (!addr) continue;
+
+  /* If the transport is limited for parallellism, enforce that here.
+  We use a hints DB entry, incremented here and decremented after
+  the transport (and any shadow transport) completes. */
+
+  if (tpt_parallel_check(tp, addr, &serialize_key))
+    {
+    if (expand_string_message)
+      {
+      logflags |= LOG_PANIC;
+      do
+       {
+       addr = addr->next;
+       post_process_one(addr, DEFER, logflags, DTYPE_TRANSPORT, 0);
+       } while ((addr = addr2));
+      }
+    continue;                  /* Loop for the next set of addresses. */
+    }
+
 
   /* So, finally, we do have some addresses that can be passed to the
   transport. Before doing so, set up variables that are relevant to a
@@ -2545,18 +2949,19 @@ while (addr_local != NULL)
   NOTE: if the condition fails because of a lookup defer, there is nothing we
   can do! */
 
-  if (tp->shadow != NULL &&
-      (tp->shadow_condition == NULL ||
-      expand_check_condition(tp->shadow_condition, tp->name, US"transport")))
+  if (  tp->shadow
+     && (  !tp->shadow_condition
+        || expand_check_condition(tp->shadow_condition, tp->name, US"transport")
+     )  )
     {
     transport_instance *stp;
     address_item *shadow_addr = NULL;
     address_item **last = &shadow_addr;
 
-    for (stp = transports; stp != NULL; stp = stp->next)
+    for (stp = transports; stp; stp = stp->next)
       if (Ustrcmp(stp->name, tp->shadow) == 0) break;
 
-    if (stp == NULL)
+    if (!stp)
       log_write(0, LOG_MAIN|LOG_PANIC, "shadow transport \"%s\" not found ",
         tp->shadow);
 
@@ -2564,25 +2969,25 @@ while (addr_local != NULL)
     the shadow_message field a pointer to the shadow_message field of the real
     address. */
 
-    else for (addr2 = addr; addr2 != NULL; addr2 = addr2->next)
-      {
-      if (addr2->transport_return != OK) continue;
-      addr3 = store_get(sizeof(address_item));
-      *addr3 = *addr2;
-      addr3->next = NULL;
-      addr3->shadow_message = (uschar *)(&(addr2->shadow_message));
-      addr3->transport = stp;
-      addr3->transport_return = DEFER;
-      addr3->return_filename = NULL;
-      addr3->return_file = -1;
-      *last = addr3;
-      last = &(addr3->next);
-      }
+    else for (addr2 = addr; addr2; addr2 = addr2->next)
+      if (addr2->transport_return == OK)
+       {
+       addr3 = store_get(sizeof(address_item));
+       *addr3 = *addr2;
+       addr3->next = NULL;
+       addr3->shadow_message = US &addr2->shadow_message;
+       addr3->transport = stp;
+       addr3->transport_return = DEFER;
+       addr3->return_filename = NULL;
+       addr3->return_file = -1;
+       *last = addr3;
+       last = &addr3->next;
+       }
 
     /* If we found any addresses to shadow, run the delivery, and stick any
     message back into the shadow_message field in the original. */
 
-    if (shadow_addr != NULL)
+    if (shadow_addr)
       {
       int save_count = transport_count;
 
@@ -2590,26 +2995,32 @@ while (addr_local != NULL)
         debug_printf(">>>>>>>>>>>>>>>> Shadow delivery >>>>>>>>>>>>>>>>\n");
       deliver_local(shadow_addr, TRUE);
 
-      for(; shadow_addr != NULL; shadow_addr = shadow_addr->next)
+      for(; shadow_addr; shadow_addr = shadow_addr->next)
         {
         int sresult = shadow_addr->transport_return;
-        *((uschar **)(shadow_addr->shadow_message)) = (sresult == OK)?
-          string_sprintf(" ST=%s", stp->name) :
-          string_sprintf(" ST=%s (%s%s%s)", stp->name,
-            (shadow_addr->basic_errno <= 0)?
-              US"" : US strerror(shadow_addr->basic_errno),
-            (shadow_addr->basic_errno <= 0 || shadow_addr->message == NULL)?
-              US"" : US": ",
-            (shadow_addr->message != NULL)? shadow_addr->message :
-              (shadow_addr->basic_errno <= 0)? US"unknown error" : US"");
+        *(uschar **)shadow_addr->shadow_message =
+         sresult == OK
+         ? string_sprintf(" ST=%s", stp->name)
+         : string_sprintf(" ST=%s (%s%s%s)", stp->name,
+             shadow_addr->basic_errno <= 0
+             ? US""
+             : US strerror(shadow_addr->basic_errno),
+             shadow_addr->basic_errno <= 0 || !shadow_addr->message
+             ? US""
+             : US": ",
+             shadow_addr->message
+             ? shadow_addr->message
+             : shadow_addr->basic_errno <= 0
+             ? US"unknown error"
+             : US"");
 
         DEBUG(D_deliver|D_transport)
           debug_printf("%s shadow transport returned %s for %s\n",
             stp->name,
-            (sresult == OK)?    "OK" :
-            (sresult == DEFER)? "DEFER" :
-            (sresult == FAIL)?  "FAIL" :
-            (sresult == PANIC)? "PANIC" : "?",
+            sresult == OK ?    "OK" :
+            sresult == DEFER ? "DEFER" :
+            sresult == FAIL ?  "FAIL" :
+            sresult == PANIC ? "PANIC" : "?",
             shadow_addr->address);
         }
 
@@ -2624,11 +3035,15 @@ while (addr_local != NULL)
 
   deliver_set_expansions(NULL);
 
+  /* If the transport was parallelism-limited, decrement the hints DB record. */
+
+  if (serialize_key) enq_end(serialize_key);
+
   /* Now we can process the results of the real transport. We must take each
   address off the chain first, because post_process_one() puts it on another
   chain. */
 
-  for (addr2 = addr; addr2 != NULL; addr2 = nextaddr)
+  for (addr2 = addr; addr2; addr2 = nextaddr)
     {
     int result = addr2->transport_return;
     nextaddr = addr2->next;
@@ -2636,10 +3051,10 @@ while (addr_local != NULL)
     DEBUG(D_deliver|D_transport)
       debug_printf("%s transport returned %s for %s\n",
         tp->name,
-        (result == OK)?    "OK" :
-        (result == DEFER)? "DEFER" :
-        (result == FAIL)?  "FAIL" :
-        (result == PANIC)? "PANIC" : "?",
+        result == OK ?    "OK" :
+        result == DEFER ? "DEFER" :
+        result == FAIL ?  "FAIL" :
+        result == PANIC ? "PANIC" : "?",
         addr2->address);
 
     /* If there is a retry_record, or if delivery is deferred, build a retry
@@ -2650,9 +3065,9 @@ while (addr_local != NULL)
 
     if (result == DEFER || testflag(addr2, af_lt_retry_exists))
       {
-      int flags = (result == DEFER)? 0 : rf_delete;
-      uschar *retry_key = string_copy((tp->retry_use_local_part)?
-        addr2->address_retry_key : addr2->domain_retry_key);
+      int flags = result == DEFER ? 0 : rf_delete;
+      uschar *retry_key = string_copy(tp->retry_use_local_part
+       ? addr2->address_retry_key : addr2->domain_retry_key);
       *retry_key = 'T';
       retry_add_item(addr2, retry_key, flags);
       }
@@ -2668,7 +3083,7 @@ while (addr_local != NULL)
 
     if (addr2->transport_return != result)
       {
-      for (addr3 = nextaddr; addr3 != NULL; addr3 = addr3->next)
+      for (addr3 = nextaddr; addr3; addr3 = addr3->next)
         {
         addr3->transport_return = addr2->transport_return;
         addr3->basic_errno = addr2->basic_errno;
@@ -2711,40 +3126,41 @@ sort_remote_deliveries(void)
 {
 int sep = 0;
 address_item **aptr = &addr_remote;
-uschar *listptr = remote_sort_domains;
+const uschar *listptr = remote_sort_domains;
 uschar *pattern;
 uschar patbuf[256];
 
-while (*aptr != NULL &&
-       (pattern = string_nextinlist(&listptr, &sep, patbuf, sizeof(patbuf)))
-       != NULL)
+while (  *aptr
+      && (pattern = string_nextinlist(&listptr, &sep, patbuf, sizeof(patbuf)))
+      )
   {
   address_item *moved = NULL;
   address_item **bptr = &moved;
 
-  while (*aptr != NULL)
+  while (*aptr)
     {
     address_item **next;
     deliver_domain = (*aptr)->domain;   /* set $domain */
-    if (match_isinlist(deliver_domain, &pattern, UCHAR_MAX+1,
+    if (match_isinlist(deliver_domain, (const uschar **)&pattern, UCHAR_MAX+1,
           &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK)
       {
-      aptr = &((*aptr)->next);
+      aptr = &(*aptr)->next;
       continue;
       }
 
-    next = &((*aptr)->next);
-    while (*next != NULL &&
-           (deliver_domain = (*next)->domain,  /* Set $domain */
-            match_isinlist(deliver_domain, &pattern, UCHAR_MAX+1,
-              &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL)) != OK)
-      next = &((*next)->next);
+    next = &(*aptr)->next;
+    while (  *next
+         && (deliver_domain = (*next)->domain,  /* Set $domain */
+            match_isinlist(deliver_domain, (const uschar **)&pattern, UCHAR_MAX+1,
+              &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL)) != OK
+         )
+      next = &(*next)->next;
 
     /* If the batch of non-matchers is at the end, add on any that were
     extracted further up the chain, and end this iteration. Otherwise,
     extract them from the chain and hang on the moved chain. */
 
-    if (*next == NULL)
+    if (!*next)
       {
       *next = moved;
       break;
@@ -2754,7 +3170,7 @@ while (*aptr != NULL &&
     *aptr = *next;
     *next = NULL;
     bptr = next;
-    aptr = &((*aptr)->next);
+    aptr = &(*aptr)->next;
     }
 
   /* If the loop ended because the final address matched, *aptr will
@@ -2763,14 +3179,14 @@ while (*aptr != NULL &&
   is, there was a string of non-matching addresses at the end. In this
   case the extracted addresses have already been added on the end. */
 
-  if (*aptr == NULL) *aptr = moved;
+  if (!*aptr) *aptr = moved;
   }
 
 DEBUG(D_deliver)
   {
   address_item *addr;
   debug_printf("remote addresses after sorting:\n");
-  for (addr = addr_remote; addr != NULL; addr = addr->next)
+  for (addr = addr_remote; addr; addr = addr->next)
     debug_printf("  %s\n", addr->address);
   }
 }
@@ -2823,6 +3239,8 @@ uschar *ptr = endptr;
 uschar *msg = p->msg;
 BOOL done = p->done;
 BOOL unfinished = TRUE;
+/* minimum size to read is header size including id, subid and length */
+int required = PIPE_HEADER_SIZE;
 
 /* Loop through all items, reading from the pipe when necessary. The pipe
 is set up to be non-blocking, but there are two different Unix mechanisms in
@@ -2845,12 +3263,15 @@ while (!done)
   {
   retry_item *r, **rp;
   int remaining = endptr - ptr;
+  uschar header[PIPE_HEADER_SIZE + 1];
+  uschar id, subid;
+  uschar *endc;
 
   /* Read (first time) or top up the chars in the buffer if necessary.
   There will be only one read if we get all the available data (i.e. don't
   fill the buffer completely). */
 
-  if (remaining < 2500 && unfinished)
+  if (remaining < required && unfinished)
     {
     int len;
     int available = big_buffer_size - remaining;
@@ -2883,25 +3304,71 @@ while (!done)
     won't read any more, as "unfinished" will get set FALSE. */
 
     endptr += len;
+    remaining += len;
     unfinished = len == available;
     }
 
   /* If we are at the end of the available data, exit the loop. */
-
   if (ptr >= endptr) break;
 
+  /* copy and read header */
+  memcpy(header, ptr, PIPE_HEADER_SIZE);
+  header[PIPE_HEADER_SIZE] = '\0';
+  id = header[0];
+  subid = header[1];
+  required = Ustrtol(header + 2, &endc, 10) + PIPE_HEADER_SIZE;     /* header + data */
+  if (*endc)
+    {
+    msg = string_sprintf("failed to read pipe from transport process "
+      "%d for transport %s: error reading size from header", pid, addr->transport->driver_name);
+    done = TRUE;
+    break;
+    }
+
+  DEBUG(D_deliver)
+    debug_printf("header read  id:%c,subid:%c,size:%s,required:%d,remaining:%d,unfinished:%d\n",
+                    id, subid, header+2, required, remaining, unfinished);
+
+  /* is there room for the dataset we want to read ? */
+  if (required > big_buffer_size - PIPE_HEADER_SIZE)
+    {
+    msg = string_sprintf("failed to read pipe from transport process "
+      "%d for transport %s: big_buffer too small! required size=%d buffer size=%d", pid, addr->transport->driver_name,
+      required, big_buffer_size - PIPE_HEADER_SIZE);
+    done = TRUE;
+    break;
+    }
+
+  /* we wrote all datasets with atomic write() calls
+     remaining < required only happens if big_buffer was too small
+     to get all available data from pipe. unfinished has to be true
+     as well. */
+  if (remaining < required)
+    {
+    if (unfinished)
+      continue;
+    msg = string_sprintf("failed to read pipe from transport process "
+      "%d for transport %s: required size=%d > remaining size=%d and unfinished=false",
+      pid, addr->transport->driver_name, required, remaining);
+    done = TRUE;
+    break;
+    }
+
+  /* step behind the header */
+  ptr += PIPE_HEADER_SIZE;
+
   /* Handle each possible type of item, assuming the complete item is
   available in store. */
 
-  switch (*ptr++)
+  switch (id)
     {
     /* Host items exist only if any hosts were marked unusable. Match
     up by checking the IP address. */
 
     case 'H':
-    for (h = addrlist->host_list; h != NULL; h = h->next)
+    for (h = addrlist->host_list; h; h = h->next)
       {
-      if (h->address == NULL || Ustrcmp(h->address, ptr+2) != 0) continue;
+      if (!h->address || Ustrcmp(h->address, ptr+2) != 0) continue;
       h->status = ptr[0];
       h->why = ptr[1];
       }
@@ -2921,7 +3388,7 @@ while (!done)
     that a "delete" item is dropped in favour of an "add" item. */
 
     case 'R':
-    if (addr == NULL) goto ADDR_MISMATCH;
+    if (!addr) goto ADDR_MISMATCH;
 
     DEBUG(D_deliver|D_retry)
       debug_printf("reading retry information for %s from subprocess\n",
@@ -2929,8 +3396,7 @@ while (!done)
 
     /* Cut out any "delete" items on the list. */
 
-    for (rp = &(addr->retries); (r = *rp) != NULL; rp = &(r->next))
-      {
+    for (rp = &(addr->retries); (r = *rp); rp = &r->next)
       if (Ustrcmp(r->key, ptr+1) == 0)           /* Found item with same key */
         {
         if ((r->flags & rf_delete) == 0) break;  /* It was not "delete" */
@@ -2938,12 +3404,11 @@ while (!done)
         DEBUG(D_deliver|D_retry)
           debug_printf("  existing delete item dropped\n");
         }
-      }
 
     /* We want to add a delete item only if there is no non-delete item;
     however we still have to step ptr through the data. */
 
-    if (r == NULL || (*ptr & rf_delete) == 0)
+    if (!r || (*ptr & rf_delete) == 0)
       {
       r = store_get(sizeof(retry_item));
       r->next = addr->retries;
@@ -2987,10 +3452,10 @@ while (!done)
     it in with the other info, in order to keep each message short enough to
     guarantee it won't be split in the pipe. */
 
-    #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
     case 'X':
-    if (addr == NULL) goto ADDR_MISMATCH;          /* Below, in 'A' handler */
-    switch (*ptr++)
+    if (!addr) goto ADDR_MISMATCH;          /* Below, in 'A' handler */
+    switch (subid)
       {
       case '1':
       addr->cipher = NULL;
@@ -3004,31 +3469,33 @@ while (!done)
       break;
 
       case '2':
-      addr->peercert = NULL;
       if (*ptr)
        (void) tls_import_cert(ptr, &addr->peercert);
+      else
+       addr->peercert = NULL;
       break;
 
       case '3':
-      addr->ourcert = NULL;
       if (*ptr)
        (void) tls_import_cert(ptr, &addr->ourcert);
+      else
+       addr->ourcert = NULL;
       break;
 
-      #ifndef DISABLE_OCSP
+ifndef DISABLE_OCSP
       case '4':
       addr->ocsp = OCSP_NOT_REQ;
       if (*ptr)
        addr->ocsp = *ptr - '0';
       break;
-      #endif
+endif
       }
     while (*ptr++);
     break;
-    #endif     /*SUPPORT_TLS*/
+#endif /*SUPPORT_TLS*/
 
     case 'C':  /* client authenticator information */
-    switch (*ptr++)
+    switch (subid)
       {
       case '1':
        addr->authenticator = (*ptr)? string_copy(ptr) : NULL;
@@ -3049,17 +3516,19 @@ while (!done)
     break;
 #endif
 
-    #ifdef EXPERIMENTAL_DSN
+    case 'K':
+    addr->flags |= af_chunking_used;
+    break;
+
     case 'D':
-    if (addr == NULL) break;
+    if (!addr) goto ADDR_MISMATCH;
     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)
+    if (!addr)
       {
       ADDR_MISMATCH:
       msg = string_sprintf("address count mismatch for data read from pipe "
@@ -3069,41 +3538,79 @@ while (!done)
       break;
       }
 
-    addr->transport_return = *ptr++;
-    addr->special_action = *ptr++;
-    memcpy(&(addr->basic_errno), ptr, sizeof(addr->basic_errno));
-    ptr += sizeof(addr->basic_errno);
-    memcpy(&(addr->more_errno), ptr, sizeof(addr->more_errno));
-    ptr += sizeof(addr->more_errno);
-    memcpy(&(addr->flags), ptr, sizeof(addr->flags));
-    ptr += sizeof(addr->flags);
-    addr->message = (*ptr)? string_copy(ptr) : NULL;
-    while(*ptr++);
-    addr->user_message = (*ptr)? string_copy(ptr) : NULL;
-    while(*ptr++);
+    switch (subid)
+      {
+#ifdef SUPPORT_SOCKS
+      case '2':        /* proxy information; must arrive before A0 and applies to that addr XXX oops*/
+       proxy_session = TRUE;   /*XXX should this be cleared somewhere? */
+       if (*ptr == 0)
+         ptr++;
+       else
+         {
+         proxy_local_address = string_copy(ptr);
+         while(*ptr++);
+         memcpy(&proxy_local_port, ptr, sizeof(proxy_local_port));
+         ptr += sizeof(proxy_local_port);
+         }
+       break;
+#endif
 
-    /* Always two strings for host information, followed by the port number and DNSSEC mark */
+#ifdef EXPERIMENTAL_DSN_INFO
+      case '1':        /* must arrive before A0, and applies to that addr */
+               /* Two strings: smtp_greeting and helo_response */
+       addr->smtp_greeting = string_copy(ptr);
+       while(*ptr++);
+       addr->helo_response = string_copy(ptr);
+       while(*ptr++);
+       break;
+#endif
 
-    if (*ptr != 0)
-      {
-      h = store_get(sizeof(host_item));
-      h->name = string_copy(ptr);
-      while (*ptr++);
-      h->address = string_copy(ptr);
-      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++;
+      case '0':
+       addr->transport_return = *ptr++;
+       addr->special_action = *ptr++;
+       memcpy(&(addr->basic_errno), ptr, sizeof(addr->basic_errno));
+       ptr += sizeof(addr->basic_errno);
+       memcpy(&(addr->more_errno), ptr, sizeof(addr->more_errno));
+       ptr += sizeof(addr->more_errno);
+       memcpy(&(addr->flags), ptr, sizeof(addr->flags));
+       ptr += sizeof(addr->flags);
+       addr->message = (*ptr)? string_copy(ptr) : NULL;
+       while(*ptr++);
+       addr->user_message = (*ptr)? string_copy(ptr) : NULL;
+       while(*ptr++);
 
-    /* Finished with this address */
+       /* Always two strings for host information, followed by the port number and DNSSEC mark */
+
+       if (*ptr != 0)
+         {
+         h = store_get(sizeof(host_item));
+         h->name = string_copy(ptr);
+         while (*ptr++);
+         h->address = string_copy(ptr);
+         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++;
+
+       /* Finished with this address */
+
+       addr = addr->next;
+       break;
+      }
+    break;
 
-    addr = addr->next;
+    /* Local interface address/port */
+    case 'I':
+    if (*ptr) sending_ip_address = string_copy(ptr);
+    while (*ptr++) ;
+    if (*ptr) sending_port = atoi(CS ptr);
+    while (*ptr++) ;
     break;
 
     /* Z marks the logical end of the data. It is followed by '0' if
@@ -3119,7 +3626,7 @@ while (!done)
       continue_hostname = NULL;
       }
     done = TRUE;
-    DEBUG(D_deliver) debug_printf("Z%c item read\n", *ptr);
+    DEBUG(D_deliver) debug_printf("Z0%c item read\n", *ptr);
     break;
 
     /* Anything else is a disaster. */
@@ -3158,7 +3665,7 @@ p->fd = -1;
 /* If we have finished without error, but haven't had data for every address,
 something is wrong. */
 
-if (msg == NULL && addr != NULL)
+if (!msg && addr)
   msg = string_sprintf("insufficient address data read from pipe "
     "for transport process %d for transport %s", pid,
       addr->transport->driver_name);
@@ -3166,15 +3673,13 @@ if (msg == NULL && addr != NULL)
 /* If an error message is set, something has gone wrong in getting back
 the delivery data. Put the message into each address and freeze it. */
 
-if (msg != NULL)
-  {
-  for (addr = addrlist; addr != NULL; addr = addr->next)
+if (msg)
+  for (addr = addrlist; addr; addr = addr->next)
     {
     addr->transport_return = DEFER;
     addr->special_action = SPECIAL_FREEZE;
     addr->message = msg;
     }
-  }
 
 /* Return TRUE to indicate we have got all we need from this process, even
 if it hasn't actually finished yet. */
@@ -3213,16 +3718,14 @@ host_item *h;
 /* If any host addresses were found to be unusable, add them to the unusable
 tree so that subsequent deliveries don't try them. */
 
-for (h = addr->host_list; h != NULL; h = h->next)
-  {
-  if (h->address == NULL) continue;
-  if (h->status >= hstatus_unusable) tree_add_unusable(h);
-  }
+for (h = addr->host_list; h; h = h->next)
+  if (h->address)
+    if (h->status >= hstatus_unusable) tree_add_unusable(h);
 
 /* Now handle each address on the chain. The transport has placed '=' or '-'
 into the special_action field for each successful delivery. */
 
-while (addr != NULL)
+while (addr)
   {
   address_item *next = addr->next;
 
@@ -3230,10 +3733,11 @@ while (addr != NULL)
   processing the main hosts and there are fallback hosts available, put the
   address on the list for fallback delivery. */
 
-  if (addr->transport_return == DEFER &&
-      addr->fallback_hosts != NULL &&
-      !fallback &&
-      msg == NULL)
+  if (  addr->transport_return == DEFER
+     && addr->fallback_hosts
+     && !fallback
+     && !msg
+     )
     {
     addr->host_list = addr->fallback_hosts;
     addr->next = addr_fallback;
@@ -3246,7 +3750,7 @@ while (addr != NULL)
 
   else
     {
-    if (msg != NULL)
+    if (msg)
       {
       addr->message = msg;
       addr->transport_return = DEFER;
@@ -3265,7 +3769,7 @@ the last address, the channel will have been closed down. Now that
 we have logged that delivery, set continue_sequence to 1 so that
 any subsequent deliveries don't get "*" incorrectly logged. */
 
-if (continue_transport == NULL) continue_sequence = 1;
+if (!continue_transport) continue_sequence = 1;
 }
 
 
@@ -3428,8 +3932,9 @@ for (;;)   /* Normally we do not repeat this loop */
          readycount > 0 && poffset < remote_max_parallel;
          poffset++)
       {
-      if ((pid = parlist[poffset].pid) != 0 &&
-           FD_ISSET(parlist[poffset].fd, &select_pipes))
+      if (  (pid = parlist[poffset].pid) != 0
+         && FD_ISSET(parlist[poffset].fd, &select_pipes)
+        )
         {
         readycount--;
         if (par_read_pipe(poffset, FALSE))    /* Finished with this pipe */
@@ -3509,7 +4014,7 @@ if ((status & 0xffff) != 0)
   if (msb != 0 || (code != SIGTERM && code != SIGKILL && code != SIGQUIT))
     addrlist->special_action = SPECIAL_FREEZE;
 
-  for (addr = addrlist; addr != NULL; addr = addr->next)
+  for (addr = addrlist; addr; addr = addr->next)
     {
     addr->transport_return = DEFER;
     addr->message = msg;
@@ -3558,13 +4063,20 @@ par_reduce(int max, BOOL fallback)
 while (parcount > max)
   {
   address_item *doneaddr = par_wait();
-  if (doneaddr == NULL)
+  if (!doneaddr)
     {
     log_write(0, LOG_MAIN|LOG_PANIC,
       "remote delivery process count got out of step");
     parcount = 0;
     }
-  else remote_post_process(doneaddr, LOG_MAIN, NULL, fallback);
+  else
+    {
+    transport_instance * tp = doneaddr->transport;
+    if (tp->max_parallel)
+      enq_end(string_sprintf("tpt-serialize-%s", tp->name));
+
+    remote_post_process(doneaddr, LOG_MAIN, NULL, fallback);
+    }
   }
 }
 
@@ -3572,10 +4084,41 @@ while (parcount > max)
 
 
 static void
-rmt_dlv_checked_write(int fd, void * buf, int size)
+rmt_dlv_checked_write(int fd, char id, char subid, void * buf, int size)
 {
-int ret = write(fd, buf, size);
-if(ret != size)
+uschar writebuffer[PIPE_HEADER_SIZE + BIG_BUFFER_SIZE];
+int header_length;
+int ret;
+
+/* we assume that size can't get larger then BIG_BUFFER_SIZE which currently is set to 16k */
+/* complain to log if someone tries with buffer sizes we can't handle*/
+
+if (size > 99999)
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+    "Failed writing transport result to pipe: can't handle buffers > 99999 bytes. truncating!\n");
+  size = 99999;
+  }
+
+/* to keep the write() atomic we build header in writebuffer and copy buf behind */
+/* two write() calls would increase the complexity of reading from pipe */
+
+/* convert size to human readable string prepended by id and subid */
+header_length = snprintf(CS writebuffer, PIPE_HEADER_SIZE+1, "%c%c%05d", id, subid, size);
+if (header_length != PIPE_HEADER_SIZE)
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "header snprintf failed\n");
+  writebuffer[0] = '\0';
+  }
+
+DEBUG(D_deliver) debug_printf("header write id:%c,subid:%c,size:%d,final:%s\n",
+                                 id, subid, size, writebuffer);
+
+if (buf && size > 0)
+  memcpy(writebuffer + PIPE_HEADER_SIZE, buf, size);
+
+size += PIPE_HEADER_SIZE;
+if ((ret = write(fd, writebuffer, size)) != size)
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Failed writing transport result to pipe: %s\n",
     ret == -1 ? strerror(errno) : "short write");
 }
@@ -3624,13 +4167,13 @@ parcount = 0;    /* Number of executing subprocesses */
 We use a local variable (parmax) to hold the maximum number of processes;
 this gets reduced from remote_max_parallel if we can't create enough pipes. */
 
-if (continue_transport != NULL) remote_max_parallel = 1;
+if (continue_transport) remote_max_parallel = 1;
 parmax = remote_max_parallel;
 
 /* If the data for keeping a list of processes hasn't yet been
 set up, do so. */
 
-if (parlist == NULL)
+if (!parlist)
   {
   parlist = store_get(remote_max_parallel * sizeof(pardata));
   for (poffset = 0; poffset < remote_max_parallel; poffset++)
@@ -3639,7 +4182,7 @@ if (parlist == NULL)
 
 /* Now loop for each remote delivery */
 
-for (delivery_count = 0; addr_remote != NULL; delivery_count++)
+for (delivery_count = 0; addr_remote; delivery_count++)
   {
   pid_t pid;
   uid_t uid;
@@ -3655,6 +4198,8 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
   address_item *addr = addr_remote;
   address_item *last = addr;
   address_item *next;
+  uschar * panicmsg;
+  uschar * serialize_key = NULL;
 
   /* Pull the first address right off the list. */
 
@@ -3666,12 +4211,11 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
 
   /* If no transport has been set, there has been a big screw-up somewhere. */
 
-  if ((tp = addr->transport) == NULL)
+  if (!(tp = addr->transport))
     {
     disable_logging = FALSE;  /* Jic */
-    remote_post_process(addr, LOG_MAIN|LOG_PANIC,
-      US"No transport set by router", fallback);
-    continue;
+    panicmsg = US"No transport set by router";
+    goto panic_continue;
     }
 
   /* Check that this base address hasn't previously been delivered to this
@@ -3684,7 +4228,7 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
 
   /* Force failure if the message is too big. */
 
-  if (tp->message_size_limit != NULL)
+  if (tp->message_size_limit)
     {
     int rc = check_message_size(tp, addr);
     if (rc != OK)
@@ -3696,9 +4240,20 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     }
 
   /* Get the flag which specifies whether the transport can handle different
-  domains that nevertheless resolve to the same set of hosts. */
+  domains that nevertheless resolve to the same set of hosts. If it needs
+  expanding, get variables set: $address_data, $domain_data, $localpart_data,
+  $host, $host_address, $host_port. */
+  if (tp->expand_multi_domain)
+    deliver_set_expansions(addr);
 
-  multi_domain = tp->multi_domain;
+  if (exp_bool(addr, US"transport", tp->name, D_transport,
+               US"multi_domain", tp->multi_domain, tp->expand_multi_domain,
+               &multi_domain) != OK)
+    {
+    deliver_set_expansions(NULL);
+    panicmsg = addr->message;
+    goto panic_continue;
+    }
 
   /* Get the maximum it can handle in one envelope, with zero meaning
   unlimited, which is forced for the MUA wrapper case. */
@@ -3746,8 +4301,9 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
   the use of these variables, but as it is so likely they will be used when the
   maximum is 1, we don't bother. Just leave the value alone. */
 
-  if (address_count_max != 1 &&
-      address_count_max < remote_delivery_count/remote_max_parallel)
+  if (  address_count_max != 1
+     && address_count_max < remote_delivery_count/remote_max_parallel
+     )
     {
     int new_max = remote_delivery_count/remote_max_parallel;
     int message_max = tp->connection_max_messages;
@@ -3767,26 +4323,35 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
   entirely different domains. The host list pointers can be NULL in the case
   where the hosts are defined in the transport. There is also a configured
   maximum limit of addresses that can be handled at once (see comments above
-  for how it is computed). */
-
-  while ((next = *anchor) != NULL && address_count < address_count_max)
-    {
-    if ((multi_domain || Ustrcmp(next->domain, addr->domain) == 0)
-        &&
-        tp == next->transport
-        &&
-        same_hosts(next->host_list, addr->host_list)
-        &&
-        same_strings(next->p.errors_address, addr->p.errors_address)
-        &&
-        same_headers(next->p.extra_headers, addr->p.extra_headers)
-        &&
-        same_ugid(tp, next, addr)
-        &&
-        (next->p.remove_headers == addr->p.remove_headers ||
-          (next->p.remove_headers != NULL &&
-           addr->p.remove_headers != NULL &&
-           Ustrcmp(next->p.remove_headers, addr->p.remove_headers) == 0)))
+  for how it is computed).
+  If the transport does not handle multiple domains, enforce that also,
+  and if it might need a per-address check for this, re-evaluate it.
+  */
+
+  while ((next = *anchor) && address_count < address_count_max)
+    {
+    BOOL md;
+    if (  (multi_domain || Ustrcmp(next->domain, addr->domain) == 0)
+       && tp == next->transport
+       && same_hosts(next->host_list, addr->host_list)
+       && same_strings(next->prop.errors_address, addr->prop.errors_address)
+       && same_headers(next->prop.extra_headers, addr->prop.extra_headers)
+       && same_ugid(tp, next, addr)
+       && (  next->prop.remove_headers == addr->prop.remove_headers
+         || (  next->prop.remove_headers
+            && addr->prop.remove_headers
+            && Ustrcmp(next->prop.remove_headers, addr->prop.remove_headers) == 0
+         )  )
+       && (  !multi_domain
+         || (  (
+               (void)(!tp->expand_multi_domain || ((void)deliver_set_expansions(next), 1)),
+               exp_bool(addr,
+                   US"transport", next->transport->name, D_transport,
+                   US"multi_domain", next->transport->multi_domain,
+                   next->transport->expand_multi_domain, &md) == OK
+               )
+            && md
+       )  )  )
       {
       *anchor = next->next;
       next->next = NULL;
@@ -3796,18 +4361,29 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
       address_count++;
       }
     else anchor = &(next->next);
+    deliver_set_expansions(NULL);
     }
 
   /* If we are acting as an MUA wrapper, all addresses must go in a single
   transaction. If not, put them back on the chain and yield FALSE. */
 
-  if (mua_wrapper && addr_remote != NULL)
+  if (mua_wrapper && addr_remote)
     {
     last->next = addr_remote;
     addr_remote = addr;
     return FALSE;
     }
 
+  /* If the transport is limited for parallellism, enforce that here.
+  The hints DB entry is decremented in par_reduce(), when we reap the
+  transport process. */
+
+  if (tpt_parallel_check(tp, addr, &serialize_key))
+    if ((panicmsg = expand_string_message))
+      goto panic_continue;
+    else
+      continue;                        /* Loop for the next set of addresses. */
+
   /* Set up the expansion variables for this set of addresses */
 
   deliver_set_expansions(addr);
@@ -3818,29 +4394,26 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
   /* Compute the return path, expanding a new one if required. The old one
   must be set first, as it might be referred to in the expansion. */
 
-  if(addr->p.errors_address != NULL)
-    return_path = addr->p.errors_address;
+  if(addr->prop.errors_address)
+    return_path = addr->prop.errors_address;
 #ifdef EXPERIMENTAL_SRS
-  else if(addr->p.srs_sender != NULL)
-    return_path = addr->p.srs_sender;
+  else if(addr->prop.srs_sender)
+    return_path = addr->prop.srs_sender;
 #endif
   else
     return_path = sender_address;
 
-  if (tp->return_path != NULL)
+  if (tp->return_path)
     {
     uschar *new_return_path = expand_string(tp->return_path);
-    if (new_return_path == NULL)
+    if (new_return_path)
+      return_path = new_return_path;
+    else if (!expand_string_forcedfail)
       {
-      if (!expand_string_forcedfail)
-        {
-        remote_post_process(addr, LOG_MAIN|LOG_PANIC,
-          string_sprintf("Failed to expand return path \"%s\": %s",
-          tp->return_path, expand_string_message), fallback);
-        continue;
-        }
+      panicmsg = string_sprintf("Failed to expand return path \"%s\": %s",
+       tp->return_path, expand_string_message);
+      goto enq_continue;
       }
-    else return_path = new_return_path;
     }
 
   /* Find the uid, gid, and use_initgroups setting for this transport. Failure
@@ -3849,8 +4422,8 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
 
   if (!findugid(addr, tp, &uid, &gid, &use_initgroups))
     {
-    remote_post_process(addr, LOG_MAIN|LOG_PANIC, NULL, fallback);
-    continue;
+    panicmsg = NULL;
+    goto enq_continue;
     }
 
   /* If this transport has a setup function, call it now so that it gets
@@ -3860,7 +4433,7 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
   That is why it is called at this point, before the continue delivery
   processing, because that might use the fallback hosts. */
 
-  if (tp->setup != NULL)
+  if (tp->setup)
     (void)((tp->setup)(addr->transport, addr, NULL, uid, gid, NULL));
 
   /* If this is a run to continue delivery down an already-established
@@ -3870,18 +4443,17 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
   host is set in the transport. */
 
   continue_more = FALSE;           /* In case got set for the last lot */
-  if (continue_transport != NULL)
+  if (continue_transport)
     {
     BOOL ok = Ustrcmp(continue_transport, tp->name) == 0;
-    if (ok && addr->host_list != NULL)
+    if (ok && addr->host_list)
       {
       host_item *h;
       ok = FALSE;
-      for (h = addr->host_list; h != NULL; h = h->next)
-        {
+      for (h = addr->host_list; h; h = h->next)
         if (Ustrcmp(h->name, continue_hostname) == 0)
+/*XXX should also check port here */
           { ok = TRUE; break; }
-        }
       }
 
     /* Addresses not suitable; defer or queue for fallback hosts (which
@@ -3890,27 +4462,30 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     if (!ok)
       {
       DEBUG(D_deliver) debug_printf("not suitable for continue_transport\n");
-      next = addr;
+      if (serialize_key) enq_end(serialize_key);
 
-      if (addr->fallback_hosts != NULL && !fallback)
+      if (addr->fallback_hosts && !fallback)
         {
-        for (;;)
+       for (next = addr; ; next = next->next)
           {
           next->host_list = next->fallback_hosts;
           DEBUG(D_deliver) debug_printf("%s queued for fallback host(s)\n", next->address);
-          if (next->next == NULL) break;
-          next = next->next;
+          if (!next->next) break;
           }
         next->next = addr_fallback;
         addr_fallback = addr;
         }
 
       else
-        {
-        while (next->next != NULL) next = next->next;
-        next->next = addr_defer;
-        addr_defer = addr;
-        }
+       {
+       for (next = addr; ; next = next->next)
+         {
+         DEBUG(D_deliver) debug_printf(" %s to def list\n", next->address);
+          if (!next->next) break;
+         }
+       next->next = addr_defer;
+       addr_defer = addr;
+       }
 
       continue;
       }
@@ -3919,14 +4494,12 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     the continued host. This tells the transport to leave the channel open,
     but not to pass it to another delivery process. */
 
-    for (next = addr_remote; next != NULL; next = next->next)
+    for (next = addr_remote; next; next = next->next)
       {
       host_item *h;
-      for (h = next->host_list; h != NULL; h = h->next)
-        {
+      for (h = next->host_list; h; h = h->next)
         if (Ustrcmp(h->name, continue_hostname) == 0)
           { continue_more = TRUE; break; }
-        }
       }
     }
 
@@ -3954,11 +4527,11 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     that it can use either of them, though it prefers O_NONBLOCK, which
     distinguishes between EOF and no-more-data. */
 
-    #ifdef O_NONBLOCK
+#ifdef O_NONBLOCK
     (void)fcntl(pfd[pipe_read], F_SETFL, O_NONBLOCK);
-    #else
+#else
     (void)fcntl(pfd[pipe_read], F_SETFL, O_NDELAY);
-    #endif
+#endif
 
     /* If the maximum number of subprocesses already exist, wait for a process
     to finish. If we ran out of file descriptors, parmax will have been reduced
@@ -3973,9 +4546,8 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
 
   if (!pipe_done)
     {
-    remote_post_process(addr, LOG_MAIN|LOG_PANIC,
-      string_sprintf("unable to create pipe: %s", strerror(errno)), fallback);
-    continue;
+    panicmsg = string_sprintf("unable to create pipe: %s", strerror(errno));
+    goto enq_continue;
     }
 
   /* Find a free slot in the pardata list. Must do this after the possible
@@ -3983,7 +4555,8 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
   up a slot. */
 
   for (poffset = 0; poffset < remote_max_parallel; poffset++)
-    if (parlist[poffset].pid == 0) break;
+    if (parlist[poffset].pid == 0)
+      break;
 
   /* If there isn't one, there has been a horrible disaster. */
 
@@ -3991,13 +4564,12 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     {
     (void)close(pfd[pipe_write]);
     (void)close(pfd[pipe_read]);
-    remote_post_process(addr, LOG_MAIN|LOG_PANIC,
-      US"Unexpectedly no free subprocess slot", fallback);
-    continue;
+    panicmsg = US"Unexpectedly no free subprocess slot";
+    goto enq_continue;
     }
 
   /* Now fork a subprocess to do the remote delivery, but before doing so,
-  ensure that any cached resourses are released so as not to interfere with
+  ensure that any cached resources are released so as not to interfere with
   what happens in the subprocess. */
 
   search_tidyup();
@@ -4015,7 +4587,7 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
 
     /* Show pids on debug output if parallelism possible */
 
-    if (parmax > 1 && (parcount > 0 || addr_remote != NULL))
+    if (parmax > 1 && (parcount > 0 || addr_remote))
       {
       DEBUG(D_any|D_v) debug_selector |= D_pid;
       DEBUG(D_deliver) debug_printf("Remote delivery process started\n");
@@ -4049,18 +4621,23 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     a dup-with-new-file-pointer. */
 
     (void)close(deliver_datafile);
-    sprintf(CS spoolname, "%s/input/%s/%s-D", spool_directory, message_subdir,
-      message_id);
-    deliver_datafile = Uopen(spoolname, O_RDWR | O_APPEND, 0);
+    {
+    uschar * fname = spool_fname(US"input", message_subdir, message_id, US"-D");
 
-    if (deliver_datafile < 0)
+    if ((deliver_datafile = Uopen(fname,
+#ifdef O_CLOEXEC
+                                       O_CLOEXEC |
+#endif
+                                       O_RDWR | O_APPEND, 0)) < 0)
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Failed to reopen %s for remote "
-        "parallel delivery: %s", spoolname, strerror(errno));
+        "parallel delivery: %s", fname, strerror(errno));
+    }
 
     /* Set the close-on-exec flag */
-
+#ifndef O_CLOEXEC
     (void)fcntl(deliver_datafile, F_SETFD, fcntl(deliver_datafile, F_GETFD) |
       FD_CLOEXEC);
+#endif
 
     /* Set the uid/gid of this process; bombs out on failure. */
 
@@ -4078,7 +4655,7 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     if (!(tp->info->code)(addr->transport, addr)) replicate_status(addr);
 
     set_process_info("delivering %s (just run %s for %s%s in subprocess)",
-      message_id, tp->name, addr->address, (addr->next == NULL)? "" : ", ...");
+      message_id, tp->name, addr->address, addr->next ? ", ..." : "");
 
     /* Ensure any cached resources that we used are now released */
 
@@ -4097,11 +4674,11 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     /* Host unusability information: for most success cases this will
     be null. */
 
-    for (h = addr->host_list; h != NULL; h = h->next)
+    for (h = addr->host_list; h; h = h->next)
       {
-      if (h->address == NULL || h->status < hstatus_unusable) continue;
-      sprintf(CS big_buffer, "H%c%c%s", h->status, h->why, h->address);
-      rmt_dlv_checked_write(fd, big_buffer, Ustrlen(big_buffer+3) + 4);
+      if (!h->address || h->status < hstatus_unusable) continue;
+      sprintf(CS big_buffer, "%c%c%s", h->status, h->why, h->address);
+      rmt_dlv_checked_write(fd, 'H', '0', big_buffer, Ustrlen(big_buffer+2) + 3);
       }
 
     /* The number of bytes written. This is the same for each address. Even
@@ -4109,129 +4686,152 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     size of each one is the same, and it's that value we have got because
     transport_count gets reset before calling transport_write_message(). */
 
-    big_buffer[0] = 'S';
-    memcpy(big_buffer+1, &transport_count, sizeof(transport_count));
-    rmt_dlv_checked_write(fd, big_buffer, sizeof(transport_count) + 1);
+    memcpy(big_buffer, &transport_count, sizeof(transport_count));
+    rmt_dlv_checked_write(fd, 'S', '0', big_buffer, sizeof(transport_count));
 
     /* Information about what happened to each address. Four item types are
     used: an optional 'X' item first, for TLS information, then an optional "C"
     item for any client-auth info followed by 'R' items for any retry settings,
     and finally an 'A' item for the remaining data. */
 
-    for(; addr != NULL; addr = addr->next)
+    for(; addr; addr = addr->next)
       {
       uschar *ptr;
       retry_item *r;
 
       /* The certificate verification status goes into the flags */
       if (tls_out.certificate_verified) setflag(addr, af_cert_verified);
+#ifdef EXPERIMENTAL_DANE
+      if (tls_out.dane_verified)        setflag(addr, af_dane_verified);
+#endif
 
       /* Use an X item only if there's something to send */
-      #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
       if (addr->cipher)
         {
-        ptr = big_buffer;
-        sprintf(CS ptr, "X1%.128s", addr->cipher);
-        while(*ptr++);
+        ptr = big_buffer + sprintf(CS big_buffer, "%.128s", addr->cipher) + 1;
         if (!addr->peerdn)
          *ptr++ = 0;
        else
           {
-          sprintf(CS ptr, "%.512s", addr->peerdn);
-          while(*ptr++);
+          ptr += sprintf(CS ptr, "%.512s", addr->peerdn);
+          ptr++;
           }
 
-        rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
+        rmt_dlv_checked_write(fd, 'X', '1', big_buffer, ptr - big_buffer);
         }
       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);
+        rmt_dlv_checked_write(fd, 'X', '2', 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);
+        rmt_dlv_checked_write(fd, 'X', '3', big_buffer, ptr - big_buffer);
        }
-      #ifndef DISABLE_OCSP
+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);
+       ptr = big_buffer + sprintf(CS big_buffer, "%c", addr->ocsp + '0') + 1;
+        rmt_dlv_checked_write(fd, 'X', '4', big_buffer, ptr - big_buffer);
        }
-      # endif
-      #endif   /*SUPPORT_TLS*/
+# endif
+#endif /*SUPPORT_TLS*/
 
       if (client_authenticator)
         {
-        ptr = big_buffer;
-       sprintf(CS big_buffer, "C1%.64s", client_authenticator);
-        while(*ptr++);
-        rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
+       ptr = big_buffer + sprintf(CS big_buffer, "%.64s", client_authenticator) + 1;
+        rmt_dlv_checked_write(fd, 'C', '1', big_buffer, ptr - big_buffer);
        }
       if (client_authenticated_id)
         {
-        ptr = big_buffer;
-       sprintf(CS big_buffer, "C2%.64s", client_authenticated_id);
-        while(*ptr++);
-        rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
+        ptr = big_buffer + sprintf(CS big_buffer, "%.64s", client_authenticated_id) + 1;
+        rmt_dlv_checked_write(fd, 'C', '2', big_buffer, ptr - big_buffer);
        }
       if (client_authenticated_sender)
         {
-        ptr = big_buffer;
-       sprintf(CS big_buffer, "C3%.64s", client_authenticated_sender);
-        while(*ptr++);
-        rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
+        ptr = big_buffer + sprintf(CS big_buffer, "%.64s", client_authenticated_sender) + 1;
+        rmt_dlv_checked_write(fd, 'C', '3', big_buffer, ptr - big_buffer);
        }
 
-      #ifndef DISABLE_PRDR
+#ifndef DISABLE_PRDR
       if (addr->flags & af_prdr_used)
-       rmt_dlv_checked_write(fd, "P", 1);
-      #endif
+       rmt_dlv_checked_write(fd, 'P', '0', NULL, 0);
+#endif
+
+      if (addr->flags & af_chunking_used)
+       rmt_dlv_checked_write(fd, 'K', '0', NULL, 0);
 
-      #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);
+      memcpy(big_buffer, &addr->dsn_aware, sizeof(addr->dsn_aware));
+      rmt_dlv_checked_write(fd, 'D', '0', big_buffer, sizeof(addr->dsn_aware));
       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. */
 
-      for (r = addr->retries; r != NULL; r = r->next)
+      for (r = addr->retries; r; r = r->next)
         {
-        uschar *ptr;
-        sprintf(CS big_buffer, "R%c%.500s", r->flags, r->key);
+        sprintf(CS big_buffer, "%c%.500s", r->flags, r->key);
         ptr = big_buffer + Ustrlen(big_buffer+2) + 3;
         memcpy(ptr, &(r->basic_errno), sizeof(r->basic_errno));
         ptr += sizeof(r->basic_errno);
         memcpy(ptr, &(r->more_errno), sizeof(r->more_errno));
         ptr += sizeof(r->more_errno);
-        if (r->message == NULL) *ptr++ = 0; else
+        if (!r->message) *ptr++ = 0; else
           {
           sprintf(CS ptr, "%.512s", r->message);
           while(*ptr++);
           }
-        rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
+        rmt_dlv_checked_write(fd, 'R', '0', big_buffer, ptr - big_buffer);
         }
 
-      /* The rest of the information goes in an 'A' item. */
+#ifdef SUPPORT_SOCKS
+      if (LOGGING(proxy) && proxy_session)
+       {
+       ptr = big_buffer;
+       if (proxy_local_address)
+         {
+         DEBUG(D_deliver) debug_printf("proxy_local_address '%s'\n", proxy_local_address);
+         ptr = big_buffer + sprintf(CS ptr, "%.128s", proxy_local_address) + 1;
+         DEBUG(D_deliver) debug_printf("proxy_local_port %d\n", proxy_local_port);
+         memcpy(ptr, &proxy_local_port, sizeof(proxy_local_port));
+         ptr += sizeof(proxy_local_port);
+         }
+       else
+         *ptr++ = '\0';
+       rmt_dlv_checked_write(fd, 'A', '2', big_buffer, ptr - big_buffer);
+       }
+#endif
+
+#ifdef EXPERIMENTAL_DSN_INFO
+/*um, are they really per-addr?  Other per-conn stuff is not (auth, tls).  But host_used is! */
+      if (addr->smtp_greeting)
+       {
+       DEBUG(D_deliver) debug_printf("smtp_greeting '%s'\n", addr->smtp_greeting);
+       ptr = big_buffer + sprintf(CS big_buffer, "%.128s", addr->smtp_greeting) + 1;
+       if (addr->helo_response)
+         {
+         DEBUG(D_deliver) debug_printf("helo_response '%s'\n", addr->helo_response);
+         ptr += sprintf(CS ptr, "%.128s", addr->helo_response) + 1;
+         }
+       else
+         *ptr++ = '\0';
+        rmt_dlv_checked_write(fd, 'A', '1', big_buffer, ptr - big_buffer);
+       }
+#endif
+
+      /* The rest of the information goes in an 'A0' item. */
 
-      ptr = big_buffer + 3;
-      sprintf(CS big_buffer, "A%c%c", addr->transport_return,
-        addr->special_action);
+      sprintf(CS big_buffer, "%c%c", addr->transport_return, addr->special_action);
+      ptr = big_buffer + 2;
       memcpy(ptr, &(addr->basic_errno), sizeof(addr->basic_errno));
       ptr += sizeof(addr->basic_errno);
       memcpy(ptr, &(addr->more_errno), sizeof(addr->more_errno));
@@ -4239,24 +4839,16 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
       memcpy(ptr, &(addr->flags), sizeof(addr->flags));
       ptr += sizeof(addr->flags);
 
-      if (addr->message == NULL) *ptr++ = 0; else
-        {
-        sprintf(CS ptr, "%.1024s", addr->message);
-        while(*ptr++);
-        }
+      if (!addr->message) *ptr++ = 0; else
+        ptr += sprintf(CS ptr, "%.1024s", addr->message) + 1;
 
-      if (addr->user_message == NULL) *ptr++ = 0; else
-        {
-        sprintf(CS ptr, "%.1024s", addr->user_message);
-        while(*ptr++);
-        }
+      if (!addr->user_message) *ptr++ = 0; else
+        ptr += sprintf(CS ptr, "%.1024s", addr->user_message) + 1;
 
-      if (addr->host_used == NULL) *ptr++ = 0; else
+      if (!addr->host_used) *ptr++ = 0; else
         {
-        sprintf(CS ptr, "%.256s", addr->host_used->name);
-        while(*ptr++);
-        sprintf(CS ptr, "%.64s", addr->host_used->address);
-        while(*ptr++);
+        ptr += sprintf(CS ptr, "%.256s", addr->host_used->name) + 1;
+        ptr += sprintf(CS ptr, "%.64s", addr->host_used->address) + 1;
         memcpy(ptr, &(addr->host_used->port), sizeof(addr->host_used->port));
         ptr += sizeof(addr->host_used->port);
 
@@ -4265,7 +4857,20 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
               : addr->host_used->dnssec==DS_NO ? '1' : '0';
 
         }
-      rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
+      rmt_dlv_checked_write(fd, 'A', '0', big_buffer, ptr - big_buffer);
+      }
+
+    /* Local interface address/port */
+#ifdef EXPERIMENTAL_DSN_INFO
+    if (sending_ip_address)
+#else
+    if (LOGGING(incoming_interface) && sending_ip_address)
+#endif
+      {
+      uschar * ptr;
+      ptr = big_buffer + sprintf(CS big_buffer, "%.128s", sending_ip_address) + 1;
+      ptr += sprintf(CS ptr, "%d", sending_port) + 1;
+      rmt_dlv_checked_write(fd, 'I', '0', big_buffer, ptr - big_buffer);
       }
 
     /* Add termination flag, close the pipe, and that's it. The character
@@ -4273,9 +4878,8 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     A change from non-NULL to NULL indicates a problem with a continuing
     connection. */
 
-    big_buffer[0] = 'Z';
-    big_buffer[1] = (continue_transport == NULL)? '0' : '1';
-    rmt_dlv_checked_write(fd, big_buffer, 2);
+    big_buffer[0] = continue_transport ? '1' : '0';
+    rmt_dlv_checked_write(fd, 'Z', '0', big_buffer, 1);
     (void)close(fd);
     exit(EXIT_SUCCESS);
     }
@@ -4289,10 +4893,9 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
   if (pid < 0)
     {
     (void)close(pfd[pipe_read]);
-    remote_post_process(addr, LOG_MAIN|LOG_PANIC,
-      string_sprintf("fork failed for remote delivery to %s: %s",
-        addr->domain, strerror(errno)), fallback);
-    continue;
+    panicmsg = string_sprintf("fork failed for remote delivery to %s: %s",
+        addr->domain, strerror(errno));
+    goto enq_continue;
     }
 
   /* Fork succeeded; increment the count, and remember relevant data for
@@ -4317,13 +4920,21 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
   (continue_transport gets set to NULL) before we consider any other addresses
   in this message. */
 
-  if (continue_transport != NULL) par_reduce(0, fallback);
+  if (continue_transport) par_reduce(0, fallback);
 
   /* Otherwise, if we are running in the test harness, wait a bit, to let the
   newly created process get going before we create another process. This should
   ensure repeatability in the tests. We only need to wait a tad. */
 
   else if (running_in_test_harness) millisleep(500);
+
+  continue;
+
+enq_continue:
+  if (serialize_key) enq_end(serialize_key);
+panic_continue:
+  remote_post_process(addr, LOG_MAIN|LOG_PANIC, panicmsg, fallback);
+  continue;
   }
 
 /* Reached the end of the list of addresses. Wait for all the subprocesses that
@@ -4355,13 +4966,17 @@ Returns:    OK
 */
 
 int
-deliver_split_address(address_item *addr)
+deliver_split_address(address_item * addr)
 {
-uschar *address = addr->address;
-uschar *domain = Ustrrchr(address, '@');
-uschar *t;
-int len = domain - address;
+uschar * address = addr->address;
+uschar * domain;
+uschar * t;
+int len;
+
+if (!(domain = Ustrrchr(address, '@')))
+  return DEFER;                /* should always have a domain, but just in case... */
 
+len = domain - address;
 addr->domain = string_copylc(domain+1);    /* Domains are always caseless */
 
 /* The implication in the RFCs (though I can't say I've seen it spelled out
@@ -4373,7 +4988,7 @@ removing quoting backslashes and any unquoted doublequotes. */
 t = addr->cc_local_part = store_get(len+1);
 while(len-- > 0)
   {
-  register int c = *address++;
+  int c = *address++;
   if (c == '\"') continue;
   if (c == '\\')
     {
@@ -4387,7 +5002,7 @@ while(len-- > 0)
 /* We do the percent hack only for those domains that are listed in
 percent_hack_domains. A loop is required, to copy with multiple %-hacks. */
 
-if (percent_hack_domains != NULL)
+if (percent_hack_domains)
   {
   int rc;
   uschar *new_address = NULL;
@@ -4395,10 +5010,11 @@ if (percent_hack_domains != NULL)
 
   deliver_domain = addr->domain;  /* set $domain */
 
-  while ((rc = match_isinlist(deliver_domain, &percent_hack_domains, 0,
-           &domainlist_anchor, addr->domain_cache, MCL_DOMAIN, TRUE, NULL))
-             == OK &&
-         (t = Ustrrchr(local_part, '%')) != NULL)
+  while (  (rc = match_isinlist(deliver_domain, (const uschar **)&percent_hack_domains, 0,
+              &domainlist_anchor, addr->domain_cache, MCL_DOMAIN, TRUE, NULL))
+             == OK
+       && (t = Ustrrchr(local_part, '%')) != NULL
+       )
     {
     new_address = string_copy(local_part);
     new_address[t - local_part] = '@';
@@ -4410,11 +5026,12 @@ if (percent_hack_domains != NULL)
 
   /* If hackery happened, set up new parent and alter the current address. */
 
-  if (new_address != NULL)
+  if (new_address)
     {
     address_item *new_parent = store_get(sizeof(address_item));
     *new_parent = *addr;
     addr->parent = new_parent;
+    new_parent->child_count = 1;
     addr->address = new_address;
     addr->unique = string_copy(new_address);
     addr->domain = deliver_domain;
@@ -4456,22 +5073,22 @@ int ptr = 0;
 uschar *para, *yield;
 uschar buffer[256];
 
-if (f == NULL) return NULL;
+if (!f) return NULL;
 
-if (Ufgets(buffer, sizeof(buffer), f) == NULL ||
-    Ustrcmp(buffer, "****\n") == 0) return NULL;
+if (!Ufgets(buffer, sizeof(buffer), f) || Ustrcmp(buffer, "****\n") == 0)
+  return NULL;
 
 para = store_get(size);
 for (;;)
   {
-  para = string_cat(para, &size, &ptr, buffer, Ustrlen(buffer));
-  if (Ufgets(buffer, sizeof(buffer), f) == NULL ||
-      Ustrcmp(buffer, "****\n") == 0) break;
+  para = string_cat(para, &size, &ptr, buffer);
+  if (!Ufgets(buffer, sizeof(buffer), f) || Ustrcmp(buffer, "****\n") == 0)
+    break;
   }
 para[ptr] = 0;
 
-yield = expand_string(para);
-if (yield != NULL) return yield;
+if ((yield = expand_string(para)))
+  return yield;
 
 log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand string from "
   "bounce_message_file or warn_message_file (%s): %s", which,
@@ -4497,17 +5114,15 @@ Returns:    DELIVER_NOT_ATTEMPTED
 static int
 continue_closedown(void)
 {
-if (continue_transport != NULL)
+if (continue_transport)
   {
   transport_instance *t;
-  for (t = transports; t != NULL; t = t->next)
-    {
+  for (t = transports; t; t = t->next)
     if (Ustrcmp(t->name, continue_transport) == 0)
       {
-      if (t->info->closedown != NULL) (t->info->closedown)(t);
+      if (t->info->closedown) (t->info->closedown)(t);
       break;
       }
-    }
   }
 return DELIVER_NOT_ATTEMPTED;
 }
@@ -4540,16 +5155,16 @@ print_address_information(address_item *addr, FILE *f, uschar *si, uschar *sc,
 BOOL yield = TRUE;
 uschar *printed = US"";
 address_item *ancestor = addr;
-while (ancestor->parent != NULL) ancestor = ancestor->parent;
+while (ancestor->parent) ancestor = ancestor->parent;
 
 fprintf(f, "%s", CS si);
 
-if (addr->parent != NULL && testflag(addr, af_hide_child))
+if (addr->parent && testflag(addr, af_hide_child))
   {
   printed = US"an undisclosed address";
   yield = FALSE;
   }
-else if (!testflag(addr, af_pfr) || addr->parent == NULL)
+else if (!testflag(addr, af_pfr) || !addr->parent)
   printed = addr->address;
 
 else
@@ -4569,14 +5184,18 @@ fprintf(f, "%s", CS string_printing(printed));
 
 if (ancestor != addr)
   {
-  uschar *original = (ancestor->onetime_parent == NULL)?
-    ancestor->address : ancestor->onetime_parent;
+  uschar *original = ancestor->onetime_parent;
+  if (!original) original= ancestor->address;
   if (strcmpic(original, printed) != 0)
     fprintf(f, "%s(%sgenerated from %s)", sc,
-      (ancestor != addr->parent)? "ultimately " : "",
+      ancestor != addr->parent ? "ultimately " : "",
       string_printing(original));
   }
 
+if (addr->host_used)
+  fprintf(f, "\n    host %s [%s]",
+         addr->host_used->name, addr->host_used->address);
+
 fprintf(f, "%s", CS se);
 return yield;
 }
@@ -4614,15 +5233,12 @@ print_address_error(address_item *addr, FILE *f, uschar *t)
 int count = Ustrlen(t);
 uschar *s = testflag(addr, af_pass_message)? addr->message : NULL;
 
-if (s == NULL)
-  {
-  if (addr->user_message != NULL) s = addr->user_message; else return;
-  }
+if (!s && !(s = addr->user_message))
+  return;
 
 fprintf(f, "\n    %s", t);
 
-while (*s != 0)
-  {
+while (*s)
   if (*s == '\\' && s[1] == 'n')
     {
     fprintf(f, "\n    ");
@@ -4639,12 +5255,59 @@ while (*s != 0)
       count = 0;
       }
     }
-  }
 }
 
 
+/***********************************************************
+*         Print Diagnostic-Code for an address             *
+************************************************************/
+
+/* This function is called to print the error information out of an address for
+a bounce or a warning message. It tries to format the message reasonably as
+required by RFC 3461 by adding a space after each newline
+
+it uses the same logic as print_address_error() above. if af_pass_message is true
+and addr->message is set it uses the remote host answer. if not addr->user_message
+is used instead if available.
+
+Arguments:
+  addr         the address
+  f            the FILE to print on
+
+Returns:       nothing
+*/
+
+static void
+print_dsn_diagnostic_code(const address_item *addr, FILE *f)
+{
+uschar *s = testflag(addr, af_pass_message) ? addr->message : NULL;
+
+/* af_pass_message and addr->message set ? print remote host answer */
+if (s)
+  {
+  DEBUG(D_deliver)
+    debug_printf("DSN Diagnostic-Code: addr->message = %s\n", addr->message);
+
+  /* search first ": ". we assume to find the remote-MTA answer there */
+  if (!(s = Ustrstr(addr->message, ": ")))
+    return;                            /* not found, bail out */
+  s += 2;  /* skip ": " */
+  fprintf(f, "Diagnostic-Code: smtp; ");
+  }
+/* no message available. do nothing */
+else return;
 
+while (*s)
+  if (*s == '\\' && s[1] == 'n')
+    {
+    fputs("\n ", f);    /* as defined in RFC 3461 */
+    s += 2;
+    }
+  else
+    fputc(*s++, f);
 
+fputc('\n', f);
+}
 
 
 /*************************************************
@@ -4670,14 +5333,14 @@ static void
 do_duplicate_check(address_item **anchor)
 {
 address_item *addr;
-while ((addr = *anchor) != NULL)
+while ((addr = *anchor))
   {
   tree_node *tnode;
   if (testflag(addr, af_pfr))
     {
     anchor = &(addr->next);
     }
-  else if ((tnode = tree_search(tree_duplicates, addr->unique)) != NULL)
+  else if ((tnode = tree_search(tree_duplicates, addr->unique)))
     {
     DEBUG(D_deliver|D_route)
       debug_printf("%s is a duplicate address: discarded\n", addr->unique);
@@ -4718,6 +5381,8 @@ A delivery operation has a process all to itself; we never deliver more than
 one message in the same process. Therefore we needn't worry too much about
 store leakage.
 
+Liable to be called as root.
+
 Arguments:
   id          the id of the message to be delivered
   forced      TRUE if delivery was forced by an administrator; this overrides
@@ -4742,15 +5407,14 @@ int final_yield = DELIVER_ATTEMPTED_NORMAL;
 time_t now = time(NULL);
 address_item *addr_last = NULL;
 uschar *filter_message = NULL;
-FILE *jread;
 int process_recipients = RECIP_ACCEPT;
 open_db dbblock;
 open_db *dbm_file;
 extern int acl_where;
 
-uschar *info = (queue_run_pid == (pid_t)0)?
-  string_sprintf("delivering %s", id) :
-  string_sprintf("delivering %s (queue run pid %d)", id, queue_run_pid);
+uschar *info = queue_run_pid == (pid_t)0
+  ? string_sprintf("delivering %s", id)
+  string_sprintf("delivering %s (queue run pid %d)", id, queue_run_pid);
 
 /* If the D_process_info bit is on, set_process_info() will output debugging
 information. If not, we want to show this initial information if D_deliver or
@@ -4758,8 +5422,9 @@ D_queue_run is set or in verbose mode. */
 
 set_process_info("%s", info);
 
-if ((debug_selector & D_process_info) == 0 &&
-    (debug_selector & (D_deliver|D_queue_run|D_v)) != 0)
+if (  !(debug_selector & D_process_info)
+   && (debug_selector & (D_deliver|D_queue_run|D_v))
+   )
   debug_printf("%s\n", info);
 
 /* Ensure that we catch any subprocesses that are created. Although Exim
@@ -4812,7 +5477,7 @@ Any failures cause messages to be written to the log, except for missing files
 while queue running - another process probably completed delivery. As part of
 opening the data file, message_subdir gets set. */
 
-if (!spool_open_datafile(id))
+if ((deliver_datafile = spool_open_datafile(id)) < 0)
   return continue_closedown();  /* yields DELIVER_NOT_ATTEMPTED */
 
 /* The value of message_size at this point has been set to the data length,
@@ -4823,53 +5488,51 @@ store, and also the list of recipients and the tree of non-recipients and
 assorted flags. It updates message_size. If there is a reading or format error,
 give up; if the message has been around for sufficiently long, remove it. */
 
-sprintf(CS spoolname, "%s-H", id);
-if ((rc = spool_read_header(spoolname, TRUE, TRUE)) != spool_read_OK)
   {
-  if (errno == ERRNO_SPOOLFORMAT)
+  uschar * spoolname = string_sprintf("%s-H", id);
+  if ((rc = spool_read_header(spoolname, TRUE, TRUE)) != spool_read_OK)
     {
-    struct stat statbuf;
-    sprintf(CS big_buffer, "%s/input/%s/%s", spool_directory, message_subdir,
-      spoolname);
-    if (Ustat(big_buffer, &statbuf) == 0)
-      log_write(0, LOG_MAIN, "Format error in spool file %s: "
-        "size=" OFF_T_FMT, spoolname, statbuf.st_size);
-    else log_write(0, LOG_MAIN, "Format error in spool file %s", spoolname);
-    }
-  else
-    log_write(0, LOG_MAIN, "Error reading spool file %s: %s", spoolname,
-      strerror(errno));
+    if (errno == ERRNO_SPOOLFORMAT)
+      {
+      struct stat statbuf;
+      if (Ustat(spool_fname(US"input", message_subdir, spoolname, US""),
+               &statbuf) == 0)
+       log_write(0, LOG_MAIN, "Format error in spool file %s: "
+         "size=" OFF_T_FMT, spoolname, statbuf.st_size);
+      else
+       log_write(0, LOG_MAIN, "Format error in spool file %s", spoolname);
+      }
+    else
+      log_write(0, LOG_MAIN, "Error reading spool file %s: %s", spoolname,
+       strerror(errno));
 
-  /* If we managed to read the envelope data, received_time contains the
-  time the message was received. Otherwise, we can calculate it from the
-  message id. */
+    /* If we managed to read the envelope data, received_time contains the
+    time the message was received. Otherwise, we can calculate it from the
+    message id. */
 
-  if (rc != spool_read_hdrerror)
-    {
-    received_time = 0;
-    for (i = 0; i < 6; i++)
-      received_time = received_time * BASE_62 + tab62[id[i] - '0'];
-    }
+    if (rc != spool_read_hdrerror)
+      {
+      received_time = 0;
+      for (i = 0; i < 6; i++)
+       received_time = received_time * BASE_62 + tab62[id[i] - '0'];
+      }
 
-  /* If we've had this malformed message too long, sling it. */
+    /* If we've had this malformed message too long, sling it. */
 
-  if (now - received_time > keep_malformed)
-    {
-    sprintf(CS spoolname, "%s/msglog/%s/%s", spool_directory, message_subdir, id);
-    Uunlink(spoolname);
-    sprintf(CS spoolname, "%s/input/%s/%s-D", spool_directory, message_subdir, id);
-    Uunlink(spoolname);
-    sprintf(CS spoolname, "%s/input/%s/%s-H", spool_directory, message_subdir, id);
-    Uunlink(spoolname);
-    sprintf(CS spoolname, "%s/input/%s/%s-J", spool_directory, message_subdir, id);
-    Uunlink(spoolname);
-    log_write(0, LOG_MAIN, "Message removed because older than %s",
-      readconf_printtime(keep_malformed));
-    }
+    if (now - received_time > keep_malformed)
+      {
+      Uunlink(spool_fname(US"msglog", message_subdir, id, US""));
+      Uunlink(spool_fname(US"input", message_subdir, id, US"-D"));
+      Uunlink(spool_fname(US"input", message_subdir, id, US"-H"));
+      Uunlink(spool_fname(US"input", message_subdir, id, US"-J"));
+      log_write(0, LOG_MAIN, "Message removed because older than %s",
+       readconf_printtime(keep_malformed));
+      }
 
-  (void)close(deliver_datafile);
-  deliver_datafile = -1;
-  return continue_closedown();   /* yields DELIVER_NOT_ATTEMPTED */
+    (void)close(deliver_datafile);
+    deliver_datafile = -1;
+    return continue_closedown();   /* yields DELIVER_NOT_ATTEMPTED */
+    }
   }
 
 /* The spool header file has been read. Look to see if there is an existing
@@ -4881,37 +5544,55 @@ existence, as it will get further successful deliveries added to it in this
 run, and it will be deleted if this function gets to its end successfully.
 Otherwise it might be needed again. */
 
-sprintf(CS spoolname, "%s/input/%s/%s-J", spool_directory, message_subdir, id);
-jread = Ufopen(spoolname, "rb");
-if (jread != NULL)
-  {
-  while (Ufgets(big_buffer, big_buffer_size, jread) != NULL)
+  {
+  uschar * fname = spool_fname(US"input", message_subdir, id, US"-J");
+  FILE * jread;
+
+  if (  (journal_fd = Uopen(fname, O_RDWR|O_APPEND
+#ifdef O_CLOEXEC
+                                   | O_CLOEXEC
+#endif
+#ifdef O_NOFOLLOW
+                                   | O_NOFOLLOW
+#endif
+       , SPOOL_MODE)) >= 0
+     && lseek(journal_fd, 0, SEEK_SET) == 0
+     && (jread = fdopen(journal_fd, "rb"))
+     )
+    {
+    while (Ufgets(big_buffer, big_buffer_size, jread))
+      {
+      int n = Ustrlen(big_buffer);
+      big_buffer[n-1] = 0;
+      tree_add_nonrecipient(big_buffer);
+      DEBUG(D_deliver) debug_printf("Previously delivered address %s taken from "
+       "journal file\n", big_buffer);
+      }
+    rewind(jread);
+    if ((journal_fd = dup(fileno(jread))) < 0)
+      journal_fd = fileno(jread);
+    else
+      (void) fclose(jread);    /* Try to not leak the FILE resource */
+
+    /* Panic-dies on error */
+    (void)spool_write_header(message_id, SW_DELIVERING, NULL);
+    }
+  else if (errno != ENOENT)
     {
-    int n = Ustrlen(big_buffer);
-    big_buffer[n-1] = 0;
-    tree_add_nonrecipient(big_buffer);
-    DEBUG(D_deliver) debug_printf("Previously delivered address %s taken from "
-      "journal file\n", big_buffer);
+    log_write(0, LOG_MAIN|LOG_PANIC, "attempt to open journal for reading gave: "
+      "%s", strerror(errno));
+    return continue_closedown();   /* yields DELIVER_NOT_ATTEMPTED */
     }
-  (void)fclose(jread);
-  /* Panic-dies on error */
-  (void)spool_write_header(message_id, SW_DELIVERING, NULL);
-  }
-else if (errno != ENOENT)
-  {
-  log_write(0, LOG_MAIN|LOG_PANIC, "attempt to open journal for reading gave: "
-    "%s", strerror(errno));
-  return continue_closedown();   /* yields DELIVER_NOT_ATTEMPTED */
-  }
 
-/* A null recipients list indicates some kind of disaster. */
+  /* A null recipients list indicates some kind of disaster. */
 
-if (recipients_list == NULL)
-  {
-  (void)close(deliver_datafile);
-  deliver_datafile = -1;
-  log_write(0, LOG_MAIN, "Spool error: no recipients for %s", spoolname);
-  return continue_closedown();   /* yields DELIVER_NOT_ATTEMPTED */
+  if (!recipients_list)
+    {
+    (void)close(deliver_datafile);
+    deliver_datafile = -1;
+    log_write(0, LOG_MAIN, "Spool error: no recipients for %s", fname);
+    return continue_closedown();   /* yields DELIVER_NOT_ATTEMPTED */
+    }
   }
 
 
@@ -4921,15 +5602,16 @@ attempted. */
 
 if (deliver_freeze)
   {
-  #ifdef SUPPORT_MOVE_FROZEN_MESSAGES
+#ifdef SUPPORT_MOVE_FROZEN_MESSAGES
   /* Moving to another directory removes the message from Exim's view. Other
   tools must be used to deal with it. Logging of this action happens in
   spool_move_message() and its subfunctions. */
 
-  if (move_frozen_messages &&
-      spool_move_message(id, message_subdir, US"", US"F"))
+  if (  move_frozen_messages
+     && spool_move_message(id, message_subdir, US"", US"F")
+     )
     return continue_closedown();   /* yields DELIVER_NOT_ATTEMPTED */
-  #endif
+#endif
 
   /* For all frozen messages (bounces or not), timeout_frozen_after sets the
   maximum time to keep messages that are frozen. Thaw if we reach it, with a
@@ -4946,10 +5628,8 @@ if (deliver_freeze)
   ignore timer is exceeded. The message will be discarded if this delivery
   fails. */
 
-  else if (sender_address[0] == 0 && message_age >= ignore_bounce_errors_after)
-    {
+  else if (!*sender_address && message_age >= ignore_bounce_errors_after)
     log_write(0, LOG_MAIN, "Unfrozen by errmsg timer");
-    }
 
   /* If this is a bounce message, or there's no auto thaw, or we haven't
   reached the auto thaw time yet, and this delivery is not forced by an admin
@@ -4959,14 +5639,13 @@ if (deliver_freeze)
 
   else
     {
-    if ((sender_address[0] == 0 ||
-         auto_thaw <= 0 ||
-         now <= deliver_frozen_at + auto_thaw
-        )
-        &&
-        (!forced || !deliver_force_thaw || !admin_user ||
-          continue_hostname != NULL
-        ))
+    if (  (  sender_address[0] == 0
+         || auto_thaw <= 0
+         || now <= deliver_frozen_at + auto_thaw
+          )
+       && (  !forced || !deliver_force_thaw
+         || !admin_user || continue_hostname
+       )  )
       {
       (void)close(deliver_datafile);
       deliver_datafile = -1;
@@ -4999,26 +5678,23 @@ done by rewriting the header spool file. */
 
 if (message_logs)
   {
-  uschar *error;
+  uschar * fname = spool_fname(US"msglog", message_subdir, id, US"");
+  uschar * error;
   int fd;
 
-  sprintf(CS spoolname, "%s/msglog/%s/%s", spool_directory, message_subdir, id);
-  fd = open_msglog_file(spoolname, SPOOL_MODE, &error);
-
-  if (fd < 0)
+  if ((fd = open_msglog_file(fname, SPOOL_MODE, &error)) < 0)
     {
     log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't %s message log %s: %s", error,
-      spoolname, strerror(errno));
+      fname, strerror(errno));
     return continue_closedown();   /* yields DELIVER_NOT_ATTEMPTED */
     }
 
   /* Make a C stream out of it. */
 
-  message_log = fdopen(fd, "a");
-  if (message_log == NULL)
+  if (!(message_log = fdopen(fd, "a")))
     {
     log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't fdopen message log %s: %s",
-      spoolname, strerror(errno));
+      fname, strerror(errno));
     return continue_closedown();   /* yields DELIVER_NOT_ATTEMPTED */
     }
   }
@@ -5030,8 +5706,8 @@ the addresses. */
 if (give_up)
   {
   struct passwd *pw = getpwuid(real_uid);
-  log_write(0, LOG_MAIN, "cancelled by %s", (pw != NULL)?
-        US pw->pw_name : string_sprintf("uid %ld", (long int)real_uid));
+  log_write(0, LOG_MAIN, "cancelled by %s",
+      pw ? US pw->pw_name : string_sprintf("uid %ld", (long int)real_uid));
   process_recipients = RECIP_FAIL;
   }
 
@@ -5046,7 +5722,7 @@ a result of timeout_frozen_after. If the system filter yields "delivered", then
 ignore the true recipients of the message. Failure of the filter file is
 logged, and the delivery attempt fails. */
 
-else if (system_filter != NULL && process_recipients != RECIP_FAIL_TIMEOUT)
+else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT)
   {
   int rc;
   int filtertype;
@@ -5116,7 +5792,7 @@ else if (system_filter != NULL && process_recipients != RECIP_FAIL_TIMEOUT)
 
   system_filtering = FALSE;
   enable_dollar_recipients = FALSE;
-  if (filter_message != NULL && filter_message[0] == 0) filter_message = NULL;
+  if (filter_message && filter_message[0] == 0) filter_message = NULL;
 
   /* Save the values of the system filter variables so that user filters
   can use them. */
@@ -5144,8 +5820,8 @@ else if (system_filter != NULL && process_recipients != RECIP_FAIL_TIMEOUT)
     deliver_frozen_at = time(NULL);
     process_recipients = RECIP_DEFER;
     frozen_info = string_sprintf(" by the system filter%s%s",
-      (filter_message == NULL)? US"" : US": ",
-      (filter_message == NULL)? US"" : filter_message);
+      filter_message ? US": " : US"",
+      filter_message ? filter_message : US"");
     }
 
   /* The filter can request that a message be failed. The error message may be
@@ -5162,12 +5838,14 @@ else if (system_filter != NULL && process_recipients != RECIP_FAIL_TIMEOUT)
 
     process_recipients = RECIP_FAIL_FILTER;
 
-    if (filter_message != NULL)
+    if (filter_message)
       {
       uschar *logend;
       colon = US": ";
-      if (filter_message[0] == '<' && filter_message[1] == '<' &&
-          (logend = Ustrstr(filter_message, ">>")) != NULL)
+      if (  filter_message[0] == '<'
+         && filter_message[1] == '<'
+        && (logend = Ustrstr(filter_message, ">>"))
+        )
         {
         logmsg = filter_message + 2;
         loglen = logend - logmsg;
@@ -5191,10 +5869,10 @@ else if (system_filter != NULL && process_recipients != RECIP_FAIL_TIMEOUT)
   else if (rc == FF_DELIVERED)
     {
     process_recipients = RECIP_IGNORE;
-    if (addr_new == NULL)
-      log_write(0, LOG_MAIN, "=> discarded (system filter)");
-    else
+    if (addr_new)
       log_write(0, LOG_MAIN, "original recipients ignored (system filter)");
+    else
+      log_write(0, LOG_MAIN, "=> discarded (system filter)");
     }
 
   /* If any new addresses were created by the filter, fake up a "parent"
@@ -5203,7 +5881,7 @@ else if (system_filter != NULL && process_recipients != RECIP_FAIL_TIMEOUT)
   pipes, files, and autoreplies, and run them as the filter uid if set,
   otherwise as the current uid. */
 
-  if (addr_new != NULL)
+  if (addr_new)
     {
     int uid = (system_filter_uid_set)? system_filter_uid : geteuid();
     int gid = (system_filter_gid_set)? system_filter_gid : getegid();
@@ -5222,11 +5900,11 @@ else if (system_filter != NULL && process_recipients != RECIP_FAIL_TIMEOUT)
     at the final address. This is used if we go on to add addresses for the
     original recipients. */
 
-    while (p != NULL)
+    while (p)
       {
-      if (parent->child_count == SHRT_MAX)
+      if (parent->child_count == USHRT_MAX)
         log_write(0, LOG_MAIN|LOG_PANIC_DIE, "system filter generated more "
-          "than %d delivery addresses", SHRT_MAX);
+          "than %d delivery addresses", USHRT_MAX);
       parent->child_count++;
       p->parent = parent;
 
@@ -5273,33 +5951,29 @@ else if (system_filter != NULL && process_recipients != RECIP_FAIL_TIMEOUT)
         /* Now find the actual transport, first expanding the name. We have
         set address_file or address_pipe above. */
 
-        if (tpname != NULL)
+        if (tpname)
           {
           uschar *tmp = expand_string(tpname);
           address_file = address_pipe = NULL;
-          if (tmp == NULL)
+          if (!tmp)
             p->message = string_sprintf("failed to expand \"%s\" as a "
               "system filter transport name", tpname);
           tpname = tmp;
           }
         else
-          {
           p->message = string_sprintf("system_filter_%s_transport is unset",
             type);
-          }
 
-        if (tpname != NULL)
+        if (tpname)
           {
           transport_instance *tp;
-          for (tp = transports; tp != NULL; tp = tp->next)
-            {
+          for (tp = transports; tp; tp = tp->next)
             if (Ustrcmp(tp->name, tpname) == 0)
               {
               p->transport = tp;
               break;
               }
-            }
-          if (tp == NULL)
+          if (!tp)
             p->message = string_sprintf("failed to find \"%s\" transport "
               "for system filter delivery", tpname);
           }
@@ -5307,11 +5981,11 @@ else if (system_filter != NULL && process_recipients != RECIP_FAIL_TIMEOUT)
         /* If we couldn't set up a transport, defer the delivery, putting the
         error on the panic log as well as the main log. */
 
-        if (p->transport == NULL)
+        if (!p->transport)
           {
           address_item *badp = p;
           p = p->next;
-          if (addr_last == NULL) addr_new = p; else addr_last->next = p;
+          if (!addr_last) addr_new = p; else addr_last->next = p;
           badp->local_part = badp->address;   /* Needed for log line */
           post_process_one(badp, DEFER, LOG_MAIN|LOG_PANIC, DTYPE_ROUTER, 0);
           continue;
@@ -5349,22 +6023,32 @@ if (process_recipients != RECIP_IGNORE)
   {
   for (i = 0; i < recipients_count; i++)
     {
-    if (tree_search(tree_nonrecipients, recipients_list[i].address) == NULL)
+    if (!tree_search(tree_nonrecipients, recipients_list[i].address))
       {
       recipient_item *r = recipients_list + i;
       address_item *new = deliver_make_addr(r->address, FALSE);
-      new->p.errors_address = r->errors_to;
+      new->prop.errors_address = r->errors_to;
+#ifdef SUPPORT_I18N
+      if ((new->prop.utf8_msg = message_smtputf8))
+       {
+       new->prop.utf8_downcvt =       message_utf8_downconvert == 1;
+       new->prop.utf8_downcvt_maybe = message_utf8_downconvert == -1;
+       DEBUG(D_deliver) debug_printf("utf8, downconvert %s\n",
+         new->prop.utf8_downcvt ? "yes"
+         : new->prop.utf8_downcvt_maybe ? "ifneeded"
+         : "no");
+       }
+#endif
 
       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 
+      /* 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
+      DEBUG(D_deliver) debug_printf("DSN: set orcpt: %s  flags: %d\n",
+       new->dsn_orcpt, new->dsn_flags);
 
       switch (process_recipients)
         {
@@ -5381,7 +6065,7 @@ if (process_recipients != RECIP_IGNORE)
 
         case RECIP_FAIL_FILTER:
         new->message =
-          (filter_message == NULL)? US"delivery cancelled" : filter_message;
+          filter_message ? filter_message : US"delivery cancelled";
         setflag(new, af_pass_message);
         goto RECIP_QUEUE_FAILED;   /* below */
 
@@ -5430,24 +6114,40 @@ if (process_recipients != RECIP_IGNORE)
         /* Value should be RECIP_ACCEPT; take this as the safe default. */
 
         default:
-        if (addr_new == NULL) addr_new = new; else addr_last->next = new;
+        if (!addr_new) addr_new = new; else addr_last->next = new;
         addr_last = new;
         break;
         }
+
+#ifndef DISABLE_EVENT
+      if (process_recipients != RECIP_ACCEPT)
+       {
+       uschar * save_local =  deliver_localpart;
+       const uschar * save_domain = deliver_domain;
+
+       deliver_localpart = expand_string(
+                     string_sprintf("${local_part:%s}", new->address));
+       deliver_domain =    expand_string(
+                     string_sprintf("${domain:%s}", new->address));
+
+       (void) event_raise(event_action,
+                     US"msg:fail:internal", new->message);
+
+       deliver_localpart = save_local;
+       deliver_domain =    save_domain;
+       }
+#endif
       }
     }
   }
 
 DEBUG(D_deliver)
   {
-  address_item *p = addr_new;
+  address_item *p;
   debug_printf("Delivery address list:\n");
-  while (p != NULL)
-    {
-    debug_printf("  %s %s\n", p->address, (p->onetime_parent == NULL)? US"" :
-      p->onetime_parent);
-    p = p->next;
-    }
+  for (p = addr_new; p; p = p->next)
+    debug_printf("  %s %s\n", p->address,
+      p->onetime_parent ? p->onetime_parent : US"");
   }
 
 /* Set up the buffers used for copying over the file when delivering. */
@@ -5496,15 +6196,14 @@ deliver_out_buffer = store_malloc(DELIVER_OUT_BUFFER_SIZE);
 */
 
 header_rewritten = FALSE;          /* No headers rewritten yet */
-while (addr_new != NULL)           /* Loop until all addresses dealt with */
+while (addr_new)           /* Loop until all addresses dealt with */
   {
   address_item *addr, *parent;
-  dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE);
 
   /* Failure to open the retry database is treated the same as if it does
   not exist. In both cases, dbm_file is NULL. */
 
-  if (dbm_file == NULL)
+  if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE)))
     {
     DEBUG(D_deliver|D_retry|D_route|D_hints_lookup)
       debug_printf("no retry data available\n");
@@ -5513,7 +6212,7 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
   /* Scan the current batch of new addresses, to handle pipes, files and
   autoreplies, and determine which others are ready for routing. */
 
-  while (addr_new != NULL)
+  while (addr_new)
     {
     int rc;
     uschar *p;
@@ -5569,11 +6268,11 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
 
       if (addr->address[0] == '>')
         {
-        while (tree_search(tree_duplicates, addr->unique) != NULL)
+        while (tree_search(tree_duplicates, addr->unique))
           addr->unique = string_sprintf(">%s", addr->unique);
         }
 
-      else if ((tnode = tree_search(tree_duplicates, addr->unique)) != NULL)
+      else if ((tnode = tree_search(tree_duplicates, addr->unique)))
         {
         DEBUG(D_deliver|D_route)
           debug_printf("%s is a duplicate address: discarded\n", addr->address);
@@ -5587,7 +6286,7 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
 
       /* Check for previous delivery */
 
-      if (tree_search(tree_nonrecipients, addr->unique) != NULL)
+      if (tree_search(tree_nonrecipients, addr->unique))
         {
         DEBUG(D_deliver|D_route)
           debug_printf("%s was previously delivered: discarded\n", addr->address);
@@ -5684,10 +6383,11 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
     delivery was forced by hand. */
 
     deliver_domain = addr->domain;  /* set $domain */
-    if (!forced && hold_domains != NULL &&
-         (rc = match_isinlist(addr->domain, &hold_domains, 0,
+    if (  !forced && hold_domains
+       && (rc = match_isinlist(addr->domain, (const uschar **)&hold_domains, 0,
            &domainlist_anchor, addr->domain_cache, MCL_DOMAIN, TRUE,
-           NULL)) != FAIL)
+           NULL)) != FAIL
+       )
       {
       if (rc == DEFER)
         {
@@ -5709,7 +6409,7 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
     The "unique" field is initialized to the same value as the "address" field,
     but gets changed here to cope with identically-named descendents. */
 
-    for (parent = addr->parent; parent != NULL; parent = parent->parent)
+    for (parent = addr->parent; parent; parent = parent->parent)
       if (strcmpic(addr->address, parent->address) == 0) break;
 
     /* If there's an ancestor with the same name, set the homonym flag. This
@@ -5719,7 +6419,7 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
     work. This means that siblings or cousins with the same names are treated
     as duplicates, which is what we want. */
 
-    if (parent != NULL)
+    if (parent)
       {
       setflag(addr, af_homonym);
       if (parent->unique[0] != '\\')
@@ -5737,7 +6437,7 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
 
     DEBUG(D_deliver|D_route) debug_printf("unique = %s\n", addr->unique);
 
-    if (tree_search(tree_nonrecipients, addr->unique) != NULL)
+    if (tree_search(tree_nonrecipients, addr->unique))
       {
       DEBUG(D_deliver|D_route)
         debug_printf("%s was previously delivered: discarded\n", addr->unique);
@@ -5755,36 +6455,38 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
     addr->address_retry_key = string_sprintf("R:%s@%s", addr->local_part,
       addr->domain);
 
-    if (dbm_file == NULL)
-      domain_retry_record = address_retry_record = NULL;
-    else
+    if (dbm_file)
       {
       domain_retry_record = dbfn_read(dbm_file, addr->domain_retry_key);
-      if (domain_retry_record != NULL &&
-          now - domain_retry_record->time_stamp > retry_data_expire)
+      if (  domain_retry_record
+         && now - domain_retry_record->time_stamp > retry_data_expire
+        )
         domain_retry_record = NULL;    /* Ignore if too old */
 
       address_retry_record = dbfn_read(dbm_file, addr->address_retry_key);
-      if (address_retry_record != NULL &&
-          now - address_retry_record->time_stamp > retry_data_expire)
+      if (  address_retry_record
+         && now - address_retry_record->time_stamp > retry_data_expire
+        )
         address_retry_record = NULL;   /* Ignore if too old */
 
-      if (address_retry_record == NULL)
+      if (!address_retry_record)
         {
         uschar *altkey = string_sprintf("%s:<%s>", addr->address_retry_key,
           sender_address);
         address_retry_record = dbfn_read(dbm_file, altkey);
-        if (address_retry_record != NULL &&
-            now - address_retry_record->time_stamp > retry_data_expire)
+        if (  address_retry_record
+          && now - address_retry_record->time_stamp > retry_data_expire)
           address_retry_record = NULL;   /* Ignore if too old */
         }
       }
+    else
+      domain_retry_record = address_retry_record = NULL;
 
     DEBUG(D_deliver|D_retry)
       {
-      if (domain_retry_record == NULL)
+      if (!domain_retry_record)
         debug_printf("no domain retry record\n");
-      if (address_retry_record == NULL)
+      if (!address_retry_record)
         debug_printf("no address retry record\n");
       }
 
@@ -5802,7 +6504,7 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
     The reason for not doing the same for address retries is that they normally
     arise from 4xx responses, not DNS timeouts. */
 
-    if (continue_hostname != NULL && domain_retry_record != NULL)
+    if (continue_hostname && domain_retry_record)
       {
       addr->message = US"reusing SMTP connection skips previous routing defer";
       addr->basic_errno = ERRNO_RRETRY;
@@ -5841,19 +6543,21 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
     which keep the retry record fresh, which can lead to us perpetually
     deferring messages. */
 
-    else if (((queue_running && !deliver_force) || continue_hostname != NULL)
-            &&
-            ((domain_retry_record != NULL &&
-              now < domain_retry_record->next_try &&
-              !domain_retry_record->expired)
-            ||
-            (address_retry_record != NULL &&
-              now < address_retry_record->next_try))
-            &&
-           (domain_retry_record != NULL ||
-            address_retry_record == NULL ||
-            !retry_ultimate_address_timeout(addr->address_retry_key,
-              addr->domain, address_retry_record, now)))
+    else if (  (  queue_running && !deliver_force
+              || continue_hostname
+              )
+            && (  (  domain_retry_record
+                 && now < domain_retry_record->next_try
+                 && !domain_retry_record->expired
+                 )
+              || (  address_retry_record
+                 && now < address_retry_record->next_try
+              )  )
+            && (  domain_retry_record
+              || !address_retry_record
+              || !retry_ultimate_address_timeout(addr->address_retry_key,
+                                addr->domain, address_retry_record, now)
+           )  )
       {
       addr->message = US"retry time not reached";
       addr->basic_errno = ERRNO_RRETRY;
@@ -5865,7 +6569,7 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
 
     else
       {
-      if (domain_retry_record != NULL || address_retry_record != NULL)
+      if (domain_retry_record || address_retry_record)
         setflag(addr, af_dr_retry_exists);
       addr->next = addr_route;
       addr_route = addr;
@@ -5877,22 +6581,22 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
   /* The database is closed while routing is actually happening. Requests to
   update it are put on a chain and all processed together at the end. */
 
-  if (dbm_file != NULL) dbfn_close(dbm_file);
+  if (dbm_file) dbfn_close(dbm_file);
 
   /* If queue_domains is set, we don't even want to try routing addresses in
   those domains. During queue runs, queue_domains is forced to be unset.
   Optimize by skipping this pass through the addresses if nothing is set. */
 
-  if (!deliver_force && queue_domains != NULL)
+  if (!deliver_force && queue_domains)
     {
     address_item *okaddr = NULL;
-    while (addr_route != NULL)
+    while (addr_route)
       {
       address_item *addr = addr_route;
       addr_route = addr->next;
 
       deliver_domain = addr->domain;  /* set $domain */
-      if ((rc = match_isinlist(addr->domain, &queue_domains, 0,
+      if ((rc = match_isinlist(addr->domain, (const uschar **)&queue_domains, 0,
             &domainlist_anchor, addr->domain_cache, MCL_DOMAIN, TRUE, NULL))
               != OK)
         {
@@ -5921,28 +6625,30 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
 
   /* Now route those addresses that are not deferred. */
 
-  while (addr_route != NULL)
+  while (addr_route)
     {
     int rc;
     address_item *addr = addr_route;
-    uschar *old_domain = addr->domain;
+    const uschar *old_domain = addr->domain;
     uschar *old_unique = addr->unique;
     addr_route = addr->next;
     addr->next = NULL;
 
     /* Just in case some router parameter refers to it. */
 
-    return_path = (addr->p.errors_address != NULL)?
-      addr->p.errors_address : sender_address;
+    if (!(return_path = addr->prop.errors_address))
+      return_path = sender_address;
 
     /* If a router defers an address, add a retry item. Whether or not to
     use the local part in the key is a property of the router. */
 
     if ((rc = route_address(addr, &addr_local, &addr_remote, &addr_new,
          &addr_succeed, v_none)) == DEFER)
-      retry_add_item(addr, (addr->router->retry_use_local_part)?
-        string_sprintf("R:%s@%s", addr->local_part, addr->domain) :
-        string_sprintf("R:%s", addr->domain), 0);
+      retry_add_item(addr,
+        addr->router->retry_use_local_part
+        ?  string_sprintf("R:%s@%s", addr->local_part, addr->domain)
+       : string_sprintf("R:%s", addr->domain),
+       0);
 
     /* Otherwise, if there is an existing retry record in the database, add
     retry items to delete both forms. We must also allow for the possibility
@@ -5984,8 +6690,9 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
     has already been delivered, because it's the unique address that finally
     gets recorded. */
 
-    if (addr->unique != old_unique &&
-        tree_search(tree_nonrecipients, addr->unique) != 0)
+    if (  addr->unique != old_unique
+       && tree_search(tree_nonrecipients, addr->unique) != 0
+       )
       {
       DEBUG(D_deliver|D_route) debug_printf("%s was previously delivered: "
         "discarded\n", addr->address);
@@ -6001,14 +6708,15 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
     to a remote transport, there are no header changes, and the domain was not
     modified by the router. */
 
-    if (addr_remote == addr &&
-        addr->router->same_domain_copy_routing &&
-        addr->p.extra_headers == NULL &&
-        addr->p.remove_headers == NULL &&
-        old_domain == addr->domain)
+    if (  addr_remote == addr
+       && addr->router->same_domain_copy_routing
+       && !addr->prop.extra_headers
+       && !addr->prop.remove_headers
+       && old_domain == addr->domain
+       )
       {
       address_item **chain = &addr_route;
-      while (*chain != NULL)
+      while (*chain)
         {
         address_item *addr2 = *chain;
         if (Ustrcmp(addr2->domain, addr->domain) != 0)
@@ -6031,7 +6739,7 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
         addr2->transport = addr->transport;
         addr2->host_list = addr->host_list;
         addr2->fallback_hosts = addr->fallback_hosts;
-        addr2->p.errors_address = addr->p.errors_address;
+        addr2->prop.errors_address = addr->prop.errors_address;
         copyflag(addr2, addr, af_hide_child | af_local_host_removed);
 
         DEBUG(D_deliver|D_route)
@@ -6052,38 +6760,23 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
 
 DEBUG(D_deliver|D_retry|D_route)
   {
-  address_item *p = addr_local;
+  address_item *p;
   debug_printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
   debug_printf("After routing:\n  Local deliveries:\n");
-  while (p != NULL)
-    {
+  for (p = addr_local; p; p = p->next)
     debug_printf("    %s\n", p->address);
-    p = p->next;
-    }
 
-  p = addr_remote;
   debug_printf("  Remote deliveries:\n");
-  while (p != NULL)
-    {
+  for (p = addr_remote; p; p = p->next)
     debug_printf("    %s\n", p->address);
-    p = p->next;
-    }
 
-  p = addr_failed;
   debug_printf("  Failed addresses:\n");
-  while (p != NULL)
-    {
+  for (p = addr_failed; p; p = p->next)
     debug_printf("    %s\n", p->address);
-    p = p->next;
-    }
 
-  p = addr_defer;
   debug_printf("  Deferred addresses:\n");
-  while (p != NULL)
-    {
+  for (p = addr_defer; p; p = p->next)
     debug_printf("    %s\n", p->address);
-    p = p->next;
-    }
   }
 
 /* Free any resources that were cached during routing. */
@@ -6110,18 +6803,19 @@ do_duplicate_check(&addr_remote);
 remote transport. The check that they all end up in one transaction happens in
 the do_remote_deliveries() function. */
 
-if (mua_wrapper && (addr_local != NULL || addr_failed != NULL ||
-                    addr_defer != NULL))
+if (  mua_wrapper
+   && (addr_local || addr_failed || addr_defer)
+   )
   {
   address_item *addr;
   uschar *which, *colon, *msg;
 
-  if (addr_local != NULL)
+  if (addr_local)
     {
     addr = addr_local;
     which = US"local";
     }
-  else if (addr_defer != NULL)
+  else if (addr_defer)
     {
     addr = addr_defer;
     which = US"deferred";
@@ -6132,9 +6826,9 @@ if (mua_wrapper && (addr_local != NULL || addr_failed != NULL ||
     which = US"failed";
     }
 
-  while (addr->parent != NULL) addr = addr->parent;
+  while (addr->parent) addr = addr->parent;
 
-  if (addr->message != NULL)
+  if (addr->message)
     {
     colon = US": ";
     msg = addr->message;
@@ -6163,14 +6857,16 @@ if (mua_wrapper && (addr_local != NULL || addr_failed != NULL ||
 /* If this is a run to continue deliveries to an external channel that is
 already set up, defer any local deliveries. */
 
-if (continue_transport != NULL)
+if (continue_transport)
   {
-  if (addr_defer == NULL) addr_defer = addr_local; else
+  if (addr_defer)
     {
     address_item *addr = addr_defer;
-    while (addr->next != NULL) addr = addr->next;
+    while (addr->next) addr = addr->next;
     addr->next = addr_local;
     }
+  else
+    addr_defer = addr_local;
   addr_local = NULL;
   }
 
@@ -6188,10 +6884,10 @@ remember them for all subsequent deliveries. This can be delayed till later if
 there is only address to be delivered - if it succeeds the spool write need not
 happen. */
 
-if (header_rewritten &&
-    ((addr_local != NULL &&
-       (addr_local->next != NULL || addr_remote != NULL)) ||
-     (addr_remote != NULL && addr_remote->next != NULL)))
+if (  header_rewritten
+   && (  addr_local && (addr_local->next || addr_remote)
+      || addr_remote && addr_remote->next
+   )  )
   {
   /* Panic-dies on error */
   (void)spool_write_header(message_id, SW_DELIVERING, NULL);
@@ -6199,46 +6895,59 @@ if (header_rewritten &&
   }
 
 
-/* If there are any deliveries to be done, open the journal file. This is used
-to record successful deliveries as soon as possible after each delivery is
-known to be complete. A file opened with O_APPEND is used so that several
-processes can run simultaneously.
+/* If there are any deliveries to be and we do not already have the journal
+file, create it. This is used to record successful deliveries as soon as
+possible after each delivery is known to be complete. A file opened with
+O_APPEND is used so that several processes can run simultaneously.
 
 The journal is just insurance against crashes. When the spool file is
 ultimately updated at the end of processing, the journal is deleted. If a
 journal is found to exist at the start of delivery, the addresses listed
 therein are added to the non-recipients. */
 
-if (addr_local != NULL || addr_remote != NULL)
+if (addr_local || addr_remote)
   {
-  sprintf(CS spoolname, "%s/input/%s/%s-J", spool_directory, message_subdir, id);
-  journal_fd = Uopen(spoolname, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE);
-
   if (journal_fd < 0)
     {
-    log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't open journal file %s: %s",
-      spoolname, strerror(errno));
-    return DELIVER_NOT_ATTEMPTED;
-    }
+    uschar * fname = spool_fname(US"input", message_subdir, id, US"-J");
+    
+    if ((journal_fd = Uopen(fname,
+#ifdef O_CLOEXEC
+                       O_CLOEXEC |
+#endif
+                       O_WRONLY|O_APPEND|O_CREAT|O_EXCL, SPOOL_MODE)) < 0)
+      {
+      log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't open journal file %s: %s",
+       fname, strerror(errno));
+      return DELIVER_NOT_ATTEMPTED;
+      }
 
-  /* Set the close-on-exec flag, make the file owned by Exim, and ensure
-  that the mode is correct - the group setting doesn't always seem to get
-  set automatically. */
+    /* Set the close-on-exec flag, make the file owned by Exim, and ensure
+    that the mode is correct - the group setting doesn't always seem to get
+    set automatically. */
 
-  if(  fcntl(journal_fd, F_SETFD, fcntl(journal_fd, F_GETFD) | FD_CLOEXEC)
-    || fchown(journal_fd, exim_uid, exim_gid)
-    || fchmod(journal_fd, SPOOL_MODE)
-    )
-    {
-    int ret = Uunlink(spoolname);
-    log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't set perms on journal file %s: %s",
-      spoolname, strerror(errno));
-    if(ret  &&  errno != ENOENT)
-      log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to unlink %s: %s",
-        spoolname, strerror(errno));
-    return DELIVER_NOT_ATTEMPTED;
+    if(  fchown(journal_fd, exim_uid, exim_gid)
+      || fchmod(journal_fd, SPOOL_MODE)
+#ifndef O_CLOEXEC
+      || fcntl(journal_fd, F_SETFD, fcntl(journal_fd, F_GETFD) | FD_CLOEXEC)
+#endif
+      )
+      {
+      int ret = Uunlink(fname);
+      log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't set perms on journal file %s: %s",
+       fname, strerror(errno));
+      if(ret  &&  errno != ENOENT)
+       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to unlink %s: %s",
+         fname, strerror(errno));
+      return DELIVER_NOT_ATTEMPTED;
+      }
     }
   }
+else if (journal_fd >= 0)
+  {
+  close(journal_fd);
+  journal_fd = -1;
+  }
 
 
 
@@ -6251,12 +6960,13 @@ for handling fallbacks, though the uid switching will have to be revised. */
 to an LHLO command, if is isn't already compiled. This may be used on both
 local and remote LMTP deliveries. */
 
-if (regex_IGNOREQUOTA == NULL) regex_IGNOREQUOTA =
-  regex_must_compile(US"\\n250[\\s\\-]IGNOREQUOTA(\\s|\\n|$)", FALSE, TRUE);
+if (!regex_IGNOREQUOTA)
+  regex_IGNOREQUOTA =
+    regex_must_compile(US"\\n250[\\s\\-]IGNOREQUOTA(\\s|\\n|$)", FALSE, TRUE);
 
 /* Handle local deliveries */
 
-if (addr_local != NULL)
+if (addr_local)
   {
   DEBUG(D_deliver|D_transport)
     debug_printf(">>>>>>>>>>>>>>>> Local deliveries >>>>>>>>>>>>>>>>\n");
@@ -6268,8 +6978,7 @@ if (addr_local != NULL)
 so just queue them all. */
 
 if (queue_run_local)
-  {
-  while (addr_remote != NULL)
+  while (addr_remote)
     {
     address_item *addr = addr_remote;
     addr_remote = addr->next;
@@ -6278,11 +6987,10 @@ if (queue_run_local)
     addr->message = US"remote deliveries suppressed";
     (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_TRANSPORT, 0);
     }
-  }
 
 /* Handle remote deliveries */
 
-if (addr_remote != NULL)
+if (addr_remote)
   {
   DEBUG(D_deliver|D_transport)
     debug_printf(">>>>>>>>>>>>>>>> Remote deliveries >>>>>>>>>>>>>>>>\n");
@@ -6290,37 +6998,13 @@ if (addr_remote != NULL)
   /* Precompile some regex that are used to recognize parameters in response
   to an EHLO command, if they aren't already compiled. */
 
-  if (regex_PIPELINING == NULL) regex_PIPELINING =
-    regex_must_compile(US"\\n250[\\s\\-]PIPELINING(\\s|\\n|$)", FALSE, TRUE);
-
-  if (regex_SIZE == NULL) regex_SIZE =
-    regex_must_compile(US"\\n250[\\s\\-]SIZE(\\s|\\n|$)", FALSE, TRUE);
-
-  if (regex_AUTH == NULL) regex_AUTH =
-    regex_must_compile(US"\\n250[\\s\\-]AUTH\\s+([\\-\\w\\s]+)(?:\\n|$)",
-      FALSE, TRUE);
-
-  #ifdef SUPPORT_TLS
-  if (regex_STARTTLS == NULL) regex_STARTTLS =
-    regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
-  #endif
-
-  #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
+  deliver_init();
 
   /* 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. */
 
-  if (remote_sort_domains != NULL) sort_remote_deliveries();
+  if (remote_sort_domains) sort_remote_deliveries();
   if (!do_remote_deliveries(FALSE))
     {
     log_write(0, LOG_MAIN, "** mua_wrapper is set but recipients cannot all "
@@ -6337,12 +7021,12 @@ if (addr_remote != NULL)
   host is used for many domains, so all can be sent in a single transaction
   (if appropriately configured). */
 
-  if (addr_fallback != NULL && !mua_wrapper)
+  if (addr_fallback && !mua_wrapper)
     {
     DEBUG(D_deliver) debug_printf("Delivering to fallback hosts\n");
     addr_remote = addr_fallback;
     addr_fallback = NULL;
-    if (remote_sort_domains != NULL) sort_remote_deliveries();
+    if (remote_sort_domains) sort_remote_deliveries();
     do_remote_deliveries(TRUE);
     }
   disable_logging = FALSE;
@@ -6370,10 +7054,10 @@ do not ever want to retry, nor do we want to send a bounce message. */
 
 if (mua_wrapper)
   {
-  if (addr_defer != NULL)
+  if (addr_defer)
     {
     address_item *addr, *nextaddr;
-    for (addr = addr_defer; addr != NULL; addr = nextaddr)
+    for (addr = addr_defer; addr; addr = nextaddr)
       {
       log_write(0, LOG_MAIN, "** %s mua_wrapper forced failure for deferred "
         "delivery", addr->address);
@@ -6386,22 +7070,27 @@ if (mua_wrapper)
 
   /* Now all should either have succeeded or failed. */
 
-  if (addr_failed == NULL) final_yield = DELIVER_MUA_SUCCEEDED; else
+  if (!addr_failed)
+    final_yield = DELIVER_MUA_SUCCEEDED;
+  else
     {
-    uschar *s = (addr_failed->user_message != NULL)?
-      addr_failed->user_message : addr_failed->message;
+    host_item * host;
+    uschar *s = addr_failed->user_message;
+
+    if (!s) s = addr_failed->message;
 
     fprintf(stderr, "Delivery failed: ");
     if (addr_failed->basic_errno > 0)
       {
       fprintf(stderr, "%s", strerror(addr_failed->basic_errno));
-      if (s != NULL) fprintf(stderr, ": ");
+      if (s) fprintf(stderr, ": ");
       }
-    if (s == NULL)
-      {
-      if (addr_failed->basic_errno <= 0) fprintf(stderr, "unknown error");
-      }
-    else fprintf(stderr, "%s", CS s);
+    if ((host = addr_failed->host_used))
+      fprintf(stderr, "H=%s [%s]: ", host->name, host->address);
+    if (s)
+      fprintf(stderr, "%s", CS s);
+    else if (addr_failed->basic_errno <= 0)
+      fprintf(stderr, "unknown error");
     fprintf(stderr, "\n");
 
     final_yield = DELIVER_MUA_FAILED;
@@ -6418,88 +7107,85 @@ retry cutoff time has expired for all alternative destinations. Bypass the
 updating of the database if the -N flag is set, which is a debugging thing that
 prevents actual delivery. */
 
-else if (!dont_deliver) retry_update(&addr_defer, &addr_failed, &addr_succeed);
+else if (!dont_deliver)
+  retry_update(&addr_defer, &addr_failed, &addr_succeed);
 
-#ifdef EXPERIMENTAL_DSN
-/* Send DSN for successful messages */
-addr_dsntmp = addr_succeed;
+/* Send DSN for successful messages if requested */
 addr_senddsn = NULL;
 
-while(addr_dsntmp != NULL)
+for (addr_dsntmp = addr_succeed; addr_dsntmp; addr_dsntmp = addr_dsntmp->next)
   {
-  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);
+  DEBUG(D_deliver) debug_printf("DSN: processing router : %s\n"
+      "DSN: processing successful delivery address: %s\n"
+      "DSN: Sender_address: %s\n"
+      "DSN: orcpt: %s  flags: %d\n"
+      "DSN: envid: %s  ret: %d\n"
+      "DSN: Final recipient: %s\n"
+      "DSN: Remote SMTP server supports DSN: %d\n",
+      addr_dsntmp->router->name,
+      addr_dsntmp->address,
+      sender_address,
+      addr_dsntmp->dsn_orcpt, addr_dsntmp->dsn_flags,
+      dsn_envid, dsn_ret,
+      addr_dsntmp->address,
+      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)))
+  if (  (  addr_dsntmp->dsn_aware != dsn_support_yes
+       || addr_dsntmp->dsn_flags & rf_dsnlasthop
+        )
+     && addr_dsntmp->dsn_flags & rf_dsnflags
+     && addr_dsntmp->dsn_flags & rf_notify_success
+     )
     {
     /* copy and relink address_item and send report with all of them at once later */
-    address_item *addr_next;
-    addr_next = addr_senddsn;
+    address_item * addr_next = addr_senddsn;
     addr_senddsn = store_get(sizeof(address_item));
-    memcpy(addr_senddsn, addr_dsntmp, sizeof(address_item));
+    *addr_senddsn = *addr_dsntmp;
     addr_senddsn->next = addr_next;
     }
   else
-    {
-      DEBUG(D_deliver) debug_printf("DSN: *** NOT SENDING DSN SUCCESS Message ***\n"); 
-    }
-
-  addr_dsntmp = addr_dsntmp->next;
+    DEBUG(D_deliver) debug_printf("DSN: not sending DSN success message\n");
   }
 
-if (addr_senddsn != NULL)
+if (addr_senddsn)
   {
   pid_t pid;
   int fd;
 
-  /* create exim process to send message */      
+  /* 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");
-
-    }    
+    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);
-  
+    uschar * bound;
+    transport_ctx tctx = {0};
+
+    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);
-  
+    bound = string_sprintf(TIME_T_FMT "-eximdsn-%d", time(NULL), rand());
+    DEBUG(D_deliver) debug_printf("DSN: MIME boundary: %s\n", bound);
+
     if (errors_reply_to)
       fprintf(f, "Reply-To: %s\n", errors_reply_to);
+
     fprintf(f, "Auto-Submitted: auto-generated\n"
        "From: Mail Delivery System <Mailer-Daemon@%s>\n"
        "To: %s\n"
@@ -6509,14 +7195,13 @@ if (addr_senddsn != NULL)
 
        "--%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);
+      qualify_domain_sender, sender_address, bound, bound);
 
-    addr_dsntmp = addr_senddsn;
-    while(addr_dsntmp)
-      {
+    for (addr_dsntmp = addr_senddsn; addr_dsntmp;
+        addr_dsntmp = addr_dsntmp->next)
       fprintf(f, "<%s> (relayed %s)\n\n",
        addr_dsntmp->address,
        (addr_dsntmp->dsn_flags & rf_dsnlasthop) == 1
@@ -6525,20 +7210,19 @@ if (addr_senddsn != NULL)
          ? "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);
+      bound, smtp_active_hostname);
 
-    if (dsn_envid != NULL) {
-      /* must be decoded from xtext: see RFC 3461:6.3a */
+    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");
+        fprintf(f, "X-Original-Envelope-ID: error decoding xtext formatted ENVID\n");
       }
     fputc('\n', f);
 
@@ -6555,40 +7239,39 @@ if (addr_senddsn != NULL)
        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",
+        fprintf(f, "Remote-MTA: dns; %s\nDiagnostic-Code: smtp; 250 Ok\n\n",
          addr_dsntmp->host_used->name);
       else
-       fprintf(f,"Diagnostic-Code: X-Exim; relayed via non %s router\n",
+       fprintf(f, "Diagnostic-Code: X-Exim; relayed via non %s router\n\n",
          (addr_dsntmp->dsn_flags & rf_dsnlasthop) == 1 ? "DSN" : "SMTP");
-      fputc('\n', f);
       }
 
-    fprintf(f, "--%s\nContent-type: text/rfc822-headers\n\n", boundaryStr);
-           
+    fprintf(f, "--%s\nContent-type: text/rfc822-headers\n\n", bound);
+
     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);
+
+    tctx.options = topt_add_return_path | topt_no_body;
+    transport_write_message(fileno(f), &tctx, 0);
     fflush(f);
 
-    fprintf(f,"\n");       
-    fprintf(f,"--%s--\n", boundaryStr);
+    fprintf(f,"\n--%s--\n", bound);
 
     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
 requirements. */
 
-while (addr_failed != NULL)
+while (addr_failed)
   {
   pid_t pid;
   int fd;
@@ -6603,7 +7286,7 @@ while (addr_failed != NULL)
   there may not be a transport (address failed by a router). */
 
   disable_logging = FALSE;
-  if (addr_failed->transport != NULL)
+  if (addr_failed->transport)
     disable_logging = addr_failed->transport->disable_logging;
 
   DEBUG(D_deliver)
@@ -6625,10 +7308,10 @@ while (addr_failed != NULL)
   If neither of these cases obtains, something has gone wrong. Log the
   incident, but then ignore the error. */
 
-  if (sender_address[0] == 0 && addr_failed->p.errors_address == NULL)
+  if (sender_address[0] == 0 && !addr_failed->prop.errors_address)
     {
-    if (!testflag(addr_failed, af_retry_timedout) &&
-        !testflag(addr_failed, af_ignore_error))
+    if (  !testflag(addr_failed, af_retry_timedout)
+       && !testflag(addr_failed, af_ignore_error))
       {
       log_write(0, LOG_MAIN|LOG_PANIC, "internal error: bounce message "
         "failure is neither frozen nor ignored (it's been ignored)");
@@ -6640,22 +7323,20 @@ while (addr_failed != NULL)
   it from the list, throw away any saved message file, log it, and
   mark the recipient done. */
 
-  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
-     )
-  {
+  if (  testflag(addr_failed, af_ignore_error)
+     || (  addr_failed->dsn_flags & rf_dsnflags
+        && (addr_failed->dsn_flags & rf_notify_failure) != rf_notify_failure
+     )  )
+    {
     addr = addr_failed;
     addr_failed = addr->next;
-    if (addr->return_filename != NULL) Uunlink(addr->return_filename);
+    if (addr->return_filename) Uunlink(addr->return_filename);
 
     log_write(0, LOG_MAIN, "%s%s%s%s: error ignored",
       addr->address,
-      (addr->parent == NULL)? US"" : US" <",
-      (addr->parent == NULL)? US"" : addr->parent->address,
-      (addr->parent == NULL)? US"" : US">");
+      !addr->parent ? US"" : US" <",
+      !addr->parent ? US"" : addr->parent->address,
+      !addr->parent ? US"" : US">");
 
     address_done(addr, logtod);
     child_done(addr, logtod);
@@ -6666,21 +7347,17 @@ while (addr_failed != NULL)
   /* Otherwise, handle the sending of a message. Find the error address for
   the first address, then send a message that includes all failed addresses
   that have the same error address. Note the bounce_recipient is a global so
-  that it can be accesssed by $bounce_recipient while creating a customized
+  that it can be accessed by $bounce_recipient while creating a customized
   error message. */
 
   else
     {
-    bounce_recipient = (addr_failed->p.errors_address == NULL)?
-      sender_address : addr_failed->p.errors_address;
+    if (!(bounce_recipient = addr_failed->prop.errors_address))
+      bounce_recipient = sender_address;
 
     /* Make a subprocess to send a message */
 
-    pid = child_open_exim(&fd);
-
-    /* Creation of child failed */
-
-    if (pid < 0)
+    if ((pid = child_open_exim(&fd)) < 0)
       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));
@@ -6698,12 +7375,10 @@ while (addr_failed != NULL)
       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 * bound;
       uschar *dsnlimitmsg;
       uschar *dsnnotifyhdr;
       int topt;
-#endif
 
       DEBUG(D_deliver)
         debug_printf("sending error message to: %s\n", bounce_recipient);
@@ -6712,28 +7387,24 @@ while (addr_failed != NULL)
       them from the addr_failed chain, and putting them on msgchain. */
 
       paddr = &addr_failed;
-      for (addr = addr_failed; addr != NULL; addr = *paddr)
-        {
-        if (Ustrcmp(bounce_recipient, (addr->p.errors_address == NULL)?
-              sender_address : addr->p.errors_address) != 0)
-          {
-          paddr = &(addr->next);      /* Not the same; skip */
-          }
-        else                          /* The same - dechain */
-          {
+      for (addr = addr_failed; addr; addr = *paddr)
+        if (Ustrcmp(bounce_recipient, addr->prop.errors_address
+             ? addr->prop.errors_address : sender_address) == 0)
+          {                          /* The same - dechain */
           *paddr = addr->next;
           *pmsgchain = addr;
           addr->next = NULL;
           pmsgchain = &(addr->next);
           }
-        }
+        else
+          paddr = &addr->next;        /* Not the same; skip */
 
       /* Include X-Failed-Recipients: for automatic interpretation, but do
       not let any one header line get too long. We do this by starting a
       new header every 50 recipients. Omit any addresses for which the
       "hide_child" flag is set. */
 
-      for (addr = msgchain; addr != NULL; addr = addr->next)
+      for (addr = msgchain; addr; addr = addr->next)
         {
         if (testflag(addr, af_hide_child)) continue;
         if (rcount >= 50)
@@ -6742,31 +7413,30 @@ while (addr_failed != NULL)
           rcount = 0;
           }
         fprintf(f, "%s%s",
-          (rcount++ == 0)? "X-Failed-Recipients: " : ",\n  ",
-          (testflag(addr, af_pfr) && addr->parent != NULL)?
-            string_printing(addr->parent->address) :
-            string_printing(addr->address));
+          rcount++ == 0
+         ? "X-Failed-Recipients: "
+         : ",\n  ",
+          testflag(addr, af_pfr) && addr->parent
+         ? string_printing(addr->parent->address)
+         : string_printing(addr->address));
         }
       if (rcount > 0) fprintf(f, "\n");
 
       /* Output the standard headers */
 
-      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", 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());
+      bound = string_sprintf(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
+       bound);
 
       /* Open a template file if one is provided. Log failure to open, but
       carry on - default texts will be used. */
@@ -6791,12 +7461,10 @@ while (addr_failed != NULL)
         fprintf(f, "Subject: Mail delivery failed%s\n\n",
           to_sender? ": returning message to sender" : "");
 
-#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
+       bound);
 
       if ((emf_text = next_emf(emf, US"intro")))
        fprintf(f, "%s", CS emf_text);
@@ -6829,7 +7497,7 @@ wording. */
       hidden. */
 
       paddr = &msgchain;
-      for (addr = msgchain; addr != NULL; addr = *paddr)
+      for (addr = msgchain; addr; addr = *paddr)
         {
         if (print_address_information(addr, f, US"  ", US"\n    ", US""))
           print_address_error(addr, f, US"");
@@ -6882,7 +7550,7 @@ wording. */
             "The following text was generated during the delivery "
             "attempt%s:\n", (filecount > 1)? "s" : "");
 
-        for (addr = msgchain; addr != NULL; addr = nextaddr)
+        for (addr = msgchain; addr; addr = nextaddr)
           {
           FILE *fm;
           address_item *topaddr = addr;
@@ -6901,9 +7569,7 @@ wording. */
 
           /* Now copy the file */
 
-          fm = Ufopen(addr->return_filename, "rb");
-
-          if (fm == NULL)
+          if (!(fm = Ufopen(addr->return_filename, "rb")))
             fprintf(f, "    +++ Exim error... failed to open text file: %s\n",
               strerror(errno));
           else
@@ -6923,12 +7589,19 @@ wording. */
        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);
+#ifdef SUPPORT_I18N
+      if (message_smtputf8)
+       fprintf(f, "--%s\n"
+           "Content-type: message/global-delivery-status\n\n"
+           "Reporting-MTA: dns; %s\n",
+         bound, smtp_active_hostname);
+      else
+#endif
+       fprintf(f, "--%s\n"
+           "Content-type: message/delivery-status\n\n"
+           "Reporting-MTA: dns; %s\n",
+         bound, smtp_active_hostname);
 
       if (dsn_envid)
        {
@@ -6937,21 +7610,39 @@ wording. */
         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");
+          fprintf(f, "X-Original-Envelope-ID: error decoding xtext formatted ENVID\n");
         }
       fputc('\n', f);
+
       for (addr = handled_addr; addr; addr = addr->next)
         {
+       host_item * hu;
         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);
-        }
+        if ((hu = addr->host_used) && hu->name)
+         {
+         const uschar * s;
+         fprintf(f, "Remote-MTA: dns; %s\n", hu->name);
+#ifdef EXPERIMENTAL_DSN_INFO
+         if (hu->address)
+           {
+           uschar * p = hu->port == 25
+             ? US"" : string_sprintf(":%d", hu->port);
+           fprintf(f, "Remote-MTA: X-ip; [%s]%s\n", hu->address, p);
+           }
+         if ((s = addr->smtp_greeting) && *s)
+           fprintf(f, "X-Remote-MTA-smtp-greeting: X-str; %s\n", s);
+         if ((s = addr->helo_response) && *s)
+           fprintf(f, "X-Remote-MTA-helo-response: X-str; %s\n", s);
+         if ((s = addr->message) && *s)
+           fprintf(f, "X-Exim-Diagnostic: X-str; %s\n", s);
 #endif
+         print_dsn_diagnostic_code(addr, f);
+         }
+       fputc('\n', f);
+        }
 
       /* 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
@@ -6960,75 +7651,18 @@ wording. */
 
       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)
-         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");
-          else fprintf(f,
-"------ This is a copy of the message's headers. ------\n");
-          }
-
-        /* While reading the "truncated" message, set return_size_limit to
-        the actual max testing value, rounded. We need to read the message
-        whether we are going to use it or not. */
-
-          {
-          int temp = bounce_return_size_limit;
-          bounce_return_size_limit = (max/1000)*1000;
-          emf_text = next_emf(emf, US"truncated");
-          bounce_return_size_limit = temp;
-          }
-
-        if (bounce_return_body && bounce_return_size_limit > 0)
-          {
-          struct stat statbuf;
-          if (fstat(deliver_datafile, &statbuf) == 0 && statbuf.st_size > max)
-            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);
-          }
-
-       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,
-          bounce_return_size_limit, NULL, NULL, NULL, NULL, NULL, 0);
-        }
-
-      /* Write final text and close the template file if one is open */
-
-      if (emf)
-        {
-        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 
+         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);
+
+      fprintf(f, "--%s\n", bound);
 
       dsnlimitmsg = US"X-Exim-DSN-Information: Due to administrative limits only headers are returned";
       dsnnotifyhdr = NULL;
@@ -7038,6 +7672,9 @@ wording. */
       if (dsn_ret == dsn_ret_hdrs)
         topt |= topt_no_body;
       else
+       {
+       struct stat statbuf;
+
         /* no full body return at all? */
         if (!bounce_return_body)
           {
@@ -7046,35 +7683,50 @@ wording. */
           if (dsn_ret == dsn_ret_full)
             dsnnotifyhdr = dsnlimitmsg;
           }
+       /* line length limited... return headers only if oversize */
         /* 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;
-            }
+       else if (  max_received_linelength > bounce_return_linesize_limit
+               || (  bounce_return_size_limit > 0
+                  && 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");
+       }
+
+#ifdef SUPPORT_I18N
+      if (message_smtputf8)
+       fputs(topt & topt_no_body ? "Content-type: message/global-headers\n\n"
+                                 : "Content-type: message/global\n\n",
+             f);
       else
-        fprintf(f,"Content-type: message/rfc822\n\n");
+#endif
+       fputs(topt & topt_no_body ? "Content-type: text/rfc822-headers\n\n"
+                                 : "Content-type: message/rfc822\n\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,
-        0, dsnnotifyhdr, NULL, NULL, NULL, NULL, 0);
+       {                             /* Dummy transport for headers add */
+       transport_ctx tctx = {0};
+       transport_instance tb = {0};
+
+       tctx.tblock = &tb;
+       tctx.options = topt;
+       tb.add_headers = dsnnotifyhdr;
+
+       transport_write_message(fileno(f), &tctx, 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*/
+
+      fprintf(f, "\n--%s--\n", bound);
 
       /* Close the file, which should send an EOF to the child process
       that is receiving the message. Wait for it to finish. */
@@ -7098,7 +7750,7 @@ wording. */
       if (rc != 0)
         {
         uschar *s = US"";
-        if (now - received_time < retry_maximum_timeout && addr_defer == NULL)
+        if (now - received_time < retry_maximum_timeout && !addr_defer)
           {
           addr_defer = (address_item *)(+1);
           deliver_freeze = TRUE;
@@ -7118,7 +7770,7 @@ wording. */
 
       else
         {
-        for (addr = handled_addr; addr != NULL; addr = addr->next)
+        for (addr = handled_addr; addr; addr = addr->next)
           {
           address_done(addr, logtod);
           child_done(addr, logtod);
@@ -7140,48 +7792,49 @@ DELIVERY_TIDYUP:
 message log if so configured, and we are using them. Otherwise, sling it.
 Then delete the message itself. */
 
-if (addr_defer == NULL)
+if (!addr_defer)
   {
+  uschar * fname;
+
   if (message_logs)
     {
-    sprintf(CS spoolname, "%s/msglog/%s/%s", spool_directory, message_subdir,
-      id);
+    fname = spool_fname(US"msglog", message_subdir, id, US"");
     if (preserve_message_logs)
       {
       int rc;
-      sprintf(CS big_buffer, "%s/msglog.OLD/%s", spool_directory, id);
-      if ((rc = Urename(spoolname, big_buffer)) < 0)
+      uschar * moname = spool_fname(US"msglog.OLD", US"", id, US"");
+
+      if ((rc = Urename(fname, moname)) < 0)
         {
-        (void)directory_make(spool_directory, US"msglog.OLD",
-          MSGLOG_DIRECTORY_MODE, TRUE);
-        rc = Urename(spoolname, big_buffer);
+        (void)directory_make(spool_directory,
+                             spool_sname(US"msglog.OLD", US""),
+                             MSGLOG_DIRECTORY_MODE, TRUE);
+        rc = Urename(fname, moname);
         }
       if (rc < 0)
         log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to move %s to the "
-          "msglog.OLD directory", spoolname);
+          "msglog.OLD directory", fname);
       }
     else
-      {
-      if (Uunlink(spoolname) < 0)
+      if (Uunlink(fname) < 0)
         log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to unlink %s: %s",
-                 spoolname, strerror(errno));
-      }
+                 fname, strerror(errno));
     }
 
   /* Remove the two message files. */
 
-  sprintf(CS spoolname, "%s/input/%s/%s-D", spool_directory, message_subdir, id);
-  if (Uunlink(spoolname) < 0)
+  fname = spool_fname(US"input", message_subdir, id, US"-D");
+  if (Uunlink(fname) < 0)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to unlink %s: %s",
-      spoolname, strerror(errno));
-  sprintf(CS spoolname, "%s/input/%s/%s-H", spool_directory, message_subdir, id);
-  if (Uunlink(spoolname) < 0)
+      fname, strerror(errno));
+  fname = spool_fname(US"input", message_subdir, id, US"-H");
+  if (Uunlink(fname) < 0)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to unlink %s: %s",
-      spoolname, strerror(errno));
+      fname, strerror(errno));
 
   /* Log the end of this message, with queue time if requested. */
 
-  if ((log_extra_selector & LX_queue_time_overall) != 0)
+  if (LOGGING(queue_time_overall))
     log_write(0, LOG_MAIN, "Completed QT=%s",
       readconf_printtime( (int) ((long)time(NULL) - (long)received_time)) );
   else
@@ -7189,6 +7842,10 @@ if (addr_defer == NULL)
 
   /* Unset deliver_freeze so that we won't try to move the spool files further down */
   deliver_freeze = FALSE;
+
+#ifndef DISABLE_EVENT
+  (void) event_raise(event_action, US"msg:complete", NULL);
+#endif
   }
 
 /* If there are deferred addresses, we are keeping this message because it is
@@ -7224,35 +7881,37 @@ else if (addr_defer != (address_item *)(+1))
   uschar *recipients = US"";
   BOOL delivery_attempted = FALSE;
 
-  deliver_domain = testflag(addr_defer, af_pfr)?
-    addr_defer->parent->domain : addr_defer->domain;
+  deliver_domain = testflag(addr_defer, af_pfr)
+    addr_defer->parent->domain : addr_defer->domain;
 
-  for (addr = addr_defer; addr != NULL; addr = addr->next)
+  for (addr = addr_defer; addr; addr = addr->next)
     {
     address_item *otaddr;
 
     if (addr->basic_errno > ERRNO_RETRY_BASE) delivery_attempted = TRUE;
 
-    if (deliver_domain != NULL)
+    if (deliver_domain)
       {
-      uschar *d = (testflag(addr, af_pfr))? addr->parent->domain : addr->domain;
+      const uschar *d = testflag(addr, af_pfr)
+       ? addr->parent->domain : addr->domain;
 
       /* The domain may be unset for an address that has never been routed
       because the system filter froze the message. */
 
-      if (d == NULL || Ustrcmp(d, deliver_domain) != 0) deliver_domain = NULL;
+      if (!d || Ustrcmp(d, deliver_domain) != 0)
+        deliver_domain = NULL;
       }
 
-    if (addr->return_filename != NULL) Uunlink(addr->return_filename);
+    if (addr->return_filename) Uunlink(addr->return_filename);
 
     /* Handle the case of one-time aliases. If any address in the ancestry
     of this one is flagged, ensure it is in the recipients list, suitably
     flagged, and that its parent is marked delivered. */
 
-    for (otaddr = addr; otaddr != NULL; otaddr = otaddr->parent)
-      if (otaddr->onetime_parent != NULL) break;
+    for (otaddr = addr; otaddr; otaddr = otaddr->parent)
+      if (otaddr->onetime_parent) break;
 
-    if (otaddr != NULL)
+    if (otaddr)
       {
       int i;
       int t = recipients_count;
@@ -7265,15 +7924,17 @@ else if (addr_defer != (address_item *)(+1))
         }
 
       /* Didn't find the address already in the list, and did find the
-      ultimate parent's address in the list. After adding the recipient,
+      ultimate parent's address in the list, and they really are different
+      (i.e. not from an identity-redirect). After adding the recipient,
       update the errors address in the recipients list. */
 
-      if (i >= recipients_count && t < recipients_count)
+      if (  i >= recipients_count && t < recipients_count
+         && Ustrcmp(otaddr->address, otaddr->parent->address) != 0)
         {
         DEBUG(D_deliver) debug_printf("one_time: adding %s in place of %s\n",
           otaddr->address, otaddr->parent->address);
         receive_add_recipient(otaddr->address, t);
-        recipients_list[recipients_count-1].errors_to = otaddr->p.errors_address;
+        recipients_list[recipients_count-1].errors_to = otaddr->prop.errors_address;
         tree_add_nonrecipient(otaddr->parent->address);
         update_spool = TRUE;
         }
@@ -7283,20 +7944,13 @@ else if (addr_defer != (address_item *)(+1))
     this deferred address or, if there is none, the sender address, is on the
     list of recipients for a warning message. */
 
-    if (sender_address[0] != 0)
+    if (sender_address[0])
       {
-      if (addr->p.errors_address == NULL)
-        {
-        if (Ustrstr(recipients, sender_address) == NULL)
-          recipients = string_sprintf("%s%s%s", recipients,
-            (recipients[0] == 0)? "" : ",", sender_address);
-        }
-      else
-        {
-        if (Ustrstr(recipients, addr->p.errors_address) == NULL)
-          recipients = string_sprintf("%s%s%s", recipients,
-            (recipients[0] == 0)? "" : ",", addr->p.errors_address);
-        }
+      uschar * s = addr->prop.errors_address;
+      if (!s) s = sender_address;
+      if (Ustrstr(recipients, s) == NULL)
+       recipients = string_sprintf("%s%s%s", recipients,
+         recipients[0] ? "," : "", s);
       }
     }
 
@@ -7305,15 +7959,18 @@ else if (addr_defer != (address_item *)(+1))
   is not sent. Another attempt will be made at the next delivery attempt (if
   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,
-            US"delay_warning", US"option")))
+  if (  !queue_2stage
+     && delivery_attempted
+     && (  ((addr_defer->dsn_flags & rf_dsnflags) == 0)
+        || (addr_defer->dsn_flags & rf_notify_delay) == rf_notify_delay
+       )
+     && delay_warning[1] > 0
+     && sender_address[0] != 0
+     && (  !delay_warning_condition
+        || expand_check_condition(delay_warning_condition,
+            US"delay_warning", US"option")
+       )
+     )
     {
     int count;
     int show_time;
@@ -7374,22 +8031,18 @@ else if (addr_defer != (address_item *)(+1))
         uschar *wmf_text;
         FILE *wmf = NULL;
         FILE *f = fdopen(fd, "wb");
-#ifdef EXPERIMENTAL_DSN
-       uschar boundaryStr[64];
-#endif
+       uschar * bound;
+       transport_ctx tctx = {0};
 
         if (warn_message_file)
-          {
-          wmf = Ufopen(warn_message_file, "rb");
-          if (wmf == NULL)
+          if (!(wmf = Ufopen(warn_message_file, "rb")))
             log_write(0, LOG_MAIN|LOG_PANIC, "Failed to open %s for warning "
               "message texts: %s", warn_message_file, strerror(errno));
-          }
 
         warnmsg_recipients = recipients;
-        warnmsg_delay = (queue_time < 120*60)?
-          string_sprintf("%d minutes", show_time/60):
-          string_sprintf("%d hours", show_time/3600);
+        warnmsg_delay = queue_time < 120*60
+         ? string_sprintf("%d minutes", show_time/60)
+         : string_sprintf("%d hours", show_time/3600);
 
         if (errors_reply_to)
           fprintf(f, "Reply-To: %s\n", errors_reply_to);
@@ -7397,16 +8050,13 @@ else if (addr_defer != (address_item *)(+1))
         moan_write_from(f);
         fprintf(f, "To: %s\n", recipients);
 
-#ifdef EXPERIMENTAL_DSN
         /* generated boundary string and output MIME-Headers */
-        snprintf(boundaryStr, sizeof(boundaryStr)-1,
-         TIME_T_FMT "-eximdsn-%d", time(NULL), rand());
+        bound = string_sprintf(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
+         bound);
 
         if ((wmf_text = next_emf(wmf, US"header")))
           fprintf(f, "%s\n", wmf_text);
@@ -7414,12 +8064,10 @@ else if (addr_defer != (address_item *)(+1))
           fprintf(f, "Subject: Warning: message %s delayed %s\n\n",
             message_id, warnmsg_delay);
 
-#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
+         bound);
 
         if ((wmf_text = next_emf(wmf, US"intro")))
          fprintf(f, "%s", CS wmf_text);
@@ -7443,7 +8091,7 @@ else if (addr_defer != (address_item *)(+1))
              "The message identifier is:     %s\n",
            warnmsg_delay, primary_hostname, message_id);
 
-          for (h = header_list; h != NULL; h = h->next)
+          for (h = header_list; h; 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)
@@ -7458,10 +8106,8 @@ else if (addr_defer != (address_item *)(+1))
 
         /* List the addresses, with error information if allowed */
 
-#ifdef EXPERIMENTAL_DSN
         /* store addr_defer for machine readable part */
         address_item *addr_dsndefer = addr_defer;
-#endif
         fputc('\n', f);
         while (addr_defer)
           {
@@ -7490,14 +8136,13 @@ else if (addr_defer != (address_item *)(+1))
 "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,
+         bound,
          smtp_active_hostname);
+
 
         if (dsn_envid)
          {
@@ -7506,41 +8151,45 @@ else if (addr_defer != (address_item *)(+1))
           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");
+            fprintf(f,"X-Original-Envelope-ID: error decoding xtext formatted ENVID\n");
           }
         fputc('\n', f);
 
-        while (addr_dsndefer)
+        for ( ; addr_dsndefer; addr_dsndefer = addr_dsndefer->next)
           {
           if (addr_dsndefer->dsn_orcpt)
-            fprintf(f,"Original-Recipient: %s\n", 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");
+          fprintf(f, "Action: delayed\n"
+             "Final-Recipient: rfc822;%s\n"
+             "Status: 4.0.0\n",
+           addr_dsndefer->address);
           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, "Remote-MTA: dns; %s\n",
+                   addr_dsndefer->host_used->name);
+            print_dsn_diagnostic_code(addr_dsndefer, f);
+            }
+         fputc('\n', f);
           }
 
-        fprintf(f, "\n--%s\n"
+        fprintf(f, "--%s\n"
            "Content-type: text/rfc822-headers\n\n",
-         boundaryStr);
+         bound);
 
         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;
+        tctx.options = 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);
+        transport_write_message(fileno(f), &tctx, 0);
         fflush(f);
 
-        fprintf(f,"\n--%s--\n", boundaryStr);
+        fprintf(f,"\n--%s--\n", bound);
 
         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. */
@@ -7577,7 +8226,7 @@ else if (addr_defer != (address_item *)(+1))
 
   if (deliver_freeze)
     {
-    if (freeze_tell != NULL && freeze_tell[0] != 0 && !local_error_message)
+    if (freeze_tell && freeze_tell[0] != 0 && !local_error_message)
       {
       uschar *s = string_copy(frozen_info);
       uschar *ss = Ustrstr(s, " by the system filter: ");
@@ -7646,17 +8295,18 @@ if (journal_fd >= 0) (void)close(journal_fd);
 
 if (remove_journal)
   {
-  sprintf(CS spoolname, "%s/input/%s/%s-J", spool_directory, message_subdir, id);
-  if (Uunlink(spoolname) < 0 && errno != ENOENT)
-    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to unlink %s: %s", spoolname,
+  uschar * fname = spool_fname(US"input", message_subdir, id, US"-J");
+
+  if (Uunlink(fname) < 0 && errno != ENOENT)
+    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to unlink %s: %s", fname,
       strerror(errno));
 
-  /* Move the message off the spool if reqested */
+  /* Move the message off the spool if requested */
 
-  #ifdef SUPPORT_MOVE_FROZEN_MESSAGES
+#ifdef SUPPORT_MOVE_FROZEN_MESSAGES
   if (deliver_freeze && move_frozen_messages)
     (void)spool_move_message(id, message_subdir, US"", US"F");
-  #endif
+#endif
   }
 
 /* Closing the data file frees the lock; if the file has been unlinked it
@@ -7678,6 +8328,92 @@ acl_where = ACL_WHERE_UNKNOWN;
 return final_yield;
 }
 
+
+
+void
+deliver_init(void)
+{
+if (!regex_PIPELINING) regex_PIPELINING =
+  regex_must_compile(US"\\n250[\\s\\-]PIPELINING(\\s|\\n|$)", FALSE, TRUE);
+
+if (!regex_SIZE) regex_SIZE =
+  regex_must_compile(US"\\n250[\\s\\-]SIZE(\\s|\\n|$)", FALSE, TRUE);
+
+if (!regex_AUTH) regex_AUTH =
+  regex_must_compile(US"\\n250[\\s\\-]AUTH\\s+([\\-\\w\\s]+)(?:\\n|$)",
+    FALSE, TRUE);
+
+#ifdef SUPPORT_TLS
+if (!regex_STARTTLS) regex_STARTTLS =
+  regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
+#endif
+
+if (!regex_CHUNKING) regex_CHUNKING =
+  regex_must_compile(US"\\n250[\\s\\-]CHUNKING(\\s|\\n|$)", FALSE, TRUE);
+
+#ifndef DISABLE_PRDR
+if (!regex_PRDR) regex_PRDR =
+  regex_must_compile(US"\\n250[\\s\\-]PRDR(\\s|\\n|$)", FALSE, TRUE);
+#endif
+
+#ifdef SUPPORT_I18N
+if (!regex_UTF8) regex_UTF8 =
+  regex_must_compile(US"\\n250[\\s\\-]SMTPUTF8(\\s|\\n|$)", FALSE, TRUE);
+#endif
+
+if (!regex_DSN) regex_DSN  =
+  regex_must_compile(US"\\n250[\\s\\-]DSN(\\s|\\n|$)", FALSE, TRUE);
+
+if (!regex_IGNOREQUOTA) regex_IGNOREQUOTA =
+  regex_must_compile(US"\\n250[\\s\\-]IGNOREQUOTA(\\s|\\n|$)", FALSE, TRUE);
+}
+
+
+uschar *
+deliver_get_sender_address (uschar * id)
+{
+int rc;
+uschar * new_sender_address,
+       * save_sender_address;
+BOOL save_qr = queue_running;
+uschar * spoolname;
+
+/* make spool_open_datafile non-noisy on fail */
+
+queue_running = TRUE;
+
+/* Side effect: message_subdir is set for the (possibly split) spool directory */
+
+deliver_datafile = spool_open_datafile(id);
+queue_running = save_qr;
+if (deliver_datafile < 0)
+  return NULL;
+
+/* Save and restore the global sender_address.  I'm not sure if we should
+not save/restore all the other global variables too, because
+spool_read_header() may change all of them. But OTOH, when this
+deliver_get_sender_address() gets called, the current message is done
+already and nobody needs the globals anymore. (HS12, 2015-08-21) */
+
+spoolname = string_sprintf("%s-H", id);
+save_sender_address = sender_address;
+
+rc = spool_read_header(spoolname, TRUE, TRUE);
+
+new_sender_address = sender_address;
+sender_address = save_sender_address;
+
+if (rc != spool_read_OK)
+  return NULL;
+
+assert(new_sender_address);
+
+(void)close(deliver_datafile);
+deliver_datafile = -1;
+
+return new_sender_address;
+}
+
 /* vi: aw ai sw=2
 */
 /* End of deliver.c */
diff --git a/src/demime.c b/src/demime.c
deleted file mode 100644 (file)
index 887678d..0000000
+++ /dev/null
@@ -1,1243 +0,0 @@
-/*************************************************
-*     Exim - an Internet mail transport agent    *
-*************************************************/
-
-/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003-???? */
-/* License: GPL */
-
-/* Code for unpacking MIME containers. Called from acl.c. */
-
-#include "exim.h"
-#ifdef WITH_OLD_DEMIME
-
-#include "demime.h"
-
-uschar demime_reason_buffer[1024];
-struct file_extension *file_extensions = NULL;
-
-int demime(uschar **listptr) {
-  int sep = 0;
-  uschar *list = *listptr;
-  uschar *option;
-  uschar option_buffer[64];
-  unsigned long mbox_size;
-  FILE *mbox_file;
-  uschar defer_error_buffer[1024];
-  int demime_rc = 0;
-
-  /* reset found_extension variable */
-  found_extension = NULL;
-
-  /* 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 demimeing */
-      return FAIL;
-    };
-  }
-  else {
-    /* no options -> no demimeing */
-    return FAIL;
-  };
-
-  /* make sure the eml mbox file is spooled up */
-  mbox_file = spool_mbox(&mbox_size, NULL);
-
-  if (mbox_file == NULL) {
-    /* error while spooling */
-    log_write(0, LOG_MAIN|LOG_PANIC,
-           "demime acl condition: error while creating mbox spool file");
-    return DEFER;
-  };
-
-  /* call demimer if not already done earlier */
-  if (!demime_ok)
-    demime_rc = mime_demux(mbox_file, defer_error_buffer);
-
-  (void)fclose(mbox_file);
-
-  if (demime_rc == DEFER) {
-    /* temporary failure (DEFER => DEFER) */
-    log_write(0, LOG_MAIN,
-        "demime acl condition: %s", defer_error_buffer);
-    return DEFER;
-  };
-
-  /* set demime_ok to avoid unpacking again */
-  demime_ok = 1;
-
-  /* check for file extensions, if there */
-  while (option != NULL) {
-    struct file_extension *this_extension = file_extensions;
-
-    /* Look for the wildcard. If it is found, we always return true.
-    The user must then use a custom condition to evaluate demime_errorlevel */
-    if (Ustrcmp(option,"*") == 0) {
-      found_extension = NULL;
-      return OK;
-    };
-
-    /* loop thru extension list */
-    while (this_extension != NULL) {
-      if (strcmpic(option, this_extension->file_extension_string) == 0) {
-        /* found one */
-        found_extension = this_extension->file_extension_string;
-        return OK;
-      };
-      this_extension = this_extension->next;
-    };
-
-    /* grab next extension from option list */
-    option = string_nextinlist(&list, &sep,
-                               option_buffer,
-                               sizeof(option_buffer));
-  };
-
-  /* nothing found */
-  return FAIL;
-}
-
-
-/*************************************************
-* small hex_str -> integer conversion function   *
-*************************************************/
-
-/* needed for quoted-printable
-*/
-
-unsigned int mime_hstr_i(uschar *cptr) {
-  unsigned int i, j = 0;
-
-  while (cptr && *cptr && isxdigit(*cptr)) {
-    i = *cptr++ - '0';
-    if (9 < i) i -= 7;
-    j <<= 4;
-    j |= (i & 0x0f);
-  }
-
-  return(j);
-}
-
-
-/*************************************************
-* decode quoted-printable chars                  *
-*************************************************/
-
-/* gets called when we hit a =
-   returns: new pointer position
-   result code in c:
-          -2 - decode error
-          -1 - soft line break, no char
-           0-255 - char to write
-*/
-
-uschar *mime_decode_qp(uschar *qp_p,int *c) {
-  uschar hex[] = {0,0,0};
-  int nan = 0;
-  uschar *initial_pos = qp_p;
-
-  /* advance one char */
-  qp_p++;
-
-  REPEAT_FIRST:
-  if ( (*qp_p == '\t') || (*qp_p == ' ') || (*qp_p == '\r') )  {
-    /* tab or whitespace may follow
-       just ignore it, but remember
-       that this is not a valid hex
-       encoding any more */
-    nan = 1;
-    qp_p++;
-    goto REPEAT_FIRST;
-  }
-  else if ( (('0' <= *qp_p) && (*qp_p <= '9')) || (('A' <= *qp_p) && (*qp_p <= 'F'))  || (('a' <= *qp_p) && (*qp_p <= 'f')) ) {
-    /* this is a valid hex char, if nan is unset */
-    if (nan) {
-      /* this is illegal */
-      *c = -2;
-      return initial_pos;
-    }
-    else {
-      hex[0] = *qp_p;
-      qp_p++;
-    };
-  }
-  else if (*qp_p == '\n') {
-    /* hit soft line break already, continue */
-    *c = -1;
-    return qp_p;
-  }
-  else {
-    /* illegal char here */
-    *c = -2;
-    return initial_pos;
-  };
-
-  if ( (('0' <= *qp_p) && (*qp_p <= '9')) || (('A' <= *qp_p) && (*qp_p <= 'F')) || (('a' <= *qp_p) && (*qp_p <= 'f')) ) {
-    if (hex[0] > 0) {
-      hex[1] = *qp_p;
-      /* do hex conversion */
-      *c = mime_hstr_i(hex);
-      qp_p++;
-      return qp_p;
-    }
-    else {
-      /* huh ? */
-      *c = -2;
-      return initial_pos;
-    };
-  }
-  else {
-    /* illegal char */
-    *c = -2;
-    return initial_pos;
-  };
-
-}
-
-
-/*************************************************
-* open new dump file                             *
-*************************************************/
-
-/* open new dump file
-   returns: -2 soft error
-            or file #, FILE * in f
-*/
-
-int mime_get_dump_file(uschar *extension, FILE **f, uschar *info) {
-  uschar file_name[1024];
-  int result;
-  unsigned int file_nr;
-  uschar default_extension[] = ".com";
-  uschar *p;
-
-  if (extension == NULL)
-    extension = default_extension;
-
-  /* scan the proposed extension.
-     if it is longer than 4 chars, or
-     contains exotic chars, use the default extension */
-
-/*  if (Ustrlen(extension) > 4) {
-    extension = default_extension;
-  };
-*/
-
-  p = extension+1;
-
-  while (*p != 0) {
-    *p = (uschar)tolower((uschar)*p);
-    if ( (*p < 97) || (*p > 122) ) {
-      extension = default_extension;
-      break;
-    };
-    p++;
-  };
-
-  /* find a new file to write to */
-  file_nr = 0;
-  do {
-    struct stat mystat;
-
-    (void)string_format(file_name,1024,"%s/scan/%s/%s-%05u%s",spool_directory,message_id,message_id,file_nr,extension);
-    file_nr++;
-    if (file_nr >= MIME_SANITY_MAX_DUMP_FILES) {
-      /* max parts reached */
-      mime_trigger_error(MIME_ERRORLEVEL_TOO_MANY_PARTS);
-      break;
-    };
-    result = stat(CS file_name,&mystat);
-  }
-  while(result != -1);
-
-  *f = modefopen(file_name,"wb+",SPOOL_MODE);
-  if (*f == NULL) {
-    /* cannot open new dump file, disk full ? -> soft error */
-    (void)string_format(info, 1024,"unable to open dump file");
-    return -2;
-  };
-
-  return file_nr;
-}
-
-
-/*************************************************
-* Find a string in a mime header                 *
-*************************************************/
-
-/* Find a string in a mime header, and optionally fill in
-   the value associated with it into *value
-
-   returns: 0 - nothing found
-            1 - found param
-            2 - found param + value
-*/
-
-int mime_header_find(uschar *header, uschar *param, uschar **value) {
-  uschar *needle;
-
-  needle = strstric(header,param,FALSE);
-  if (needle != NULL) {
-    if (value != NULL) {
-      needle += Ustrlen(param);
-      if (*needle == '=') {
-        uschar *value_start;
-        uschar *value_end;
-
-        value_start = needle + 1;
-        value_end = strstric(value_start,US";",FALSE);
-        if (value_end != NULL) {
-          /* allocate mem for value */
-          *value = (uschar *)malloc((value_end - value_start)+1);
-          if (*value == NULL)
-            return 0;
-
-          Ustrncpy(*value,value_start,(value_end - value_start));
-          (*value)[(value_end - value_start)] = '\0';
-          return 2;
-        };
-      };
-    };
-    return 1;
-  };
-  return 0;
-}
-
-
-/*************************************************
-* Read a line of MIME input                      *
-*************************************************/
-/* returns status code, one of
-   MIME_READ_LINE_EOF 0
-   MIME_READ_LINE_OK 1
-   MIME_READ_LINE_OVERFLOW 2
-
-   In header mode, the line will be "cooked".
-*/
-
-int mime_read_line(FILE *f, int mime_demux_mode, uschar *buffer, long *num_copied) {
-  int c = EOF;
-  int done = 0;
-  int header_value_mode = 0;
-  int header_open_brackets = 0;
-
-  *num_copied = 0;
-
-  while(!done) {
-
-    c = fgetc(f);
-    if (c == EOF) break;
-
-    /* --------- header mode -------------- */
-    if (mime_demux_mode == MIME_DEMUX_MODE_MIME_HEADERS) {
-
-      /* 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;
-
-      /* skip whitespace + tabs */
-      if ( (c == ' ') || (c == '\t') )
-        continue;
-
-      if (header_value_mode) {
-        /* --------- value mode ----------- */
-        /* skip quotes */
-        if (c == '"') continue;
-
-        /* leave value mode on ';' */
-        if (c == ';') {
-          header_value_mode = 0;
-        };
-        /* -------------------------------- */
-      }
-      else {
-        /* -------- non-value mode -------- */
-        if (c == '\\') {
-          /* quote next char. can be used
-          to escape brackets. */
-          c = fgetc(f);
-          if (c == EOF) break;
-        }
-        else if (c == '(') {
-          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;
-        };
-
-        /* skip chars while we are in a comment */
-        if (header_open_brackets > 0)
-          continue;
-        /* -------------------------------- */
-      };
-    }
-    /* ------------------------------------ */
-    else {
-    /* ----------- non-header mode -------- */
-      /* break on '\n' */
-      if (c == '\n')
-        done = 1;
-    /* ------------------------------------ */
-    };
-
-    /* copy the char to the buffer */
-    buffer[*num_copied] = (uschar)c;
-    /* raise counter */
-    (*num_copied)++;
-
-    /* break if buffer is full */
-    if (*num_copied > MIME_SANITY_MAX_LINE_LENGTH-1) {
-      done = 1;
-    };
-  }
-
-  /* 0-terminate */
-  buffer[*num_copied] = '\0';
-
-  if (*num_copied > MIME_SANITY_MAX_LINE_LENGTH-1)
-    return MIME_READ_LINE_OVERFLOW;
-  else
-    if (c == EOF)
-      return MIME_READ_LINE_EOF;
-    else
-      return MIME_READ_LINE_OK;
-}
-
-
-/*************************************************
-* Check for a MIME boundary                      *
-*************************************************/
-
-/* returns: 0 - no boundary found
-            1 - start boundary found
-            2 - end boundary found
-*/
-
-int mime_check_boundary(uschar *line, struct boundary *boundaries) {
-  struct boundary *thisboundary = boundaries;
-  uschar workbuf[MIME_SANITY_MAX_LINE_LENGTH+1];
-  unsigned int i,j=0;
-
-  /* check for '--' first */
-  if (Ustrncmp(line,"--",2) == 0) {
-
-    /* strip tab and space */
-    for (i = 2; i < Ustrlen(line); i++) {
-      if ((line[i] != ' ') && (line[i] != '\t')) {
-        workbuf[j] = line[i];
-        j++;
-      };
-    };
-    workbuf[j+1]='\0';
-
-    while(thisboundary != NULL) {
-      if (Ustrncmp(workbuf,thisboundary->boundary_string,Ustrlen(thisboundary->boundary_string)) == 0) {
-        if (Ustrncmp(&workbuf[Ustrlen(thisboundary->boundary_string)],"--",2) == 0) {
-          /* final boundary found */
-          return 2;
-        };
-        return 1;
-      };
-      thisboundary = thisboundary->next;
-    };
-  };
-
-  return 0;
-}
-
-
-/*************************************************
-* Check for start of a UUENCODE block            *
-*************************************************/
-
-/* returns 0 for no hit,
-           >0 for hit
-*/
-
-int mime_check_uu_start(uschar *line, uschar *uu_file_extension, int *has_tnef) {
-
-  if ( (strncmpic(line,US"begin ",6) == 0)) {
-    uschar *uu_filename = &line[6];
-
-    /* skip perms, if present */
-    Ustrtoul(&line[6],&uu_filename,10);
-
-    /* advance one char */
-    uu_filename++;
-
-    /* This should be the filename.
-    Check if winmail.dat is present,
-    which indicates TNEF. */
-    if (strncmpic(uu_filename,US"winmail.dat",11) == 0) {
-      *has_tnef = 1;
-    };
-
-    /* reverse to dot if present,
-    copy up to 4 chars for the extension */
-    if (Ustrrchr(uu_filename,'.') != NULL)
-      uu_filename = Ustrrchr(uu_filename,'.');
-
-    return sscanf(CS uu_filename, "%4[.0-9A-Za-z]",CS uu_file_extension);
-  }
-  else {
-    /* nothing found */
-    return 0;
-  };
-}
-
-
-/*************************************************
-* Decode a uu line                               *
-*************************************************/
-
-/* returns number of decoded bytes
-         -2 for soft errors
-*/
-
-int warned_about_uudec_line_sanity_1 = 0;
-int warned_about_uudec_line_sanity_2 = 0;
-long uu_decode_line(uschar *line, uschar **data, long line_len, uschar *info) {
-  uschar *p;
-  long num_decoded = 0;
-  uschar tmp_c;
-  uschar *work;
-  int uu_decoded_line_len, uu_encoded_line_len;
-
-  /* allocate memory for data and work buffer */
-  *data = (uschar *)malloc(line_len);
-  if (*data == NULL) {
-    (void)string_format(info, 1024,"unable to allocate %lu bytes",line_len);
-    return -2;
-  };
-
-  work = (uschar *)malloc(line_len);
-  if (work == NULL) {
-    (void)string_format(info, 1024,"unable to allocate %lu bytes",line_len);
-    return -2;
-  };
-
-  memcpy(work,line,line_len);
-
-  /* First char is line length
-  This is microsofts way of getting it. Scary. */
-  if (work[0] < 32) {
-    /* ignore this line */
-    return 0;
-  }
-  else {
-    uu_decoded_line_len = uudec[work[0]];
-  };
-
-  p = &work[1];
-
-  while (*p > 32) {
-    *p = uudec[*p];
-    p++;
-  };
-
-  uu_encoded_line_len = (p - &work[1]);
-  p = &work[1];
-
-  /* check that resulting line length is a multiple of 4 */
-  if ( ( uu_encoded_line_len % 4 ) != 0) {
-    if (!warned_about_uudec_line_sanity_1) {
-      mime_trigger_error(MIME_ERRORLEVEL_UU_MISALIGNED);
-      warned_about_uudec_line_sanity_1 = 1;
-    };
-    return -1;
-  };
-
-  /* check that the line length matches */
-  if ( ( (((uu_encoded_line_len/4)*3)-2) > uu_decoded_line_len ) || (((uu_encoded_line_len/4)*3) < uu_decoded_line_len) ) {
-    if (!warned_about_uudec_line_sanity_2) {
-      mime_trigger_error(MIME_ERRORLEVEL_UU_LINE_LENGTH);
-      warned_about_uudec_line_sanity_2 = 1;
-    };
-    return -1;
-  };
-
-  while ( ((p - &work[1]) < uu_encoded_line_len) && (num_decoded < uu_decoded_line_len)) {
-
-    /* byte 0 ---------------------- */
-    if ((p - &work[1] + 1) >= uu_encoded_line_len) {
-      return 0;
-    }
-
-    (*data)[num_decoded] = *p;
-    (*data)[num_decoded] <<= 2;
-
-    tmp_c = *(p+1);
-    tmp_c >>= 4;
-    (*data)[num_decoded] |= tmp_c;
-
-    num_decoded++;
-    p++;
-
-    /* byte 1 ---------------------- */
-    if ((p - &work[1] + 1) >= uu_encoded_line_len) {
-      return 0;
-    }
-
-    (*data)[num_decoded] = *p;
-    (*data)[num_decoded] <<= 4;
-
-    tmp_c = *(p+1);
-    tmp_c >>= 2;
-    (*data)[num_decoded] |= tmp_c;
-
-    num_decoded++;
-    p++;
-
-    /* byte 2 ---------------------- */
-    if ((p - &work[1] + 1) >= uu_encoded_line_len) {
-      return 0;
-    }
-
-    (*data)[num_decoded] = *p;
-    (*data)[num_decoded] <<= 6;
-
-    (*data)[num_decoded] |= *(p+1);
-
-    num_decoded++;
-    p+=2;
-
-  };
-
-  return uu_decoded_line_len;
-}
-
-
-/*************************************************
-* Decode a b64 or qp line                        *
-*************************************************/
-
-/* returns number of decoded bytes
-         -1 for hard errors
-         -2 for soft errors
-*/
-
-int warned_about_b64_line_length = 0;
-int warned_about_b64_line_sanity = 0;
-int warned_about_b64_illegal_char = 0;
-int warned_about_qp_line_sanity = 0;
-long mime_decode_line(int mime_demux_mode,uschar *line, uschar **data, long max_data_len, uschar *info) {
-  uschar *p;
-  long num_decoded = 0;
-  int offset = 0;
-  uschar tmp_c;
-
-  /* allocate memory for data */
-  *data = (uschar *)malloc(max_data_len);
-  if (*data == NULL) {
-    (void)string_format(info, 1024,"unable to allocate %lu bytes",max_data_len);
-    return -2;
-  };
-
-  if (mime_demux_mode == MIME_DEMUX_MODE_BASE64) {
-    /* ---------------------------------------------- */
-
-    /* NULL out trailing '\r' and '\n' chars */
-    while (Ustrrchr(line,'\r') != NULL) {
-      *(Ustrrchr(line,'\r')) = '\0';
-    };
-    while (Ustrrchr(line,'\n') != NULL) {
-      *(Ustrrchr(line,'\n')) = '\0';
-    };
-
-    /* check maximum base 64 line length */
-    if (Ustrlen(line) > MIME_SANITY_MAX_B64_LINE_LENGTH ) {
-      if (!warned_about_b64_line_length) {
-        mime_trigger_error(MIME_ERRORLEVEL_B64_LINE_LENGTH);
-        warned_about_b64_line_length = 1;
-      };
-    };
-
-    p = line;
-    offset = 0;
-    while (*(p+offset) != '\0') {
-      /* hit illegal char ? */
-      if (b64[*(p+offset)] == 128) {
-        if (!warned_about_b64_illegal_char) {
-          mime_trigger_error(MIME_ERRORLEVEL_B64_ILLEGAL_CHAR);
-          warned_about_b64_illegal_char = 1;
-        };
-        offset++;
-      }
-      else {
-        *p = b64[*(p+offset)];
-        p++;
-      };
-    };
-    *p = 255;
-
-    /* check that resulting line length is a multiple of 4 */
-    if ( ( (p - &line[0]) % 4 ) != 0) {
-      if (!warned_about_b64_line_sanity) {
-        mime_trigger_error(MIME_ERRORLEVEL_B64_MISALIGNED);
-        warned_about_b64_line_sanity = 1;
-      };
-    };
-
-    /* line is translated, start bit shifting */
-    p = line;
-    num_decoded = 0;
-
-    while(*p != 255) {
-
-      /* byte 0 ---------------------- */
-      if (*(p+1) == 255) {
-        break;
-      }
-
-      (*data)[num_decoded] = *p;
-      (*data)[num_decoded] <<= 2;
-
-      tmp_c = *(p+1);
-      tmp_c >>= 4;
-      (*data)[num_decoded] |= tmp_c;
-
-      num_decoded++;
-      p++;
-
-      /* byte 1 ---------------------- */
-      if (*(p+1) == 255) {
-        break;
-      }
-
-      (*data)[num_decoded] = *p;
-      (*data)[num_decoded] <<= 4;
-
-      tmp_c = *(p+1);
-      tmp_c >>= 2;
-      (*data)[num_decoded] |= tmp_c;
-
-      num_decoded++;
-      p++;
-
-      /* byte 2 ---------------------- */
-      if (*(p+1) == 255) {
-        break;
-      }
-
-      (*data)[num_decoded] = *p;
-      (*data)[num_decoded] <<= 6;
-
-      (*data)[num_decoded] |= *(p+1);
-
-      num_decoded++;
-      p+=2;
-
-    };
-    return num_decoded;
-    /* ---------------------------------------------- */
-  }
-  else if (mime_demux_mode == MIME_DEMUX_MODE_QP) {
-    /* ---------------------------------------------- */
-    p = line;
-
-    while (*p != 0) {
-      if (*p == '=') {
-        int decode_qp_result;
-
-        p = mime_decode_qp(p,&decode_qp_result);
-
-        if (decode_qp_result == -2) {
-          /* Error from decoder. p is unchanged. */
-          if (!warned_about_qp_line_sanity) {
-            mime_trigger_error(MIME_ERRORLEVEL_QP_ILLEGAL_CHAR);
-            warned_about_qp_line_sanity = 1;
-          };
-          (*data)[num_decoded] = '=';
-          num_decoded++;
-          p++;
-        }
-        else if (decode_qp_result == -1) {
-          /* End of the line with soft line break.
-          Bail out. */
-          goto QP_RETURN;
-        }
-        else if (decode_qp_result >= 0) {
-          (*data)[num_decoded] = decode_qp_result;
-          num_decoded++;
-        };
-      }
-      else {
-        (*data)[num_decoded] = *p;
-        num_decoded++;
-        p++;
-      };
-    };
-    QP_RETURN:
-    return num_decoded;
-    /* ---------------------------------------------- */
-  };
-
-  return 0;
-}
-
-
-
-/*************************************************
-* Log demime errors and set mime error level     *
-*************************************************/
-
-/* This sets the global demime_reason expansion
-variable and the demime_errorlevel gauge. */
-
-void mime_trigger_error(int level, uschar *format, ...) {
-  char *f;
-  va_list ap;
-
-  if( (f = malloc(16384+23)) != NULL ) {
-    /* first log the incident */
-    sprintf(f,"demime acl condition: ");
-    f+=22;
-    va_start(ap, format);
-    (void)string_vformat(US f, 16383,(char *)format, ap);
-    va_end(ap);
-    f-=22;
-    log_write(0, LOG_MAIN, "%s", f);
-    /* then copy to demime_reason_buffer if new
-    level is greater than old level */
-    if (level > demime_errorlevel) {
-      demime_errorlevel = level;
-      Ustrcpy(demime_reason_buffer, US f);
-      demime_reason = demime_reason_buffer;
-    };
-    free(f);
-  };
-}
-
-/*************************************************
-* Demultiplex MIME stream.                       *
-*************************************************/
-
-/* We can handle BASE64, QUOTED-PRINTABLE, and UUENCODE.
- UUENCODE does not need to have a proper
- transfer-encoding header, we detect it with "begin"
-
- This function will report human parsable errors in
- *info.
-
- returns DEFER -> soft error (see *info)
-         OK    -> EOF hit, all ok
-*/
-
-int mime_demux(FILE *f, uschar *info) {
-  int mime_demux_mode = MIME_DEMUX_MODE_MIME_HEADERS;
-  int uu_mode = MIME_UU_MODE_OFF;
-  FILE *mime_dump_file = NULL;
-  FILE *uu_dump_file = NULL;
-  uschar *line;
-  int mime_read_line_status = MIME_READ_LINE_OK;
-  long line_len;
-  struct boundary *boundaries = NULL;
-  struct mime_part mime_part_p;
-  int has_tnef = 0;
-  int has_rfc822 = 0;
-
-  /* allocate room for our linebuffer */
-  line = (uschar *)malloc(MIME_SANITY_MAX_LINE_LENGTH);
-  if (line == NULL) {
-    (void)string_format(info, 1024,"unable to allocate %u bytes",MIME_SANITY_MAX_LINE_LENGTH);
-    return DEFER;
-  };
-
-  /* clear MIME header structure */
-  memset(&mime_part_p,0,sizeof(mime_part));
-
-  /* ----------------------- start demux loop --------------------- */
-  while (mime_read_line_status == MIME_READ_LINE_OK) {
-
-    /* read a line of input. Depending on the mode we are in,
-    the returned format will differ. */
-    mime_read_line_status = mime_read_line(f,mime_demux_mode,line,&line_len);
-
-    if (mime_read_line_status == MIME_READ_LINE_OVERFLOW) {
-      mime_trigger_error(MIME_ERRORLEVEL_LONG_LINE);
-      /* despite the error, continue  .. */
-      mime_read_line_status = MIME_READ_LINE_OK;
-      continue;
-    }
-    else if (mime_read_line_status == MIME_READ_LINE_EOF) {
-      break;
-    };
-
-    if (mime_demux_mode == MIME_DEMUX_MODE_MIME_HEADERS) {
-      /* -------------- header mode --------------------- */
-
-      /* Check for an empty line, which is the end of the headers.
-       In HEADER mode, the line is returned "cooked", with the
-       final '\n' replaced by a ';' */
-      if (line_len == 1) {
-        int tmp;
-
-        /* We have reached the end of the headers. Start decoding
-        with the collected settings. */
-        if (mime_part_p.seen_content_transfer_encoding > 1) {
-          mime_demux_mode = mime_part_p.seen_content_transfer_encoding;
-        }
-        else {
-          /* default to plain mode if no specific encoding type found */
-          mime_demux_mode = MIME_DEMUX_MODE_PLAIN;
-        };
-
-        /* open new dump file */
-        tmp = mime_get_dump_file(mime_part_p.extension, &mime_dump_file, info);
-        if (tmp < 0) {
-          return DEFER;
-        };
-
-        /* clear out mime_part */
-        memset(&mime_part_p,0,sizeof(mime_part));
-      }
-      else {
-        /* Another header to check for file extensions,
-        encoding type and boundaries */
-        if (strncmpic(US"content-type:",line,Ustrlen("content-type:")) == 0) {
-          /* ---------------------------- Content-Type header ------------------------------- */
-          uschar *value = line;
-
-          /* check for message/partial MIME type and reject it */
-          if (mime_header_find(line,US"message/partial",NULL) > 0)
-            mime_trigger_error(MIME_ERRORLEVEL_MESSAGE_PARTIAL);
-
-          /* check for TNEF content type, remember to unpack TNEF later. */
-          if (mime_header_find(line,US"application/ms-tnef",NULL) > 0)
-            has_tnef = 1;
-
-          /* check for message/rfcxxx attachments */
-          if (mime_header_find(line,US"message/rfc822",NULL) > 0)
-            has_rfc822 = 1;
-
-          /* find the file extension, but do not fill it in
-          it is already set, since content-disposition has
-          precedence. */
-          if (mime_part_p.extension == NULL) {
-            if (mime_header_find(line,US"name",&value) == 2) {
-              if (Ustrlen(value) > MIME_SANITY_MAX_FILENAME)
-                mime_trigger_error(MIME_ERRORLEVEL_FILENAME_LENGTH);
-              mime_part_p.extension = value;
-              mime_part_p.extension = Ustrrchr(value,'.');
-              if (mime_part_p.extension == NULL) {
-                /* file without extension, setting
-                NULL will use the default extension later */
-                mime_part_p.extension = NULL;
-              }
-              else {
-                struct file_extension *this_extension =
-                  (struct file_extension *)malloc(sizeof(file_extension));
-
-                this_extension->file_extension_string =
-                  (uschar *)malloc(Ustrlen(mime_part_p.extension)+1);
-                Ustrcpy(this_extension->file_extension_string,
-                        mime_part_p.extension+1);
-                this_extension->next = file_extensions;
-                file_extensions = this_extension;
-              };
-            };
-          };
-
-          /* find a boundary and add it to the list, if present */
-          value = line;
-          if (mime_header_find(line,US"boundary",&value) == 2) {
-            struct boundary *thisboundary;
-
-            if (Ustrlen(value) > MIME_SANITY_MAX_BOUNDARY_LENGTH) {
-              mime_trigger_error(MIME_ERRORLEVEL_BOUNDARY_LENGTH);
-            }
-            else {
-              thisboundary = (struct boundary*)malloc(sizeof(boundary));
-              thisboundary->next = boundaries;
-              thisboundary->boundary_string = value;
-              boundaries = thisboundary;
-            };
-          };
-
-          if (mime_part_p.seen_content_type == 0) {
-            mime_part_p.seen_content_type = 1;
-          }
-          else {
-            mime_trigger_error(MIME_ERRORLEVEL_DOUBLE_HEADERS);
-          };
-          /* ---------------------------------------------------------------------------- */
-        }
-        else if (strncmpic(US"content-transfer-encoding:",line,Ustrlen("content-transfer-encoding:")) == 0) {
-          /* ---------------------------- Content-Transfer-Encoding header -------------- */
-
-         if (mime_part_p.seen_content_transfer_encoding == 0) {
-            if (mime_header_find(line,US"base64",NULL) > 0) {
-              mime_part_p.seen_content_transfer_encoding = MIME_DEMUX_MODE_BASE64;
-            }
-            else if (mime_header_find(line,US"quoted-printable",NULL) > 0) {
-              mime_part_p.seen_content_transfer_encoding = MIME_DEMUX_MODE_QP;
-            }
-            else {
-              mime_part_p.seen_content_transfer_encoding = MIME_DEMUX_MODE_PLAIN;
-            };
-          }
-          else {
-            mime_trigger_error(MIME_ERRORLEVEL_DOUBLE_HEADERS);
-          };
-          /* ---------------------------------------------------------------------------- */
-        }
-        else if (strncmpic(US"content-disposition:",line,Ustrlen("content-disposition:")) == 0) {
-          /* ---------------------------- Content-Disposition header -------------------- */
-          uschar *value = line;
-
-          if (mime_part_p.seen_content_disposition == 0) {
-            mime_part_p.seen_content_disposition = 1;
-
-            if (mime_header_find(line,US"filename",&value) == 2) {
-              if (Ustrlen(value) > MIME_SANITY_MAX_FILENAME)
-                mime_trigger_error(MIME_ERRORLEVEL_FILENAME_LENGTH);
-              mime_part_p.extension = value;
-              mime_part_p.extension = Ustrrchr(value,'.');
-              if (mime_part_p.extension == NULL) {
-                /* file without extension, setting
-                NULL will use the default extension later */
-                mime_part_p.extension = NULL;
-              }
-              else {
-                struct file_extension *this_extension =
-                  (struct file_extension *)malloc(sizeof(file_extension));
-
-                this_extension->file_extension_string =
-                  (uschar *)malloc(Ustrlen(mime_part_p.extension)+1);
-                Ustrcpy(this_extension->file_extension_string,
-                        mime_part_p.extension+1);
-                this_extension->next = file_extensions;
-                file_extensions = this_extension;
-              };
-            };
-          }
-          else {
-            mime_trigger_error(MIME_ERRORLEVEL_DOUBLE_HEADERS);
-          };
-          /* ---------------------------------------------------------------------------- */
-        };
-      };    /* End of header checks */
-      /* ------------------------------------------------ */
-    }
-    else {
-      /* -------------- non-header mode ----------------- */
-      int tmp;
-
-      if (uu_mode == MIME_UU_MODE_OFF) {
-        uschar uu_file_extension[5];
-        /* We are not currently decoding UUENCODE
-        Check for possible UUENCODE start tag. */
-        if (mime_check_uu_start(line,uu_file_extension,&has_tnef)) {
-          /* possible UUENCODING start detected.
-          Set unconfirmed mode first. */
-          uu_mode = MIME_UU_MODE_UNCONFIRMED;
-          /* open new uu dump file */
-          tmp = mime_get_dump_file(uu_file_extension, &uu_dump_file, info);
-          if (tmp < 0) {
-            free(line);
-            return DEFER;
-          };
-        };
-      }
-      else {
-        uschar *data;
-        long data_len = 0;
-
-        if (uu_mode == MIME_UU_MODE_UNCONFIRMED) {
-         /* We are in unconfirmed UUENCODE mode. */
-
-         data_len = uu_decode_line(line,&data,line_len,info);
-
-         if (data_len == -2) {
-           /* temp error, turn off uudecode mode */
-           if (uu_dump_file != NULL) {
-            (void)fclose(uu_dump_file); uu_dump_file = NULL;
-           };
-           uu_mode = MIME_UU_MODE_OFF;
-           return DEFER;
-         }
-         else if (data_len == -1) {
-           if (uu_dump_file != NULL) {
-            (void)fclose(uu_dump_file); uu_dump_file = NULL;
-           };
-           uu_mode = MIME_UU_MODE_OFF;
-           data_len = 0;
-         }
-         else if (data_len > 0) {
-           /* we have at least decoded a valid byte
-           turn on confirmed mode */
-           uu_mode = MIME_UU_MODE_CONFIRMED;
-         };
-        }
-        else if (uu_mode == MIME_UU_MODE_CONFIRMED) {
-          /* If we are in confirmed UU mode,
-          check for single "end" tag on line */
-          if ((strncmpic(line,US"end",3) == 0) && (line[3] < 32)) {
-            if (uu_dump_file != NULL) {
-              (void)fclose(uu_dump_file); uu_dump_file = NULL;
-            };
-            uu_mode = MIME_UU_MODE_OFF;
-          }
-          else {
-            data_len = uu_decode_line(line,&data,line_len,info);
-            if (data_len == -2) {
-               /* temp error, turn off uudecode mode */
-               if (uu_dump_file != NULL) {
-                 (void)fclose(uu_dump_file); uu_dump_file = NULL;
-               };
-               uu_mode = MIME_UU_MODE_OFF;
-               return DEFER;
-             }
-             else if (data_len == -1) {
-               /* skip this line */
-               data_len = 0;
-             };
-          };
-        };
-
-        /* write data to dump file, if available */
-        if (data_len > 0) {
-          if (fwrite(data,1,data_len,uu_dump_file) < data_len) {
-            /* short write */
-            (void)string_format(info, 1024,"short write on uudecode dump file");
-            free(line);
-            return DEFER;
-          };
-        };
-      };
-
-      if (mime_demux_mode != MIME_DEMUX_MODE_SCANNING) {
-        /* Non-scanning and Non-header mode. That means
-        we are currently decoding data to the dump
-        file. */
-
-        /* Check for a known boundary. */
-        tmp = mime_check_boundary(line,boundaries);
-        if (tmp == 1) {
-          /* We have hit a known start boundary.
-          That will put us back in header mode. */
-          mime_demux_mode = MIME_DEMUX_MODE_MIME_HEADERS;
-          if (mime_dump_file != NULL) {
-            /* if the attachment was a RFC822 message, recurse into it */
-            if (has_rfc822) {
-              has_rfc822 = 0;
-              rewind(mime_dump_file);
-              mime_demux(mime_dump_file,info);
-            };
-
-            (void)fclose(mime_dump_file); mime_dump_file = NULL;
-          };
-        }
-        else if (tmp == 2) {
-          /* We have hit a known end boundary.
-          That puts us into scanning mode, which will end when we hit another known start boundary */
-          mime_demux_mode = MIME_DEMUX_MODE_SCANNING;
-          if (mime_dump_file != NULL) {
-            /* if the attachment was a RFC822 message, recurse into it */
-            if (has_rfc822) {
-              has_rfc822 = 0;
-              rewind(mime_dump_file);
-              mime_demux(mime_dump_file,info);
-            };
-
-            (void)fclose(mime_dump_file); mime_dump_file = NULL;
-          };
-        }
-        else {
-          uschar *data;
-          long data_len = 0;
-
-          /* decode the line with the appropriate method */
-          if (mime_demux_mode == MIME_DEMUX_MODE_PLAIN) {
-            /* in plain mode, just dump the line */
-            data = line;
-            data_len = line_len;
-          }
-          else if ( (mime_demux_mode == MIME_DEMUX_MODE_QP) || (mime_demux_mode == MIME_DEMUX_MODE_BASE64) ) {
-            data_len = mime_decode_line(mime_demux_mode,line,&data,line_len,info);
-            if (data_len < 0) {
-              /* Error reported from the line decoder. */
-              data_len = 0;
-            };
-          };
-
-          /* write data to dump file */
-          if (data_len > 0) {
-            if (fwrite(data,1,data_len,mime_dump_file) < data_len) {
-              /* short write */
-              (void)string_format(info, 1024,"short write on dump file");
-              free(line);
-              return DEFER;
-            };
-          };
-
-        };
-      }
-      else {
-        /* Scanning mode. We end up here after a end boundary.
-        This will usually be at the end of a message or at
-        the end of a MIME container.
-        We need to look for another start boundary to get
-        back into header mode. */
-        if (mime_check_boundary(line,boundaries) == 1) {
-          mime_demux_mode = MIME_DEMUX_MODE_MIME_HEADERS;
-        };
-
-      };
-      /* ------------------------------------------------ */
-    };
-  };
-  /* ----------------------- end demux loop ----------------------- */
-
-  /* close files, they could still be open */
-  if (mime_dump_file != NULL)
-    (void)fclose(mime_dump_file);
-  if (uu_dump_file != NULL)
-    (void)fclose(uu_dump_file);
-
-  /* release line buffer */
-  free(line);
-
-  /* FIXME: release boundary buffers.
-  Not too much of a problem since
-  this instance of exim is not resident. */
-
-  if (has_tnef) {
-    uschar file_name[1024];
-    /* at least one file could be TNEF encoded.
-    attempt to send all decoded files thru the TNEF decoder */
-
-    (void)string_format(file_name,1024,"%s/scan/%s",spool_directory,message_id);
-    /* Removed FTTB. We need to decide on TNEF inclusion */
-    /* mime_unpack_tnef(file_name); */
-  };
-
-  return 0;
-}
-
-#endif
diff --git a/src/demime.h b/src/demime.h
deleted file mode 100644 (file)
index 0fec5be..0000000
+++ /dev/null
@@ -1,134 +0,0 @@
-/*************************************************
-*     Exim - an Internet mail transport agent    *
-*************************************************/
-
-/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003-???? */
-/* License: GPL */
-
-/* demime defines */
-
-#ifdef WITH_OLD_DEMIME
-
-#define MIME_DEMUX_MODE_SCANNING     0
-#define MIME_DEMUX_MODE_MIME_HEADERS 1
-#define MIME_DEMUX_MODE_BASE64       2
-#define MIME_DEMUX_MODE_QP           3
-#define MIME_DEMUX_MODE_PLAIN        4
-
-#define MIME_UU_MODE_OFF             0
-#define MIME_UU_MODE_UNCONFIRMED     1
-#define MIME_UU_MODE_CONFIRMED       2
-
-#define MIME_MAX_EXTENSION 128
-
-#define MIME_READ_LINE_EOF 0
-#define MIME_READ_LINE_OK 1
-#define MIME_READ_LINE_OVERFLOW 2
-
-#define MIME_SANITY_MAX_LINE_LENGTH 131071
-#define MIME_SANITY_MAX_FILENAME 512
-#define MIME_SANITY_MAX_HEADER_OPTION_VALUE 1024
-#define MIME_SANITY_MAX_B64_LINE_LENGTH 76
-#define MIME_SANITY_MAX_BOUNDARY_LENGTH 1024
-#define MIME_SANITY_MAX_DUMP_FILES 1024
-
-
-
-/* MIME errorlevel settings */
-
-#define MIME_ERRORLEVEL_LONG_LINE        3,US"line length in message or single header size exceeds %u bytes",MIME_SANITY_MAX_LINE_LENGTH
-#define MIME_ERRORLEVEL_TOO_MANY_PARTS   3,US"too many MIME parts (max %u)",MIME_SANITY_MAX_DUMP_FILES
-#define MIME_ERRORLEVEL_MESSAGE_PARTIAL  3,US"'message/partial' MIME type"
-#define MIME_ERRORLEVEL_FILENAME_LENGTH  3,US"proposed filename exceeds %u characters",MIME_SANITY_MAX_FILENAME
-#define MIME_ERRORLEVEL_BOUNDARY_LENGTH  3,US"boundary length exceeds %u characters",MIME_SANITY_MAX_BOUNDARY_LENGTH
-#define MIME_ERRORLEVEL_DOUBLE_HEADERS   2,US"double headers (content-type, content-disposition or content-transfer-encoding)"
-#define MIME_ERRORLEVEL_UU_MISALIGNED    1,US"uuencoded line length is not a multiple of 4 characters"
-#define MIME_ERRORLEVEL_UU_LINE_LENGTH   1,US"uuencoded line length does not match advertised number of bytes"
-#define MIME_ERRORLEVEL_B64_LINE_LENGTH  1,US"base64 line length exceeds %u characters",MIME_SANITY_MAX_B64_LINE_LENGTH
-#define MIME_ERRORLEVEL_B64_ILLEGAL_CHAR 2,US"base64 line contains illegal character"
-#define MIME_ERRORLEVEL_B64_MISALIGNED   1,US"base64 line length is not a multiple of 4 characters"
-#define MIME_ERRORLEVEL_QP_ILLEGAL_CHAR  1,US"quoted-printable encoding contains illegal character"
-
-
-/* demime structures */
-
-typedef struct mime_part {
-  /* true if there was a content-type header */
-  int seen_content_type;
-  /* true if there was a content-transfer-encoding header
-     contains the encoding type */
-  int seen_content_transfer_encoding;
-  /* true if there was a content-disposition header */
-  int seen_content_disposition;
-  /* pointer to a buffer with the proposed file extension */
-  uschar *extension;
-} mime_part;
-
-typedef struct boundary {
-  struct boundary *next;
-  uschar *boundary_string;
-} boundary;
-
-typedef struct file_extension {
-  struct file_extension *next;
-  uschar *file_extension_string;
-} file_extension;
-
-/* demime.c prototypes */
-
-unsigned int mime_hstr_i(uschar *);
-uschar      *mime_decode_qp(uschar *, int *);
-int          mime_get_dump_file(uschar *, FILE **, uschar *);
-int          mime_header_find(uschar *, uschar *, uschar **);
-int          mime_read_line(FILE *, int, uschar *, long *);
-int          mime_check_boundary(uschar *, struct boundary *);
-int          mime_check_uu_start(uschar *, uschar *, int *);
-long         uu_decode_line(uschar *, uschar **, long, uschar *);
-long         mime_decode_line(int ,uschar *, uschar **, long, uschar *);
-void         mime_trigger_error(int, uschar *, ...);
-int          mime_demux(FILE *, uschar *);
-
-
-
-/* BASE64 decoder matrix */
-static unsigned char b64[256]={
-/*   0 */ 128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
-/*  16 */ 128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
-/*  32 */ 128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,   62,  128,  128,  128,   63,
-/*  48 */  52,   53,   54,   55,   56,   57,   58,   59,   60,   61,  128,  128,  128,  255,  128,  128,
-/*  64 */ 128,    0,    1,    2,    3,    4,    5,    6,    7,    8,    9,   10,   11,   12,   13,   14,
-/*  80 */  15,   16,   17,   18,   19,   20,   21,   22,   23,   24,   25,  128,  128,  128,  128,  128,
-/*  96 */ 128,   26,   27,   28,   29,   30,   31,   32,   33,   34,   35,   36,   37,   38,   39,   40,
-  41,   42,   43,   44,   45,   46,   47,   48,   49,   50,   51,  128,  128,  128,  128,  128,
- 128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
- 128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
- 128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
- 128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
- 128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
- 128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
- 128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
- 128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128
-};
-
-
-/* Microsoft-Style uudecode matrix */
-static unsigned char uudec[256]={
-/*   0 */   0,   33,   34,   35,   36,   37,   38,   39,   40,   41,   42,   43,   44,   45,   46,   47,
-/*  16 */  48,   49,   50,   51,   52,   53,   54,   55,   56,   57,   58,   59,   60,   61,   62,   63,
-/*  32 */   0,    1,    2,    3,    4,    5,    6,    7,    8,    9,   10,   11,   12,   13,   14,   15,
-/*  48 */  16,   17,   18,   19,   20,   21,   22,   23,   24,   25,   26,   27,   28,   29,   30,   31,
-/*  64 */  32,   33,   34,   35,   36,   37,   38,   39,   40,   41,   42,   43,   44,   45,   46,   47,
-/*  80 */  48,   49,   50,   51,   52,   53,   54,   55,   56,   57,   58,   59,   60,   61,   62,   63,
-/*  96 */   0,    1,    2,    3,    4,    5,    6,    7,    8,    9,   10,   11,   12,   13,   14,   15,
-/* 112 */  16,   17,   18,   19,   20,   21,   22,   23,   24,   25,   26,   27,   28,   29,   30,   31,
-/* 128 */  32,   33,   34,   35,   36,   37,   38,   39,   40,   41,   42,   43,   44,   45,   46,   47,
-/* 144 */  48,   49,   50,   51,   52,   53,   54,   55,   56,   57,   58,   59,   60,   61,   62,   63,
-/* 160 */   0,    1,    2,    3,    4,    5,    6,    7,    8,    9,   10,   11,   12,   13,   14,   15,
-/* 176 */  16,   17,   18,   19,   20,   21,   22,   23,   24,   25,   26,   27,   28,   29,   30,   31,
-/* 192 */  32,   33,   34,   35,   36,   37,   38,   39,   40,   41,   42,   43,   44,   45,   46,   47,
-/* 208 */  48,   49,   50,   51,   52,   53,   54,   55,   56,   57,   58,   59,   60,   61,   62,   63,
-/* 224 */   0,    1,    2,    3,    4,    5,    6,    7,    8,    9,   10,   11,   12,   13,   14,   15,
-/* 240 */  16,   17,   18,   19,   20,   21,   22,   23,   24,   25,   26,   27,   28,   29,   30,   31
-};
-
-#endif
dissimilarity index 94%
index 171fccc..f644fbf 100644 (file)
-/*************************************************
-*     Exim - an Internet mail transport agent    *
-*************************************************/
-
-/* Copyright (c) University of Cambridge, 1995 - 2007 */
-/* See the file NOTICE for conditions of use and distribution. */
-
-/* Code for DKIM support. Other DKIM relevant code is in
-   receive.c, transport.c and transports/smtp.c */
-
-#include "exim.h"
-
-#ifndef DISABLE_DKIM
-
-#include "pdkim/pdkim.h"
-
-pdkim_ctx       *dkim_verify_ctx = NULL;
-pdkim_signature *dkim_signatures = NULL;
-pdkim_signature *dkim_cur_sig    = NULL;
-
-int dkim_exim_query_dns_txt(char *name, char *answer) {
-  dns_answer dnsa;
-  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 */
-  for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
-       rr != NULL;
-       rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
-    if (rr->type == T_TXT) break;
-
-  /* Copy record content to the answer buffer */
-  if (rr != NULL) {
-    int rr_offset = 0;
-    int answer_offset = 0;
-    while (rr_offset < rr->size) {
-      uschar len = (rr->data)[rr_offset++];
-      snprintf(answer+(answer_offset),
-               PDKIM_DNS_TXT_MAX_RECLEN-(answer_offset),
-               "%.*s", (int)len, (char *)((rr->data)+rr_offset));
-      rr_offset+=len;
-      answer_offset+=len;
-      if (answer_offset >= PDKIM_DNS_TXT_MAX_RECLEN) {
-        return PDKIM_FAIL;
-      }
-    }
-  }
-  else return PDKIM_FAIL;
-
-  return PDKIM_OK;
-}
-
-
-void dkim_exim_verify_init(void) {
-
-  /* Free previous context if there is one */
-  if (dkim_verify_ctx) pdkim_free_ctx(dkim_verify_ctx);
-
-  /* Create new context */
-  dkim_verify_ctx = pdkim_init_verify(PDKIM_INPUT_SMTP,
-                                      &dkim_exim_query_dns_txt
-                                     );
-
-  if (dkim_verify_ctx != NULL) {
-    dkim_collect_input = TRUE;
-    pdkim_set_debug_stream(dkim_verify_ctx,debug_file);
-  }
-  else dkim_collect_input = FALSE;
-
-}
-
-
-void dkim_exim_verify_feed(uschar *data, int len) {
-  if (dkim_collect_input &&
-      pdkim_feed(dkim_verify_ctx,
-                 (char *)data,
-                 len) != PDKIM_OK) dkim_collect_input = FALSE;
-}
-
-
-void dkim_exim_verify_finish(void) {
-  pdkim_signature *sig = NULL;
-  int dkim_signers_size = 0;
-  int dkim_signers_ptr = 0;
-  dkim_signers = NULL;
-
-  /* Delete eventual previous signature chain */
-  dkim_signatures = NULL;
-
-  /* If we have arrived here with dkim_collect_input == FALSE, it
-     means there was a processing error somewhere along the way.
-     Log the incident and disable futher verification. */
-  if (!dkim_collect_input) {
-    log_write(0, LOG_MAIN, "DKIM: Error while running this message through validation, disabling signature verification.");
-    dkim_disable_verify = TRUE;
-    return;
-  }
-  dkim_collect_input = FALSE;
-
-  /* Finish DKIM operation and fetch link to signatures chain */
-  if (pdkim_feed_finish(dkim_verify_ctx,&dkim_signatures) != PDKIM_OK) return;
-
-  sig = dkim_signatures;
-  while (sig != NULL) {
-    int size = 0;
-    int ptr = 0;
-    /* Log a line for each signature */
-    uschar *logmsg = string_append(NULL, &size, &ptr, 5,
-
-      string_sprintf( "d=%s s=%s c=%s/%s a=%s ",
-                      sig->domain,
-                      sig->selector,
-                      (sig->canon_headers == PDKIM_CANON_SIMPLE)?"simple":"relaxed",
-                      (sig->canon_body    == PDKIM_CANON_SIMPLE)?"simple":"relaxed",
-                      (sig->algo          == PDKIM_ALGO_RSA_SHA256)?"rsa-sha256":"rsa-sha1"
-                    ),
-      ((sig->identity != NULL)?
-        string_sprintf("i=%s ", sig->identity)
-        :
-        US""
-      ),
-      ((sig->created > 0)?
-        string_sprintf("t=%lu ", sig->created)
-        :
-        US""
-      ),
-      ((sig->expires > 0)?
-        string_sprintf("x=%lu ", sig->expires)
-        :
-        US""
-      ),
-      ((sig->bodylength > -1)?
-        string_sprintf("l=%lu ", sig->bodylength)
-        :
-        US""
-      )
-    );
-
-    switch(sig->verify_status) {
-      case PDKIM_VERIFY_NONE:
-        logmsg = string_append(logmsg, &size, &ptr, 1, "[not verified]");
-      break;
-      case PDKIM_VERIFY_INVALID:
-        logmsg = string_append(logmsg, &size, &ptr, 1, "[invalid - ");
-        switch (sig->verify_ext_status) {
-          case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
-            logmsg = string_append(logmsg, &size, &ptr, 1, "public key record (currently?) unavailable]");
-          break;
-          case PDKIM_VERIFY_INVALID_BUFFER_SIZE:
-            logmsg = string_append(logmsg, &size, &ptr, 1, "overlong public key record]");
-          break;
-          case PDKIM_VERIFY_INVALID_PUBKEY_PARSING:
-            logmsg = string_append(logmsg, &size, &ptr, 1, "syntax error in public key record]");
-          break;
-          default:
-            logmsg = string_append(logmsg, &size, &ptr, 1, "unspecified problem]");
-        }
-      break;
-      case PDKIM_VERIFY_FAIL:
-        logmsg = string_append(logmsg, &size, &ptr, 1, "[verification failed - ");
-        switch (sig->verify_ext_status) {
-          case PDKIM_VERIFY_FAIL_BODY:
-            logmsg = string_append(logmsg, &size, &ptr, 1, "body hash mismatch (body probably modified in transit)]");
-          break;
-          case PDKIM_VERIFY_FAIL_MESSAGE:
-            logmsg = string_append(logmsg, &size, &ptr, 1, "signature did not verify (headers probably modified in transit)]");
-          break;
-          default:
-            logmsg = string_append(logmsg, &size, &ptr, 1, "unspecified reason]");
-        }
-      break;
-      case PDKIM_VERIFY_PASS:
-        logmsg = string_append(logmsg, &size, &ptr, 1, "[verification succeeded]");
-      break;
-    }
-
-    logmsg[ptr] = '\0';
-    log_write(0, LOG_MAIN, "DKIM: %s", logmsg);
-
-    /* Build a colon-separated list of signing domains (and identities, if present) in dkim_signers */
-    dkim_signers = string_append(dkim_signers,
-                                 &dkim_signers_size,
-                                 &dkim_signers_ptr,
-                                 2,
-                                 sig->domain,
-                                 ":"
-                                );
-
-    if (sig->identity != NULL) {
-      dkim_signers = string_append(dkim_signers,
-                                   &dkim_signers_size,
-                                   &dkim_signers_ptr,
-                                   2,
-                                   sig->identity,
-                                   ":"
-                                  );
-    }
-
-    /* Process next signature */
-    sig = sig->next;
-  }
-
-  /* NULL-terminate and chop the last colon from the domain list */
-  if (dkim_signers != NULL) {
-    dkim_signers[dkim_signers_ptr] = '\0';
-    if (Ustrlen(dkim_signers) > 0)
-      dkim_signers[Ustrlen(dkim_signers)-1] = '\0';
-  }
-}
-
-
-void dkim_exim_acl_setup(uschar *id) {
-  pdkim_signature *sig = dkim_signatures;
-  dkim_cur_sig = NULL;
-  dkim_cur_signer = id;
-  if (dkim_disable_verify ||
-      !id || !dkim_verify_ctx) return;
-  /* Find signature to run ACL on */
-  while (sig != NULL) {
-    uschar *cmp_val = NULL;
-    if (Ustrchr(id,'@') != NULL) cmp_val = (uschar *)sig->identity;
-                            else cmp_val = (uschar *)sig->domain;
-    if (cmp_val && (strcmpic(cmp_val,id) == 0)) {
-      dkim_cur_sig = sig;
-      /* The "dkim_domain" and "dkim_selector" expansion variables have
-         related globals, since they are used in the signing code too.
-         Instead of inventing separate names for verification, we set
-         them here. This is easy since a domain and selector is guaranteed
-         to be in a signature. The other dkim_* expansion items are
-         dynamically fetched from dkim_cur_sig at expansion time (see
-         function below). */
-      dkim_signing_domain   = (uschar *)sig->domain;
-      dkim_signing_selector = (uschar *)sig->selector;
-      return;
-    }
-    sig = sig->next;
-  }
-}
-
-
-uschar *dkim_exim_expand_query(int what) {
-
-  if (!dkim_verify_ctx ||
-      dkim_disable_verify ||
-      !dkim_cur_sig) return dkim_exim_expand_defaults(what);
-
-  switch(what) {
-    case DKIM_ALGO:
-      switch(dkim_cur_sig->algo) {
-        case PDKIM_ALGO_RSA_SHA1:
-          return US"rsa-sha1";
-        case PDKIM_ALGO_RSA_SHA256:
-        default:
-          return US"rsa-sha256";
-      }
-    case DKIM_BODYLENGTH:
-      return (dkim_cur_sig->bodylength >= 0)?
-              (uschar *)string_sprintf(OFF_T_FMT,(LONGLONG_T)dkim_cur_sig->bodylength)
-              :dkim_exim_expand_defaults(what);
-    case DKIM_CANON_BODY:
-      switch(dkim_cur_sig->canon_body) {
-        case PDKIM_CANON_RELAXED:
-          return US"relaxed";
-        case PDKIM_CANON_SIMPLE:
-        default:
-          return US"simple";
-      }
-    case DKIM_CANON_HEADERS:
-      switch(dkim_cur_sig->canon_headers) {
-        case PDKIM_CANON_RELAXED:
-          return US"relaxed";
-        case PDKIM_CANON_SIMPLE:
-        default:
-          return US"simple";
-      }
-    case DKIM_COPIEDHEADERS:
-      return dkim_cur_sig->copiedheaders?
-              (uschar *)(dkim_cur_sig->copiedheaders)
-              :dkim_exim_expand_defaults(what);
-    case DKIM_CREATED:
-      return (dkim_cur_sig->created > 0)?
-              (uschar *)string_sprintf("%llu",dkim_cur_sig->created)
-              :dkim_exim_expand_defaults(what);
-    case DKIM_EXPIRES:
-      return (dkim_cur_sig->expires > 0)?
-              (uschar *)string_sprintf("%llu",dkim_cur_sig->expires)
-              :dkim_exim_expand_defaults(what);
-    case DKIM_HEADERNAMES:
-      return dkim_cur_sig->headernames?
-              (uschar *)(dkim_cur_sig->headernames)
-              :dkim_exim_expand_defaults(what);
-    case DKIM_IDENTITY:
-      return dkim_cur_sig->identity?
-              (uschar *)(dkim_cur_sig->identity)
-              :dkim_exim_expand_defaults(what);
-    case DKIM_KEY_GRANULARITY:
-      return dkim_cur_sig->pubkey?
-              (dkim_cur_sig->pubkey->granularity?
-                (uschar *)(dkim_cur_sig->pubkey->granularity)
-                :dkim_exim_expand_defaults(what)
-              )
-              :dkim_exim_expand_defaults(what);
-    case DKIM_KEY_SRVTYPE:
-      return dkim_cur_sig->pubkey?
-              (dkim_cur_sig->pubkey->srvtype?
-                (uschar *)(dkim_cur_sig->pubkey->srvtype)
-                :dkim_exim_expand_defaults(what)
-              )
-              :dkim_exim_expand_defaults(what);
-    case DKIM_KEY_NOTES:
-      return dkim_cur_sig->pubkey?
-              (dkim_cur_sig->pubkey->notes?
-                (uschar *)(dkim_cur_sig->pubkey->notes)
-                :dkim_exim_expand_defaults(what)
-              )
-              :dkim_exim_expand_defaults(what);
-    case DKIM_KEY_TESTING:
-      return dkim_cur_sig->pubkey?
-              (dkim_cur_sig->pubkey->testing?
-                US"1"
-                :dkim_exim_expand_defaults(what)
-              )
-              :dkim_exim_expand_defaults(what);
-    case DKIM_NOSUBDOMAINS:
-      return dkim_cur_sig->pubkey?
-              (dkim_cur_sig->pubkey->no_subdomaining?
-                US"1"
-                :dkim_exim_expand_defaults(what)
-              )
-              :dkim_exim_expand_defaults(what);
-    case DKIM_VERIFY_STATUS:
-      switch(dkim_cur_sig->verify_status) {
-        case PDKIM_VERIFY_INVALID:
-          return US"invalid";
-        case PDKIM_VERIFY_FAIL:
-          return US"fail";
-        case PDKIM_VERIFY_PASS:
-          return US"pass";
-        case PDKIM_VERIFY_NONE:
-        default:
-          return US"none";
-      }
-    case DKIM_VERIFY_REASON:
-      switch (dkim_cur_sig->verify_ext_status) {
-        case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
-          return US"pubkey_unavailable";
-        case PDKIM_VERIFY_INVALID_PUBKEY_PARSING:
-          return US"pubkey_syntax";
-        case PDKIM_VERIFY_FAIL_BODY:
-          return US"bodyhash_mismatch";
-        case PDKIM_VERIFY_FAIL_MESSAGE:
-          return US"signature_incorrect";
-      }
-    default:
-      return US"";
-  }
-}
-
-
-uschar *dkim_exim_expand_defaults(int what) {
-  switch(what) {
-    case DKIM_ALGO:               return US"";
-    case DKIM_BODYLENGTH:         return US"9999999999999";
-    case DKIM_CANON_BODY:         return US"";
-    case DKIM_CANON_HEADERS:      return US"";
-    case DKIM_COPIEDHEADERS:      return US"";
-    case DKIM_CREATED:            return US"0";
-    case DKIM_EXPIRES:            return US"9999999999999";
-    case DKIM_HEADERNAMES:        return US"";
-    case DKIM_IDENTITY:           return US"";
-    case DKIM_KEY_GRANULARITY:    return US"*";
-    case DKIM_KEY_SRVTYPE:        return US"*";
-    case DKIM_KEY_NOTES:          return US"";
-    case DKIM_KEY_TESTING:        return US"0";
-    case DKIM_NOSUBDOMAINS:       return US"0";
-    case DKIM_VERIFY_STATUS:      return US"none";
-    case DKIM_VERIFY_REASON:      return US"";
-    default:                      return US"";
-  }
-}
-
-
-uschar *dkim_exim_sign(int dkim_fd,
-                       uschar *dkim_private_key,
-                       uschar *dkim_domain,
-                       uschar *dkim_selector,
-                       uschar *dkim_canon,
-                       uschar *dkim_sign_headers) {
-  int sep = 0;
-  uschar *seen_items = NULL;
-  int seen_items_size = 0;
-  int seen_items_offset = 0;
-  uschar itembuf[256];
-  uschar *dkim_canon_expanded;
-  uschar *dkim_sign_headers_expanded;
-  uschar *dkim_private_key_expanded;
-  pdkim_ctx *ctx = NULL;
-  uschar *rc = NULL;
-  uschar *sigbuf = NULL;
-  int sigsize = 0;
-  int sigptr = 0;
-  pdkim_signature *signature;
-  int pdkim_canon;
-  int pdkim_rc;
-  int sread;
-  char buf[4096];
-  int save_errno = 0;
-  int old_pool = store_pool;
-
-  store_pool = POOL_MAIN;
-
-  dkim_domain = expand_string(dkim_domain);
-  if (dkim_domain == NULL) {
-    /* expansion error, do not send message. */
-    log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
-          "dkim_domain: %s", expand_string_message);
-    rc = NULL;
-    goto CLEANUP;
-  }
-
-  /* Set $dkim_domain expansion variable to each unique domain in list. */
-  while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep,
-                                                  itembuf,
-                                                  sizeof(itembuf))) != NULL) {
-    if (!dkim_signing_domain || (dkim_signing_domain[0] == '\0')) continue;
-    /* Only sign once for each domain, no matter how often it
-       appears in the expanded list. */
-    if (seen_items != NULL) {
-      uschar *seen_items_list = seen_items;
-      if (match_isinlist(dkim_signing_domain,
-                         &seen_items_list,0,NULL,NULL,MCL_STRING,TRUE,NULL) == OK)
-        continue;
-      seen_items = string_append(seen_items,&seen_items_size,&seen_items_offset,1,":");
-    }
-    seen_items = string_append(seen_items,&seen_items_size,&seen_items_offset,1,dkim_signing_domain);
-    seen_items[seen_items_offset] = '\0';
-
-    /* Set up $dkim_selector expansion variable. */
-    dkim_signing_selector = expand_string(dkim_selector);
-    if (dkim_signing_selector == NULL) {
-      log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
-                "dkim_selector: %s", expand_string_message);
-      rc = NULL;
-      goto CLEANUP;
-    }
-
-    /* Get canonicalization to use */
-    dkim_canon_expanded = expand_string(dkim_canon?dkim_canon:US"relaxed");
-    if (dkim_canon_expanded == NULL) {
-      /* expansion error, do not send message. */
-      log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
-                "dkim_canon: %s", expand_string_message);
-      rc = NULL;
-      goto CLEANUP;
-    }
-    if (Ustrcmp(dkim_canon_expanded, "relaxed") == 0)
-      pdkim_canon = PDKIM_CANON_RELAXED;
-    else if (Ustrcmp(dkim_canon_expanded, "simple") == 0)
-      pdkim_canon = PDKIM_CANON_SIMPLE;
-    else {
-      log_write(0, LOG_MAIN, "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",dkim_canon_expanded);
-      pdkim_canon = PDKIM_CANON_RELAXED;
-    }
-
-    if (dkim_sign_headers) {
-      dkim_sign_headers_expanded = expand_string(dkim_sign_headers);
-      if (dkim_sign_headers_expanded == NULL) {
-        log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
-                  "dkim_sign_headers: %s", expand_string_message);
-        rc = NULL;
-        goto CLEANUP;
-      }
-    }
-    else {
-      /* pass NULL, which means default header list */
-      dkim_sign_headers_expanded = NULL;
-    }
-
-    /* Get private key to use. */
-    dkim_private_key_expanded = expand_string(dkim_private_key);
-    if (dkim_private_key_expanded == NULL) {
-      log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
-                "dkim_private_key: %s", expand_string_message);
-      rc = NULL;
-      goto CLEANUP;
-    }
-    if ( (Ustrlen(dkim_private_key_expanded) == 0) ||
-         (Ustrcmp(dkim_private_key_expanded,"0") == 0) ||
-         (Ustrcmp(dkim_private_key_expanded,"false") == 0) ) {
-      /* don't sign, but no error */
-      continue;
-    }
-
-    if (dkim_private_key_expanded[0] == '/') {
-      int privkey_fd = 0;
-      /* Looks like a filename, load the private key. */
-      memset(big_buffer,0,big_buffer_size);
-      privkey_fd = open(CS dkim_private_key_expanded,O_RDONLY);
-      if (privkey_fd < 0) {
-        log_write(0, LOG_MAIN|LOG_PANIC, "unable to open "
-                  "private key file for reading: %s", dkim_private_key_expanded);
-        rc = NULL;
-        goto CLEANUP;
-      }
-      if (read(privkey_fd,big_buffer,(big_buffer_size-2)) < 0) {
-        log_write(0, LOG_MAIN|LOG_PANIC, "unable to read private key file: %s",
-         dkim_private_key_expanded);
-        rc = NULL;
-        goto CLEANUP;
-      }
-      (void)close(privkey_fd);
-      dkim_private_key_expanded = big_buffer;
-    }
-
-    ctx = pdkim_init_sign(PDKIM_INPUT_SMTP,
-                          (char *)dkim_signing_domain,
-                          (char *)dkim_signing_selector,
-                          (char *)dkim_private_key_expanded
-                         );
-
-    pdkim_set_debug_stream(ctx,debug_file);
-
-    pdkim_set_optional(ctx,
-                       (char *)dkim_sign_headers_expanded,
-                       NULL,
-                       pdkim_canon,
-                       pdkim_canon,
-                       -1,
-                       PDKIM_ALGO_RSA_SHA256,
-                       0,
-                       0);
-
-    lseek(dkim_fd, 0, SEEK_SET);
-    while((sread = read(dkim_fd,&buf,4096)) > 0) {
-      if (pdkim_feed(ctx,buf,sread) != PDKIM_OK) {
-        rc = NULL;
-        goto CLEANUP;
-      }
-    }
-    /* Handle failed read above. */
-    if (sread == -1) {
-      debug_printf("DKIM: Error reading -K file.\n");
-      save_errno = errno;
-      rc = NULL;
-      goto CLEANUP;
-    }
-
-    pdkim_rc = pdkim_feed_finish(ctx,&signature);
-    if (pdkim_rc != PDKIM_OK) {
-      log_write(0, LOG_MAIN|LOG_PANIC, "DKIM: signing failed (RC %d)", pdkim_rc);
-      rc = NULL;
-      goto CLEANUP;
-    }
-
-    sigbuf = string_append(sigbuf, &sigsize, &sigptr, 2,
-                           US signature->signature_header,
-                           US"\r\n");
-
-    pdkim_free_ctx(ctx);
-    ctx = NULL;
-  }
-
-  if (sigbuf != NULL) {
-    sigbuf[sigptr] = '\0';
-    rc = sigbuf;
-  } else
-    rc = US"";
-
-  CLEANUP:
-  if (ctx != NULL)
-    pdkim_free_ctx(ctx);
-  store_pool = old_pool;
-  errno = save_errno;
-  return rc;
-}
-
-#endif
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) University of Cambridge, 1995 - 2017 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* Code for DKIM support. Other DKIM relevant code is in
+   receive.c, transport.c and transports/smtp.c */
+
+#include "exim.h"
+
+#ifndef DISABLE_DKIM
+
+#include "pdkim/pdkim.h"
+
+int dkim_verify_oldpool;
+pdkim_ctx *dkim_verify_ctx = NULL;
+pdkim_signature *dkim_signatures = NULL;
+pdkim_signature *dkim_cur_sig = NULL;
+static BOOL dkim_collect_error = FALSE;
+
+static int
+dkim_exim_query_dns_txt(char *name, char *answer)
+{
+dns_answer dnsa;
+dns_scan dnss;
+dns_record *rr;
+
+lookup_dnssec_authenticated = NULL;
+if (dns_lookup(&dnsa, US name, T_TXT, NULL) != DNS_SUCCEED)
+  return PDKIM_FAIL;   /*XXX better error detail?  logging? */
+
+/* Search for TXT record */
+
+for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
+     rr;
+     rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
+  if (rr->type == T_TXT)
+    {
+    int rr_offset = 0;
+    int answer_offset = 0;
+
+    /* Copy record content to the answer buffer */
+
+    while (rr_offset < rr->size)
+      {
+      uschar len = rr->data[rr_offset++];
+      snprintf(answer + answer_offset,
+               PDKIM_DNS_TXT_MAX_RECLEN - answer_offset,
+               "%.*s", (int)len, (char *) (rr->data + rr_offset));
+      rr_offset += len;
+      answer_offset += len;
+      if (answer_offset >= PDKIM_DNS_TXT_MAX_RECLEN)
+       return PDKIM_FAIL;      /*XXX better error detail?  logging? */
+      }
+    return PDKIM_OK;
+    }
+
+return PDKIM_FAIL;     /*XXX better error detail?  logging? */
+}
+
+
+void
+dkim_exim_init(void)
+{
+pdkim_init();
+}
+
+
+
+void
+dkim_exim_verify_init(BOOL dot_stuffing)
+{
+/* There is a store-reset between header & body reception
+so cannot use the main pool. Any allocs done by Exim
+memory-handling must use the perm pool. */
+
+dkim_verify_oldpool = store_pool;
+store_pool = POOL_PERM;
+
+/* Free previous context if there is one */
+
+if (dkim_verify_ctx)
+  pdkim_free_ctx(dkim_verify_ctx);
+
+/* Create new context */
+
+dkim_verify_ctx = pdkim_init_verify(&dkim_exim_query_dns_txt, dot_stuffing);
+dkim_collect_input = !!dkim_verify_ctx;
+dkim_collect_error = FALSE;
+
+/* Start feed up with any cached data */
+receive_get_cache();
+
+store_pool = dkim_verify_oldpool;
+}
+
+
+void
+dkim_exim_verify_feed(uschar * data, int len)
+{
+int rc;
+
+store_pool = POOL_PERM;
+if (  dkim_collect_input
+   && (rc = pdkim_feed(dkim_verify_ctx, CS data, len)) != PDKIM_OK)
+  {
+  log_write(0, LOG_MAIN,
+            "DKIM: validation error: %.100s", pdkim_errstr(rc));
+  dkim_collect_error = TRUE;
+  dkim_collect_input = FALSE;
+  }
+store_pool = dkim_verify_oldpool;
+}
+
+
+void
+dkim_exim_verify_finish(void)
+{
+pdkim_signature * sig = NULL;
+int dkim_signers_size = 0;
+int dkim_signers_ptr = 0;
+int rc;
+
+store_pool = POOL_PERM;
+
+/* Delete eventual previous signature chain */
+
+dkim_signers = NULL;
+dkim_signatures = NULL;
+
+if (dkim_collect_error)
+  {
+  log_write(0, LOG_MAIN,
+            "DKIM: Error while running this message through validation,"
+            " disabling signature verification.");
+  dkim_disable_verify = TRUE;
+  goto out;
+  }
+
+dkim_collect_input = FALSE;
+
+/* Finish DKIM operation and fetch link to signatures chain */
+
+if ((rc = pdkim_feed_finish(dkim_verify_ctx, &dkim_signatures)) != PDKIM_OK)
+  {
+  log_write(0, LOG_MAIN,
+            "DKIM: validation error: %.100s", pdkim_errstr(rc));
+  goto out;
+  }
+
+for (sig = dkim_signatures; sig; sig = sig->next)
+  {
+  int size = 0, ptr = 0;
+  uschar * logmsg = NULL, * s;
+
+  /* Log a line for each signature */
+
+  if (!(s = sig->domain)) s = US"<UNSET>";
+  logmsg = string_append(logmsg, &size, &ptr, 2, "d=", s);
+  if (!(s = sig->selector)) s = US"<UNSET>";
+  logmsg = string_append(logmsg, &size, &ptr, 2, " s=", s);
+  logmsg = string_append(logmsg, &size, &ptr, 7, 
+       " c=", sig->canon_headers == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
+       "/",   sig->canon_body    == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
+       " a=", sig->algo == PDKIM_ALGO_RSA_SHA256
+               ? "rsa-sha256"
+               : sig->algo == PDKIM_ALGO_RSA_SHA1 ? "rsa-sha1" : "err",
+       string_sprintf(" b=%d",
+                       (int)sig->sighash.len > -1 ? sig->sighash.len * 8 : 0));
+  if ((s= sig->identity)) string_append(logmsg, &size, &ptr, 2, " i=", s);
+  if (sig->created > 0) string_append(logmsg, &size, &ptr, 1,
+                                     string_sprintf(" t=%lu", sig->created));
+  if (sig->expires > 0) string_append(logmsg, &size, &ptr, 1,
+                                     string_sprintf(" x=%lu", sig->expires));
+  if (sig->bodylength > -1) string_append(logmsg, &size, &ptr, 1,
+                                     string_sprintf(" l=%lu", sig->bodylength));
+
+  switch (sig->verify_status)
+    {
+    case PDKIM_VERIFY_NONE:
+      logmsg = string_append(logmsg, &size, &ptr, 1, " [not verified]");
+      break;
+
+    case PDKIM_VERIFY_INVALID:
+      logmsg = string_append(logmsg, &size, &ptr, 1, " [invalid - ");
+      switch (sig->verify_ext_status)
+       {
+       case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
+         logmsg = string_append(logmsg, &size, &ptr, 1,
+                       "public key record (currently?) unavailable]");
+         break;
+
+       case PDKIM_VERIFY_INVALID_BUFFER_SIZE:
+         logmsg = string_append(logmsg, &size, &ptr, 1,
+                       "overlong public key record]");
+         break;
+
+       case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD:
+       case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT:
+         logmsg = string_append(logmsg, &size, &ptr, 1,
+                      "syntax error in public key record]");
+         break;
+
+        case PDKIM_VERIFY_INVALID_SIGNATURE_ERROR:
+          logmsg = string_append(logmsg, &size, &ptr, 1,
+                       "signature tag missing or invalid]");
+          break;
+
+        case PDKIM_VERIFY_INVALID_DKIM_VERSION:
+          logmsg = string_append(logmsg, &size, &ptr, 1,
+                       "unsupported DKIM version]");
+          break;
+
+       default:
+         logmsg = string_append(logmsg, &size, &ptr, 1,
+                       "unspecified problem]");
+       }
+      break;
+
+    case PDKIM_VERIFY_FAIL:
+      logmsg =
+       string_append(logmsg, &size, &ptr, 1, " [verification failed - ");
+      switch (sig->verify_ext_status)
+       {
+       case PDKIM_VERIFY_FAIL_BODY:
+         logmsg = string_append(logmsg, &size, &ptr, 1,
+                      "body hash mismatch (body probably modified in transit)]");
+         break;
+
+       case PDKIM_VERIFY_FAIL_MESSAGE:
+         logmsg = string_append(logmsg, &size, &ptr, 1,
+                      "signature did not verify (headers probably modified in transit)]");
+       break;
+
+       default:
+         logmsg = string_append(logmsg, &size, &ptr, 1, "unspecified reason]");
+       }
+      break;
+
+    case PDKIM_VERIFY_PASS:
+      logmsg =
+       string_append(logmsg, &size, &ptr, 1, " [verification succeeded]");
+      break;
+    }
+
+  logmsg[ptr] = '\0';
+  log_write(0, LOG_MAIN, "DKIM: %s", logmsg);
+
+  /* Build a colon-separated list of signing domains (and identities, if present) in dkim_signers */
+
+  if (sig->domain)
+    dkim_signers = string_append_listele(dkim_signers, ':', sig->domain);
+
+  if (sig->identity)
+    dkim_signers = string_append_listele(dkim_signers, ':', sig->identity);
+
+  /* Process next signature */
+  }
+
+out:
+store_pool = dkim_verify_oldpool;
+}
+
+
+void
+dkim_exim_acl_setup(uschar * id)
+{
+pdkim_signature * sig;
+uschar * cmp_val;
+
+dkim_cur_sig = NULL;
+dkim_cur_signer = id;
+
+if (dkim_disable_verify || !id || !dkim_verify_ctx)
+  return;
+
+/* Find signature to run ACL on */
+
+for (sig = dkim_signatures; sig; sig = sig->next)
+  if (  (cmp_val = Ustrchr(id, '@') != NULL ? US sig->identity : US sig->domain)
+     && strcmpic(cmp_val, id) == 0
+     )
+    {
+    dkim_cur_sig = sig;
+
+    /* The "dkim_domain" and "dkim_selector" expansion variables have
+       related globals, since they are used in the signing code too.
+       Instead of inventing separate names for verification, we set
+       them here. This is easy since a domain and selector is guaranteed
+       to be in a signature. The other dkim_* expansion items are
+       dynamically fetched from dkim_cur_sig at expansion time (see
+       function below). */
+
+    dkim_signing_domain = US sig->domain;
+    dkim_signing_selector = US sig->selector;
+    dkim_key_length = sig->sighash.len * 8;
+    return;
+    }
+}
+
+
+static uschar *
+dkim_exim_expand_defaults(int what)
+{
+switch (what)
+  {
+  case DKIM_ALGO:              return US"";
+  case DKIM_BODYLENGTH:                return US"9999999999999";
+  case DKIM_CANON_BODY:                return US"";
+  case DKIM_CANON_HEADERS:     return US"";
+  case DKIM_COPIEDHEADERS:     return US"";
+  case DKIM_CREATED:           return US"0";
+  case DKIM_EXPIRES:           return US"9999999999999";
+  case DKIM_HEADERNAMES:       return US"";
+  case DKIM_IDENTITY:          return US"";
+  case DKIM_KEY_GRANULARITY:   return US"*";
+  case DKIM_KEY_SRVTYPE:       return US"*";
+  case DKIM_KEY_NOTES:         return US"";
+  case DKIM_KEY_TESTING:       return US"0";
+  case DKIM_NOSUBDOMAINS:      return US"0";
+  case DKIM_VERIFY_STATUS:     return US"none";
+  case DKIM_VERIFY_REASON:     return US"";
+  default:                     return US"";
+  }
+}
+
+
+uschar *
+dkim_exim_expand_query(int what)
+{
+if (!dkim_verify_ctx || dkim_disable_verify || !dkim_cur_sig)
+  return dkim_exim_expand_defaults(what);
+
+switch (what)
+  {
+  case DKIM_ALGO:
+    switch (dkim_cur_sig->algo)
+      {
+      case PDKIM_ALGO_RSA_SHA1:        return US"rsa-sha1";
+      case PDKIM_ALGO_RSA_SHA256:
+      default:                 return US"rsa-sha256";
+      }
+
+  case DKIM_BODYLENGTH:
+    return dkim_cur_sig->bodylength >= 0
+      ? string_sprintf(OFF_T_FMT, (LONGLONG_T) dkim_cur_sig->bodylength)
+      : dkim_exim_expand_defaults(what);
+
+  case DKIM_CANON_BODY:
+    switch (dkim_cur_sig->canon_body)
+      {
+      case PDKIM_CANON_RELAXED:        return US"relaxed";
+      case PDKIM_CANON_SIMPLE:
+      default:                 return US"simple";
+      }
+
+  case DKIM_CANON_HEADERS:
+  switch (dkim_cur_sig->canon_headers)
+    {
+    case PDKIM_CANON_RELAXED:  return US"relaxed";
+    case PDKIM_CANON_SIMPLE:
+    default:                   return US"simple";
+    }
+
+  case DKIM_COPIEDHEADERS:
+    return dkim_cur_sig->copiedheaders
+      ? US dkim_cur_sig->copiedheaders : dkim_exim_expand_defaults(what);
+
+  case DKIM_CREATED:
+    return dkim_cur_sig->created > 0
+      ? string_sprintf("%llu", dkim_cur_sig->created)
+      : dkim_exim_expand_defaults(what);
+
+  case DKIM_EXPIRES:
+    return dkim_cur_sig->expires > 0
+      ? string_sprintf("%llu", dkim_cur_sig->expires)
+      : dkim_exim_expand_defaults(what);
+
+  case DKIM_HEADERNAMES:
+    return dkim_cur_sig->headernames
+      ? dkim_cur_sig->headernames : dkim_exim_expand_defaults(what);
+
+  case DKIM_IDENTITY:
+    return dkim_cur_sig->identity
+      ? US dkim_cur_sig->identity : dkim_exim_expand_defaults(what);
+
+  case DKIM_KEY_GRANULARITY:
+    return dkim_cur_sig->pubkey
+      ? dkim_cur_sig->pubkey->granularity
+      ? US dkim_cur_sig->pubkey->granularity
+      : dkim_exim_expand_defaults(what)
+      : dkim_exim_expand_defaults(what);
+
+  case DKIM_KEY_SRVTYPE:
+    return dkim_cur_sig->pubkey
+      ? dkim_cur_sig->pubkey->srvtype
+      ? US dkim_cur_sig->pubkey->srvtype
+      : dkim_exim_expand_defaults(what)
+      : dkim_exim_expand_defaults(what);
+
+  case DKIM_KEY_NOTES:
+    return dkim_cur_sig->pubkey
+      ? dkim_cur_sig->pubkey->notes
+      ? US dkim_cur_sig->pubkey->notes
+      : dkim_exim_expand_defaults(what)
+      : dkim_exim_expand_defaults(what);
+
+  case DKIM_KEY_TESTING:
+    return dkim_cur_sig->pubkey
+      ? dkim_cur_sig->pubkey->testing
+      ? US"1"
+      : dkim_exim_expand_defaults(what)
+      : dkim_exim_expand_defaults(what);
+
+  case DKIM_NOSUBDOMAINS:
+    return dkim_cur_sig->pubkey
+      ? dkim_cur_sig->pubkey->no_subdomaining
+      ? US"1"
+      : dkim_exim_expand_defaults(what)
+      : dkim_exim_expand_defaults(what);
+
+  case DKIM_VERIFY_STATUS:
+    switch (dkim_cur_sig->verify_status)
+      {
+      case PDKIM_VERIFY_INVALID:       return US"invalid";
+      case PDKIM_VERIFY_FAIL:          return US"fail";
+      case PDKIM_VERIFY_PASS:          return US"pass";
+      case PDKIM_VERIFY_NONE:
+      default:                         return US"none";
+      }
+
+  case DKIM_VERIFY_REASON:
+    switch (dkim_cur_sig->verify_ext_status)
+      {
+      case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
+                                               return US"pubkey_unavailable";
+      case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD:return US"pubkey_dns_syntax";
+      case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT: return US"pubkey_der_syntax";
+      case PDKIM_VERIFY_FAIL_BODY:             return US"bodyhash_mismatch";
+      case PDKIM_VERIFY_FAIL_MESSAGE:          return US"signature_incorrect";
+      }
+
+  default:
+    return US"";
+  }
+}
+
+
+uschar *
+dkim_exim_sign(int dkim_fd, struct ob_dkim * dkim)
+{
+const uschar * dkim_domain;
+int sep = 0;
+uschar *seen_items = NULL;
+int seen_items_size = 0;
+int seen_items_offset = 0;
+uschar itembuf[256];
+uschar *dkim_canon_expanded;
+uschar *dkim_sign_headers_expanded;
+uschar *dkim_private_key_expanded;
+pdkim_ctx *ctx = NULL;
+uschar *rc = NULL;
+uschar *sigbuf = NULL;
+int sigsize = 0;
+int sigptr = 0;
+pdkim_signature *signature;
+int pdkim_canon;
+int pdkim_rc;
+int sread;
+char buf[4096];
+int save_errno = 0;
+int old_pool = store_pool;
+
+store_pool = POOL_MAIN;
+
+if (!(dkim_domain = expand_cstring(dkim->dkim_domain)))
+  {
+  /* expansion error, do not send message. */
+  log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
+            "dkim_domain: %s", expand_string_message);
+  goto bad;
+  }
+
+/* Set $dkim_domain expansion variable to each unique domain in list. */
+
+while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep,
+                                  itembuf, sizeof(itembuf))))
+  {
+  if (!dkim_signing_domain || dkim_signing_domain[0] == '\0')
+    continue;
+
+  /* Only sign once for each domain, no matter how often it
+  appears in the expanded list. */
+
+  if (seen_items)
+    {
+    const uschar *seen_items_list = seen_items;
+    if (match_isinlist(dkim_signing_domain,
+                       &seen_items_list, 0, NULL, NULL, MCL_STRING, TRUE,
+                       NULL) == OK)
+      continue;
+
+    seen_items =
+      string_append(seen_items, &seen_items_size, &seen_items_offset, 1, ":");
+    }
+
+  seen_items =
+    string_append(seen_items, &seen_items_size, &seen_items_offset, 1,
+                dkim_signing_domain);
+  seen_items[seen_items_offset] = '\0';
+
+  /* Set up $dkim_selector expansion variable. */
+
+  if (!(dkim_signing_selector = expand_string(dkim->dkim_selector)))
+    {
+    log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
+              "dkim_selector: %s", expand_string_message);
+    goto bad;
+    }
+
+  /* Get canonicalization to use */
+
+  dkim_canon_expanded = dkim->dkim_canon
+    ? expand_string(dkim->dkim_canon) : US"relaxed";
+  if (!dkim_canon_expanded)
+    {
+    /* expansion error, do not send message. */
+    log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
+              "dkim_canon: %s", expand_string_message);
+    goto bad;
+    }
+
+  if (Ustrcmp(dkim_canon_expanded, "relaxed") == 0)
+    pdkim_canon = PDKIM_CANON_RELAXED;
+  else if (Ustrcmp(dkim_canon_expanded, "simple") == 0)
+    pdkim_canon = PDKIM_CANON_SIMPLE;
+  else
+    {
+    log_write(0, LOG_MAIN,
+              "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",
+              dkim_canon_expanded);
+    pdkim_canon = PDKIM_CANON_RELAXED;
+    }
+
+  dkim_sign_headers_expanded = NULL;
+  if (dkim->dkim_sign_headers)
+    if (!(dkim_sign_headers_expanded = expand_string(dkim->dkim_sign_headers)))
+      {
+      log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
+                "dkim_sign_headers: %s", expand_string_message);
+      goto bad;
+      }
+                       /* else pass NULL, which means default header list */
+
+  /* Get private key to use. */
+
+  if (!(dkim_private_key_expanded = expand_string(dkim->dkim_private_key)))
+    {
+    log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
+              "dkim_private_key: %s", expand_string_message);
+    goto bad;
+    }
+
+  if (  Ustrlen(dkim_private_key_expanded) == 0
+     || Ustrcmp(dkim_private_key_expanded, "0") == 0
+     || Ustrcmp(dkim_private_key_expanded, "false") == 0
+     )
+    continue;          /* don't sign, but no error */
+
+  if (dkim_private_key_expanded[0] == '/')
+    {
+    int privkey_fd, off = 0, len;
+
+    /* Looks like a filename, load the private key. */
+
+    memset(big_buffer, 0, big_buffer_size);
+
+    if ((privkey_fd = open(CS dkim_private_key_expanded, O_RDONLY)) < 0)
+      {
+      log_write(0, LOG_MAIN | LOG_PANIC, "unable to open "
+                "private key file for reading: %s",
+                dkim_private_key_expanded);
+      goto bad;
+      }
+
+    do
+      {
+      if ((len = read(privkey_fd, big_buffer + off, big_buffer_size - 2 - off)) < 0)
+       {
+       (void) close(privkey_fd);
+       log_write(0, LOG_MAIN|LOG_PANIC, "unable to read private key file: %s",
+                  dkim_private_key_expanded);
+       goto bad;
+       }
+      off += len;
+      }
+    while (len > 0);
+
+    (void) close(privkey_fd);
+    big_buffer[off] = '\0';
+    dkim_private_key_expanded = big_buffer;
+    }
+
+  ctx = pdkim_init_sign(CS dkim_signing_domain,
+                       CS dkim_signing_selector,
+                       CS dkim_private_key_expanded,
+                       PDKIM_ALGO_RSA_SHA256,
+                       dkim->dot_stuffed,
+                       &dkim_exim_query_dns_txt
+                       );
+  dkim_private_key_expanded[0] = '\0';
+  pdkim_set_optional(ctx,
+                     CS dkim_sign_headers_expanded,
+                     NULL,
+                     pdkim_canon,
+                     pdkim_canon, -1, 0, 0);
+
+  lseek(dkim_fd, 0, SEEK_SET);
+
+  while ((sread = read(dkim_fd, &buf, sizeof(buf))) > 0)
+    if ((pdkim_rc = pdkim_feed(ctx, buf, sread)) != PDKIM_OK)
+      goto pk_bad;
+
+  /* Handle failed read above. */
+  if (sread == -1)
+    {
+    debug_printf("DKIM: Error reading -K file.\n");
+    save_errno = errno;
+    goto bad;
+    }
+
+  if ((pdkim_rc = pdkim_feed_finish(ctx, &signature)) != PDKIM_OK)
+    goto pk_bad;
+
+  sigbuf = string_append(sigbuf, &sigsize, &sigptr, 2,
+                         US signature->signature_header, US"\r\n");
+
+  pdkim_free_ctx(ctx);
+  ctx = NULL;
+  }
+
+if (sigbuf)
+  {
+  sigbuf[sigptr] = '\0';
+  rc = sigbuf;
+  }
+else
+  rc = US"";
+
+CLEANUP:
+  if (ctx)
+    pdkim_free_ctx(ctx);
+  store_pool = old_pool;
+  errno = save_errno;
+  return rc;
+
+pk_bad:
+  log_write(0, LOG_MAIN|LOG_PANIC,
+               "DKIM: signing failed: %.100s", pdkim_errstr(pdkim_rc));
+bad:
+  rc = NULL;
+  goto CLEANUP;
+}
+
+#endif
index 15a7e60..8474962 100644 (file)
@@ -2,16 +2,16 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge, 1995 - 2007 */
+/* Copyright (c) University of Cambridge, 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
-uschar *dkim_exim_sign(int,uschar *,uschar *,uschar *,uschar *,uschar *);
-void    dkim_exim_verify_init(void);
+void    dkim_exim_init(void);
+uschar *dkim_exim_sign(int, struct ob_dkim *);
+void    dkim_exim_verify_init(BOOL);
 void    dkim_exim_verify_feed(uschar *, int);
 void    dkim_exim_verify_finish(void);
 void    dkim_exim_acl_setup(uschar *);
 uschar *dkim_exim_expand_query(int);
-uschar *dkim_exim_expand_defaults(int);
 
 #define DKIM_ALGO               1
 #define DKIM_BODYLENGTH         2
index 769e4d4..c005d4a 100644 (file)
@@ -57,7 +57,7 @@ static dmarc_exim_p dmarc_policy_description[] = {
 static error_block *
 add_to_eblock(error_block *eblock, uschar *t1, uschar *t2)
 {
-error_block *eb = malloc(sizeof(error_block));
+error_block *eb = store_malloc(sizeof(error_block));
 if (eblock == NULL)
   eblock = eb;
 else
@@ -150,6 +150,63 @@ int dmarc_store_data(header_line *hdr) {
 }
 
 
+static 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)
+  return;
+
+if (  dmarc_policy == DMARC_POLICY_REJECT     && action == DMARC_RESULT_REJECT
+   || dmarc_policy == DMARC_POLICY_QUARANTINE && action == DMARC_RESULT_QUARANTINE
+   || dmarc_policy == DMARC_POLICY_NONE       && action == DMARC_RESULT_REJECT
+   || dmarc_policy == DMARC_POLICY_NONE       && 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)
+       log_write(0, LOG_MAIN|LOG_PANIC,
+         "failure to send DMARC forensic report to %s", recipient);
+      }
+    }
+}
+
 /* dmarc_process adds the envelope sender address to the existing
    context (if any), retrieves the result, sets up expansion
    strings and evaluates the condition outcome. */
@@ -295,7 +352,8 @@ if (!dmarc_abort && !sender_host_authenticated)
       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 :
+       ves == PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD   ? ARES_RESULT_PERMERROR :
+       ves == PDKIM_VERIFY_INVALID_PUBKEY_IMPORT      ? ARES_RESULT_PERMERROR :
        ARES_RESULT_UNKNOWN :
       ARES_RESULT_UNKNOWN;
     dkim_history_buffer = string_sprintf("%sdkim %s %d\n", dkim_history_buffer,
@@ -517,60 +575,6 @@ else
 return DMARC_HIST_OK;
 }
 
-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)
-  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)
-       log_write(0, LOG_MAIN|LOG_PANIC,
-         "failure to send DMARC forensic report to %s", recipient);
-      }
-    }
-}
-
 uschar *
 dmarc_exim_expand_query(int what)
 {
index 63d451c..78e2a5b 100644 (file)
@@ -24,7 +24,6 @@ uschar *dmarc_exim_expand_query(int);
 uschar *dmarc_exim_expand_defaults(int);
 uschar *dmarc_auth_results_header(header_line *,uschar *);
 int dmarc_write_history_file();
-void dmarc_send_forensic_report(u_char **);
 
 #define DMARC_AR_HEADER        US"Authentication-Results:"
 #define DMARC_VERIFY_STATUS    1
index 6efb88d..e29f86c 100644 (file)
--- a/src/dns.c
+++ b/src/dns.c
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2017 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for interfacing with the DNS. */
 #include "exim.h"
 
 
-/* Function declaration needed for mutual recursion when A6 records
-are supported. */
-
-#if HAVE_IPV6
-#ifdef SUPPORT_A6
-static void dns_complete_a6(dns_address ***, dns_answer *, dns_record *,
-  int, uschar *);
-#endif
-#endif
-
 
 /*************************************************
 *               Fake DNS resolver                *
@@ -33,7 +23,7 @@ If not, it passes its arguments on to res_search(). The fake nameserver may
 also return a code specifying that the name should be passed on.
 
 Background: the original test suite required a real nameserver to carry the
-test zones, whereas the new test suit has the fake server for portability. This
+test zones, whereas the new test suite has the fake server for portability. This
 code supports both.
 
 Arguments:
@@ -46,11 +36,10 @@ Returns:      length of returned data, or -1 on error (h_errno set)
 */
 
 static int
-fakens_search(uschar *domain, int type, uschar *answerptr, int size)
+fakens_search(const uschar *domain, int type, uschar *answerptr, int size)
 {
 int len = Ustrlen(domain);
 int asize = size;                  /* Locally modified */
-uschar *endname;
 uschar name[256];
 uschar utilname[256];
 uschar *aptr = answerptr;          /* Locally modified */
@@ -61,38 +50,11 @@ struct stat statbuf;
 if (domain[len - 1] == '.') len--;
 Ustrncpy(name, domain, len);
 name[len] = 0;
-endname = name + len;
-
-/* This code, for forcing TRY_AGAIN and NO_RECOVERY, is here so that it works
-for the old test suite that uses a real nameserver. When the old test suite is
-eventually abandoned, this code could be moved into the fakens utility. */
-
-if (len >= 14 && Ustrcmp(endname - 14, "test.again.dns") == 0)
-  {
-  int delay = Uatoi(name);  /* digits at the start of the name */
-  DEBUG(D_dns) debug_printf("Return from DNS lookup of %s (%s) faked for testing\n",
-    name, dns_text_type(type));
-  if (delay > 0)
-    {
-    DEBUG(D_dns) debug_printf("delaying %d seconds\n", delay);
-    sleep(delay);
-    }
-  h_errno = TRY_AGAIN;
-  return -1;
-  }
-
-if (len >= 13 && Ustrcmp(endname - 13, "test.fail.dns") == 0)
-  {
-  DEBUG(D_dns) debug_printf("Return from DNS lookup of %s (%s) faked for testing\n",
-    name, dns_text_type(type));
-  h_errno = NO_RECOVERY;
-  return -1;
-  }
 
 /* Look for the fakens utility, and if it exists, call it. */
 
-(void)string_format(utilname, sizeof(utilname), "%s/../bin/fakens",
-  spool_directory);
+(void)string_format(utilname, sizeof(utilname), "%s/bin/fakens",
+  config_main_directory);
 
 if (stat(CS utilname, &statbuf) >= 0)
   {
@@ -100,11 +62,10 @@ if (stat(CS utilname, &statbuf) >= 0)
   int infd, outfd, rc;
   uschar *argv[5];
 
-  DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) using fakens\n",
-    name, dns_text_type(type));
+  DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) using fakens\n", name, dns_text_type(type));
 
   argv[0] = utilname;
-  argv[1] = spool_directory;
+  argv[1] = config_main_directory;
   argv[2] = name;
   argv[3] = dns_text_type(type);
   argv[4] = NULL;
@@ -123,6 +84,13 @@ if (stat(CS utilname, &statbuf) >= 0)
     asize -= rc;      /* may need to be passed on to res_search(). */
     }
 
+  /* If we ran out of output buffer before exhausting the return,
+  carry on reading and counting it. */
+
+  if (asize == 0)
+    while ((rc = read(outfd, name, sizeof(name))) > 0)
+      len += rc;
+
   if (rc < 0)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "read from fakens failed: %s",
       strerror(errno));
@@ -139,6 +107,10 @@ if (stat(CS utilname, &statbuf) >= 0)
     DEBUG(D_dns) debug_printf("fakens returned PASS_ON\n");
     }
   }
+else
+  {
+  DEBUG(D_dns) debug_printf("fakens (%s) not found\n", utilname);
+  }
 
 /* fakens utility not found, or it returned "pass on" */
 
@@ -257,9 +229,9 @@ Returns:     nothing
 */
 
 void
-dns_build_reverse(uschar *string, uschar *buffer)
+dns_build_reverse(const uschar *string, uschar *buffer)
 {
-uschar *p = string + Ustrlen(string);
+const uschar *p = string + Ustrlen(string);
 uschar *pp = buffer;
 
 /* Handle IPv4 address */
@@ -271,7 +243,7 @@ if (Ustrchr(string, ':') == NULL)
   int i;
   for (i = 0; i < 4; i++)
     {
-    uschar *ppp = p;
+    const uschar *ppp = p;
     while (ppp > string && ppp[-1] != '.') ppp--;
     Ustrncpy(pp, ppp, p - ppp);
     pp += p - ppp;
@@ -333,6 +305,15 @@ else
 
 
 
+/* Increment the aptr in dnss, checking against dnsa length.
+Return: TRUE for a bad result
+*/
+static BOOL
+dnss_inc_aptr(const dns_answer * dnsa, dns_scan * dnss, unsigned delta)
+{
+return (dnss->aptr += delta) >= dnsa->answer + dnsa->answerlen;
+}
+
 /*************************************************
 *       Get next DNS record from answer block    *
 *************************************************/
@@ -345,59 +326,89 @@ The result is in static storage which must be copied if it is to be preserved.
 Arguments:
   dnsa      pointer to dns answer block
   dnss      pointer to dns scan block
-  reset     option specifing what portion to scan, as described above
+  reset     option specifying what portion to scan, as described above
 
 Returns:    next dns record, or NULL when no more
 */
 
 dns_record *
-dns_next_rr(dns_answer *dnsa, dns_scan *dnss, int reset)
+dns_next_rr(const dns_answer *dnsa, dns_scan *dnss, int reset)
 {
-HEADER *h = (HEADER *)dnsa->answer;
+const HEADER * h = (const HEADER *)dnsa->answer;
 int namelen;
 
+char * trace = NULL;
+#ifdef rr_trace
+# define TRACE DEBUG(D_dns)
+#else
+trace = trace;
+# define TRACE if (FALSE)
+#endif
+
 /* Reset the saved data when requested to, and skip to the first required RR */
 
 if (reset != RESET_NEXT)
   {
   dnss->rrcount = ntohs(h->qdcount);
+  TRACE debug_printf("%s: reset (Q rrcount %d)\n", __FUNCTION__, dnss->rrcount);
   dnss->aptr = dnsa->answer + sizeof(HEADER);
 
   /* Skip over questions; failure to expand the name just gives up */
 
   while (dnss->rrcount-- > 0)
     {
+    TRACE trace = "Q-namelen";
     namelen = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
-      dnss->aptr, (DN_EXPAND_ARG4_TYPE) &(dnss->srr.name), DNS_MAXNAME);
-    if (namelen < 0) { dnss->rrcount = 0; return NULL; }
-    dnss->aptr += namelen + 4;    /* skip name & type & class */
+      dnss->aptr, (DN_EXPAND_ARG4_TYPE) &dnss->srr.name, DNS_MAXNAME);
+    if (namelen < 0) goto null_return;
+    /* skip name & type & class */
+    TRACE trace = "Q-skip";
+    if (dnss_inc_aptr(dnsa, dnss, namelen+4)) goto null_return;
     }
 
   /* Get the number of answer records. */
 
   dnss->rrcount = ntohs(h->ancount);
+  TRACE debug_printf("%s: reset (A rrcount %d)\n", __FUNCTION__, dnss->rrcount);
 
   /* Skip over answers if we want to look at the authority section. Also skip
   the NS records (i.e. authority section) if wanting to look at the additional
   records. */
 
-  if (reset == RESET_ADDITIONAL) dnss->rrcount += ntohs(h->nscount);
+  if (reset == RESET_ADDITIONAL)
+    {
+    TRACE debug_printf("%s: additional\n", __FUNCTION__);
+    dnss->rrcount += ntohs(h->nscount);
+    TRACE debug_printf("%s: reset (NS rrcount %d)\n", __FUNCTION__, dnss->rrcount);
+    }
 
   if (reset == RESET_AUTHORITY || reset == RESET_ADDITIONAL)
     {
+    TRACE if (reset == RESET_AUTHORITY)
+      debug_printf("%s: authority\n", __FUNCTION__);
     while (dnss->rrcount-- > 0)
       {
+      TRACE trace = "A-namelen";
       namelen = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
-        dnss->aptr, (DN_EXPAND_ARG4_TYPE) &(dnss->srr.name), DNS_MAXNAME);
-      if (namelen < 0) { dnss->rrcount = 0; return NULL; }
-      dnss->aptr += namelen + 8;            /* skip name, type, class & TTL */
+        dnss->aptr, (DN_EXPAND_ARG4_TYPE) &dnss->srr.name, DNS_MAXNAME);
+      if (namelen < 0) goto null_return;
+      /* skip name, type, class & TTL */
+      TRACE trace = "A-hdr";
+      if (dnss_inc_aptr(dnsa, dnss, namelen+8)) goto null_return;
       GETSHORT(dnss->srr.size, dnss->aptr); /* size of data portion */
-      dnss->aptr += dnss->srr.size;         /* skip over it */
+      /* skip over it */
+      TRACE trace = "A-skip";
+      if (dnss_inc_aptr(dnsa, dnss, dnss->srr.size)) goto null_return;
       }
-    dnss->rrcount = (reset == RESET_AUTHORITY)
+    dnss->rrcount = reset == RESET_AUTHORITY
       ? ntohs(h->nscount) : ntohs(h->arcount);
+    TRACE debug_printf("%s: reset (%s rrcount %d)\n", __FUNCTION__,
+      reset == RESET_AUTHORITY ? "NS" : "AR", dnss->rrcount);
     }
+  TRACE debug_printf("%s: %d RRs to read\n", __FUNCTION__, dnss->rrcount);
   }
+else
+  TRACE debug_printf("%s: next (%d left)\n", __FUNCTION__, dnss->rrcount);
 
 /* The variable dnss->aptr is now pointing at the next RR, and dnss->rrcount
 contains the number of RR records left. */
@@ -407,24 +418,67 @@ if (dnss->rrcount-- <= 0) return NULL;
 /* If expanding the RR domain name fails, behave as if no more records
 (something safe). */
 
+TRACE trace = "R-namelen";
 namelen = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, dnss->aptr,
-  (DN_EXPAND_ARG4_TYPE) &(dnss->srr.name), DNS_MAXNAME);
-if (namelen < 0) { dnss->rrcount = 0; return NULL; }
+  (DN_EXPAND_ARG4_TYPE) &dnss->srr.name, DNS_MAXNAME);
+if (namelen < 0) goto null_return;
 
 /* Move the pointer past the name and fill in the rest of the data structure
 from the following bytes. */
 
-dnss->aptr += namelen;
-GETSHORT(dnss->srr.type, dnss->aptr); /* Record type */
-dnss->aptr += 6;                      /* Don't want class or TTL */
-GETSHORT(dnss->srr.size, dnss->aptr); /* Size of data portion */
-dnss->srr.data = dnss->aptr;          /* The record's data follows */
-dnss->aptr += dnss->srr.size;         /* Advance to next RR */
+TRACE trace = "R-name";
+if (dnss_inc_aptr(dnsa, dnss, namelen)) goto null_return;
+
+GETSHORT(dnss->srr.type, dnss->aptr);          /* Record type */
+TRACE trace = "R-class";
+if (dnss_inc_aptr(dnsa, dnss, 2)) goto null_return;    /* Don't want class */
+GETLONG(dnss->srr.ttl, dnss->aptr);            /* TTL */
+GETSHORT(dnss->srr.size, dnss->aptr);          /* Size of data portion */
+dnss->srr.data = dnss->aptr;                   /* The record's data follows */
+
+/* Unchecked increment ok here since no further access on this iteration;
+will be checked on next at "R-name". */
+
+dnss->aptr += dnss->srr.size;                  /* Advance to next RR */
 
 /* Return a pointer to the dns_record structure within the dns_answer. This is
 for convenience so that the scans can use nice-looking for loops. */
 
-return &(dnss->srr);
+return &dnss->srr;
+
+null_return:
+  TRACE debug_printf("%s: terminate (%d RRs left). Last op: %s; errno %d %s\n",
+    __FUNCTION__, dnss->rrcount, trace, errno, strerror(errno));
+  dnss->rrcount = 0;
+  return NULL;
+}
+
+
+/* Extract the AUTHORITY information from the answer. If the answer isn't
+authoritative (AA not set), we do not extract anything.
+
+The AUTHORITY section contains NS records if the name in question was found,
+it contains a SOA record otherwise. (This is just from experience and some
+tests, is there some spec?)
+
+Scan the whole AUTHORITY section, since it may contain other records
+(e.g. NSEC3) too.
+
+Return: name for the authority, in an allocated string, or NULL if none found */
+
+static const uschar *
+dns_extract_auth_name(const dns_answer * dnsa) /* FIXME: const dns_answer */
+{
+dns_scan dnss;
+dns_record * rr;
+const HEADER * h = (const HEADER *) dnsa->answer;
+
+if (h->nscount && h->aa)
+  for (rr = dns_next_rr(dnsa, &dnss, RESET_AUTHORITY);
+       rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
+    if (rr->type == (h->ancount ? T_NS : T_SOA))
+      return string_copy(rr->name);
+return NULL;
 }
 
 
@@ -436,25 +490,74 @@ return &(dnss->srr);
 
 /* We do not perform DNSSEC work ourselves; if the administrator has installed
 a verifying resolver which sets AD as appropriate, though, we'll use that.
-(AD = Authentic Data)
+(AD = Authentic Data, AA = Authoritative Answer)
 
 Argument:   pointer to dns answer block
 Returns:    bool indicating presence of AD bit
 */
 
 BOOL
-dns_is_secure(dns_answer *dnsa)
+dns_is_secure(const dns_answer * dnsa)
 {
 #ifdef DISABLE_DNSSEC
 DEBUG(D_dns)
   debug_printf("DNSSEC support disabled at build-time; dns_is_secure() false\n");
 return FALSE;
 #else
-HEADER *h = (HEADER *)dnsa->answer;
-return h->ad ? TRUE : FALSE;
+const HEADER * h = (const HEADER *) dnsa->answer;
+const uschar * auth_name;
+const uschar * trusted;
+
+if (h->ad) return TRUE;
+
+/* If the resolver we ask is authoritative for the domain in question, it
+* may not set the AD but the AA bit. If we explicitly trust
+* the resolver for that domain (via a domainlist in dns_trust_aa),
+* we return TRUE to indicate a secure answer.
+*/
+
+if (  !h->aa
+   || !dns_trust_aa
+   || !(trusted = expand_string(dns_trust_aa))
+   || !*trusted
+   || !(auth_name = dns_extract_auth_name(dnsa))
+   || OK != match_isinlist(auth_name, &trusted, 0, NULL, NULL,
+                           MCL_DOMAIN, TRUE, NULL)
+   )
+  return FALSE;
+
+DEBUG(D_dns) debug_printf("DNS faked the AD bit "
+  "(got AA and matched with dns_trust_aa (%s in %s))\n",
+  auth_name, dns_trust_aa);
+
+return TRUE;
+#endif
+}
+
+static void
+dns_set_insecure(dns_answer * dnsa)
+{
+#ifndef DISABLE_DNSSEC
+HEADER * h = (HEADER *)dnsa->answer;
+h->aa = h->ad = 0;
 #endif
 }
 
+/************************************************
+ *     Check whether the AA bit is set         *
+ *     We need this to warn if we requested AD *
+ *     from an authoritative server            *
+ ************************************************/
+
+BOOL
+dns_is_aa(const dns_answer *dnsa)
+{
+#ifdef DISABLE_DNSSEC
+return FALSE;
+#else
+return ((const HEADER*)dnsa->answer)->aa;
+#endif
+}
 
 
 
@@ -510,26 +613,24 @@ Returns:     the return code
 */
 
 static int
-dns_return(uschar *name, int type, int rc)
+dns_return(const uschar * name, int type, int rc)
 {
 res_state resp = os_get_dns_resolver_res();
 tree_node *node = store_get_perm(sizeof(tree_node) + 290);
 sprintf(CS node->name, "%.255s-%s-%lx", name, dns_text_type(type),
-  resp->options);
+  (unsigned long) resp->options);
 node->data.val = rc;
 (void)tree_insertnode(&tree_dns_fails, node);
 return rc;
 }
 
-
-
 /*************************************************
 *              Do basic DNS lookup               *
 *************************************************/
 
 /* Call the resolver to look up the given domain name, using the given type,
 and check the result. The error code TRY_AGAIN is documented as meaning "non-
-Authoritive Host not found, or SERVERFAIL". Sometimes there are badly set
+Authoritative Host not found, or SERVERFAIL". Sometimes there are badly set
 up nameservers that produce this error continually, so there is the option of
 providing a list of domains for which this is treated as a non-existent
 host.
@@ -549,11 +650,11 @@ Returns:    DNS_SUCCEED   successful lookup
 */
 
 int
-dns_basic_lookup(dns_answer *dnsa, uschar *name, int type)
+dns_basic_lookup(dns_answer *dnsa, const uschar *name, int type)
 {
 #ifndef STAND_ALONE
 int rc = -1;
-uschar *save;
+const uschar *save_domain;
 #endif
 res_state resp = os_get_dns_resolver_res();
 
@@ -566,9 +667,8 @@ have many addresses in the same domain. We rely on the resolver and name server
 caching for successful lookups. */
 
 sprintf(CS node_name, "%.255s-%s-%lx", name, dns_text_type(type),
-  resp->options);
-previous = tree_search(tree_dns_fails, node_name);
-if (previous != NULL)
+  (unsigned long) resp->options);
+if ((previous = tree_search(tree_dns_fails, node_name)))
   {
   DEBUG(D_dns) debug_printf("DNS lookup of %.255s-%s: using cached value %s\n",
     name, dns_text_type(type),
@@ -579,7 +679,26 @@ if (previous != NULL)
   return previous->data.val;
   }
 
-/* If configured, check the hygene of the name passed to lookup. Otherwise,
+#ifdef SUPPORT_I18N
+/* Convert all names to a-label form before doing lookup */
+  {
+  uschar * alabel;
+  uschar * errstr = NULL;
+  DEBUG(D_dns) if (string_is_utf8(name))
+    debug_printf("convert utf8 '%s' to alabel for for lookup\n", name);
+  if ((alabel = string_domain_utf8_to_alabel(name, &errstr)), errstr)
+    {
+    DEBUG(D_dns)
+      debug_printf("DNS name '%s' utf8 conversion to alabel failed: %s\n", name,
+        errstr);
+    host_find_failed_syntax = TRUE;
+    return DNS_NOMATCH;
+    }
+  name = alabel;
+  }
+#endif
+
+/* If configured, check the hygiene of the name passed to lookup. Otherwise,
 although DNS lookups may give REFUSED at the lower level, some resolvers
 turn this into TRY_AGAIN, which is silly. Give a NOMATCH return, since such
 domains cannot be in the DNS. The check is now done by a regular expression;
@@ -597,24 +716,22 @@ For SRV records, we omit the initial _smtp._tcp. components at the start. */
 
 if (check_dns_names_pattern[0] != 0 && type != T_PTR && type != T_TXT)
   {
-  uschar *checkname = name;
+  const uschar *checkname = name;
   int ovector[3*(EXPAND_MAXN+1)];
 
-  if (regex_check_dns_names == NULL)
-    regex_check_dns_names =
-      regex_must_compile(check_dns_names_pattern, FALSE, TRUE);
+  dns_pattern_init();
 
   /* For an SRV lookup, skip over the first two components (the service and
   protocol names, which both start with an underscore). */
 
-  if (type == T_SRV)
+  if (type == T_SRV || type == T_TLSA)
     {
     while (*checkname++ != '.');
     while (*checkname++ != '.');
     }
 
-  if (pcre_exec(regex_check_dns_names, NULL, CS checkname, Ustrlen(checkname),
-      0, PCRE_EOPT, ovector, sizeof(ovector)/sizeof(int)) < 0)
+  if (pcre_exec(regex_check_dns_names, NULL, CCS checkname, Ustrlen(checkname),
+      0, PCRE_EOPT, ovector, nelem(ovector)) < 0)
     {
     DEBUG(D_dns)
       debug_printf("DNS name syntax check failed: %s (%s)\n", name,
@@ -635,75 +752,70 @@ the IP address instead of returning -1 with h_error=HOST_NOT_FOUND. Some
 nameservers are also believed to do this. It is, of course, contrary to the
 specification of the DNS, so we lock it out. */
 
-if ((
-    #ifdef SUPPORT_A6
-    type == T_A6 ||
-    #endif
-    type == T_A || type == T_AAAA) &&
-    string_is_ip_address(name, NULL) != 0)
+if ((type == T_A || type == T_AAAA) && string_is_ip_address(name, NULL) != 0)
   return DNS_NOMATCH;
 
 /* If we are running in the test harness, instead of calling the normal resolver
 (res_search), we call fakens_search(), which recognizes certain special
 domains, and interfaces to a fake nameserver for certain special zones. */
 
-if (running_in_test_harness)
-  dnsa->answerlen = fakens_search(name, type, dnsa->answer, MAXPACKET);
-else
-  dnsa->answerlen = res_search(CS name, C_IN, type, dnsa->answer, MAXPACKET);
+dnsa->answerlen = running_in_test_harness
+  ? fakens_search(name, type, dnsa->answer, sizeof(dnsa->answer))
+  : res_search(CCS name, C_IN, type, dnsa->answer, sizeof(dnsa->answer));
 
-if (dnsa->answerlen > MAXPACKET)
+if (dnsa->answerlen > (int) sizeof(dnsa->answer))
   {
-  DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) resulted in overlong packet (size %d), truncating to %d.\n",
-    name, dns_text_type(type), dnsa->answerlen, MAXPACKET);
-  dnsa->answerlen = MAXPACKET;
+  DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) resulted in overlong packet"
+    " (size %d), truncating to %u.\n",
+    name, dns_text_type(type), dnsa->answerlen, (unsigned int) sizeof(dnsa->answer));
+  dnsa->answerlen = sizeof(dnsa->answer);
   }
 
 if (dnsa->answerlen < 0) switch (h_errno)
   {
   case HOST_NOT_FOUND:
-  DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave HOST_NOT_FOUND\n"
-    "returning DNS_NOMATCH\n", name, dns_text_type(type));
-  return dns_return(name, type, DNS_NOMATCH);
+    DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave HOST_NOT_FOUND\n"
+      "returning DNS_NOMATCH\n", name, dns_text_type(type));
+    return dns_return(name, type, DNS_NOMATCH);
 
   case TRY_AGAIN:
-  DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave TRY_AGAIN\n",
-    name, dns_text_type(type));
-
-  /* Cut this out for various test programs */
-  #ifndef STAND_ALONE
-  save = deliver_domain;
-  deliver_domain = name;  /* set $domain */
-  rc = match_isinlist(name, &dns_again_means_nonexist, 0, NULL, NULL,
-    MCL_DOMAIN, TRUE, NULL);
-  deliver_domain = save;
-  if (rc != OK)
-    {
-    DEBUG(D_dns) debug_printf("returning DNS_AGAIN\n");
-    return dns_return(name, type, DNS_AGAIN);
-    }
-  DEBUG(D_dns) debug_printf("%s is in dns_again_means_nonexist: returning "
-    "DNS_NOMATCH\n", name);
-  return dns_return(name, type, DNS_NOMATCH);
+    DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave TRY_AGAIN\n",
+      name, dns_text_type(type));
 
-  #else   /* For stand-alone tests */
-  return dns_return(name, type, DNS_AGAIN);
-  #endif
+    /* Cut this out for various test programs */
+#ifndef STAND_ALONE
+    save_domain = deliver_domain;
+    deliver_domain = string_copy(name);  /* set $domain */
+    rc = match_isinlist(name, (const uschar **)&dns_again_means_nonexist, 0, NULL, NULL,
+      MCL_DOMAIN, TRUE, NULL);
+    deliver_domain = save_domain;
+    if (rc != OK)
+      {
+      DEBUG(D_dns) debug_printf("returning DNS_AGAIN\n");
+      return dns_return(name, type, DNS_AGAIN);
+      }
+    DEBUG(D_dns) debug_printf("%s is in dns_again_means_nonexist: returning "
+      "DNS_NOMATCH\n", name);
+    return dns_return(name, type, DNS_NOMATCH);
+
+#else   /* For stand-alone tests */
+    return dns_return(name, type, DNS_AGAIN);
+#endif
 
   case NO_RECOVERY:
-  DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave NO_RECOVERY\n"
-    "returning DNS_FAIL\n", name, dns_text_type(type));
-  return dns_return(name, type, DNS_FAIL);
+    DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave NO_RECOVERY\n"
+      "returning DNS_FAIL\n", name, dns_text_type(type));
+    return dns_return(name, type, DNS_FAIL);
 
   case NO_DATA:
-  DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave NO_DATA\n"
-    "returning DNS_NODATA\n", name, dns_text_type(type));
-  return dns_return(name, type, DNS_NODATA);
+    DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave NO_DATA\n"
+      "returning DNS_NODATA\n", name, dns_text_type(type));
+    return dns_return(name, type, DNS_NODATA);
 
   default:
-  DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave unknown DNS error %d\n"
-    "returning DNS_FAIL\n", name, dns_text_type(type), h_errno);
-  return dns_return(name, type, DNS_FAIL);
+    DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave unknown DNS error %d\n"
+      "returning DNS_FAIL\n", name, dns_text_type(type), h_errno);
+    return dns_return(name, type, DNS_FAIL);
   }
 
 DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) succeeded\n",
@@ -732,7 +844,8 @@ won't return any.
 If fully_qualified_name is not NULL, set it to point to the full name
 returned by the resolver, if this is different to what it is given, unless
 the returned name starts with "*" as some nameservers seem to be returning
-wildcards in this form.
+wildcards in this form.  In international mode "different" means "alabel
+forms are different".
 
 Arguments:
   dnsa                  pointer to dns_answer structure
@@ -749,23 +862,26 @@ Returns:                DNS_SUCCEED   successful lookup
 */
 
 int
-dns_lookup(dns_answer *dnsa, uschar *name, int type, uschar **fully_qualified_name)
+dns_lookup(dns_answer *dnsa, const uschar *name, int type,
+  const uschar **fully_qualified_name)
 {
 int i;
-uschar *orig_name = name;
+const uschar *orig_name = name;
+BOOL secure_so_far = TRUE;
 
 /* Loop to follow CNAME chains so far, but no further... */
 
 for (i = 0; i < 10; i++)
   {
-  uschar data[256];
+  uschar * data;
   dns_record *rr, cname_rr, type_rr;
   dns_scan dnss;
   int datalen, rc;
 
   /* DNS lookup failures get passed straight back. */
 
-  if ((rc = dns_basic_lookup(dnsa, name, type)) != DNS_SUCCEED) return rc;
+  if ((rc = dns_basic_lookup(dnsa, name, type)) != DNS_SUCCEED)
+    return rc;
 
   /* We should have either records of the required type, or a CNAME record,
   or both. We need to know whether both exist for getting the fully qualified
@@ -775,51 +891,61 @@ for (i = 0; i < 10; i++)
 
   cname_rr.data = type_rr.data = NULL;
   for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
-       rr != NULL;
-       rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
-    {
+       rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
     if (rr->type == type)
       {
       if (type_rr.data == NULL) type_rr = *rr;
       if (cname_rr.data != NULL) break;
       }
-    else if (rr->type == T_CNAME) cname_rr = *rr;
-    }
+    else if (rr->type == T_CNAME)
+      cname_rr = *rr;
 
   /* For the first time round this loop, if a CNAME was found, take the fully
   qualified name from it; otherwise from the first data record, if present. */
 
-  if (i == 0 && fully_qualified_name != NULL)
+  if (i == 0 && fully_qualified_name)
     {
-    if (cname_rr.data != NULL)
-      {
-      if (Ustrcmp(cname_rr.name, *fully_qualified_name) != 0 &&
-          cname_rr.name[0] != '*')
-        *fully_qualified_name = string_copy_dnsdomain(cname_rr.name);
-      }
-    else if (type_rr.data != NULL)
-      {
-      if (Ustrcmp(type_rr.name, *fully_qualified_name) != 0 &&
-          type_rr.name[0] != '*')
-        *fully_qualified_name = string_copy_dnsdomain(type_rr.name);
-      }
+    uschar * rr_name = cname_rr.data
+      ? cname_rr.name : type_rr.data ? type_rr.name : NULL;
+    if (  rr_name
+       && Ustrcmp(rr_name, *fully_qualified_name) != 0
+       && rr_name[0] != '*'
+#ifdef SUPPORT_I18N
+       && (  !string_is_utf8(*fully_qualified_name)
+         || Ustrcmp(rr_name,
+              string_domain_utf8_to_alabel(*fully_qualified_name, NULL)) != 0
+         )
+#endif
+       )
+        *fully_qualified_name = string_copy_dnsdomain(rr_name);
     }
 
   /* If any data records of the correct type were found, we are done. */
 
-  if (type_rr.data != NULL) return DNS_SUCCEED;
+  if (type_rr.data)
+    {
+    if (!secure_so_far)        /* mark insecure if any element of CNAME chain was */
+      dns_set_insecure(dnsa);
+    return DNS_SUCCEED;
+    }
 
   /* If there are no data records, we need to re-scan the DNS using the
   domain given in the CNAME record, which should exist (otherwise we should
   have had a failure from dns_lookup). However code against the possibility of
   its not existing. */
 
-  if (cname_rr.data == NULL) return DNS_FAIL;
-  datalen = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
-    cname_rr.data, (DN_EXPAND_ARG4_TYPE)data, 256);
-  if (datalen < 0) return DNS_FAIL;
+  if (!cname_rr.data)
+    return DNS_FAIL;
+
+  data = store_get(256);
+  if ((datalen = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
+    cname_rr.data, (DN_EXPAND_ARG4_TYPE)data, 256)) < 0)
+    return DNS_FAIL;
   name = data;
 
+  if (!dns_is_secure(dnsa))
+    secure_so_far = FALSE;
+
   DEBUG(D_dns) debug_printf("CNAME found: change to %s\n", name);
   }       /* Loop back to do another lookup */
 
@@ -839,7 +965,7 @@ return DNS_FAIL;
 *    Do a DNS lookup and handle virtual types   *
 ************************************************/
 
-/* This function handles some invented "lookup types" that synthesize feature
+/* This function handles some invented "lookup types" that synthesize features
 not available in the basic types. The special types all have negative values.
 Positive type values are passed straight on to dns_lookup().
 
@@ -858,176 +984,178 @@ Returns:                DNS_SUCCEED   successful lookup
 */
 
 int
-dns_special_lookup(dns_answer *dnsa, uschar *name, int type,
-  uschar **fully_qualified_name)
+dns_special_lookup(dns_answer *dnsa, const uschar *name, int type,
+  const uschar **fully_qualified_name)
 {
-if (type >= 0) return dns_lookup(dnsa, name, type, fully_qualified_name);
-
-/* The "mx hosts only" type doesn't require any special action here */
-
-if (type == T_MXH) return dns_lookup(dnsa, name, T_MX, fully_qualified_name);
-
-/* Find nameservers for the domain or the nearest enclosing zone, excluding the
-root servers. */
-
-if (type == T_ZNS)
+switch (type)
   {
-  uschar *d = name;
-  while (d != 0)
+  /* The "mx hosts only" type doesn't require any special action here */
+  case T_MXH:
+    return dns_lookup(dnsa, name, T_MX, fully_qualified_name);
+
+  /* Find nameservers for the domain or the nearest enclosing zone, excluding
+  the root servers. */
+  case T_ZNS:
+    type = T_NS;
+    /* FALLTHROUGH */
+  case T_SOA:
     {
-    int rc = dns_lookup(dnsa, d, T_NS, fully_qualified_name);
-    if (rc != DNS_NOMATCH && rc != DNS_NODATA) return rc;
-    while (*d != 0 && *d != '.') d++;
-    if (*d++ == 0) break;
+    const uschar *d = name;
+    while (d != 0)
+      {
+      int rc = dns_lookup(dnsa, d, type, fully_qualified_name);
+      if (rc != DNS_NOMATCH && rc != DNS_NODATA) return rc;
+      while (*d != 0 && *d != '.') d++;
+      if (*d++ == 0) break;
+      }
+    return DNS_NOMATCH;
     }
-  return DNS_NOMATCH;
-  }
 
-/* Try to look up the Client SMTP Authorization SRV record for the name. If
-there isn't one, search from the top downwards for a CSA record in a parent
-domain, which might be making assertions about subdomains. If we find a record
-we set fully_qualified_name to whichever lookup succeeded, so that the caller
-can tell whether to look at the explicit authorization field or the subdomain
-assertion field. */
-
-if (type == T_CSA)
-  {
-  uschar *srvname, *namesuff, *tld, *p;
-  int priority, weight, port;
-  int limit, rc, i;
-  BOOL ipv6;
-  dns_record *rr;
-  dns_scan dnss;
-
-  DEBUG(D_dns) debug_printf("CSA lookup of %s\n", name);
-
-  srvname = string_sprintf("_client._smtp.%s", name);
-  rc = dns_lookup(dnsa, srvname, T_SRV, NULL);
-  if (rc == DNS_SUCCEED || rc == DNS_AGAIN)
+  /* Try to look up the Client SMTP Authorization SRV record for the name. If
+  there isn't one, search from the top downwards for a CSA record in a parent
+  domain, which might be making assertions about subdomains. If we find a record
+  we set fully_qualified_name to whichever lookup succeeded, so that the caller
+  can tell whether to look at the explicit authorization field or the subdomain
+  assertion field. */
+  case T_CSA:
     {
-    if (rc == DNS_SUCCEED) *fully_qualified_name = name;
-    return rc;
-    }
-
-  /* Search for CSA subdomain assertion SRV records from the top downwards,
-  starting with the 2nd level domain. This order maximizes cache-friendliness.
-  We skip the top level domains to avoid loading their nameservers and because
-  we know they'll never have CSA SRV records. */
-
-  namesuff = Ustrrchr(name, '.');
-  if (namesuff == NULL) return DNS_NOMATCH;
-  tld = namesuff + 1;
-  ipv6 = FALSE;
-  limit = dns_csa_search_limit;
+    uschar *srvname, *namesuff, *tld, *p;
+    int priority, weight, port;
+    int limit, rc, i;
+    BOOL ipv6;
+    dns_record *rr;
+    dns_scan dnss;
 
-  /* Use more appropriate search parameters if we are in the reverse DNS. */
+    DEBUG(D_dns) debug_printf("CSA lookup of %s\n", name);
 
-  if (strcmpic(namesuff, US".arpa") == 0)
-    {
-    if (namesuff - 8 > name && strcmpic(namesuff - 8, US".in-addr.arpa") == 0)
-      {
-      namesuff -= 8;
-      tld = namesuff + 1;
-      limit = 3;
-      }
-    else if (namesuff - 4 > name && strcmpic(namesuff - 4, US".ip6.arpa") == 0)
+    srvname = string_sprintf("_client._smtp.%s", name);
+    rc = dns_lookup(dnsa, srvname, T_SRV, NULL);
+    if (rc == DNS_SUCCEED || rc == DNS_AGAIN)
       {
-      namesuff -= 4;
-      tld = namesuff + 1;
-      ipv6 = TRUE;
-      limit = 3;
+      if (rc == DNS_SUCCEED) *fully_qualified_name = string_copy(name);
+      return rc;
       }
-    }
-
-  DEBUG(D_dns) debug_printf("CSA TLD %s\n", tld);
 
-  /* Do not perform the search if the top level or 2nd level domains do not
-  exist. This is quite common, and when it occurs all the search queries would
-  go to the root or TLD name servers, which is not friendly. So we check the
-  AUTHORITY section; if it contains the root's SOA record or the TLD's SOA then
-  the TLD or the 2LD (respectively) doesn't exist and we can skip the search.
-  If the TLD and the 2LD exist but the explicit CSA record lookup failed, then
-  the AUTHORITY SOA will be the 2LD's or a subdomain thereof. */
-
-  if (rc == DNS_NOMATCH)
-    {
-    /* This is really gross. The successful return value from res_search() is
-    the packet length, which is stored in dnsa->answerlen. If we get a
-    negative DNS reply then res_search() returns -1, which causes the bounds
-    checks for name decompression to fail when it is treated as a packet
-    length, which in turn causes the authority search to fail. The correct
-    packet length has been lost inside libresolv, so we have to guess a
-    replacement value. (The only way to fix this properly would be to
-    re-implement res_search() and res_query() so that they don't muddle their
-    success and packet length return values.) For added safety we only reset
-    the packet length if the packet header looks plausible. */
-
-    HEADER *h = (HEADER *)dnsa->answer;
-    if (h->qr == 1 && h->opcode == QUERY && h->tc == 0
-        && (h->rcode == NOERROR || h->rcode == NXDOMAIN)
-        && ntohs(h->qdcount) == 1 && ntohs(h->ancount) == 0
-        && ntohs(h->nscount) >= 1)
-      dnsa->answerlen = MAXPACKET;
-
-    for (rr = dns_next_rr(dnsa, &dnss, RESET_AUTHORITY);
-         rr != NULL;
-         rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
-      if (rr->type != T_SOA) continue;
-      else if (strcmpic(rr->name, US"") == 0 ||
-               strcmpic(rr->name, tld) == 0) return DNS_NOMATCH;
-      else break;
-    }
-
-  for (i = 0; i < limit; i++)
-    {
-    if (ipv6)
+    /* Search for CSA subdomain assertion SRV records from the top downwards,
+    starting with the 2nd level domain. This order maximizes cache-friendliness.
+    We skip the top level domains to avoid loading their nameservers and because
+    we know they'll never have CSA SRV records. */
+
+    namesuff = Ustrrchr(name, '.');
+    if (namesuff == NULL) return DNS_NOMATCH;
+    tld = namesuff + 1;
+    ipv6 = FALSE;
+    limit = dns_csa_search_limit;
+
+    /* Use more appropriate search parameters if we are in the reverse DNS. */
+
+    if (strcmpic(namesuff, US".arpa") == 0)
+      if (namesuff - 8 > name && strcmpic(namesuff - 8, US".in-addr.arpa") == 0)
+       {
+       namesuff -= 8;
+       tld = namesuff + 1;
+       limit = 3;
+       }
+      else if (namesuff - 4 > name && strcmpic(namesuff - 4, US".ip6.arpa") == 0)
+       {
+       namesuff -= 4;
+       tld = namesuff + 1;
+       ipv6 = TRUE;
+       limit = 3;
+       }
+
+    DEBUG(D_dns) debug_printf("CSA TLD %s\n", tld);
+
+    /* Do not perform the search if the top level or 2nd level domains do not
+    exist. This is quite common, and when it occurs all the search queries would
+    go to the root or TLD name servers, which is not friendly. So we check the
+    AUTHORITY section; if it contains the root's SOA record or the TLD's SOA then
+    the TLD or the 2LD (respectively) doesn't exist and we can skip the search.
+    If the TLD and the 2LD exist but the explicit CSA record lookup failed, then
+    the AUTHORITY SOA will be the 2LD's or a subdomain thereof. */
+
+    if (rc == DNS_NOMATCH)
       {
-      /* Scan through the IPv6 reverse DNS in chunks of 16 bits worth of IP
-      address, i.e. 4 hex chars and 4 dots, i.e. 8 chars. */
-      namesuff -= 8;
-      if (namesuff <= name) return DNS_NOMATCH;
+      /* This is really gross. The successful return value from res_search() is
+      the packet length, which is stored in dnsa->answerlen. If we get a
+      negative DNS reply then res_search() returns -1, which causes the bounds
+      checks for name decompression to fail when it is treated as a packet
+      length, which in turn causes the authority search to fail. The correct
+      packet length has been lost inside libresolv, so we have to guess a
+      replacement value. (The only way to fix this properly would be to
+      re-implement res_search() and res_query() so that they don't muddle their
+      success and packet length return values.) For added safety we only reset
+      the packet length if the packet header looks plausible. */
+
+      const HEADER * h = (const HEADER *)dnsa->answer;
+      if (h->qr == 1 && h->opcode == QUERY && h->tc == 0
+         && (h->rcode == NOERROR || h->rcode == NXDOMAIN)
+         && ntohs(h->qdcount) == 1 && ntohs(h->ancount) == 0
+         && ntohs(h->nscount) >= 1)
+           dnsa->answerlen = MAXPACKET;
+
+      for (rr = dns_next_rr(dnsa, &dnss, RESET_AUTHORITY);
+          rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
+         )
+       if (rr->type != T_SOA) continue;
+       else if (strcmpic(rr->name, US"") == 0 ||
+                strcmpic(rr->name, tld) == 0) return DNS_NOMATCH;
+       else break;
       }
-    else
-      /* Find the start of the preceding domain name label. */
-      do
-        if (--namesuff <= name) return DNS_NOMATCH;
-      while (*namesuff != '.');
 
-    DEBUG(D_dns) debug_printf("CSA parent search at %s\n", namesuff + 1);
-
-    srvname = string_sprintf("_client._smtp.%s", namesuff + 1);
-    rc = dns_lookup(dnsa, srvname, T_SRV, NULL);
-    if (rc == DNS_AGAIN) return rc;
-    if (rc != DNS_SUCCEED) continue;
-
-    /* Check that the SRV record we have found is worth returning. We don't
-    just return the first one we find, because some lower level SRV record
-    might make stricter assertions than its parent domain. */
-
-    for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
-         rr != NULL;
-         rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
+    for (i = 0; i < limit; i++)
       {
-      if (rr->type != T_SRV) continue;
-
-      /* Extract the numerical SRV fields (p is incremented) */
-      p = rr->data;
-      GETSHORT(priority, p);
-      GETSHORT(weight, p);
-      GETSHORT(port, p);
-
-      /* Check the CSA version number */
-      if (priority != 1) continue;
-
-      /* If it's making an interesting assertion, return this response. */
-      if (port & 1)
-        {
-        *fully_qualified_name = namesuff + 1;
-        return DNS_SUCCEED;
-        }
+      if (ipv6)
+       {
+       /* Scan through the IPv6 reverse DNS in chunks of 16 bits worth of IP
+       address, i.e. 4 hex chars and 4 dots, i.e. 8 chars. */
+       namesuff -= 8;
+       if (namesuff <= name) return DNS_NOMATCH;
+       }
+      else
+       /* Find the start of the preceding domain name label. */
+       do
+         if (--namesuff <= name) return DNS_NOMATCH;
+       while (*namesuff != '.');
+
+      DEBUG(D_dns) debug_printf("CSA parent search at %s\n", namesuff + 1);
+
+      srvname = string_sprintf("_client._smtp.%s", namesuff + 1);
+      rc = dns_lookup(dnsa, srvname, T_SRV, NULL);
+      if (rc == DNS_AGAIN) return rc;
+      if (rc != DNS_SUCCEED) continue;
+
+      /* Check that the SRV record we have found is worth returning. We don't
+      just return the first one we find, because some lower level SRV record
+      might make stricter assertions than its parent domain. */
+
+      for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
+          rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) if (rr->type == T_SRV)
+       {
+       const uschar * p = rr->data;
+
+       /* Extract the numerical SRV fields (p is incremented) */
+       GETSHORT(priority, p);
+       GETSHORT(weight, p);    weight = weight; /* compiler quietening */
+       GETSHORT(port, p);
+
+       /* Check the CSA version number */
+       if (priority != 1) continue;
+
+       /* If it's making an interesting assertion, return this response. */
+       if (port & 1)
+         {
+         *fully_qualified_name = namesuff + 1;
+         return DNS_SUCCEED;
+         }
+       }
       }
+    return DNS_NOMATCH;
     }
-  return DNS_NOMATCH;
+
+  default:
+    if (type >= 0)
+      return dns_lookup(dnsa, name, type, fully_qualified_name);
   }
 
 /* Control should never reach here */
@@ -1037,224 +1165,67 @@ return DNS_FAIL;
 
 
 
-/* Support for A6 records has been commented out since they were demoted to
-experimental status at IETF 51. */
-
-#if HAVE_IPV6 && defined(SUPPORT_A6)
-
-/*************************************************
-*        Search DNS block for prefix RRs         *
-*************************************************/
-
-/* Called from dns_complete_a6() to search an additional section or a main
-answer section for required prefix records to complete an IPv6 address obtained
-from an A6 record. For each prefix record, a recursive call to dns_complete_a6
-is made, with a new copy of the address so far.
-
-Arguments:
-  dnsa       the DNS answer block
-  which      RESET_ADDITIONAL or RESET_ANSWERS
-  name       name of prefix record
-  yptrptr    pointer to the pointer that points to where to hang the next
-               dns_address structure
-  bits       number of bits we have already got
-  bitvec     the bits we have already got
-
-Returns:     TRUE if any records were found
-*/
-
-static BOOL
-dns_find_prefix(dns_answer *dnsa, int which, uschar *name, dns_address
-  ***yptrptr, int bits, uschar *bitvec)
-{
-BOOL yield = FALSE;
-dns_record *rr;
-dns_scan dnss;
-
-for (rr = dns_next_rr(dnsa, &dnss, which);
-     rr != NULL;
-     rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
-  {
-  uschar cbitvec[16];
-  if (rr->type != T_A6 || strcmpic(rr->name, name) != 0) continue;
-  yield = TRUE;
-  memcpy(cbitvec, bitvec, sizeof(cbitvec));
-  dns_complete_a6(yptrptr, dnsa, rr, bits, cbitvec);
-  }
-
-return yield;
-}
-
-
-
-/*************************************************
-*            Follow chains of A6 records         *
-*************************************************/
-
-/* A6 records may be incomplete, with pointers to other records containing more
-bits of the address. There can be a tree structure, leading to a number of
-addresses originating from a single initial A6 record.
-
-Arguments:
-  yptrptr    pointer to the pointer that points to where to hang the next
-               dns_address structure
-  dnsa       the current DNS answer block
-  rr         the RR we have at present
-  bits       number of bits we have already got
-  bitvec     the bits we have already got
-
-Returns:     nothing
-*/
-
-static void
-dns_complete_a6(dns_address ***yptrptr, dns_answer *dnsa, dns_record *rr,
-  int bits, uschar *bitvec)
-{
-static uschar bitmask[] = { 0xff, 0xfe, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x80 };
-uschar *p = (uschar *)(rr->data);
-int prefix_len, suffix_len;
-int i, j, k;
-uschar *chainptr;
-uschar chain[264];
-dns_answer cdnsa;
-
-/* The prefix length is the first byte. It defines the prefix which is missing
-from the data in this record as a number of bits. Zero means this is the end of
-a chain. The suffix is the data in this record; only sufficient bytes to hold
-it are supplied. There may be zero bytes. We have to ignore trailing bits that
-we have already obtained from earlier RRs in the chain. */
-
-prefix_len = *p++;                      /* bits */
-suffix_len = (128 - prefix_len + 7)/8;  /* bytes */
-
-/* If the prefix in this record is greater than the prefix in the previous
-record in the chain, we have to ignore the record (RFC 2874). */
-
-if (prefix_len > 128 - bits) return;
-
-/* In this little loop, the number of bits up to and including the current byte
-is held in k. If we have none of the bits in this byte, we can just or it into
-the current data. If we have all of the bits in this byte, we skip it.
-Otherwise, some masking has to be done. */
-
-for (i = suffix_len - 1, j = 15, k = 8; i >= 0; i--)
-  {
-  int required = k - bits;
-  if (required >= 8) bitvec[j] |= p[i];
-    else if (required > 0) bitvec[j] |= p[i] & bitmask[required];
-  j--;     /* I tried putting these in the "for" statement, but gcc muttered */
-  k += 8;  /* about computed values not being used. */
-  }
-
-/* If the prefix_length is zero, we are at the end of a chain. Build a
-dns_address item with the current data, hang it onto the end of the chain,
-adjust the hanging pointer, and we are done. */
-
-if (prefix_len == 0)
-  {
-  dns_address *new = store_get(sizeof(dns_address) + 50);
-  inet_ntop(AF_INET6, bitvec, CS new->address, 50);
-  new->next = NULL;
-  **yptrptr = new;
-  *yptrptr = &(new->next);
-  return;
-  }
-
-/* Prefix length is not zero. Reset the number of bits that we have collected
-so far, and extract the chain name. */
-
-bits = 128 - prefix_len;
-p += suffix_len;
-
-chainptr = chain;
-while ((i = *p++) != 0)
-  {
-  if (chainptr != chain) *chainptr++ = '.';
-  memcpy(chainptr, p, i);
-  chainptr += i;
-  p += i;
-  }
-*chainptr = 0;
-chainptr = chain;
-
-/* Now scan the current DNS response record to see if the additional section
-contains the records we want. This processing can be cut out for testing
-purposes. */
-
-if (dns_find_prefix(dnsa, RESET_ADDITIONAL, chainptr, yptrptr, bits, bitvec))
-  return;
-
-/* No chain records were found in the current DNS response block. Do a new DNS
-lookup to try to find these records. This opens up the possibility of DNS
-failures. We ignore them at this point; if all branches of the tree fail, there
-will be no addresses at the end. */
-
-if (dns_lookup(&cdnsa, chainptr, T_A6, NULL) == DNS_SUCCEED)
-  (void)dns_find_prefix(&cdnsa, RESET_ANSWERS, chainptr, yptrptr, bits, bitvec);
-}
-#endif  /* HAVE_IPV6 && defined(SUPPORT_A6) */
-
-
 
 
 /*************************************************
 *          Get address(es) from DNS record       *
 *************************************************/
 
-/* The record type is either T_A for an IPv4 address or T_AAAA (or T_A6 when
-supported) for an IPv6 address. In the A6 case, there may be several addresses,
-generated by following chains. A recursive function does all the hard work. A6
-records now look like passing into history, so the code is only included when
-explicitly asked for.
+/* The record type is either T_A for an IPv4 address or T_AAAA for an IPv6 address.
 
 Argument:
   dnsa       the DNS answer block
   rr         the RR
 
-Returns:     pointer a chain of dns_address items
+Returns:     pointer to a chain of dns_address items; NULL when the dnsa was overrun
 */
 
 dns_address *
 dns_address_from_rr(dns_answer *dnsa, dns_record *rr)
 {
-dns_address *yield = NULL;
-
-#if HAVE_IPV6 && defined(SUPPORT_A6)
-dns_address **yieldptr = &yield;
-uschar bitvec[16];
-#else
-dnsa = dnsa;    /* Stop picky compilers warning */
-#endif
+dns_address * yield = NULL;
+uschar * dnsa_lim = dnsa->answer + dnsa->answerlen;
 
 if (rr->type == T_A)
   {
-  uschar *p = (uschar *)(rr->data);
-  yield = store_get(sizeof(dns_address) + 20);
-  (void)sprintf(CS yield->address, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
-  yield->next = NULL;
+  uschar *p = US rr->data;
+  if (p + 4 <= dnsa_lim)
+    {
+    yield = store_get(sizeof(dns_address) + 20);
+    (void)sprintf(CS yield->address, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
+    yield->next = NULL;
+    }
   }
 
 #if HAVE_IPV6
 
-#ifdef SUPPORT_A6
-else if (rr->type == T_A6)
-  {
-  memset(bitvec, 0, sizeof(bitvec));
-  dns_complete_a6(&yieldptr, dnsa, rr, 0, bitvec);
-  }
-#endif  /* SUPPORT_A6 */
-
 else
   {
-  yield = store_get(sizeof(dns_address) + 50);
-  inet_ntop(AF_INET6, (uschar *)(rr->data), CS yield->address, 50);
-  yield->next = NULL;
+  if (rr->data + 16 <= dnsa_lim)
+    {
+    struct in6_addr in6;
+    int i;
+    for (i = 0; i < 16; i++) in6.s6_addr[i] = rr->data[i];
+    yield = store_get(sizeof(dns_address) + 50);
+    inet_ntop(AF_INET6, &in6, CS yield->address, 50);
+    yield->next = NULL;
+    }
   }
 #endif  /* HAVE_IPV6 */
 
 return yield;
 }
 
+
+
+void
+dns_pattern_init(void)
+{
+if (check_dns_names_pattern[0] != 0 && !regex_check_dns_names)
+  regex_check_dns_names =
+    regex_must_compile(check_dns_names_pattern, FALSE, TRUE);
+}
+
 /* vi: aw ai sw=2
 */
 /* End of dns.c */
index c2d8668..3e1c5e6 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -21,7 +21,7 @@ int lookup_list_count = 0;
 
 static int lookup_list_init_done = 0;
 
-/* Table of information about all possible authentication mechamisms. All
+/* Table of information about all possible authentication mechanisms. All
 entries are always present if any mechanism is declared, but the functions are
 set to NULL for those that are not compiled into the binary. */
 
@@ -53,6 +53,10 @@ set to NULL for those that are not compiled into the binary. */
 #include "auths/spa.h"
 #endif
 
+#ifdef AUTH_TLS
+#include "auths/tls.h"
+#endif
+
 auth_info auths_available[] = {
 
 /* Checking by an expansion condition on plain text */
@@ -155,6 +159,20 @@ auth_info auths_available[] = {
   },
 #endif
 
+#ifdef AUTH_TLS
+  {
+  US"tls",                                   /* lookup name */
+  auth_tls_options,
+  &auth_tls_options_count,
+  &auth_tls_option_defaults,
+  sizeof(auth_tls_options_block),
+  auth_tls_init,                             /* init function */
+  auth_tls_server,                           /* server function */
+  NULL,                                      /* client function */
+  NULL                                       /* diagnostic function */
+  },
+#endif
+
 { US"", NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL  }
 };
 
@@ -210,6 +228,10 @@ exim binary. */
 #include "transports/pipe.h"
 #endif
 
+#ifdef EXPERIMENTAL_QUEUEFILE
+#include "transports/queuefile.h"
+#endif
+
 #ifdef TRANSPORT_SMTP
 #include "transports/smtp.h"
 #endif
@@ -371,6 +393,20 @@ transport_info transports_available[] = {
   TRUE                                         /* local flag */
   },
 #endif
+#ifdef EXPERIMENTAL_QUEUEFILE
+  {
+  US"queuefile",                               /* driver name */
+  queuefile_transport_options,                 /* local options table */
+  &queuefile_transport_options_count,          /* number of entries */
+  &queuefile_transport_option_defaults,        /* private options defaults */
+  sizeof(queuefile_transport_options_block),   /* size of private block */
+  queuefile_transport_init,                    /* init entry point */
+  queuefile_transport_entry,                   /* main entry point */
+  NULL,                                        /* no tidyup entry */
+  NULL,                                        /* no closedown entry */
+  TRUE                                         /* local flag */
+  },
+#endif
 #ifdef TRANSPORT_SMTP
   {
   US"smtp",                                    /* driver name */
@@ -397,37 +433,42 @@ struct lookupmodulestr
 
 static struct lookupmodulestr *lookupmodules = NULL;
 
-static void addlookupmodule(void *dl, struct lookup_module_info *info)
+static void
+addlookupmodule(void *dl, struct lookup_module_info *info)
 {
-  struct lookupmodulestr *p = store_malloc(sizeof(struct lookupmodulestr));
-  p->dl = dl;
-  p->info = info;
-  p->next = lookupmodules;
-  lookupmodules = p;
-  lookup_list_count += info->lookupcount;
+struct lookupmodulestr *p = store_malloc(sizeof(struct lookupmodulestr));
+
+p->dl = dl;
+p->info = info;
+p->next = lookupmodules;
+lookupmodules = p;
+lookup_list_count += info->lookupcount;
 }
 
 /* only valid after lookup_list and lookup_list_count are assigned */
-static void add_lookup_to_list(lookup_info *info)
+static void
+add_lookup_to_list(lookup_info *info)
 {
-  /* need to add the lookup to lookup_list, sorted */
-  int pos = 0;
-
-  /* strategy is to go through the list until we find
-   * either an empty spot or a name that is higher.
-   * this can't fail because we have enough space. */
-  while (lookup_list[pos]
-      && (Ustrcmp(lookup_list[pos]->name, info->name) <= 0)) {
-    pos++;
-  }
-  if (lookup_list[pos]) {
-    /* need to insert it, so move all the other items up
-     * (last slot is still empty, of course) */
-    memmove(&lookup_list[pos+1],
-            &lookup_list[pos],
-            sizeof(lookup_info **) * (lookup_list_count-pos-1));
+/* need to add the lookup to lookup_list, sorted */
+int pos = 0;
+
+/* strategy is to go through the list until we find
+either an empty spot or a name that is higher.
+this can't fail because we have enough space. */
+
+while (lookup_list[pos] && (Ustrcmp(lookup_list[pos]->name, info->name) <= 0))
+  pos++;
+
+if (lookup_list[pos])
+  {
+  /* need to insert it, so move all the other items up
+  (last slot is still empty, of course) */
+
+  memmove(&lookup_list[pos+1],
+         &lookup_list[pos],
+         sizeof(lookup_info *) * (lookup_list_count-pos-1));
   }
-  lookup_list[pos] = info;
+lookup_list[pos] = info;
 }
 
 
@@ -435,62 +476,67 @@ static void add_lookup_to_list(lookup_info *info)
  * which give parse errors on an extern in function scope.  Each entry needs
  * to also be invoked in init_lookup_list() below  */
 
-#if defined(LOOKUP_WHOSON) && LOOKUP_WHOSON!=2
-extern lookup_module_info whoson_lookup_module_info;
+#if defined(LOOKUP_CDB) && LOOKUP_CDB!=2
+extern lookup_module_info cdb_lookup_module_info;
 #endif
-#if defined(LOOKUP_TESTDB) && LOOKUP_TESTDB!=2
-extern lookup_module_info testdb_lookup_module_info;
+#if defined(LOOKUP_DBM) && LOOKUP_DBM!=2
+extern lookup_module_info dbmdb_lookup_module_info;
 #endif
-#if defined(LOOKUP_SQLITE) && LOOKUP_SQLITE!=2
-extern lookup_module_info sqlite_lookup_module_info;
+#if defined(LOOKUP_DNSDB) && LOOKUP_DNSDB!=2
+extern lookup_module_info dnsdb_lookup_module_info;
 #endif
-#ifdef EXPERIMENTAL_SPF
-extern lookup_module_info spf_lookup_module_info;
+#if defined(LOOKUP_DSEARCH) && LOOKUP_DSEARCH!=2
+extern lookup_module_info dsearch_lookup_module_info;
 #endif
-#ifdef EXPERIMENTAL_REDIS
-extern lookup_module_info redis_lookup_module_info;
+#if defined(LOOKUP_IBASE) && LOOKUP_IBASE!=2
+extern lookup_module_info ibase_lookup_module_info;
 #endif
-#if defined(LOOKUP_PGSQL) && LOOKUP_PGSQL!=2
-extern lookup_module_info pgsql_lookup_module_info;
+#if defined(LOOKUP_LDAP)
+extern lookup_module_info ldap_lookup_module_info;
 #endif
-#if defined(LOOKUP_PASSWD) && LOOKUP_PASSWD!=2
-extern lookup_module_info passwd_lookup_module_info;
+#if defined(LOOKUP_LSEARCH) && LOOKUP_LSEARCH!=2
+extern lookup_module_info lsearch_lookup_module_info;
 #endif
-#if defined(LOOKUP_ORACLE) && LOOKUP_ORACLE!=2
-extern lookup_module_info oracle_lookup_module_info;
+#if defined(LOOKUP_MYSQL) && LOOKUP_MYSQL!=2
+extern lookup_module_info mysql_lookup_module_info;
+#endif
+#if defined(LOOKUP_NIS) && LOOKUP_NIS!=2
+extern lookup_module_info nis_lookup_module_info;
 #endif
 #if defined(LOOKUP_NISPLUS) && LOOKUP_NISPLUS!=2
 extern lookup_module_info nisplus_lookup_module_info;
 #endif
-#if defined(LOOKUP_NIS) && LOOKUP_NIS!=2
-extern lookup_module_info nis_lookup_module_info;
+#if defined(LOOKUP_ORACLE) && LOOKUP_ORACLE!=2
+extern lookup_module_info oracle_lookup_module_info;
 #endif
-#if defined(LOOKUP_MYSQL) && LOOKUP_MYSQL!=2
-extern lookup_module_info mysql_lookup_module_info;
+#if defined(LOOKUP_PASSWD) && LOOKUP_PASSWD!=2
+extern lookup_module_info passwd_lookup_module_info;
 #endif
-#if defined(LOOKUP_LSEARCH) && LOOKUP_LSEARCH!=2
-extern lookup_module_info lsearch_lookup_module_info;
+#if defined(LOOKUP_PGSQL) && LOOKUP_PGSQL!=2
+extern lookup_module_info pgsql_lookup_module_info;
 #endif
-#ifdef LOOKUP_LDAP
-extern lookup_module_info ldap_lookup_module_info;
+#if defined(LOOKUP_REDIS) && LOOKUP_REDIS!=2
+extern lookup_module_info redis_lookup_module_info;
 #endif
-#if defined(LOOKUP_IBASE) && LOOKUP_IBASE!=2
-extern lookup_module_info ibase_lookup_module_info;
+#if defined(EXPERIMENTAL_LMDB)
+extern lookup_module_info lmdb_lookup_module_info;
 #endif
-#if defined(LOOKUP_DSEARCH) && LOOKUP_DSEARCH!=2
-extern lookup_module_info dsearch_lookup_module_info;
+#if defined(EXPERIMENTAL_SPF)
+extern lookup_module_info spf_lookup_module_info;
 #endif
-#if defined(LOOKUP_DNSDB) && LOOKUP_DNSDB!=2
-extern lookup_module_info dnsdb_lookup_module_info;
+#if defined(LOOKUP_SQLITE) && LOOKUP_SQLITE!=2
+extern lookup_module_info sqlite_lookup_module_info;
 #endif
-#if defined(LOOKUP_DBM) && LOOKUP_DBM!=2
-extern lookup_module_info dbmdb_lookup_module_info;
+#if defined(LOOKUP_TESTDB) && LOOKUP_TESTDB!=2
+extern lookup_module_info testdb_lookup_module_info;
 #endif
-#if defined(LOOKUP_CDB) && LOOKUP_CDB!=2
-extern lookup_module_info cdb_lookup_module_info;
+#if defined(LOOKUP_WHOSON) && LOOKUP_WHOSON!=2
+extern lookup_module_info whoson_lookup_module_info;
 #endif
 
-void init_lookup_list(void)
+
+void
+init_lookup_list(void)
 {
 #ifdef LOOKUP_MODULE_DIR
   DIR *dd;
@@ -499,8 +545,6 @@ void init_lookup_list(void)
   int moduleerrors = 0;
 #endif
   struct lookupmodulestr *p;
-  const pcre *regex_islookupmod = regex_must_compile(
-      US"\\." DYNLIB_FN_EXT "$", FALSE, TRUE);
 
   if (lookup_list_init_done)
     return;
@@ -558,10 +602,14 @@ void init_lookup_list(void)
   addlookupmodule(NULL, &pgsql_lookup_module_info);
 #endif
 
-#ifdef EXPERIMENTAL_REDIS
+#if defined(LOOKUP_REDIS) && LOOKUP_REDIS!=2
   addlookupmodule(NULL, &redis_lookup_module_info);
 #endif
 
+#ifdef EXPERIMENTAL_LMDB
+  addlookupmodule(NULL, &lmdb_lookup_module_info);
+#endif
+
 #ifdef EXPERIMENTAL_SPF
   addlookupmodule(NULL, &spf_lookup_module_info);
 #endif
@@ -585,6 +633,9 @@ void init_lookup_list(void)
     log_write(0, LOG_MAIN, "Couldn't open %s: not loading lookup modules\n", LOOKUP_MODULE_DIR);
   }
   else {
+    const pcre *regex_islookupmod = regex_must_compile(
+      US"\\." DYNLIB_FN_EXT "$", FALSE, TRUE);
+
     DEBUG(D_lookup) debug_printf("Loading lookup modules from %s\n", LOOKUP_MODULE_DIR);
     while ((ent = readdir(dd)) != NULL) {
       char *name = ent->d_name;
@@ -641,14 +692,13 @@ void init_lookup_list(void)
         countmodules++;
       }
     }
+    store_free((void*)regex_islookupmod);
     closedir(dd);
   }
 
   DEBUG(D_lookup) debug_printf("Loaded %d lookup modules\n", countmodules);
 #endif
 
-  store_free((void*)regex_islookupmod);
-
   DEBUG(D_lookup) debug_printf("Total %d lookups\n", lookup_list_count);
 
   lookup_list = store_malloc(sizeof(lookup_info *) * lookup_list_count);
index 6cd243c..573fc00 100644 (file)
--- a/src/enq.c
+++ b/src/enq.c
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions concerned with serialization. */
 /* This function is called when a host is listed for serialization of
 connections. It is also called when ETRN is listed for serialization. We open
 the misc database and look for a record, which implies an existing connection
-or ETRN run. If not found, create one and return TRUE.
+or ETRN run. If increasing the count would take us past the given limit
+value return FALSE.  If not, bump it and return TRUE.  If not found, create
+one with value 1 and return TRUE.
 
 Arguments:
   key            string on which to serialize
+  lim            parallelism limit
 
 Returns:         TRUE if OK to proceed; FALSE otherwise
 */
 
 
 BOOL
-enq_start(uschar *key)
+enq_start(uschar *key, unsigned lim)
 {
 dbdata_serialize *serial_record;
 dbdata_serialize new_record;
@@ -44,25 +47,31 @@ deliberate; the dbfn_open() function - which is an Exim function - always tries
 to create if it can't open a read/write file. It expects only O_RDWR or
 O_RDONLY as its argument. */
 
-dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE);
-if (dbm_file == NULL) return FALSE;
+if (!(dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE)))
+  return FALSE;
 
 /* See if there is a record for this host or queue run; if there is, we cannot
 proceed with the connection unless the record is very old. */
 
 serial_record = dbfn_read(dbm_file, key);
-if (serial_record != NULL && time(NULL) - serial_record->time_stamp < 6*60*60)
+if (serial_record && time(NULL) - serial_record->time_stamp < 6*60*60)
   {
-  dbfn_close(dbm_file);
-  DEBUG(D_transport) debug_printf("outstanding serialization record for %s\n",
-    key);
-  return FALSE;
+  if (serial_record->count >= lim)
+    {
+    dbfn_close(dbm_file);
+    DEBUG(D_transport) debug_printf("outstanding serialization record for %s\n",
+      key);
+    return FALSE;
+    }
+  new_record.count = serial_record->count + 1;
   }
+else
+  new_record.count = 1;
 
-/* We can proceed - insert a new record or update the old one. At present
-the count field is not used; just set it to 1. */
+/* We can proceed - insert a new record or update the old one. */
 
-new_record.count = 1;
+DEBUG(D_transport) debug_printf("write serialization record for %s val %d\n",
+      key, new_record.count);
 dbfn_write(dbm_file, key, &new_record, (int)sizeof(dbdata_serialize));
 dbfn_close(dbm_file);
 return TRUE;
@@ -88,12 +97,25 @@ enq_end(uschar *key)
 {
 open_db dbblock;
 open_db *dbm_file;
+dbdata_serialize *serial_record;
 
 DEBUG(D_transport) debug_printf("end serialized: %s\n", key);
 
-dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE);
-if (dbm_file == NULL) return;
-dbfn_delete(dbm_file, key);
+if (  !(dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE))
+   || !(serial_record = dbfn_read(dbm_file, key))
+   )
+  return;
+if (--serial_record->count > 0)
+  {
+  DEBUG(D_transport) debug_printf("write serialization record for %s val %d\n",
+      key, serial_record->count);
+  dbfn_write(dbm_file, key, serial_record, (int)sizeof(dbdata_serialize));
+  }
+else
+  {
+  DEBUG(D_transport) debug_printf("remove serialization record for %s\n", key);
+  dbfn_delete(dbm_file, key);
+  }
 dbfn_close(dbm_file);
 }
 
diff --git a/src/environment.c b/src/environment.c
new file mode 100644 (file)
index 0000000..c394eb7
--- /dev/null
@@ -0,0 +1,72 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Heiko Schlittermann 2016
+ * hs@schlittermann.de
+ * See the file NOTICE for conditions of use and distribution.
+ */
+
+#include "exim.h"
+
+extern char **environ;
+
+/* The cleanup_environment() function is used during the startup phase
+of the Exim process, right after reading the configurations main
+part, before any expansions take place. It retains the environment
+variables we trust (via the keep_environment option) and allows to
+set additional variables (via add_environment).
+
+Returns:    TRUE if successful
+            FALSE otherwise
+*/
+
+BOOL
+cleanup_environment()
+{
+if (!keep_environment || *keep_environment == '\0')
+  {
+  /* From: https://github.com/dovecot/core/blob/master/src/lib/env-util.c#L55
+  Try to clear the environment.
+  a) environ = NULL crashes on OS X.
+  b) *environ = NULL doesn't work on FreeBSD 7.0.
+  c) environ = emptyenv doesn't work on Haiku OS
+  d) environ = calloc() should work everywhere */
+
+  if (environ) *environ = NULL;
+
+  }
+else if (Ustrcmp(keep_environment, "*") != 0)
+  {
+  uschar **p;
+  if (environ) for (p = USS environ; *p; /* see below */)
+    {
+    /* It's considered broken if we do not find the '=', according to
+    Florian Weimer. For now we ignore such strings. unsetenv() would complain,
+    getenv() would complain. */
+    uschar * eqp = Ustrchr(*p, '=');
+
+    if (eqp)
+      {
+      uschar * name = string_copyn(*p, eqp - *p);
+
+      if (OK != match_isinlist(name, CUSS &keep_environment,
+          0, NULL, NULL, MCL_NOEXPAND, FALSE, NULL))
+        if (os_unsetenv(name) < 0) return FALSE;
+        else p = USS environ; /* RESTART from the beginning */
+      else p++;
+      store_reset(name);
+      }
+    }
+  }
+if (add_environment)
+  {
+    uschar * p;
+    int sep = 0;
+    const uschar * envlist = add_environment;
+
+    while ((p = string_nextinlist(&envlist, &sep, NULL, 0))) putenv(CS p);
+  }
+
+  return TRUE;
+}
index 01d1f2f..4fb160a 100644 (file)
@@ -1,6 +1,6 @@
 #! /bin/sh
 
-# Copyright (c) University of Cambridge, 1995 - 2007
+# Copyright (c) University of Cambridge, 1995 - 2015
 # See the file NOTICE for conditions of use and distribution.
 
 # This script takes the following command line arguments:
@@ -232,16 +232,16 @@ $b
 # Now do the job. First remove the files that have "fallen off the bottom".
 # Look for both the compressed and uncompressed forms.
 
-if [ $keep -lt 10 ]; then keept=0$keep; else keept=$keep; fi;
+if [ $keep -lt 10 ]; then rotation=0$keep; else rotation=$keep; fi;
 
-if [ -f $mainlog.$keept ]; then $rm $mainlog.$keept; fi;
-if [ -f $mainlog.$keept.$suffix ]; then $rm $mainlog.$keept.$suffix; fi;
+if [ -f $mainlog.$rotation ]; then $rm $mainlog.$rotation; fi;
+if [ -f $mainlog.$rotation.$suffix ]; then $rm $mainlog.$rotation.$suffix; fi;
 
-if [ -f $rejectlog.$keept ]; then $rm $rejectlog.$keept; fi;
-if [ -f $rejectlog.$keept.$suffix ]; then $rm $rejectlog.$keept.$suffix; fi;
+if [ -f $rejectlog.$rotation ]; then $rm $rejectlog.$rotation; fi;
+if [ -f $rejectlog.$rotation.$suffix ]; then $rm $rejectlog.$rotation.$suffix; fi;
 
-if [ -f $paniclog.$keept ]; then $rm $paniclog.$keept; fi;
-if [ -f $paniclog.$keept.$suffix ]; then $rm $paniclog.$keept.$suffix; fi;
+if [ -f $paniclog.$rotation ]; then $rm $paniclog.$rotation; fi;
+if [ -f $paniclog.$rotation.$suffix ]; then $rm $paniclog.$rotation.$suffix; fi;
 
 # Now rename all the previous old files by increasing their numbers by 1.
 # When the number is less than 10, insert a leading zero.
@@ -282,28 +282,34 @@ done
 
 if [ $keep -gt 99 ]; then first=001; else first=01; fi
 
+# Grab our pid ro avoid race in file creation
+ourpid=$$
+
 if [ -f $mainlog ]; then
   $mv $mainlog $mainlog.$first
   $chown $user:$group $mainlog.$first
-  $touch $mainlog
-  $chown $user:$group $mainlog
-  $chmod 640 $mainlog
+  $touch $mainlog.$ourpid
+  $chown $user:$group $mainlog.$ourpid
+  $chmod 640 $mainlog.$ourpid
+  $mv $mainlog.$ourpid $mainlog
 fi
 
 if [ -f $rejectlog ]; then
   $mv $rejectlog $rejectlog.$first
   $chown $user:$group $rejectlog.$first
-  $touch $rejectlog
-  $chown $user:$group $rejectlog
-  $chmod 640 $rejectlog
+  $touch $rejectlog.$ourpid
+  $chown $user:$group $rejectlog.$ourpid
+  $chmod 640 $rejectlog.$ourpid
+  $mv $rejectlog.$ourpid $rejectlog
 fi
 
 if [ -f $paniclog ]; then
   $mv $paniclog $paniclog.$first
   $chown $user:$group $paniclog.$first
-  $touch $paniclog
-  $chown $user:$group $paniclog
-  $chmod 640 $paniclog
+  $touch $paniclog.$ourpid
+  $chown $user:$group $paniclog.$ourpid
+  $chmod 640 $paniclog.$ourpid
+  $mv $paniclog.$ourpid $paniclog
 fi
 
 # Now scan the (0)02 and later files, compressing where necessary, and
index 2d3b40c..faa5cb7 100644 (file)
@@ -1,8 +1,10 @@
-#! PERL_COMMAND -w
+#! PERL_COMMAND
 
+use warnings;
 use strict;
+BEGIN { pop @INC if $INC[-1] eq '.' };
 
-# Copyright (c) 2007-2014 University of Cambridge.
+# Copyright (c) 2007-2015 University of Cambridge.
 # See the file NOTICE for conditions of use and distribution.
 
 # Except when they appear in comments, the following placeholders in this
@@ -60,6 +62,11 @@ return $seconds;
 
 my (%saved, %id_list, $pattern, $queue_time, $insensitive, $invert);
 
+# If using "related" option, have to track extra message IDs
+my $related;
+my $related_re='';
+my @Mids = ();
+
 sub do_line {
 
 # Convert syslog lines to mainlog format, as in eximstats.
@@ -90,8 +97,16 @@ if (defined $id)
     }
   else
     {
-    $id_list{$id} = 1 if defined $id_list{$id} ||
-      ($insensitive && /$pattern/io) || /$pattern/o;
+    if (defined $id_list{$id} ||
+      ($insensitive && /$pattern/io) || /$pattern/o)
+      {
+      $id_list{$id} = 1;
+      get_related_ids($id) if $related;
+      }
+    elsif ($related && $related_re)
+      {
+      grep_for_related($_, $id);
+      }
     }
 
   # See if this is a completion for some message. If it is interesting,
@@ -161,7 +176,7 @@ sub detect_compressor_capable
     {
     if ($filename =~ /\.(?:$ext)$/)
       {
-      # Just die if compressor not found; if this occurrs in the middle of
+      # Just die if compressor not found; if this occurs 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 '');
@@ -173,16 +188,30 @@ sub detect_compressor_capable
   return $cmdline;
   }
 
+sub grep_for_related {
+  my ($line,$id) = @_;
+  $id_list{$id} = 1 if $line =~ m/$related_re/;
+}
+
+sub get_related_ids {
+  my ($id) = @_;
+  push @Mids, $id unless grep /\b$id\b/, @Mids;
+  my $re = join '|', @Mids;
+  $related_re = qr/$re/;
+}
+
 # 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
-# which is an additional condition.
+# which is an additional condition. The -M flag will also display "related"
+# loglines (msgid from matched lines is searched in following lines).
 
-getopts('Ilvt:',\my %args);
+getopts('Ilvt:M',\my %args);
 $queue_time  = $args{'t'}? $args{'t'} : -1;
 $insensitive = $args{'I'}? 0 : 1;
 $invert      = $args{'v'}? 1 : 0;
+$related     = $args{'M'}? 1 : 0;
 
-die "usage: exigrep [-I] [-l] [-t <seconds>] [-v] <pattern> [<log file>]...\n"
+die "usage: exigrep [-I] [-l] [-M] [-t <seconds>] [-v] <pattern> [<log file>]...\n"
   if ($#ARGV < 0);
 
 $pattern = shift @ARGV;
@@ -197,7 +226,7 @@ if (@ARGV)
   foreach (@ARGV)
     {
     my $filename = $_;
-    if ($filename =~ /\.(?:COMPRESS_SUFFIX)$/o)
+    if (-x 'ZCAT_COMMAND' && $filename =~ /\.(?:COMPRESS_SUFFIX)$/o)
       {
       open(LOG, "ZCAT_COMMAND $filename |") ||
         die "Unable to zcat $filename: $!\n";
index 517b543..a6a1ea8 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -12,6 +12,17 @@ Also a few functions that don't naturally fit elsewhere. */
 
 #include "exim.h"
 
+#ifdef __GLIBC__
+# include <gnu/libc-version.h>
+#endif
+
+#ifdef USE_GNUTLS
+# include <gnutls/gnutls.h>
+# if GNUTLS_VERSION_NUMBER < 0x030103 && !defined(DISABLE_OCSP)
+#  define DISABLE_OCSP
+# endif
+#endif
+
 extern void init_lookup_list(void);
 
 
@@ -81,7 +92,7 @@ Returns:      pointer to the compiled pattern
 */
 
 const pcre *
-regex_must_compile(uschar *pattern, BOOL caseless, BOOL use_malloc)
+regex_must_compile(const uschar *pattern, BOOL caseless, BOOL use_malloc)
 {
 int offset;
 int options = PCRE_COPT;
@@ -93,7 +104,7 @@ if (use_malloc)
   pcre_free = function_store_free;
   }
 if (caseless) options |= PCRE_CASELESS;
-yield = pcre_compile(CS pattern, options, (const char **)&error, &offset, NULL);
+yield = pcre_compile(CCS pattern, options, (const char **)&error, &offset, NULL);
 pcre_malloc = function_store_get;
 pcre_free = function_dummy_free;
 if (yield == NULL)
@@ -124,10 +135,11 @@ Returns:      TRUE or FALSE
 */
 
 BOOL
-regex_match_and_setup(const pcre *re, uschar *subject, int options, int setup)
+regex_match_and_setup(const pcre *re, const uschar *subject, int options, int setup)
 {
 int ovector[3*(EXPAND_MAXN+1)];
-int n = pcre_exec(re, NULL, CS subject, Ustrlen(subject), 0,
+uschar * s = string_copy(subject);     /* de-constifying */
+int n = pcre_exec(re, NULL, CS s, Ustrlen(s), 0,
   PCRE_EOPT | options, ovector, sizeof(ovector)/sizeof(int));
 BOOL yield = n >= 0;
 if (n == 0) n = EXPAND_MAXN + 1;
@@ -137,7 +149,7 @@ if (yield)
   expand_nmax = (setup < 0)? 0 : setup + 1;
   for (nn = (setup < 0)? 0 : 2; nn < n*2; nn += 2)
     {
-    expand_nstring[expand_nmax] = subject + ovector[nn];
+    expand_nstring[expand_nmax] = s + ovector[nn];
     expand_nlength[expand_nmax++] = ovector[nn+1] - ovector[nn];
     }
   expand_nmax--;
@@ -162,10 +174,8 @@ Returns:   nothing
 void
 set_process_info(const char *format, ...)
 {
-int len;
+int len = sprintf(CS process_info, "%5d ", (int)getpid());
 va_list ap;
-sprintf(CS process_info, "%5d ", (int)getpid());
-len = Ustrlen(process_info);
 va_start(ap, format);
 if (!string_vformat(process_info + len, PROCESS_INFO_SIZE - len - 2, format, ap))
   Ustrcpy(process_info + len, "**** string overflowed buffer ****");
@@ -222,7 +232,7 @@ to disrupt whatever is going on outside the signal handler. */
 
 if (fd < 0) return;
 
-{int dummy = write(fd, process_info, process_info_len); dummy = dummy; }
+(void)write(fd, process_info, process_info_len);
 (void)close(fd);
 }
 
@@ -267,6 +277,10 @@ will wait for ever, so we panic in this instance. (There was a case of this
 when a bug in a function that calls milliwait() caused it to pass invalid data.
 That's when I added the check. :-)
 
+We assume it to be not worth sleeping for under 100us; this value will
+require revisiting as hardware advances.  This avoids the issue of
+a zero-valued timer setting meaning "never fire".
+
 Argument:  an itimerval structure containing the interval
 Returns:   nothing
 */
@@ -276,6 +290,9 @@ milliwait(struct itimerval *itval)
 {
 sigset_t sigmask;
 sigset_t old_sigmask;
+
+if (itval->it_value.tv_usec < 100 && itval->it_value.tv_sec == 0)
+  return;
 (void)sigemptyset(&sigmask);                           /* Empty mask */
 (void)sigaddset(&sigmask, SIGALRM);                    /* Add SIGALRM */
 (void)sigprocmask(SIG_BLOCK, &sigmask, &old_sigmask);  /* Block SIGALRM */
@@ -348,7 +365,7 @@ return 0;
 /* Exim uses a time + a pid to generate a unique identifier in two places: its
 message IDs, and in file names for maildir deliveries. Because some OS now
 re-use pids within the same second, sub-second times are now being used.
-However, for absolute certaintly, we must ensure the clock has ticked before
+However, for absolute certainty, we must ensure the clock has ticked before
 allowing the relevant process to complete. At the time of implementation of
 this code (February 2003), the speed of processors is such that the clock will
 invariably have ticked already by the time a process has done its job. This
@@ -398,11 +415,11 @@ if (exim_tvcmp(&now_tv, then_tv) <= 0)
     {
     if (!running_in_test_harness)
       {
-      debug_printf("tick check: %lu.%06lu %lu.%06lu\n",
+      debug_printf("tick check: " TIME_T_FMT ".%06lu " TIME_T_FMT ".%06lu\n",
         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,
-        (long) itval.it_value.tv_usec);
+      debug_printf("waiting " TIME_T_FMT ".%06lu\n",
+        itval.it_value.tv_sec, (long) itval.it_value.tv_usec);
       }
     }
 
@@ -802,15 +819,36 @@ fprintf(f, "Support for:");
 #ifndef DISABLE_DKIM
   fprintf(f, " DKIM");
 #endif
-#ifdef WITH_OLD_DEMIME
-  fprintf(f, " Old_Demime");
+#ifndef DISABLE_DNSSEC
+  fprintf(f, " DNSSEC");
 #endif
-#ifndef DISABLE_PRDR
-  fprintf(f, " PRDR");
+#ifndef DISABLE_EVENT
+  fprintf(f, " Event");
+#endif
+#ifdef SUPPORT_I18N
+  fprintf(f, " I18N");
 #endif
 #ifndef DISABLE_OCSP
   fprintf(f, " OCSP");
 #endif
+#ifndef DISABLE_PRDR
+  fprintf(f, " PRDR");
+#endif
+#ifdef SUPPORT_PROXY
+  fprintf(f, " PROXY");
+#endif
+#ifdef SUPPORT_SOCKS
+  fprintf(f, " SOCKS");
+#endif
+#ifdef TCP_FASTOPEN
+  fprintf(f, " TCP_Fast_Open");
+#endif
+#ifdef EXPERIMENTAL_LMDB
+  fprintf(f, " Experimental_LMDB");
+#endif
+#ifdef EXPERIMENTAL_QUEUEFILE
+  fprintf(f, " Experimental_QUEUEFILE");
+#endif
 #ifdef EXPERIMENTAL_SPF
   fprintf(f, " Experimental_SPF");
 #endif
@@ -820,26 +858,17 @@ fprintf(f, "Support for:");
 #ifdef EXPERIMENTAL_BRIGHTMAIL
   fprintf(f, " Experimental_Brightmail");
 #endif
+#ifdef EXPERIMENTAL_DANE
+  fprintf(f, " Experimental_DANE");
+#endif
 #ifdef EXPERIMENTAL_DCC
   fprintf(f, " Experimental_DCC");
 #endif
 #ifdef EXPERIMENTAL_DMARC
   fprintf(f, " Experimental_DMARC");
 #endif
-#ifdef EXPERIMENTAL_PROXY
-  fprintf(f, " Experimental_Proxy");
-#endif
-#ifdef EXPERIMENTAL_TPDA
-  fprintf(f, " Experimental_TPDA");
-#endif
-#ifdef EXPERIMENTAL_REDIS
-  fprintf(f, " Experimental_Redis");
-#endif
-#ifdef EXPERIMENTAL_CERTNAMES
-  fprintf(f, " Experimental_Certnames");
-#endif
-#ifdef EXPERIMENTAL_DSN
-  fprintf(f, " Experimental_DSN");
+#ifdef EXPERIMENTAL_DSN_INFO
+  fprintf(f, " Experimental_DSN_info");
 #endif
 fprintf(f, "\n");
 
@@ -865,6 +894,9 @@ fprintf(f, "Lookups (built-in):");
 #if defined(LOOKUP_LDAP) && LOOKUP_LDAP!=2
   fprintf(f, " ldap ldapdn ldapm");
 #endif
+#ifdef EXPERIMENTAL_LMDB
+  fprintf(f, " lmdb");
+#endif
 #if defined(LOOKUP_MYSQL) && LOOKUP_MYSQL!=2
   fprintf(f, " mysql");
 #endif
@@ -883,6 +915,9 @@ fprintf(f, "Lookups (built-in):");
 #if defined(LOOKUP_PGSQL) && LOOKUP_PGSQL!=2
   fprintf(f, " pgsql");
 #endif
+#if defined(LOOKUP_REDIS) && LOOKUP_REDIS!=2
+  fprintf(f, " redis");
+#endif
 #if defined(LOOKUP_SQLITE) && LOOKUP_SQLITE!=2
   fprintf(f, " sqlite");
 #endif
@@ -916,6 +951,9 @@ fprintf(f, "Authenticators:");
 #ifdef AUTH_SPA
   fprintf(f, " spa");
 #endif
+#ifdef AUTH_TLS
+  fprintf(f, " tls");
+#endif
 fprintf(f, "\n");
 
 fprintf(f, "Routers:");
@@ -964,6 +1002,9 @@ fprintf(f, "Transports:");
 #ifdef TRANSPORT_PIPE
   fprintf(f, " pipe");
 #endif
+#ifdef EXPERIMENTAL_QUEUEFILE
+  fprintf(f, " queuefile");
+#endif
 #ifdef TRANSPORT_SMTP
   fprintf(f, " smtp");
 #endif
@@ -978,6 +1019,8 @@ if (fixed_never_users[0] > 0)
   fprintf(f, "%d\n", (unsigned int)fixed_never_users[i]);
   }
 
+fprintf(f, "Configure owner: %d:%d\n", config_uid, config_gid);
+
 fprintf(f, "Size of off_t: " SIZE_T_FMT "\n", sizeof(off_t));
 
 /* Everything else is details which are only worth reporting when debugging.
@@ -1001,21 +1044,30 @@ DEBUG(D_any) do {
   fprintf(f, "Compiler: <unknown>\n");
 #endif
 
+#ifdef __GLIBC__
+  fprintf(f, "Library version: Glibc: Compile: %d.%d\n",
+               __GLIBC__, __GLIBC_MINOR__);
+  if (__GLIBC_PREREQ(2, 1))
+    fprintf(f, "                        Runtime: %s\n",
+               gnu_get_libc_version());
+#endif
+
 #ifdef SUPPORT_TLS
   tls_version_report(f);
 #endif
+#ifdef SUPPORT_I18N
+  utf8_version_report(f);
+#endif
 
-  for (authi = auths_available; *authi->driver_name != '\0'; ++authi) {
-    if (authi->version_report) {
+  for (authi = auths_available; *authi->driver_name != '\0'; ++authi)
+    if (authi->version_report)
       (*authi->version_report)(f);
-    }
-  }
 
   /* PCRE_PRERELEASE is either defined and empty or a bare sequence of
   characters; unless it's an ancient version of PCRE in which case it
   is not defined. */
 #ifndef PCRE_PRERELEASE
-#define PCRE_PRERELEASE
+# define PCRE_PRERELEASE
 #endif
 #define QUOTE(X) #X
 #define EXPAND_AND_QUOTE(X) QUOTE(X)
@@ -1029,10 +1081,8 @@ DEBUG(D_any) do {
 
   init_lookup_list();
   for (i = 0; i < lookup_list_count; i++)
-    {
     if (lookup_list[i]->version_report)
       lookup_list[i]->version_report(f);
-    }
 
 #ifdef WHITELIST_D_MACROS
   fprintf(f, "WHITELIST_D_MACROS: \"%s\"\n", WHITELIST_D_MACROS);
@@ -1113,23 +1163,23 @@ for (t = lpart; !needs_quote && *t != 0; t++)
 if (!needs_quote) return lpart;
 
 size = ptr = 0;
-yield = string_cat(NULL, &size, &ptr, US"\"", 1);
+yield = string_catn(NULL, &size, &ptr, US"\"", 1);
 
 for (;;)
   {
   uschar *nq = US Ustrpbrk(lpart, "\\\"");
   if (nq == NULL)
     {
-    yield = string_cat(yield, &size, &ptr, lpart, Ustrlen(lpart));
+    yield = string_cat(yield, &size, &ptr, lpart);
     break;
     }
-  yield = string_cat(yield, &size, &ptr, lpart, nq - lpart);
-  yield = string_cat(yield, &size, &ptr, US"\\", 1);
-  yield = string_cat(yield, &size, &ptr, nq, 1);
+  yield = string_catn(yield, &size, &ptr, lpart, nq - lpart);
+  yield = string_catn(yield, &size, &ptr, US"\\", 1);
+  yield = string_catn(yield, &size, &ptr, nq, 1);
   lpart = nq + 1;
   }
 
-yield = string_cat(yield, &size, &ptr, US"\"", 1);
+yield = string_catn(yield, &size, &ptr, US"\"", 1);
 yield[ptr] = 0;
 return yield;
 }
@@ -1243,15 +1293,16 @@ for (i = 0;; i++)
     while (p < ss && isspace(*p)) p++;   /* leading space after cont */
     }
 
-  yield = string_cat(yield, &size, &ptr, p, ss - p);
+  yield = string_catn(yield, &size, &ptr, p, ss - p);
 
   #ifdef USE_READLINE
   if (fn_readline != NULL) free(readline_line);
   #endif
 
+  /* yield can only be NULL if ss==p */
   if (ss == p || yield[ptr-1] != '\\')
     {
-    yield[ptr] = 0;
+    if (yield) yield[ptr] = 0;
     break;
     }
   yield[--ptr] = 0;
@@ -1280,7 +1331,7 @@ static void
 exim_usage(uschar *progname)
 {
 
-/* Handle specific program invocation varients */
+/* Handle specific program invocation variants */
 if (Ustrcmp(progname, US"-mailq") == 0)
   {
   fprintf(stderr,
@@ -1307,12 +1358,12 @@ exit(EXIT_FAILURE);
 /* Typically, Exim will drop privileges if macros are supplied.  In some
 cases, we want to not do so.
 
-Arguments:    none (macros is a global)
+Arguments:    opt_D_used - true if the commandline had a "-D" option
 Returns:      true if trusted, false otherwise
 */
 
 static BOOL
-macros_trusted(void)
+macros_trusted(BOOL opt_D_used)
 {
 #ifdef WHITELIST_D_MACROS
 macro_item *m;
@@ -1322,7 +1373,7 @@ size_t len;
 BOOL prev_char_item, found;
 #endif
 
-if (macros == NULL)
+if (!opt_D_used)
   return TRUE;
 #ifndef WHITELIST_D_MACROS
 return FALSE;
@@ -1379,8 +1430,9 @@ for (p = whitelisted, i = 0; (p != end) && (i < white_count); ++p)
   }
 whites[i] = NULL;
 
-/* The list of macros should be very short.  Accept the N*M complexity. */
-for (m = macros; m != NULL; m = m->next)
+/* The list of commandline macros should be very short.
+Accept the N*M complexity. */
+for (m = macros; m; m = m->next) if (m->command_line)
   {
   found = FALSE;
   for (w = whites; *w; ++w)
@@ -1466,9 +1518,11 @@ BOOL f_end_dot = FALSE;
 BOOL deliver_give_up = FALSE;
 BOOL list_queue = FALSE;
 BOOL list_options = FALSE;
+BOOL list_config = FALSE;
 BOOL local_queue_only;
 BOOL more = TRUE;
 BOOL one_msg_action = FALSE;
+BOOL opt_D_used = FALSE;
 BOOL queue_only_set = FALSE;
 BOOL receiving_message = TRUE;
 BOOL sender_ident_set = FALSE;
@@ -1489,6 +1543,7 @@ uschar *ftest_domain = NULL;
 uschar *ftest_localpart = NULL;
 uschar *ftest_prefix = NULL;
 uschar *ftest_suffix = NULL;
+uschar *log_oneline = NULL;
 uschar *malware_test_file = NULL;
 uschar *real_sender_address;
 uschar *originator_home = US"/";
@@ -1581,8 +1636,9 @@ if (!route_findgroup(US CONFIGURE_GROUPNAME, &config_gid))
   }
 #endif
 
-/* In the Cygwin environment, some initialization needs doing. It is fudged
-in by means of this macro. */
+/* In the Cygwin environment, some initialization used to need doing.
+It was fudged in by means of this macro; now no longer but we'll leave
+it in case of others. */
 
 #ifdef OS_INIT
 OS_INIT
@@ -1608,13 +1664,16 @@ os_non_restarting_signal(SIGALRM, sigalrm_handler);
 /* Ensure we have a buffer for constructing log entries. Use malloc directly,
 because store_malloc writes a log entry on failure. */
 
-log_buffer = (uschar *)malloc(LOG_BUFFER_SIZE);
-if (log_buffer == NULL)
+if (!(log_buffer = US malloc(LOG_BUFFER_SIZE)))
   {
   fprintf(stderr, "exim: failed to get store for log buffer\n");
   exit(EXIT_FAILURE);
   }
 
+/* Initialize the default log options. */
+
+bits_set(log_selector, log_selector_size, log_default);
+
 /* Set log_stderr to stderr, provided that stderr exists. This gets reset to
 NULL when the daemon is run and the file is closed. We have to use this
 indirection, because some systems don't allow writing to the variable "stderr".
@@ -1725,6 +1784,7 @@ regex_whitelisted_macro =
   regex_must_compile(US"^[A-Za-z0-9_/.-]*$", FALSE, TRUE);
 #endif
 
+for (i = 0; i < REGEX_VARS; i++) regex_vars[i] = NULL;
 
 /* If the program is called as "mailq" treat it as equivalent to "exim -bp";
 this seems to be a generally accepted convention, since one finds symbolic
@@ -1840,7 +1900,7 @@ for (i = 1; i < argc; i++)
     break;
     }
 
-  /* An option consistion of -- terminates the options */
+  /* An option consisting of -- terminates the options */
 
   if (Ustrcmp(arg, "--") == 0)
     {
@@ -1965,7 +2025,7 @@ for (i = 1; i < argc; i++)
 
     else if (*argrest == 'F')
       {
-      filter_test |= FTEST_SYSTEM;
+      filter_test |= checking = FTEST_SYSTEM;
       if (*(++argrest) != 0) { badarg = TRUE; break; }
       if (++i < argc) filter_test_sfile = argv[i]; else
         {
@@ -1985,7 +2045,7 @@ for (i = 1; i < argc; i++)
       {
       if (*(++argrest) == 0)
         {
-        filter_test |= FTEST_USER;
+        filter_test |= checking = FTEST_USER;
         if (++i < argc) filter_test_ufile = argv[i]; else
           {
           fprintf(stderr, "exim: file name expected after %s\n", argv[i-1]);
@@ -2015,6 +2075,7 @@ for (i = 1; i < argc; i++)
       sender_host_address = argv[i];
       host_checking = checking = log_testing_mode = TRUE;
       host_checking_callout = argrest[1] == 'c';
+      message_logs = FALSE;
       }
 
     /* -bi: This option is used by sendmail to initialize *the* alias file,
@@ -2060,6 +2121,7 @@ for (i = 1; i < argc; i++)
     else if (Ustrcmp(argrest, "malware") == 0)
       {
       if (++i >= argc) { badarg = TRUE; break; }
+      checking = TRUE;
       malware_test_file = argv[i];
       }
 
@@ -2122,15 +2184,26 @@ for (i = 1; i < argc; i++)
 
     else if (Ustrcmp(argrest, "P") == 0)
       {
-      list_options = TRUE;
-      debug_selector |= D_v;
-      debug_file = stderr;
+      /* -bP config: we need to setup here, because later,
+       * when list_options is checked, the config is read already */
+      if (argv[i+1] && Ustrcmp(argv[i+1], "config") == 0)
+        {
+        list_config = TRUE;
+        readconf_save_config(version_string);
+        }
+      else
+        {
+        list_options = TRUE;
+        debug_selector |= D_v;
+        debug_file = stderr;
+        }
       }
 
     /* -brt: Test retry configuration lookup */
 
     else if (Ustrcmp(argrest, "rt") == 0)
       {
+      checking = TRUE;
       test_retry_arg = i + 1;
       goto END_ARG;
       }
@@ -2139,6 +2212,7 @@ for (i = 1; i < argc; i++)
 
     else if (Ustrcmp(argrest, "rw") == 0)
       {
+      checking = TRUE;
       test_rewrite_arg = i + 1;
       goto END_ARG;
       }
@@ -2181,6 +2255,7 @@ for (i = 1; i < argc; i++)
       printf("%s\n", CS version_copyright);
       version_printed = TRUE;
       show_whats_supported(stdout);
+      log_testing_mode = TRUE;
       }
 
     /* -bw: inetd wait mode, accept a listening socket as stdin */
@@ -2219,7 +2294,7 @@ for (i = 1; i < argc; i++)
       #ifdef ALT_CONFIG_PREFIX
       int sep = 0;
       int len = Ustrlen(ALT_CONFIG_PREFIX);
-      uschar *list = argrest;
+      const uschar *list = argrest;
       uschar *filename;
       while((filename = string_nextinlist(&list, &sep, big_buffer,
              big_buffer_size)) != NULL)
@@ -2295,7 +2370,7 @@ for (i = 1; i < argc; i++)
               if (nr_configs)
                 {
                 int sep = 0;
-                uschar *list = argrest;
+                const uschar *list = argrest;
                 uschar *filename;
                 while (trusted_config && (filename = string_nextinlist(&list,
                         &sep, big_buffer, big_buffer_size)) != NULL)
@@ -2347,11 +2422,11 @@ for (i = 1; i < argc; i++)
     #else
       {
       int ptr = 0;
-      macro_item *mlast = NULL;
       macro_item *m;
       uschar name[24];
       uschar *s = argrest;
 
+      opt_D_used = TRUE;
       while (isspace(*s)) s++;
 
       if (*s < 'A' || *s > 'Z')
@@ -2375,22 +2450,14 @@ for (i = 1; i < argc; i++)
         while (isspace(*s)) s++;
         }
 
-      for (m = macros; m != NULL; m = m->next)
-        {
+      for (m = macros; m; m = m->next)
         if (Ustrcmp(m->name, name) == 0)
           {
           fprintf(stderr, "exim: duplicated -D in command line\n");
           exit(EXIT_FAILURE);
           }
-        mlast = m;
-        }
 
-      m = store_get(sizeof(macro_item) + Ustrlen(name));
-      m->next = NULL;
-      m->command_line = TRUE;
-      if (mlast == NULL) macros = m; else mlast->next = m;
-      Ustrcpy(m->name, name);
-      m->replacement = string_copy(s);
+      m = macro_create(name, s, TRUE, FALSE);
 
       if (clmacro_count >= MAX_CLMACROS)
         {
@@ -2427,8 +2494,8 @@ for (i = 1; i < argc; i++)
         argrest++;
         }
       if (*argrest != 0)
-        decode_bits(&selector, NULL, D_memory, 0, argrest, debug_options,
-          debug_options_count, US"debug", 0);
+        decode_bits(&selector, 1, debug_notall, argrest,
+          debug_options, debug_options_count, US"debug", 0);
       debug_selector = selector;
       }
     break;
@@ -2500,7 +2567,7 @@ for (i = 1; i < argc; i++)
 
     case 'f':
       {
-      int start, end;
+      int dummy_start, dummy_end;
       uschar *errmess;
       if (*argrest == 0)
         {
@@ -2508,9 +2575,7 @@ for (i = 1; i < argc; i++)
           { badarg = TRUE; break; }
         }
       if (*argrest == 0)
-        {
         sender_address = string_sprintf("");  /* Ensure writeable memory */
-        }
       else
         {
         uschar *temp = argrest + Ustrlen(argrest) - 1;
@@ -2518,8 +2583,15 @@ for (i = 1; i < argc; i++)
         if (temp >= argrest && *temp == '.') f_end_dot = TRUE;
         allow_domain_literals = TRUE;
         strip_trailing_dot = TRUE;
-        sender_address = parse_extract_address(argrest, &errmess, &start, &end,
-          &sender_address_domain, TRUE);
+#ifdef SUPPORT_I18N
+       allow_utf8_domains = TRUE;
+#endif
+        sender_address = parse_extract_address(argrest, &errmess,
+          &dummy_start, &dummy_end, &sender_address_domain, TRUE);
+#ifdef SUPPORT_I18N
+       message_smtputf8 =  string_is_utf8(sender_address);
+       allow_utf8_domains = FALSE;
+#endif
         allow_domain_literals = FALSE;
         strip_trailing_dot = FALSE;
         if (sender_address == NULL)
@@ -2653,68 +2725,63 @@ for (i = 1; i < argc; i++)
       break;
       }
 
+    else if (*argrest == 'C' && argrest[1] && !argrest[2])
+      {
+       switch(argrest[1])
+       {
     /* -MCA: set the smtp_authenticated flag; this is useful only when it
     precedes -MC (see above). The flag indicates that the host to which
     Exim is connected has accepted an AUTH sequence. */
 
-    else if (Ustrcmp(argrest, "CA") == 0)
-      {
-      smtp_authenticated = TRUE;
-      break;
-      }
+       case 'A': smtp_authenticated = TRUE; 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
+
+       case 'D': smtp_peer_options |= PEER_OFFERED_DSN; break;
+
+    /* -MCG: set the queue name, to a non-default value */
+
+       case 'G': if (++i < argc) queue_name = string_copy(argv[i]);
+                 else badarg = TRUE;
+                 break;
+
+    /* -MCK: the peer offered CHUNKING.  Must precede -MC */
+
+       case 'K': smtp_peer_options |= PEER_OFFERED_CHUNKING; break;
 
     /* -MCP: set the smtp_use_pipelining flag; this is useful only when
     it preceded -MC (see above) */
 
-    else if (Ustrcmp(argrest, "CP") == 0)
-      {
-      smtp_use_pipelining = TRUE;
-      break;
-      }
+       case 'P': smtp_peer_options |= PEER_OFFERED_PIPE; break;
 
     /* -MCQ: pass on the pid of the queue-running process that started
     this chain of deliveries and the fd of its synchronizing pipe; this
     is useful only when it precedes -MC (see above) */
 
-    else if (Ustrcmp(argrest, "CQ") == 0)
-      {
-      if(++i < argc) passed_qr_pid = (pid_t)(Uatol(argv[i]));
-        else badarg = TRUE;
-      if(++i < argc) passed_qr_pipe = (int)(Uatol(argv[i]));
-        else badarg = TRUE;
-      break;
-      }
+       case 'Q': if (++i < argc) passed_qr_pid = (pid_t)(Uatol(argv[i]));
+                 else badarg = TRUE;
+                 if (++i < argc) passed_qr_pipe = (int)(Uatol(argv[i]));
+                 else badarg = TRUE;
+                 break;
 
     /* -MCS: set the smtp_use_size flag; this is useful only when it
     precedes -MC (see above) */
 
-    else if (Ustrcmp(argrest, "CS") == 0)
-      {
-      smtp_use_size = TRUE;
-      break;
-      }
+       case 'S': smtp_peer_options |= PEER_OFFERED_SIZE; break;
 
+#ifdef SUPPORT_TLS
     /* -MCT: set the tls_offered flag; this is useful only when it
     precedes -MC (see above). The flag indicates that the host to which
     Exim is connected has offered TLS support. */
 
-    #ifdef SUPPORT_TLS
-    else if (Ustrcmp(argrest, "CT") == 0)
-      {
-      tls_offered = TRUE;
-      break;
+       case 'T': smtp_peer_options |= PEER_OFFERED_TLS; break;
+#endif
+
+       default:  badarg = TRUE; break;
+       }
+       break;
       }
-    #endif
 
     /* -M[x]: various operations on the following list of message ids:
        -M    deliver the messages, ignoring next retry times and thawing
@@ -3128,7 +3195,10 @@ for (i = 1; i < argc; i++)
         }
       else
         {
+       int old_pool = store_pool;
+       store_pool = POOL_PERM;
         received_protocol = string_copyn(argrest, hn - argrest);
+       store_pool = old_pool;
         sender_host_name = hn + 1;
         }
       }
@@ -3165,7 +3235,7 @@ for (i = 1; i < argc; i++)
     if (*argrest == 'f')
       {
       queue_run_force = TRUE;
-      if (*(++argrest) == 'f')
+      if (*++argrest == 'f')
         {
         deliver_force_thaw = TRUE;
         argrest++;
@@ -3180,8 +3250,19 @@ for (i = 1; i < argc; i++)
       argrest++;
       }
 
-    /* -q[f][f][l]: Run the queue, optionally forced, optionally local only,
-    optionally starting from a given message id. */
+    /* -q[f][f][l][G<name>]... Work on the named queue */
+
+    if (*argrest == 'G')
+      {
+      int i;
+      for (argrest++, i = 0; argrest[i] && argrest[i] != '/'; ) i++;
+      queue_name = string_copyn(argrest, i);
+      argrest += i;
+      if (*argrest == '/') argrest++;
+      }
+
+    /* -q[f][f][l][G<name>]: Run the queue, optionally forced, optionally local
+    only, optionally named, optionally starting from a given message id. */
 
     if (*argrest == 0 &&
         (i + 1 >= argc || argv[i+1][0] == '-' || mac_ismsgid(argv[i+1])))
@@ -3193,20 +3274,14 @@ for (i = 1; i < argc; i++)
         stop_queue_run_id = argv[++i];
       }
 
-    /* -q[f][f][l]<n>: Run the queue at regular intervals, optionally forced,
-    optionally local only. */
+    /* -q[f][f][l][G<name>/]<n>: Run the queue at regular intervals, optionally
+    forced, optionally local only, optionally named. */
 
-    else
+    else if ((queue_interval = readconf_readtime(*argrest ? argrest : argv[++i],
+                                               0, FALSE)) <= 0)
       {
-      if (*argrest != 0)
-        queue_interval = readconf_readtime(argrest, 0, FALSE);
-      else
-        queue_interval = readconf_readtime(argv[++i], 0, FALSE);
-      if (queue_interval <= 0)
-        {
-        fprintf(stderr, "exim: bad time value %s: abandoned\n", argv[i]);
-        exit(EXIT_FAILURE);
-        }
+      fprintf(stderr, "exim: bad time value %s: abandoned\n", argv[i]);
+      exit(EXIT_FAILURE);
       }
     break;
 
@@ -3226,8 +3301,7 @@ for (i = 1; i < argc; i++)
     if (*argrest != 0)
       {
       int i;
-      for (i = 0; i < sizeof(rsopts)/sizeof(uschar *); i++)
-        {
+      for (i = 0; i < nelem(rsopts); i++)
         if (Ustrcmp(argrest, rsopts[i]) == 0)
           {
           if (i != 2) queue_run_force = TRUE;
@@ -3235,21 +3309,20 @@ for (i = 1; i < argc; i++)
           if (i == 1 || i == 4) deliver_force_thaw = TRUE;
           argrest += Ustrlen(rsopts[i]);
           }
-        }
       }
 
     /* -R: Set string to match in addresses for forced queue run to
     pick out particular messages. */
 
-    if (*argrest == 0)
+    if (*argrest)
+      deliver_selectstring = argrest;
+    else if (i+1 < argc)
+      deliver_selectstring = argv[++i];
+    else
       {
-      if (i+1 < argc) deliver_selectstring = argv[++i]; else
-        {
-        fprintf(stderr, "exim: string expected after -R\n");
-        exit(EXIT_FAILURE);
-        }
+      fprintf(stderr, "exim: string expected after -R\n");
+      exit(EXIT_FAILURE);
       }
-    else deliver_selectstring = argrest;
     break;
 
 
@@ -3270,11 +3343,10 @@ for (i = 1; i < argc; i++)
     in all cases provided there are no further characters in this
     argument. */
 
-    if (*argrest != 0)
+    if (*argrest)
       {
       int i;
-      for (i = 0; i < sizeof(rsopts)/sizeof(uschar *); i++)
-        {
+      for (i = 0; i < nelem(rsopts); i++)
         if (Ustrcmp(argrest, rsopts[i]) == 0)
           {
           if (i != 2) queue_run_force = TRUE;
@@ -3282,21 +3354,20 @@ for (i = 1; i < argc; i++)
           if (i == 1 || i == 4) deliver_force_thaw = TRUE;
           argrest += Ustrlen(rsopts[i]);
           }
-        }
       }
 
     /* -S: Set string to match in addresses for forced queue run to
     pick out particular messages. */
 
-    if (*argrest == 0)
+    if (*argrest)
+      deliver_selectstring_sender = argrest;
+    else if (i+1 < argc)
+      deliver_selectstring_sender = argv[++i];
+    else
       {
-      if (i+1 < argc) deliver_selectstring_sender = argv[++i]; else
-        {
-        fprintf(stderr, "exim: string expected after -S\n");
-        exit(EXIT_FAILURE);
-        }
+      fprintf(stderr, "exim: string expected after -S\n");
+      exit(EXIT_FAILURE);
       }
-    else deliver_selectstring_sender = argrest;
     break;
 
     /* -Tqt is an option that is exclusively for use by the testing suite.
@@ -3374,13 +3445,20 @@ for (i = 1; i < argc; i++)
 
     case 'X':
     if (*argrest == '\0')
-      {
       if (++i >= argc)
         {
         fprintf(stderr, "exim: string expected after -X\n");
         exit(EXIT_FAILURE);
         }
-      }
+    break;
+
+    case 'z':
+    if (*argrest == '\0')
+      if (++i < argc) log_oneline = argv[i]; else
+        {
+        fprintf(stderr, "exim: file name expected after %s\n", argv[i-1]);
+        exit(EXIT_FAILURE);
+        }
     break;
 
     /* All other initial characters are errors */
@@ -3403,8 +3481,9 @@ for (i = 1; i < argc; i++)
 
 /* If -R or -S have been specified without -q, assume a single queue run. */
 
-if ((deliver_selectstring != NULL || deliver_selectstring_sender != NULL) &&
-  queue_interval < 0) queue_interval = 0;
+if (  (deliver_selectstring || deliver_selectstring_sender)
+   && queue_interval < 0)
+    queue_interval = 0;
 
 
 END_ARG:
@@ -3420,12 +3499,12 @@ if ((
     ) ||
     (
     msg_action_arg > 0 &&
-    (daemon_listen || queue_interval >= 0 || list_options ||
+    (daemon_listen || queue_interval > 0 || list_options ||
       (checking && msg_action != MSG_LOAD) ||
       bi_option || test_retry_arg >= 0 || test_rewrite_arg >= 0)
     ) ||
     (
-    (daemon_listen || queue_interval >= 0) &&
+    (daemon_listen || queue_interval > 0) &&
     (sender_address != NULL || list_options || list_queue || checking ||
       bi_option)
     ) ||
@@ -3612,7 +3691,7 @@ configuration file changes and macro definitions haven't happened. */
 
 if ((                                            /* EITHER */
     (!trusted_config ||                          /* Config changed, or */
-     !macros_trusted()) &&                       /*  impermissible macros and */
+     !macros_trusted(opt_D_used)) &&            /*  impermissible macros and */
     real_uid != root_uid &&                      /* Not root, and */
     !running_in_test_harness                     /* Not fudged */
     ) ||                                         /*   OR   */
@@ -3681,11 +3760,48 @@ is equivalent to the ability to modify a setuid binary!
 This needs to happen before we read the main configuration. */
 init_lookup_list();
 
+#ifdef SUPPORT_I18N
+if (running_in_test_harness) smtputf8_advertise_hosts = NULL;
+#endif
+
 /* Read the main runtime configuration data; this gives up if there
 is a failure. It leaves the configuration file open so that the subsequent
-configuration data for delivery can be read if needed. */
+configuration data for delivery can be read if needed.
+
+NOTE: immediatly after opening the configuration file we change the working
+directory to "/"! Later we change to $spool_directory. We do it there, because
+during readconf_main() some expansion takes place already. */
+
+/* Store the initial cwd before we change directories */
+if ((initial_cwd = os_getcwd(NULL, 0)) == NULL)
+  {
+  perror("exim: can't get the current working directory");
+  exit(EXIT_FAILURE);
+  }
+
+/* checking:
+    -be[m] expansion test        -
+    -b[fF] filter test           new
+    -bh[c] host test             -
+    -bmalware malware_test_file  new
+    -brt   retry test            new
+    -brw   rewrite test          new
+    -bt    address test          -
+    -bv[s] address verify        -
+   list_options:
+    -bP <option> (except -bP config, which sets list_config)
+
+If any of these options is set, we suppress warnings about configuration
+issues (currently about tls_advertise_hosts and keep_environment not being
+defined) */
+
+readconf_main(checking || list_options);
+
+/* Now in directory "/" */
+
+if (cleanup_environment() == FALSE)
+  log_write(0, LOG_PANIC_DIE, "Can't cleanup environment");
 
-readconf_main();
 
 /* If an action on specific messages is requested, or if a daemon or queue
 runner is being started, we need to know if Exim was called by an admin user.
@@ -3749,14 +3865,17 @@ else
 
 /* Handle the decoding of logging options. */
 
-decode_bits(&log_write_selector, &log_extra_selector, 0, 0,
+decode_bits(log_selector, log_selector_size, log_notall,
   log_selector_string, log_options, log_options_count, US"log", 0);
 
 DEBUG(D_any)
   {
+  int i;
   debug_printf("configuration file is %s\n", config_main_filename);
-  debug_printf("log selectors = %08x %08x\n", log_write_selector,
-    log_extra_selector);
+  debug_printf("log selectors =");
+  for (i = 0; i < log_selector_size; i++)
+    debug_printf(" %08x", log_selector[i]);
+  debug_printf("\n");
   }
 
 /* If domain literals are not allowed, check the sender address that was
@@ -3823,26 +3942,35 @@ if (Ustrlen(syslog_processname) > 32)
   log_write(0, LOG_MAIN|LOG_PANIC_DIE,
     "syslog_processname is longer than 32 chars: aborting");
 
+if (log_oneline)
+  if (admin_user)
+    {
+    log_write(0, LOG_MAIN, "%s", log_oneline);
+    return EXIT_SUCCESS;
+    }
+  else
+    return EXIT_FAILURE;
+
 /* In some operating systems, the environment variable TMPDIR controls where
 temporary files are created; Exim doesn't use these (apart from when delivering
 to MBX mailboxes), but called libraries such as DBM libraries may require them.
 If TMPDIR is found in the environment, reset it to the value defined in the
-TMPDIR macro, if this macro is defined. */
+EXIM_TMPDIR macro, if this macro is defined.  For backward compatibility this
+macro may be called TMPDIR in old "Local/Makefile"s. It's converted to
+EXIM_TMPDIR by the build scripts.
+*/
 
-#ifdef TMPDIR
+#ifdef EXIM_TMPDIR
   {
   uschar **p;
-  for (p = USS environ; *p != NULL; p++)
-    {
-    if (Ustrncmp(*p, "TMPDIR=", 7) == 0 &&
-        Ustrcmp(*p+7, TMPDIR) != 0)
+  if (environ) for (p = USS environ; *p; p++)
+    if (Ustrncmp(*p, "TMPDIR=", 7) == 0 && Ustrcmp(*p+7, EXIM_TMPDIR) != 0)
       {
-      uschar *newp = malloc(Ustrlen(TMPDIR) + 8);
-      sprintf(CS newp, "TMPDIR=%s", TMPDIR);
+      uschar * newp = store_malloc(Ustrlen(EXIM_TMPDIR) + 8);
+      sprintf(CS newp, "TMPDIR=%s", EXIM_TMPDIR);
       *p = newp;
-      DEBUG(D_any) debug_printf("reset TMPDIR=%s in environment\n", TMPDIR);
+      DEBUG(D_any) debug_printf("reset TMPDIR=%s in environment\n", EXIM_TMPDIR);
       }
-    }
   }
 #endif
 
@@ -3856,33 +3984,28 @@ about this earlier - but hopefully nothing will normally be logged earlier than
 this. We have to make a new environment if TZ is wrong, but don't bother if
 timestamps_utc is set, because then all times are in UTC anyway. */
 
-if (timezone_string != NULL && strcmpic(timezone_string, US"UTC") == 0)
-  {
+if (timezone_string && strcmpic(timezone_string, US"UTC") == 0)
   timestamps_utc = TRUE;
-  }
 else
   {
   uschar *envtz = US getenv("TZ");
-  if ((envtz == NULL && timezone_string != NULL) ||
-      (envtz != NULL &&
-        (timezone_string == NULL ||
-         Ustrcmp(timezone_string, envtz) != 0)))
+  if (envtz
+      ? !timezone_string || Ustrcmp(timezone_string, envtz) != 0
+      : timezone_string != NULL
+     )
     {
     uschar **p = USS environ;
     uschar **new;
     uschar **newp;
     int count = 0;
-    while (*p++ != NULL) count++;
-    if (envtz == NULL) count++;
-    newp = new = malloc(sizeof(uschar *) * (count + 1));
-    for (p = USS environ; *p != NULL; p++)
-      {
-      if (Ustrncmp(*p, "TZ=", 3) == 0) continue;
-      *newp++ = *p;
-      }
-    if (timezone_string != NULL)
+    if (environ) while (*p++) count++;
+    if (!envtz) count++;
+    newp = new = store_malloc(sizeof(uschar *) * (count + 1));
+    if (environ) for (p = USS environ; *p; p++)
+      if (Ustrncmp(*p, "TZ=", 3) != 0) *newp++ = *p;
+    if (timezone_string)
       {
-      *newp = malloc(Ustrlen(timezone_string) + 4);
+      *newp = store_malloc(Ustrlen(timezone_string) + 4);
       sprintf(CS *newp++, "TZ=%s", timezone_string);
       }
     *newp = NULL;
@@ -3914,16 +4037,15 @@ Exim user", but it hasn't, because either the -D option set macros, or the
       root for -C or -D, the caller must either be root or be invoking a
       trusted configuration file (when deliver_drop_privilege is false). */
 
-if (removed_privilege && (!trusted_config || macros != NULL) &&
-    real_uid == exim_uid)
-  {
+if (  removed_privilege
+   && (!trusted_config || opt_D_used)
+   && real_uid == exim_uid)
   if (deliver_drop_privilege)
     really_exim = TRUE; /* let logging work normally */
   else
     log_write(0, LOG_MAIN|LOG_PANIC,
       "exim user lost privilege for using %s option",
       trusted_config? "-D" : "-C");
-  }
 
 /* Start up Perl interpreter if Perl support is configured and there is a
 perl_startup option, and the configuration or the command line specifies
@@ -3952,21 +4074,22 @@ a debugging feature for finding out what arguments certain MUAs actually use.
 Don't attempt it if logging is disabled, or if listing variables or if
 verifying/testing addresses or expansions. */
 
-if (((debug_selector & D_any) != 0 || (log_extra_selector & LX_arguments) != 0)
+if (((debug_selector & D_any) != 0 || LOGGING(arguments))
       && really_exim && !list_options && !checking)
   {
   int i;
   uschar *p = big_buffer;
-  char * dummy;
   Ustrcpy(p, "cwd= (failed)");
-  dummy = /* quieten compiler */ getcwd(CS p+4, big_buffer_size - 4);
+
+  Ustrncpy(p + 4, initial_cwd, big_buffer_size-5);
+
   while (*p) p++;
   (void)string_format(p, big_buffer_size - (p - big_buffer), " %d args:", argc);
   while (*p) p++;
   for (i = 0; i < argc; i++)
     {
     int len = Ustrlen(argv[i]);
-    uschar *printing;
+    const uschar *printing;
     uschar *quote;
     if (p + len + 8 >= big_buffer + big_buffer_size)
       {
@@ -3978,7 +4101,7 @@ if (((debug_selector & D_any) != 0 || (log_extra_selector & LX_arguments) != 0)
     printing = string_printing(argv[i]);
     if (printing[0] == 0) quote = US"\""; else
       {
-      uschar *pp = printing;
+      const uschar *pp = printing;
       quote = US"";
       while (*pp != 0) if (isspace(*pp++)) { quote = US"\""; break; }
       }
@@ -3987,7 +4110,7 @@ if (((debug_selector & D_any) != 0 || (log_extra_selector & LX_arguments) != 0)
     while (*p) p++;
     }
 
-  if ((log_extra_selector & LX_arguments) != 0)
+  if (LOGGING(arguments))
     log_write(0, LOG_MAIN, "%s", big_buffer);
   else
     debug_printf("%s\n", big_buffer);
@@ -4091,7 +4214,7 @@ real, but are permitted when checking things (-be, -bv, -bt, -bh, -bf, -bF).
 Note that authority for performing certain actions on messages is tested in the
 queue_action() function. */
 
-if (!trusted_caller && !checking && filter_test == FTEST_NONE)
+if (!trusted_caller && !checking)
   {
   sender_host_name = sender_host_address = interface_address =
     sender_ident = received_protocol = NULL;
@@ -4465,7 +4588,8 @@ if (list_options)
           (Ustrcmp(argv[i], "router") == 0 ||
            Ustrcmp(argv[i], "transport") == 0 ||
            Ustrcmp(argv[i], "authenticator") == 0 ||
-           Ustrcmp(argv[i], "macro") == 0))
+           Ustrcmp(argv[i], "macro") == 0 ||
+           Ustrcmp(argv[i], "environment") == 0))
         {
         readconf_print(argv[i+1], argv[i], flag_n);
         i++;
@@ -4475,6 +4599,20 @@ if (list_options)
   exim_exit(EXIT_SUCCESS);
   }
 
+if (list_config)
+  {
+  set_process_info("listing config");
+  readconf_print(US"config", NULL, flag_n);
+  exim_exit(EXIT_SUCCESS);
+  }
+
+
+/* Initialise subsystems as required */
+#ifndef DISABLE_DKIM
+dkim_exim_init();
+#endif
+deliver_init();
+
 
 /* Handle a request to deliver one or more messages that are already on the
 queue. Values of msg_action other than MSG_DELIVER and MSG_LOAD are dealt with
@@ -4531,7 +4669,10 @@ if (queue_interval == 0 && !daemon_listen)
     (start_queue_run_id == NULL)? US"" : start_queue_run_id,
     (stop_queue_run_id == NULL)?  US"" : US" stopping at ",
     (stop_queue_run_id == NULL)?  US"" : stop_queue_run_id);
-  set_process_info("running the queue (single queue run)");
+  if (*queue_name)
+    set_process_info("running the '%s' queue (single queue run)", queue_name);
+  else
+    set_process_info("running the queue (single queue run)");
   queue_run(start_queue_run_id, stop_queue_run_id, FALSE);
   exim_exit(EXIT_SUCCESS);
   }
@@ -4730,8 +4871,7 @@ if ((!smtp_input && sender_address == NULL) ||
   if (sender_address == NULL             /* No sender_address set */
        ||                                /*         OR            */
        (sender_address[0] != 0 &&        /* Non-empty sender address, AND */
-       !checking &&                      /* Not running tests, AND */
-       filter_test == FTEST_NONE))       /* Not testing a filter */
+       !checking))                       /* Not running tests, including filter tests */
     {
     sender_address = originator_login;
     sender_address_forced = FALSE;
@@ -4816,6 +4956,7 @@ Otherwise, if -bem was used, read a message from stdin. */
 
 if (expansion_test)
   {
+  dns_init(FALSE, FALSE, FALSE);
   if (msg_action_arg > 0 && msg_action == MSG_LOAD)
     {
     uschar spoolname[256];  /* Not big_buffer; used in spool_read_header() */
@@ -4826,7 +4967,7 @@ if (expansion_test)
       }
     message_id = argv[msg_action_arg];
     (void)string_format(spoolname, sizeof(spoolname), "%s-H", message_id);
-    if (!spool_open_datafile(message_id))
+    if ((deliver_datafile = spool_open_datafile(message_id)) < 0)
       printf ("Failed to load message datafile %s\n", message_id);
     if (spool_read_header(spoolname, TRUE, FALSE) != spool_read_OK)
       printf ("Failed to load message %s\n", message_id);
@@ -4949,7 +5090,7 @@ if (host_checking)
       verify_get_ident(1413);
     }
 
-  /* In case the given address is a non-canonical IPv6 address, canonicize
+  /* In case the given address is a non-canonical IPv6 address, canonicalize
   it. The code works for both IPv4 and IPv6, as it happens. */
 
   size = host_aton(sender_host_address, x);
@@ -4971,8 +5112,9 @@ if (host_checking)
     "**** This is not for real!\n\n",
       sender_host_address);
 
+  memset(sender_host_cache, 0, sizeof(sender_host_cache));
   if (verify_check_host(&hosts_connection_nolog) == OK)
-    log_write_selector &= ~L_smtp_connection;
+    BIT_CLEAR(log_selector, log_selector_size, Li_smtp_connection);
   log_write(L_smtp_connection, LOG_MAIN, "%s", smtp_get_connection_info());
 
   /* NOTE: We do *not* call smtp_log_no_mail() if smtp_start_session() fails,
@@ -4982,12 +5124,21 @@ if (host_checking)
 
   if (smtp_start_session())
     {
-    reset_point = store_get(0);
-    for (;;)
+    for (reset_point = store_get(0); ; store_reset(reset_point))
       {
-      store_reset(reset_point);
       if (smtp_setup_msg() <= 0) break;
       if (!receive_msg(FALSE)) break;
+
+      return_path = sender_address = NULL;
+      dnslist_domain = dnslist_matched = NULL;
+#ifndef DISABLE_DKIM
+      dkim_cur_signer = NULL;
+#endif
+      acl_var_m = NULL;
+      deliver_localpart_orig = NULL;
+      deliver_domain_orig = NULL;
+      callout_address = sending_ip_address = NULL;
+      sender_rate = sender_rate_limit = sender_rate_period = NULL;
       }
     smtp_log_no_mail();
     }
@@ -5041,6 +5192,9 @@ if (mua_wrapper)
   deliver_drop_privilege = TRUE;
   queue_smtp = FALSE;
   queue_smtp_domains = NULL;
+#ifdef SUPPORT_I18N
+  message_utf8_downconvert = -1;       /* convert-if-needed */
+#endif
   }
 
 
@@ -5107,8 +5261,11 @@ if (smtp_input)
   }
 else
   {
-  if (received_protocol == NULL)
+  int old_pool = store_pool;
+  store_pool = POOL_PERM;
+  if (!received_protocol)
     received_protocol = string_sprintf("local%s", called_as);
+  store_pool = old_pool;
   set_process_info("accepting a local non-SMTP message from <%s>",
     sender_address);
   }
@@ -5142,8 +5299,9 @@ if (smtp_input)
   {
   smtp_in = stdin;
   smtp_out = stdout;
+  memset(sender_host_cache, 0, sizeof(sender_host_cache));
   if (verify_check_host(&hosts_connection_nolog) == OK)
-    log_write_selector &= ~L_smtp_connection;
+    BIT_CLEAR(log_selector, log_selector_size, Li_smtp_connection);
   log_write(L_smtp_connection, LOG_MAIN, "%s", smtp_get_connection_info());
   if (!smtp_start_session())
     {
@@ -5193,7 +5351,7 @@ February 2003: That's *still* not the end of the story. There are now versions
 of Linux (where SIG_IGN does work) that are picky. If, having set SIG_IGN, a
 process then calls waitpid(), a grumble is written to the system log, because
 this is logically inconsistent. In other words, it doesn't like the paranoia.
-As a consequenc of this, the waitpid() below is now excluded if we are sure
+As a consequence of this, the waitpid() below is now excluded if we are sure
 that SIG_IGN works. */
 
 if (!synchronous_delivery)
@@ -5221,7 +5379,6 @@ collapsed). */
 
 while (more)
   {
-  store_reset(reset_point);
   message_id[0] = 0;
 
   /* Handle the SMTP case; call smtp_setup_mst() to deal with the initial SMTP
@@ -5263,7 +5420,7 @@ while (more)
       more = receive_msg(extract_recipients);
       if (message_id[0] == 0)
         {
-        if (more) continue;
+        if (more) goto moreloop;
         smtp_log_no_mail();               /* Log no mail if configured */
         exim_exit(EXIT_FAILURE);
         }
@@ -5319,7 +5476,6 @@ while (more)
 
         if (recipients_max > 0 && ++rcount > recipients_max &&
             !extract_recipients)
-          {
           if (error_handling == ERRORS_STDERR)
             {
             fprintf(stderr, "exim: too many recipients\n");
@@ -5331,11 +5487,22 @@ while (more)
               moan_to_sender(ERRMESS_TOOMANYRECIP, NULL, NULL, stdin, TRUE)?
                 errors_sender_rc : EXIT_FAILURE;
             }
-          }
 
+#ifdef SUPPORT_I18N
+       {
+       BOOL b = allow_utf8_domains;
+       allow_utf8_domains = TRUE;
+#endif
         recipient =
           parse_extract_address(s, &errmess, &start, &end, &domain, FALSE);
 
+#ifdef SUPPORT_I18N
+       if (string_is_utf8(recipient))
+         message_smtputf8 = TRUE;
+       else
+         allow_utf8_domains = b;
+       }
+#endif
         if (domain == 0 && !allow_unqualified_recipient)
           {
           recipient = NULL;
@@ -5387,7 +5554,7 @@ while (more)
     ignored; rejecting here would just add complication, and it can just as
     well be done later. Allow $recipients to be visible in the ACL. */
 
-    if (acl_not_smtp_start != NULL)
+    if (acl_not_smtp_start)
       {
       uschar *user_msg, *log_msg;
       enable_dollar_recipients = TRUE;
@@ -5396,6 +5563,20 @@ while (more)
       enable_dollar_recipients = FALSE;
       }
 
+    /* Pause for a while waiting for input.  If none received in that time,
+    close the logfile, if we had one open; then if we wait for a long-running
+    datasource (months, in one use-case) log rotation will not leave us holding
+    the file copy. */
+
+    if (!receive_timeout)
+      {
+      struct timeval t = { 30*60, 0 }; /* 30 minutes */
+      fd_set r;
+
+      FD_ZERO(&r); FD_SET(0, &r);
+      if (select(1, &r, NULL, NULL, &t) == 0) mainlog_close();
+      }
+
     /* Read the data for the message. If filter_test is not FTEST_NONE, this
     will just read the headers for the message, and not write anything onto the
     spool. */
@@ -5435,9 +5616,7 @@ while (more)
       return_path = string_copy(sender_address);
       }
     else
-      {
       printf("Return-path = %s\n", (return_path[0] == 0)? US"<>" : return_path);
-      }
     printf("Sender      = %s\n", (sender_address[0] == 0)? US"<>" : sender_address);
 
     receive_add_recipient(
@@ -5562,8 +5741,8 @@ while (more)
 
       if (geteuid() != root_uid && !deliver_drop_privilege && !unprivileged)
         {
-        (void)child_exec_exim(CEE_EXEC_EXIT, FALSE, NULL, FALSE, 2, US"-Mc",
-          message_id);
+        (void)child_exec_exim(CEE_EXEC_EXIT, FALSE, NULL, FALSE,
+               2, US"-Mc", message_id);
         /* Control does not return here. */
         }
 
@@ -5604,6 +5783,23 @@ while (more)
   #ifndef SIG_IGN_WORKS
   while (waitpid(-1, NULL, WNOHANG) > 0);
   #endif
+
+moreloop:
+  return_path = sender_address = NULL;
+  authenticated_sender = NULL;
+  deliver_localpart_orig = NULL;
+  deliver_domain_orig = NULL;
+  deliver_host = deliver_host_address = NULL;
+  dnslist_domain = dnslist_matched = NULL;
+#ifdef WITH_CONTENT_SCAN
+  malware_name = NULL;
+#endif
+  callout_address = NULL;
+  sending_ip_address = NULL;
+  acl_var_m = NULL;
+  { int i; for(i=0; i<REGEX_VARS; i++) regex_vars[i] = NULL; }
+
+  store_reset(reset_point);
   }
 
 exim_exit(EXIT_SUCCESS);   /* Never returns */
index fb48a43..d03b48c 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -11,6 +11,9 @@ that is needed. They don't all need everything, of course, but it's far too
 messy to have each one importing its own list, and anyway, most of them need
 most of these includes. */
 
+#ifndef EXIM_H
+#define EXIM_H
+
 /* Assume most systems have statfs() unless os.h undefines this macro */
 
 #define HAVE_STATFS
@@ -418,7 +421,7 @@ iconv(). It's os.h file defines ICONV_ARG2_TYPE. For the rest, define a default
 here. */
 
 #ifndef ICONV_ARG2_TYPE
-# define ICONV_ARG2_TYPE const char **
+# define ICONV_ARG2_TYPE char **
 #endif
 
 /* One OS uses a different type for the 5th argument of getsockopt */
@@ -490,6 +493,7 @@ config.h, mytypes.h, and store.h, so we don't need to mention them explicitly.
 #include "dbstuff.h"
 #include "structs.h"
 #include "globals.h"
+#include "hash.h"
 #include "functions.h"
 #include "dbfunctions.h"
 #include "osfunctions.h"
@@ -547,10 +551,16 @@ union sockaddr_46 {
 };
 
 /* If SUPPORT_TLS is not defined, ensure that USE_GNUTLS is also not defined
-so that if USE_GNUTLS *is* set, we can assume SUPPORT_TLS is also set. */
+so that if USE_GNUTLS *is* set, we can assume SUPPORT_TLS is also set.
+Likewise, OSCP, AUTH_TLS and CERTNAMES cannot be supported. */
 
 #ifndef SUPPORT_TLS
 # undef USE_GNUTLS
+# ifndef DISABLE_OCSP
+#  define DISABLE_OCSP
+# endif
+# undef EXPERIMENTAL_CERTNAMES
+# undef AUTH_TLS
 #endif
 
 /* If SPOOL_DIRECTORY, LOG_FILE_PATH or PID_FILE_PATH have not been defined,
@@ -581,14 +591,10 @@ default to EDQUOT if it exists, otherwise ENOSPC. */
 # endif
 #endif
 
-/* Ensure PATH_MAX is defined */
-
-#ifndef PATH_MAX
-  #ifdef MAXPATHLEN
-  # define PATH_MAX MAXPATHLEN
-  #else
-  # define PATH_MAX 1024
-  #endif
+/* DANE w/o DNSSEC is useless */
+#if defined(EXPERIMENTAL_DANE) && defined(DISABLE_DNSSEC)
+# undef DISABLE_DNSSEC
 #endif
 
+#endif
 /* End of exim.h */
index 84d03c0..a780a29 100755 (executable)
@@ -63,6 +63,7 @@ if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim; fi
 
 PERL_COMMAND - $exim_path $args <<'End'
 
+BEGIN { pop @INC if $INC[-1] eq '.' };
 use FileHandle;
 use IPC::Open2;
 
index ce06f16..85ae901 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -88,10 +88,10 @@ Returns:   the value of the character escape
 */
 
 int
-string_interpret_escape(uschar **pp)
+string_interpret_escape(const uschar **pp)
 {
 int ch;
-uschar *p = *pp;
+const uschar *p = *pp;
 ch = *(++p);
 if (isdigit(ch) && ch != '8' && ch != '9')
   {
@@ -329,7 +329,7 @@ while (Ufgets(line, max_insize, f) != NULL)
       keystart = t;
       while (*s != 0 && *s != '\"')
         {
-        if (*s == '\\') *t++ = string_interpret_escape(&s);
+        if (*s == '\\') *t++ = string_interpret_escape((const uschar **)&s);
           else *t++ = *s;
         s++;
         }
@@ -478,9 +478,11 @@ if (yield == 0 || yield == 1)
 else
   {
   printf("dbmbuild abandoned\n");
-  #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
+#if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
+  /* We created it, so safe to delete despite the name coming from outside */
+  /* coverity[tainted_string] */
   Uunlink(temp_dbmname);
-  #else
+#else
   if (is_db)
     {
     sprintf(CS real_dbmname, "%s.db", temp_dbmname);
@@ -493,7 +495,7 @@ else
     sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
     Uunlink(real_dbmname);
     }
-  #endif /* USE_DB || USE_TDB */
+#endif /* USE_DB || USE_TDB */
   }
 
 return yield;
index 124303a..c710772 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -259,7 +259,7 @@ uschar buffer[256];
 ensures that Exim has exclusive use of the database before it even tries to
 open it. If there is a database, there should be a lock file in existence. */
 
-sprintf(CS buffer, "%s/db/%s.lockfile", spool_directory, name);
+sprintf(CS buffer, "%s/db/%.200s.lockfile", spool_directory, name);
 
 dbblock->lockfd = Uopen(buffer, flags, 0);
 if (dbblock->lockfd < 0)
@@ -285,8 +285,9 @@ if (sigalrm_seen) errno = ETIMEDOUT;
 if (rc < 0)
   {
   printf("** Failed to get %s lock for %s: %s",
-    ((flags & O_RDONLY) != 0)? "read" : "write", buffer,
-    (errno == ETIMEDOUT)? "timed out" : strerror(errno));
+    flags & O_WRONLY ? "write" : "read",
+    buffer,
+    errno == ETIMEDOUT ? "timed out" : strerror(errno));
   (void)close(dbblock->lockfd);
   return NULL;
   }
@@ -356,15 +357,19 @@ Returns: a pointer to the retrieved record, or
 */
 
 void *
-dbfn_read_with_length(open_db *dbblock, uschar *key, int *length)
+dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
 {
 void *yield;
 EXIM_DATUM key_datum, result_datum;
+int klen = Ustrlen(key) + 1;
+uschar * key_copy = store_get(klen);
+
+memcpy(key_copy, key, klen);
 
 EXIM_DATUM_INIT(key_datum);         /* Some DBM libraries require the datum */
 EXIM_DATUM_INIT(result_datum);      /* to be cleared before use. */
-EXIM_DATUM_DATA(key_datum) = CS key;
-EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
+EXIM_DATUM_DATA(key_datum) = CS key_copy;
+EXIM_DATUM_SIZE(key_datum) = klen;
 
 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
 
@@ -396,16 +401,20 @@ Returns:    the yield of the underlying dbm or db "write" function. If this
 */
 
 int
-dbfn_write(open_db *dbblock, uschar *key, void *ptr, int length)
+dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
 {
 EXIM_DATUM key_datum, value_datum;
 dbdata_generic *gptr = (dbdata_generic *)ptr;
+int klen = Ustrlen(key) + 1;
+uschar * key_copy = store_get(klen);
+
+memcpy(key_copy, key, klen);
 gptr->time_stamp = time(NULL);
 
 EXIM_DATUM_INIT(key_datum);         /* Some DBM libraries require the datum */
 EXIM_DATUM_INIT(value_datum);       /* to be cleared before use. */
-EXIM_DATUM_DATA(key_datum) = CS key;
-EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
+EXIM_DATUM_DATA(key_datum) = CS key_copy;
+EXIM_DATUM_SIZE(key_datum) = klen;
 EXIM_DATUM_DATA(value_datum) = CS ptr;
 EXIM_DATUM_SIZE(value_datum) = length;
 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
@@ -426,12 +435,16 @@ Returns: the yield of the underlying dbm or db "delete" function.
 */
 
 int
-dbfn_delete(open_db *dbblock, uschar *key)
+dbfn_delete(open_db *dbblock, const uschar *key)
 {
+int klen = Ustrlen(key) + 1;
+uschar * key_copy = store_get(klen);
+
+memcpy(key_copy, key, klen);
 EXIM_DATUM key_datum;
 EXIM_DATUM_INIT(key_datum);         /* Some DBM libraries require clearing */
-EXIM_DATUM_DATA(key_datum) = CS key;
-EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
+EXIM_DATUM_DATA(key_datum) = CS key_copy;
+EXIM_DATUM_SIZE(key_datum) = klen;
 return EXIM_DBDEL(dbblock->dbptr, key_datum);
 }
 
@@ -630,19 +643,6 @@ while (key != NULL)
         printf("\n");
         }
 
-      /* Old-style domain record, without separate timestamps. This code can
-      eventually be thrown away, say in 5 years' time (it's now Feb 2003). */
-
-      else
-        {
-        printf("%s %s callout=%s postmaster=%s random=%s\n",
-          print_time(((dbdata_generic *)value)->time_stamp),
-          keybuffer,
-          print_cache(callout->result),
-          print_cache(callout->postmaster_result),
-          print_cache(callout->random_result));
-        }
-
       break;
 
       case type_ratelimit:
@@ -802,13 +802,13 @@ for(;;)
         if (record == NULL) printf("not found\n"); else
           {
           time_t tt;
-          int length = 0;     /* Stops compiler warning */
+          /*int length = 0;      Stops compiler warning */
 
           switch(dbdata_type)
             {
             case type_retry:
             retry = (dbdata_retry *)record;
-            length = sizeof(dbdata_retry) + Ustrlen(retry->text);
+            /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
 
             switch(fieldno)
               {
@@ -858,7 +858,7 @@ for(;;)
 
             case type_callout:
             callout = (dbdata_callout_cache *)record;
-            length = sizeof(dbdata_callout_cache);
+            /* length = sizeof(dbdata_callout_cache); */
             switch(fieldno)
               {
               case 0:
index 37d9744..0682168 100644 (file)
@@ -9,6 +9,8 @@ Options:  -fcntl    use fcntl() lock
 Default is -fcntl -lockfile.
 
 Argument: the name of the lock file
+
+Copyright (c) The Exim Maintainers 2016
 */
 
 #include "os.h"
@@ -36,7 +38,7 @@ in sys/file.h. */
 #endif
 
 
-typedef int BOOL;
+typedef unsigned BOOL;
 #define FALSE 0
 #define TRUE  1
 
@@ -216,7 +218,7 @@ for (i = 1; i < argc; i++)
   else usage();
   }
 
-if (quiet) verbose = 0;
+if (quiet) verbose = FALSE;
 
 /* Can't use flock() if the OS doesn't provide it */
 
@@ -322,7 +324,7 @@ for (j = 0; j < lock_retries; j++)
 
   if (use_lockfile)
     {
-    int rc;
+    int rc, rc2;
     if (verbose) printf("exim_lock: creating lock file\n");
     hd = open(hitchname, O_WRONLY | O_CREAT | O_EXCL, 0440);
     if (hd < 0)
@@ -334,11 +336,12 @@ for (j = 0; j < lock_retries; j++)
 
     /* Apply hitching post algorithm. */
 
-    if ((rc = link(hitchname, lockname)) != 0) fstat(hd, &statbuf);
+    if ((rc = link(hitchname, lockname)) != 0)
+     rc2 = fstat(hd, &statbuf);
     (void)close(hd);
     unlink(hitchname);
 
-    if (rc != 0 && statbuf.st_nlink != 2)
+    if (rc != 0 && (rc2 != 0 || statbuf.st_nlink != 2))
       {
       printf("exim_lock: failed to link hitching post to lock file\n");
       hd = -1;
@@ -354,8 +357,7 @@ for (j = 0; j < lock_retries; j++)
 
   /* Open the file for writing. */
 
-  fd = open(filename, O_RDWR + O_APPEND);
-  if (fd < 0)
+  if ((fd = open(filename, O_RDWR + O_APPEND)) < 0)
     {
     printf("exim_lock: failed to open %s for writing: %s\n", filename,
       strerror(errno));
@@ -374,7 +376,6 @@ for (j = 0; j < lock_retries; j++)
   a timeout changes it to blocking. */
 
   if (!use_mbx && (use_fcntl || use_flock))
-    {
     if (apply_lock(fd, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
         lock_flock_timeout) >= 0)
       {
@@ -385,8 +386,8 @@ for (j = 0; j < lock_retries; j++)
         }
       break;
       }
-    else goto RETRY;   /* Message already output */
-    }
+    else
+      goto RETRY;   /* Message already output */
 
   /* Lock using MBX rules. This is complicated and is documented with the
   source of the c-client library that goes with Pine and IMAP. What has to
@@ -583,12 +584,37 @@ else
 if (restore_times)
   {
   struct stat strestore;
+#ifdef EXIM_HAVE_OPENAT
+  int fd = open(filename, O_RDWR); /* use fd for both get & restore */
+  struct timespec tt[2];
+
+  if (fd < 0)
+    {
+    printf("open '%s': %s\n", filename, strerror(errno));
+    yield = 1;
+    goto CLEAN_UP;
+    }
+  if (fstat(fd, &strestore) != 0)
+    {
+    printf("fstat '%s': %s\n", filename, strerror(errno));
+    yield = 1;
+    close(fd);
+    goto CLEAN_UP;
+    }
+  i = system(command);
+  tt[0] = strestore.st_atim;
+  tt[1] = strestore.st_mtim;
+  (void) futimens(fd, tt);
+  (void) close(fd);
+#else
   struct utimbuf ut;
+
   stat(filename, &strestore);
   i = system(command);
   ut.actime = strestore.st_atime;
   ut.modtime = strestore.st_mtime;
   utime(filename, &ut);
+#endif
   }
 else i = system(command);
 
index fac2420..d461ccf 100644 (file)
@@ -4,7 +4,7 @@
 # The build process concatenates on the front of this various settings from
 # os-specific files and from the user's configuration file.
 
-# Copyright (c) 2004 - 2012 University of Cambridge.
+# Copyright (c) 2004 - 2015 University of Cambridge.
 # See the file NOTICE for conditions of use and distribution.
 
 # Except when they appear in comments, the following placeholders in this
@@ -79,11 +79,12 @@ LOG_FILE_PATH=`$EXIM_PATH -C $config -bP log_file_path | sed 's/.*=[  ]*//'`
 
 # If log_file_path is "syslog" then logging is only to syslog, and the monitor
 # is unable to display a log tail unless EXIMON_LOG_FILE_PATH is set to tell
-# it where the log data is. Otherwise, remove any occurrences of
-# "syslog:" or ":syslog" (spaces allowed in various places) and look at the
-# remainder of the entry. If it's null, the default is "mainlog" in the
-# "log" directory in the spool directory. Otherwise, set the name from the
-# given path.
+# it where the log data is. If log_file_path is unset (i.e. empty) the default
+# is "mainlog" in the "log" directory in the spool directory. Otherwise,
+# remove any occurrences of "syslog:" or ":syslog" (spaces allowed in various
+# places) and look at the remainder of the entry. If it's null, check whether
+# LOG_FILE_NAME was set a compile time and contains a path. Otherwise fall
+# back to the default path.
 
 if [ "$EXIMON_LOG_FILE_PATH" != "" ] ; then
   LOG_FILE_NAME="$EXIMON_LOG_FILE_PATH"
@@ -94,6 +95,8 @@ elif [ "$LOG_FILE_PATH" = "syslog" ] ; then
   echo MAIL.INFO syslog messages into a separate file, you can point eximon at
   echo that file with the EXIMON_LOG_FILE_PATH environment variable.
   echo \*\*\*
+elif [ "$LOG_FILE_PATH" = "" ] ; then
+    LOG_FILE_NAME=$SPOOL_DIRECTORY/log/mainlog
 else
   LOG_FILE_NAME=`echo $LOG_FILE_PATH | \
     sed -e 's/ *: *syslog *: */:/' \
@@ -101,7 +104,17 @@ else
         -e 's/^ *syslog *: *//' \
         -e 's/%s/main/'`
   if [ "$LOG_FILE_NAME" = "" ] ; then
-    LOG_FILE_NAME=$SPOOL_DIRECTORY/log/mainlog
+    COMPILETIMEDEFAULT=`$EXIM_PATH -C /dev/null -bP log_file_path | \
+      sed -e 's/.*=[  ]*//' \
+        -e 's/ *: *syslog *: */:/' \
+        -e 's/ *: *syslog *$//' \
+        -e 's/^ *syslog *: *//' \
+        -e 's/%s/main/'`
+    if [ "$COMPILETIMEDEFAULT" != "" ] ; then
+      LOG_FILE_NAME="$COMPILETIMEDEFAULT"
+    else
+      LOG_FILE_NAME=$SPOOL_DIRECTORY/log/mainlog
+    fi
   fi
 fi
 
index 4370b4e..a2113f1 100644 (file)
@@ -1,6 +1,6 @@
-#!PERL_COMMAND -w
+#!PERL_COMMAND
 
-# Copyright (c) 2001-2014 University of Cambridge.
+# Copyright (c) 2001-2016 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.
@@ -73,7 +73,7 @@
 # 2001-10-21  Removed -domain flag and added -bydomain, -byhost, and -byemail.
 #             We now generate our main parsing subroutine as an eval statement
 #             which improves performance dramatically when not all the results
-#             are required. We also cache the last timestamp to time convertion.
+#             are required. We also cache the last timestamp to time conversion.
 #
 #             NOTE: 'Top 50 destinations by (message count|volume)' lines are
 #             now 'Top N (host|email|domain) destinations by (message count|volume)'
 #             in HTML output. Also added code to convert them back with -merge.
 #             Fixed timestamp offsets to convert to seconds rather than minutes.
 #             Updated -merge to work with output files using timezones.
-#             Added cacheing to speed up the calculation of timezone offsets.
+#             Added caching to speed up the calculation of timezone offsets.
 #
 # 2003-02-07  V1.25 Steve Campbell
 #             Optimised the usage of mktime() in the seconds subroutine.
 #             Bernard Massot.
 #
 # 2003-06-03  V1.28 John Newman
-#             Added in the ability to skip over the parsing and evaulation of
+#             Added in the ability to skip over the parsing and evaluation of
 #             specific transports as passed to eximstats via the new "-nt/.../"
 #             command line argument.  This new switch allows the viewing of
 #             not more accurate statistics but more applicable statistics when
 #             Added -xls and the ability to specify output files.
 #
 # 2005-04-29  V1.38 Steve Campbell
-#             Use FileHandles for outputing results.
+#             Use FileHandles for outputting results.
 #             Allow any combination of xls, txt, and html output.
 #             Fixed display of large numbers with -nvr option
 #             Fixed merging of reports with empty tables.
@@ -547,7 +547,9 @@ Merging of xls files is not (yet) possible. Be free to implement :)
 
 =cut
 
+use warnings;
 use integer;
+BEGIN { pop @INC if $INC[-1] eq '.' };
 use strict;
 use IO::File;
 
@@ -598,9 +600,9 @@ $WEEK   =  7 * $DAY;
 use vars qw($total_received_data  $total_received_data_gigs  $total_received_count);
 use vars qw($total_delivered_data $total_delivered_data_gigs $total_delivered_messages $total_delivered_addresses);
 use vars qw(%timestamp2time);                   #Hash of timestamp => time.
-use vars qw($last_timestamp $last_time);        #The last time convertion done.
-use vars qw($last_date $date_seconds);          #The last date convertion done.
-use vars qw($last_offset $offset_seconds);      #The last time offset convertion done.
+use vars qw($last_timestamp $last_time);        #The last time conversion done.
+use vars qw($last_date $date_seconds);          #The last date conversion done.
+use vars qw($last_offset $offset_seconds);      #The last time offset conversion done.
 use vars qw($localtime_offset);
 use vars qw($i);                                #General loop counter.
 use vars qw($debug);                            #Debug mode?
@@ -614,7 +616,7 @@ use vars qw(%ham_count_by_ip %spam_count_by_ip);
 use vars qw(%rejected_count_by_ip %rejected_count_by_reason);
 use vars qw(%temporarily_rejected_count_by_ip %temporarily_rejected_count_by_reason);
 
-#For use in Speadsheed::WriteExcel
+#For use in Spreadsheet::WriteExcel
 use vars qw($workbook $ws_global $ws_relayed $ws_errors);
 use vars qw($row $col $row_hist $col_hist);
 use vars qw($run_hist);
@@ -757,8 +759,8 @@ sub volume_rounded {
   }
   else {
     # We don't want any rounding to be done.
-    # and we don't need broken formated output which on one hand avoids numbers from
-    # being interpreted as string by Spreadsheed Calculators, on the other hand
+    # and we don't need broken formatted output which on one hand avoids numbers from
+    # being interpreted as string by Spreadsheet Calculators, on the other hand
     # breaks if more than 4 digits! -> flexible length instead of fixed length
     # Format the return value at the output routine! -fh
     #$rounded = sprintf("%d", ($g * $gig) + $x);
@@ -871,10 +873,10 @@ $p;
 # Eg 3h20m5s => 12005
 #######################################################################
 sub unformat_time {
-  my($formated_time) = pop @_;
+  my($formatted_time) = pop @_;
   my $time = 0;
 
-  while ($formated_time =~ s/^(\d+)([wdhms]?)//) {
+  while ($formatted_time =~ s/^(\d+)([wdhms]?)//) {
     $time +=  $1 if ($2 eq '' || $2 eq 's');
     $time +=  $1 * 60 if ($2 eq 'm');
     $time +=  $1 * 60 * 60 if ($2 eq 'h');
@@ -928,7 +930,7 @@ sub seconds {
   }
   my $time = $date_seconds + ($5 * 3600) + ($6 * 60) + $7;
 
-  # SC. Use cacheing. Also note we want seconds not minutes.
+  # SC. Use caching. Also note we want seconds not minutes.
   #my($this_offset) = ($10 * 60 + $11) * ($9 . "1") if defined $8;
   if (defined $8 && ($8 ne $last_offset)) {
     $last_offset = $8;
@@ -1650,7 +1652,7 @@ sub top_n_sort {
 
     # Create a dummy hash entry for the key if required.
     # Note that setting the dummy_hash value sets it for both href2 &
-    # href3. Also note that currently we are guarenteed to have a real
+    # href3. Also note that currently we are guaranteed to have a real
     # value for href3 if a real value for href2 exists so don't need to
     # test for it as well.
     $dummy_hash{$key} = 0 unless exists $href2->{$key};
@@ -2729,7 +2731,7 @@ sub print_grandtotals {
       if ($messages > 0) {
         @content = ($total_aref->[0], '', $messages, '');
 
-        #Count the number of distict IPs for the Hosts column.
+        #Count the number of distinct IPs for the Hosts column.
         push(@content,scalar(keys %{$total_aref->[1]})) if $do_sender{Host};
 
         #These rows do not have entries for the following columns (if specified)
@@ -3360,8 +3362,8 @@ sub parse_old_eximstat_reports {
         my $previous_seconds_on_queue = 0;
         if (/^\s*(Under|Over|)\s+(\d+[smhdw])\s+(\d+)/) {
           print STDERR "Parsing $_" if $debug;
-          my($modifier,$formated_time,$count) = ($1,$2,$3);
-          my $seconds = unformat_time($formated_time);
+          my($modifier,$formatted_time,$count) = ($1,$2,$3);
+          my $seconds = unformat_time($formatted_time);
           my $time_on_queue = ($seconds + $previous_seconds_on_queue) / 2;
           $previous_seconds_on_queue = $seconds;
           $time_on_queue = $seconds * 2 if ($modifier eq 'Over');
@@ -3676,7 +3678,7 @@ sub update_relayed {
 #
 #  add_to_totals(\%totals,\@keys,$values);
 #
-# Given a line of space seperated values, add them into the provided hash using @keys
+# Given a line of space separated values, add them into the provided hash using @keys
 # as the hash keys.
 #
 # If the value contains a '%', then the value is set rather than added. Otherwise, we
@@ -3706,7 +3708,7 @@ sub add_to_totals {
 #
 #  line_to_hash(\%hash,\@keys,$line);
 #
-# Given a line of space seperated values, set them into the provided hash
+# Given a line of space separated values, set them into the provided hash
 # using @keys as the hash keys.
 #######################################################################
 sub line_to_hash {
@@ -3772,7 +3774,7 @@ sub html2txt {
 # until we've got all of the argument.
 #
 # This isn't perfect as all white space gets reduced to one space,
-# but it's as good as we can get! If it's esential that spacing
+# but it's as good as we can get! If it's essential that spacing
 # be preserved precisely, then you get that by not using shell
 # variables.
 #######################################################################
@@ -3814,7 +3816,7 @@ sub set_worksheet_line {
 #######################################################################
 # @rcpt_times = parse_time_list($string);
 #
-# Parse a comma seperated list of time values in seconds given by
+# Parse a comma separated list of time values in seconds given by
 # the user and fill an array.
 #
 # Return a default list if $string is undefined.
index 182e395..9c42735 100644 (file)
@@ -115,6 +115,9 @@ fi
 
 perl - $exim_path "$eximmacdef" $argone $spool_directory $qualify_domain $config <<'End'
 
+  # We don't import anything, but guard against future changes which do
+  BEGIN { pop @INC if $INC[-1] eq '.' };
+
   # Name the arguments
 
   $exim = $ARGV[0];
index 4708ebb..4999d84 100644 (file)
@@ -1,7 +1,9 @@
 #!PERL_COMMAND
 
-# This variable should be set by the building process to Exim's spool directory.
-my $spool = 'SPOOL_DIRECTORY';
+# This variables should be set by the building process
+my $spool = 'SPOOL_DIRECTORY'; # may be overridden later
+my $exim  = 'BIN_DIRECTORY/exim';
+
 # Need to set this dynamically during build, but it's not used right now anyway.
 my $charset = 'ISO-8859-1';
 
@@ -10,6 +12,7 @@ my $charset = 'ISO-8859-1';
 #       http://www.exim.org/eximwiki/ToolExipickManPage
 
 use strict;
+BEGIN { pop @INC if $INC[-1] eq '.' };
 use Getopt::Long;
 
 my($p_name)   = $0 =~ m|/?([^/]+)$|;
@@ -79,7 +82,7 @@ GetOptions(
   'show-tests'  => \$G::show_tests  # display tests as applied to each message
 ) || exit(1);
 
-# if both freeze and thaw specified, only thaw as it is less desctructive
+# if both freeze and thaw specified, only thaw as it is less destructive
 $G::freeze = undef               if ($G::freeze && $G::thaw);
 freeze_start()                   if ($G::freeze);
 thaw_start()                     if ($G::thaw);
@@ -111,7 +114,9 @@ $G::and             = $G::and;             # shut up -w
 $G::msg_ids         = {};                  # short circuit when crit is only MID
 $G::caseless        = $G::caseful ? 0 : 1; # nocase by default, case if both
 @G::recipients_crit = ();                  # holds per-recip criteria
-$spool              = $G::spool if ($G::spool);
+$spool              = defined $G::spool ? $G::spool
+                     : do { chomp($_ = `$exim -n -bP spool_directory`);
+                       $_ // $spool };
 my $input_dir       = $G::input_dir || ($G::finput ? "Finput" : "input");
 my $count_only      = 1 if ($G::mailq_bpc  || $G::qgrep_c);
 my $unsorted        = 1 if ($G::mailq_bpr  || $G::mailq_bpra ||
@@ -757,7 +762,7 @@ sub _decode_2047 {
               $i += 2;
             }
           }
-          elsif ($ow[$i] =~ /\s/) { # whitspace is illegal
+          elsif ($ow[$i] =~ /\s/) { # whitespace is illegal
             $e = 1;
             last;
           }
@@ -1387,7 +1392,7 @@ Display only the message IDs (exiqgrep)
 
 =item --input-dir <inputname>
 
-Set the name of the directory under the spool directory.  By defaut this is "input".  If this starts with '/', the value of --spool is ignored.  See also --finput.
+Set the name of the directory under the spool directory.  By default this is "input".  If this starts with '/', the value of --spool is ignored.  See also --finput.
 
 =item -l
 
@@ -1427,7 +1432,8 @@ Same as '$shown_message_size eq <string>' (exiqgrep)
 
 =item --spool <path>
 
-Set the path to the exim spool to use.  This value will have the argument to --input or 'input' appended, or be ignored if --input is a full path.
+Set the path to the exim spool to use.  This value will have the argument to --input or 'input' appended, or be ignored if --input is a full path. If not specified, exipick uses the value from C<exim -bP spool_directory>, and if this fails, the  F<SPOOL_DIRECTORY>
+from build time (F<Local/Makefile>) is used.
 
 =item --show-rules
 
index afecbff..d900e99 100644 (file)
@@ -18,6 +18,7 @@
 # Version 1.2
 
 use strict;
+BEGIN { pop @INC if $INC[-1] eq '.' };
 use Getopt::Std;
 
 # Have this variable point to your exim binary.
@@ -44,6 +45,7 @@ if ($^O eq 'darwin') { # aka MacOS X
 };
 
 getopts('hf:r:y:o:s:C:zxlibRca',\%opt);
+if ($ARGV[0]) { &help; exit;}
 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}; }
@@ -86,7 +88,7 @@ EOF
 }
 
 sub collect() {
-       open(QUEUE,"$exim $eargs |") or die("Error openning pipe: $!\n");
+       open(QUEUE,"$exim $eargs |") or die("Error opening pipe: $!\n");
        while(<QUEUE>) {
                chomp();
                my $line = $_;
index fc5ad26..99a304f 100644 (file)
@@ -1,4 +1,4 @@
-#! PERL_COMMAND -w
+#! PERL_COMMAND
 
 # Mail Queue Summary
 # Christoph Lameter, 21 May 1997
@@ -27,7 +27,7 @@
 #   typo. Fix provided by Chris Liddiard.
 # November 2006 by Jori Hamalainen
 #   Added feature to separate frozen and bounced messages from queue
-#   Adedd feature to list queue per source - destination pair
+#   Added feature to list queue per source - destination pair
 #   Changed regexps to compile once to very minor speed optimization
 #   Short circuit for empty lines
 #
@@ -41,6 +41,9 @@
 
 # Slightly modified sub from eximstats
 
+use warnings;
+BEGIN { pop @INC if $INC[-1] eq '.' };
+
 sub print_volume_rounded {
 my($x) = pop @_;
 if ($x < 10000)
index 70d7c7d..2027eb8 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -13,8 +13,8 @@
 
 /* Recursively called function */
 
-static uschar *expand_string_internal(uschar *, BOOL, uschar **, BOOL, BOOL, BOOL *);
-static int_eximarith_t expanded_string_integer(uschar *, BOOL);
+static uschar *expand_string_internal(const uschar *, BOOL, const uschar **, BOOL, BOOL, BOOL *);
+static int_eximarith_t expanded_string_integer(const uschar *, BOOL);
 
 #ifdef STAND_ALONE
 #ifndef SUPPORT_CRYPTEQ
@@ -46,7 +46,7 @@ the first 8 characters of the password using a 20-round version of crypt
 (standard crypt does 25 rounds).  It then crypts the next 8 characters,
 or an empty block if the password is less than 9 characters, using a
 20-round version of crypt and the same salt as was used for the first
-block.  Charaters after the first 16 are ignored.  It always generates
+block.  Characters after the first 16 are ignored.  It always generates
 a 16-byte hash, which is expressed together with the salt as a string
 of 24 base 64 digits.  Here are some links to peruse:
 
@@ -94,10 +94,6 @@ bcrypt ({CRYPT}$2a$).
 
 
 
-#ifndef nelements
-# define nelements(arr) (sizeof(arr) / sizeof(*arr))
-#endif
-
 /*************************************************
 *            Local statics and tables            *
 *************************************************/
@@ -109,11 +105,15 @@ static uschar *item_table[] = {
   US"acl",
   US"certextract",
   US"dlfunc",
+  US"env",
   US"extract",
   US"filter",
   US"hash",
   US"hmac",
   US"if",
+#ifdef SUPPORT_I18N
+  US"imapfolder",
+#endif
   US"length",
   US"listextract",
   US"lookup",
@@ -127,6 +127,7 @@ static uschar *item_table[] = {
   US"reduce",
   US"run",
   US"sg",
+  US"sort",
   US"substr",
   US"tr" };
 
@@ -134,11 +135,15 @@ enum {
   EITEM_ACL,
   EITEM_CERTEXTRACT,
   EITEM_DLFUNC,
+  EITEM_ENV,
   EITEM_EXTRACT,
   EITEM_FILTER,
   EITEM_HASH,
   EITEM_HMAC,
   EITEM_IF,
+#ifdef SUPPORT_I18N
+  EITEM_IMAPFOLDER,
+#endif
   EITEM_LENGTH,
   EITEM_LISTEXTRACT,
   EITEM_LOOKUP,
@@ -152,6 +157,7 @@ enum {
   EITEM_REDUCE,
   EITEM_RUN,
   EITEM_SG,
+  EITEM_SORT,
   EITEM_SUBSTR,
   EITEM_TR };
 
@@ -166,7 +172,14 @@ static uschar *op_table_underscore[] = {
   US"quote_local_part",
   US"reverse_ip",
   US"time_eval",
-  US"time_interval"};
+  US"time_interval"
+#ifdef SUPPORT_I18N
+ ,US"utf8_domain_from_alabel",
+  US"utf8_domain_to_alabel",
+  US"utf8_localpart_from_alabel",
+  US"utf8_localpart_to_alabel"
+#endif
+  };
 
 enum {
   EOP_FROM_UTF8,
@@ -174,15 +187,27 @@ enum {
   EOP_QUOTE_LOCAL_PART,
   EOP_REVERSE_IP,
   EOP_TIME_EVAL,
-  EOP_TIME_INTERVAL };
+  EOP_TIME_INTERVAL
+#ifdef SUPPORT_I18N
+ ,EOP_UTF8_DOMAIN_FROM_ALABEL,
+  EOP_UTF8_DOMAIN_TO_ALABEL,
+  EOP_UTF8_LOCALPART_FROM_ALABEL,
+  EOP_UTF8_LOCALPART_TO_ALABEL
+#endif
+  };
 
 static uschar *op_table_main[] = {
   US"address",
   US"addresses",
+  US"base32",
+  US"base32d",
   US"base62",
   US"base62d",
+  US"base64",
+  US"base64d",
   US"domain",
   US"escape",
+  US"escape8bit",
   US"eval",
   US"eval10",
   US"expand",
@@ -190,6 +215,8 @@ static uschar *op_table_main[] = {
   US"hash",
   US"hex2b64",
   US"hexquote",
+  US"ipv6denorm",
+  US"ipv6norm",
   US"l",
   US"lc",
   US"length",
@@ -207,6 +234,7 @@ static uschar *op_table_main[] = {
   US"s",
   US"sha1",
   US"sha256",
+  US"sha3",
   US"stat",
   US"str2b64",
   US"strlen",
@@ -215,12 +243,17 @@ static uschar *op_table_main[] = {
   US"utf8clean" };
 
 enum {
-  EOP_ADDRESS =  sizeof(op_table_underscore)/sizeof(uschar *),
+  EOP_ADDRESS =  nelem(op_table_underscore),
   EOP_ADDRESSES,
+  EOP_BASE32,
+  EOP_BASE32D,
   EOP_BASE62,
   EOP_BASE62D,
+  EOP_BASE64,
+  EOP_BASE64D,
   EOP_DOMAIN,
   EOP_ESCAPE,
+  EOP_ESCAPE8BIT,
   EOP_EVAL,
   EOP_EVAL10,
   EOP_EXPAND,
@@ -228,6 +261,8 @@ enum {
   EOP_HASH,
   EOP_HEX2B64,
   EOP_HEXQUOTE,
+  EOP_IPV6DENORM,
+  EOP_IPV6NORM,
   EOP_L,
   EOP_LC,
   EOP_LENGTH,
@@ -245,6 +280,7 @@ enum {
   EOP_S,
   EOP_SHA1,
   EOP_SHA256,
+  EOP_SHA3,
   EOP_STAT,
   EOP_STR2B64,
   EOP_STRLEN,
@@ -442,17 +478,16 @@ static var_entry var_table[] = {
   { "bounce_return_size_limit", vtype_int,    &bounce_return_size_limit },
   { "caller_gid",          vtype_gid,         &real_gid },
   { "caller_uid",          vtype_uid,         &real_uid },
+  { "callout_address",     vtype_stringptr,   &callout_address },
   { "compile_date",        vtype_stringptr,   &version_date },
   { "compile_number",      vtype_stringptr,   &version_cnumber },
+  { "config_dir",          vtype_stringptr,   &config_main_directory },
+  { "config_file",         vtype_stringptr,   &config_main_filename },
   { "csa_status",          vtype_stringptr,   &csa_status },
 #ifdef EXPERIMENTAL_DCC
   { "dcc_header",          vtype_stringptr,   &dcc_header },
   { "dcc_result",          vtype_stringptr,   &dcc_result },
 #endif
-#ifdef WITH_OLD_DEMIME
-  { "demime_errorlevel",   vtype_int,         &demime_errorlevel },
-  { "demime_reason",       vtype_stringptr,   &demime_reason },
-#endif
 #ifndef DISABLE_DKIM
   { "dkim_algo",           vtype_dkim,        (void *)DKIM_ALGO },
   { "dkim_bodylength",     vtype_dkim,        (void *)DKIM_BODYLENGTH },
@@ -466,6 +501,7 @@ static var_entry var_table[] = {
   { "dkim_headernames",    vtype_dkim,        (void *)DKIM_HEADERNAMES },
   { "dkim_identity",       vtype_dkim,        (void *)DKIM_IDENTITY },
   { "dkim_key_granularity",vtype_dkim,        (void *)DKIM_KEY_GRANULARITY },
+  { "dkim_key_length",     vtype_int,         &dkim_key_length },
   { "dkim_key_nosubdomains",vtype_dkim,       (void *)DKIM_NOSUBDOMAINS },
   { "dkim_key_notes",      vtype_dkim,        (void *)DKIM_KEY_NOTES },
   { "dkim_key_srvtype",    vtype_dkim,        (void *)DKIM_KEY_SRVTYPE },
@@ -488,12 +524,18 @@ static var_entry var_table[] = {
   { "dnslist_value",       vtype_stringptr,   &dnslist_value },
   { "domain",              vtype_stringptr,   &deliver_domain },
   { "domain_data",         vtype_stringptr,   &deliver_domain_data },
+#ifndef DISABLE_EVENT
+  { "event_data",          vtype_stringptr,   &event_data },
+
+  /*XXX want to use generic vars for as many of these as possible*/
+  { "event_defer_errno",   vtype_int,         &event_defer_errno },
+
+  { "event_name",          vtype_stringptr,   &event_name },
+#endif
   { "exim_gid",            vtype_gid,         &exim_gid },
   { "exim_path",           vtype_stringptr,   &exim_path },
   { "exim_uid",            vtype_uid,         &exim_uid },
-#ifdef WITH_OLD_DEMIME
-  { "found_extension",     vtype_stringptr,   &found_extension },
-#endif
+  { "exim_version",        vtype_stringptr,   &version_string },
   { "headers_added",       vtype_string_func, &fn_hdrs_added },
   { "home",                vtype_stringptr,   &deliver_home },
   { "host",                vtype_stringptr,   &deliver_host },
@@ -501,6 +543,8 @@ static var_entry var_table[] = {
   { "host_data",           vtype_stringptr,   &host_data },
   { "host_lookup_deferred",vtype_int,         &host_lookup_deferred },
   { "host_lookup_failed",  vtype_int,         &host_lookup_failed },
+  { "host_port",           vtype_int,         &deliver_host_port },
+  { "initial_cwd",         vtype_stringptr,   &initial_cwd },
   { "inode",               vtype_ino,         &deliver_inode },
   { "interface_address",   vtype_stringptr,   &interface_address },
   { "interface_port",      vtype_int,         &interface_port },
@@ -535,6 +579,9 @@ static var_entry var_table[] = {
   { "message_id",          vtype_stringptr,   &message_id },
   { "message_linecount",   vtype_int,         &message_linecount },
   { "message_size",        vtype_int,         &message_size },
+#ifdef SUPPORT_I18N
+  { "message_smtputf8",    vtype_bool,        &message_smtputf8 },
+#endif
 #ifdef WITH_CONTENT_SCAN
   { "mime_anomaly_level",  vtype_int,         &mime_anomaly_level },
   { "mime_anomaly_text",   vtype_stringptr,   &mime_anomaly_text },
@@ -570,19 +617,23 @@ static var_entry var_table[] = {
   { "parent_domain",       vtype_stringptr,   &deliver_domain_parent },
   { "parent_local_part",   vtype_stringptr,   &deliver_localpart_parent },
   { "pid",                 vtype_pid,         NULL },
+#ifndef DISABLE_PRDR
+  { "prdr_requested",      vtype_bool,        &prdr_requested },
+#endif
   { "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 },
+#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS)
+  { "proxy_external_address",vtype_stringptr, &proxy_external_address },
+  { "proxy_external_port", vtype_int,         &proxy_external_port },
+  { "proxy_local_address", vtype_stringptr,   &proxy_local_address },
+  { "proxy_local_port",    vtype_int,         &proxy_local_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 },
   { "qualify_domain",      vtype_stringptr,   &qualify_domain_sender },
   { "qualify_recipient",   vtype_stringptr,   &qualify_domain_recipient },
+  { "queue_name",          vtype_stringptr,   &queue_name },
   { "rcpt_count",          vtype_int,         &rcpt_count },
   { "rcpt_defer_count",    vtype_int,         &rcpt_defer_count },
   { "rcpt_fail_count",     vtype_int,         &rcpt_fail_count },
@@ -611,6 +662,7 @@ static var_entry var_table[] = {
   { "sender_address_local_part", vtype_localpart, &sender_address },
   { "sender_data",         vtype_stringptr,   &sender_data },
   { "sender_fullhost",     vtype_stringptr,   &sender_fullhost },
+  { "sender_helo_dnssec",  vtype_bool,        &sender_helo_dnssec },
   { "sender_helo_name",    vtype_stringptr,   &sender_helo_name },
   { "sender_host_address", vtype_stringptr,   &sender_host_address },
   { "sender_host_authenticated",vtype_stringptr, &sender_host_authenticated },
@@ -641,6 +693,7 @@ static var_entry var_table[] = {
   { "sn8",                 vtype_filter_int,  &filter_sn[8] },
   { "sn9",                 vtype_filter_int,  &filter_sn[9] },
 #ifdef WITH_CONTENT_SCAN
+  { "spam_action",         vtype_stringptr,   &spam_action },
   { "spam_bar",            vtype_stringptr,   &spam_bar },
   { "spam_report",         vtype_stringptr,   &spam_report },
   { "spam_score",          vtype_stringptr,   &spam_score },
@@ -684,6 +737,9 @@ static var_entry var_table[] = {
   { "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 },
+#ifdef EXPERIMENTAL_DANE
+  { "tls_out_dane",        vtype_bool,        &tls_out.dane_verified },
+#endif
   { "tls_out_ocsp",        vtype_int,         &tls_out.ocsp },
   { "tls_out_ourcert",     vtype_cert,        &tls_out.ourcert },
   { "tls_out_peercert",    vtype_cert,        &tls_out.peercert },
@@ -691,6 +747,9 @@ static var_entry var_table[] = {
 #if defined(SUPPORT_TLS)
   { "tls_out_sni",         vtype_stringptr,   &tls_out.sni },
 #endif
+#ifdef EXPERIMENTAL_DANE
+  { "tls_out_tlsa_usage",  vtype_int,         &tls_out.tlsa_usage },
+#endif
 
   { "tls_peerdn",          vtype_stringptr,   &tls_in.peerdn },        /* mind the alphabetical order! */
 #if defined(SUPPORT_TLS)
@@ -705,18 +764,9 @@ static var_entry var_table[] = {
   { "tod_logfile",         vtype_todlf,       NULL },
   { "tod_zone",            vtype_todzone,     NULL },
   { "tod_zulu",            vtype_todzulu,     NULL },
-#ifdef EXPERIMENTAL_TPDA
-  { "tpda_defer_errno",     vtype_int,         &tpda_defer_errno },
-  { "tpda_defer_errstr",    vtype_stringptr,   &tpda_defer_errstr },
-  { "tpda_delivery_confirmation", vtype_stringptr,   &tpda_delivery_confirmation },
-  { "tpda_delivery_domain", vtype_stringptr,   &tpda_delivery_domain },
-  { "tpda_delivery_fqdn",   vtype_stringptr,   &tpda_delivery_fqdn },
-  { "tpda_delivery_ip",     vtype_stringptr,   &tpda_delivery_ip },
-  { "tpda_delivery_local_part",vtype_stringptr,&tpda_delivery_local_part },
-  { "tpda_delivery_port",   vtype_int,         &tpda_delivery_port },
-#endif
   { "transport_name",      vtype_stringptr,   &transport_name },
   { "value",               vtype_stringptr,   &lookup_value },
+  { "verify_mode",         vtype_stringptr,   &verify_mode },
   { "version_number",      vtype_stringptr,   &version_string },
   { "warn_message_delay",  vtype_stringptr,   &warnmsg_delay },
   { "warn_message_recipient",vtype_stringptr, &warnmsg_recipients },
@@ -726,7 +776,7 @@ static var_entry var_table[] = {
   { "warnmsg_recipients",  vtype_stringptr,   &warnmsg_recipients }
 };
 
-static int var_table_size = sizeof(var_table)/sizeof(var_entry);
+static int var_table_size = nelem(var_table);
 static uschar var_buffer[256];
 static BOOL malformed_header;
 
@@ -794,6 +844,9 @@ static int utf8_table2[] = { 0xff, 0x1f, 0x0f, 0x07, 0x03, 0x01};
     }
 
 
+
+static uschar * base32_chars = US"abcdefghijklmnopqrstuvwxyz234567";
+
 /*************************************************
 *           Binary chop search on a table        *
 *************************************************/
@@ -913,7 +966,9 @@ vaguely_random_number(int max)
 #ifdef HAVE_ARC4RANDOM
       /* cryptographically strong randomness, common on *BSD platforms, not
       so much elsewhere.  Alas. */
+#ifndef NOT_HAVE_ARC4RANDOM_STIR
       arc4random_stir();
+#endif
 #elif defined(HAVE_SRANDOM) || defined(HAVE_SRANDOMDEV)
 #ifdef HAVE_SRANDOMDEV
       /* uses random(4) for seeding */
@@ -961,8 +1016,8 @@ Note: The test for *s != 0 in the while loop is necessary because
 Ustrchr() yields non-NULL if the character is zero (which is not something
 I expected). */
 
-static uschar *
-read_name(uschar *name, int max, uschar *s, uschar *extras)
+static const uschar *
+read_name(uschar *name, int max, const uschar *s, uschar *extras)
 {
 int ptr = 0;
 while (*s != 0 && (isalnum(*s) || Ustrchr(extras, *s) != NULL))
@@ -995,8 +1050,8 @@ Arguments:
 Returns:    a pointer to the first character after the header name
 */
 
-static uschar *
-read_header_name(uschar *name, int max, uschar *s)
+static const uschar *
+read_header_name(uschar *name, int max, const uschar *s)
 {
 int prelen = Ustrchr(name, '_') - name + 1;
 int ptr = Ustrlen(name) - prelen;
@@ -1024,6 +1079,8 @@ return s;
 
 Returns:  a pointer to the character after the last digit
 */
+/*XXX consider expanding to int_eximarith_t.  But the test for
+"overbig numbers" in 0002 still needs to overflow it. */
 
 static uschar *
 read_number(int *n, uschar *s)
@@ -1033,6 +1090,14 @@ while (isdigit(*s)) *n = *n * 10 + (*s++ - '0');
 return s;
 }
 
+static const uschar *
+read_cnumber(int *n, const uschar *s)
+{
+*n = 0;
+while (isdigit(*s)) *n = *n * 10 + (*s++ - '0');
+return s;
+}
+
 
 
 /*************************************************
@@ -1050,7 +1115,7 @@ Returns:    NULL if the subfield was not found, or
 */
 
 static uschar *
-expand_getkeyed(uschar *key, uschar *s)
+expand_getkeyed(uschar *key, const uschar *s)
 {
 int length = Ustrlen(key);
 while (isspace(*s)) s++;
@@ -1061,7 +1126,7 @@ while (*s != 0)
   {
   int dkeylength;
   uschar *data;
-  uschar *dkey = s;
+  const uschar *dkey = s;
 
   while (*s != 0 && *s != '=' && !isspace(*s)) s++;
   dkeylength = s - dkey;
@@ -1172,17 +1237,17 @@ return fieldtext;
 
 
 static uschar *
-expand_getlistele(int field, uschar * list)
+expand_getlistele(int field, const uschar * list)
 {
-uschar * tlist= list;
+const 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);
@@ -1222,7 +1287,7 @@ certfield * cp;
 
 if (!(vp = find_var_ent(certvar)))
   {
-  expand_string_message = 
+  expand_string_message =
     string_sprintf("no variable named \"%s\"", certvar);
   return NULL;          /* Unknown variable name */
   }
@@ -1230,7 +1295,7 @@ if (!(vp = find_var_ent(certvar)))
 want to do that in future */
 if (vp->type != vtype_cert)
   {
-  expand_string_message = 
+  expand_string_message =
     string_sprintf("\"%s\" is not a certificate", certvar);
   return NULL;          /* Unknown variable name */
   }
@@ -1241,7 +1306,7 @@ if (*field >= '0' && *field <= '9')
   return tls_cert_ext_by_oid(*(void **)vp->value, field, 0);
 
 for(cp = certfields;
-    cp < certfields + nelements(certfields);
+    cp < certfields + nelem(certfields);
     cp++)
   if (Ustrncmp(cp->name, field, cp->namelen) == 0)
     {
@@ -1250,7 +1315,7 @@ for(cp = certfields;
     return (*cp->getfn)( *(void **)vp->value, modifier );
     }
 
-expand_string_message = 
+expand_string_message =
   string_sprintf("bad field selector \"%s\" for certextract", field);
 return NULL;
 }
@@ -1411,7 +1476,7 @@ unsigned long int total = 0; /* no overflow */
 
 while (*s != 0)
   {
-  if (i == 0) i = sizeof(prime)/sizeof(int) - 1;
+  if (i == 0) i = nelem(prime) - 1;
   total += prime[i--] * (unsigned int)(*s++);
   }
 
@@ -1524,7 +1589,7 @@ for (i = 0; i < 2; i++)
 
         size += ilen + comma + 1;  /* +1 for the newline */
 
-        /* Second pass - concatentate the data, up to a maximum. Note that
+        /* Second pass - concatenate the data, up to a maximum. Note that
         the loop stops when size hits the limit. */
 
         if (i != 0)
@@ -1616,9 +1681,8 @@ if (!enable_dollar_recipients) return NULL; else
   uschar * s = store_get(size);
   for (i = 0; i < recipients_count; i++)
     {
-    if (i != 0) s = string_cat(s, &size, &ptr, US", ", 2);
-    s = string_cat(s, &size, &ptr, recipients_list[i].address,
-      Ustrlen(recipients_list[i].address));
+    if (i != 0) s = string_catn(s, &size, &ptr, US", ", 2);
+    s = string_cat(s, &size, &ptr, recipients_list[i].address);
     }
   s[ptr] = 0;     /* string_cat() leaves room */
   return s;
@@ -1672,7 +1736,7 @@ if ((Ustrncmp(name, "acl_c", 5) == 0 || Ustrncmp(name, "acl_m", 5) == 0) &&
   {
   tree_node *node =
     tree_search((name[4] == 'c')? acl_var_c : acl_var_m, name + 4);
-  return (node == NULL)? (strict_acl_vars? NULL : US"") : node->data.ptr;
+  return node ? node->data.ptr : strict_acl_vars ? NULL : US"";
   }
 
 /* Handle $auth<n> variables. */
@@ -1682,7 +1746,14 @@ if (Ustrncmp(name, "auth", 4) == 0)
   uschar *endptr;
   int n = Ustrtoul(name + 4, &endptr, 10);
   if (*endptr == 0 && n != 0 && n <= AUTH_VARS)
-    return (auth_vars[n-1] == NULL)? US"" : auth_vars[n-1];
+    return !auth_vars[n-1] ? US"" : auth_vars[n-1];
+  }
+else if (Ustrncmp(name, "regex", 5) == 0)
+  {
+  uschar *endptr;
+  int n = Ustrtoul(name + 5, &endptr, 10);
+  if (*endptr == 0 && n != 0 && n <= REGEX_VARS)
+    return !regex_vars[n-1] ? US"" : regex_vars[n-1];
   }
 
 /* For all other variables, search the table */
@@ -1700,153 +1771,150 @@ val = vp->value;
 switch (vp->type)
   {
   case vtype_filter_int:
-  if (!filter_running) return NULL;
-  /* Fall through */
-  /* VVVVVVVVVVVV */
+    if (!filter_running) return NULL;
+    /* Fall through */
+    /* VVVVVVVVVVVV */
   case vtype_int:
-  sprintf(CS var_buffer, "%d", *(int *)(val)); /* Integer */
-  return var_buffer;
+    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;
+    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;
+    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;
+    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;
+    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;
+    return (s = *((uschar **)(val))) ? s : US"";
 
   case vtype_pid:
-  sprintf(CS var_buffer, "%d", (int)getpid()); /* pid */
-  return var_buffer;
+    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;
+    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;
+    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;
+    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;
+    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);
+    return find_header(NULL, exists_only, newsize, FALSE, NULL);
 
   case vtype_msgheaders_raw:
-  return find_header(NULL, exists_only, newsize, TRUE, NULL);
+    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)
+    ss = (uschar **)(val);
+    if (*ss == NULL && deliver_datafile >= 0)  /* Read body when needed */
       {
-      body[len] = 0;
-      if (message_body_newlines)   /* Separate loops for efficiency */
+      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)
        {
-       while (len > 0)
-         { if (body[--len] == 0) body[len] = ' '; }
+       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;
+         }
        }
-      else
+      if (lseek(deliver_datafile, start_offset, SEEK_SET) < 0)
+       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "deliver_datafile lseek: %s",
+         strerror(errno));
+      len = read(deliver_datafile, body, len);
+      if (len > 0)
        {
-       while (len > 0)
-         { if (body[--len] == '\n' || body[len] == 0) body[len] = ' '; }
+       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);
+    return tod_stamp(tod_bsdin);
 
   case vtype_tode:                           /* Unix epoch time of day */
-  return tod_stamp(tod_epoch);
+    return tod_stamp(tod_epoch);
 
   case vtype_todel:                          /* Unix epoch/usec time of day */
-  return tod_stamp(tod_epoch_l);
+    return tod_stamp(tod_epoch_l);
 
   case vtype_todf:                           /* Full time of day */
-  return tod_stamp(tod_full);
+    return tod_stamp(tod_full);
 
   case vtype_todl:                           /* Log format time of day */
-  return tod_stamp(tod_log_bare);            /* (without timezone) */
+    return tod_stamp(tod_log_bare);            /* (without timezone) */
 
   case vtype_todzone:                        /* Time zone offset only */
-  return tod_stamp(tod_zone);
+    return tod_stamp(tod_zone);
 
   case vtype_todzulu:                        /* Zulu time */
-  return tod_stamp(tod_zulu);
+    return tod_stamp(tod_zulu);
 
   case vtype_todlf:                          /* Log file datestamp tod */
-  return tod_stamp(tod_log_datestamp_daily);
+    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;
+    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:
     {
@@ -1871,12 +1939,12 @@ switch (vp->type)
   return var_buffer;
 
   case vtype_cert:
-  return *(void **)val ? US"<cert>" : US"";
+    return *(void **)val ? US"<cert>" : US"";
 
-  #ifndef DISABLE_DKIM
+#ifndef DISABLE_DKIM
   case vtype_dkim:
-  return dkim_exim_expand_query((int)(long)val);
-  #endif
+    return dkim_exim_expand_query((int)(long)val);
+#endif
 
   }
 
@@ -1898,6 +1966,7 @@ return;          /* Unknown variable name, fail silently */
 
 
 
+
 /*************************************************
 *           Read and expand substrings           *
 *************************************************/
@@ -1924,23 +1993,28 @@ Returns:     0 OK; string pointer updated
 */
 
 static int
-read_subs(uschar **sub, int n, int m, uschar **sptr, BOOL skipping,
+read_subs(uschar **sub, int n, int m, const uschar **sptr, BOOL skipping,
   BOOL check_end, uschar *name, BOOL *resetok)
 {
 int i;
-uschar *s = *sptr;
+const uschar *s = *sptr;
 
 while (isspace(*s)) s++;
 for (i = 0; i < n; i++)
   {
   if (*s != '{')
     {
-    if (i < m) return 1;
+    if (i < m)
+      {
+      expand_string_message = string_sprintf("Not enough arguments for '%s' "
+       "(min is %d)", name, m);
+      return 1;
+      }
     sub[i] = NULL;
     break;
     }
-  sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, resetok);
-  if (sub[i] == NULL) return 3;
+  if (!(sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, resetok)))
+    return 3;
   if (*s++ != '}') return 1;
   while (isspace(*s)) s++;
   }
@@ -1948,10 +2022,11 @@ if (check_end && *s++ != '}')
   {
   if (s[-1] == '{')
     {
-    expand_string_message = string_sprintf("Too many arguments for \"%s\" "
+    expand_string_message = string_sprintf("Too many arguments for '%s' "
       "(max is %d)", name, n);
     return 2;
     }
+  expand_string_message = string_sprintf("missing '}' after '%s'", name);
   return 1;
   }
 
@@ -1994,7 +2069,7 @@ Load args from sub array to globals, and call acl_check().
 Sub array will be corrupted on return.
 
 Returns:       OK         access is granted by an ACCEPT verb
-               DISCARD    access is granted by a DISCARD verb
+               DISCARD    access is (apparently) granted by a DISCARD verb
               FAIL       access is denied
               FAIL_DROP  access is denied; drop the connection
               DEFER      can't tell at the moment
@@ -2004,15 +2079,15 @@ static int
 eval_acl(uschar ** sub, int nsub, uschar ** user_msgp)
 {
 int i;
-uschar *tmp;
 int sav_narg = acl_narg;
 int ret;
+uschar * dummy_logmsg;
 extern int acl_where;
 
-if(--nsub > sizeof(acl_arg)/sizeof(*acl_arg)) nsub = sizeof(acl_arg)/sizeof(*acl_arg);
+if(--nsub > nelem(acl_arg)) nsub = nelem(acl_arg);
 for (i = 0; i < nsub && sub[i+1]; i++)
   {
-  tmp = acl_arg[i];
+  uschar * tmp = acl_arg[i];
   acl_arg[i] = sub[i+1];       /* place callers args in the globals */
   sub[i+1] = tmp;              /* stash the old args using our caller's storage */
   }
@@ -2024,12 +2099,12 @@ while (i < nsub)
   }
 
 DEBUG(D_expand)
-  debug_printf("expanding: acl: %s  arg: %s%s\n",
+  debug_printf_indent("expanding: acl: %s  arg: %s%s\n",
     sub[0],
     acl_narg>0 ? acl_arg[0] : US"<none>",
     acl_narg>1 ? " +more"   : "");
 
-ret = acl_eval(acl_where, sub[0], user_msgp, &tmp);
+ret = acl_eval(acl_where, sub[0], user_msgp, &dummy_logmsg);
 
 for (i = 0; i < nsub; i++)
   acl_arg[i] = sub[i+1];       /* restore old args */
@@ -2060,8 +2135,8 @@ Returns:   a pointer to the first character after the condition, or
            NULL after an error
 */
 
-static uschar *
-eval_condition(uschar *s, BOOL *resetok, BOOL *yield)
+static const uschar *
+eval_condition(const uschar *s, BOOL *resetok, BOOL *yield)
 {
 BOOL testfor = TRUE;
 BOOL tempcond, combined_cond;
@@ -2071,7 +2146,7 @@ int i, rc, cond_type, roffset;
 int_eximarith_t num[2];
 struct stat statbuf;
 uschar name[256];
-uschar *sub[10];
+const uschar *sub[10];
 
 const pcre *re;
 const uschar *rerror;
@@ -2111,7 +2186,7 @@ if (name[0] == 0)
 
 /* Find which condition we are dealing with, and switch on it */
 
-cond_type = chop_match(name, cond_table, sizeof(cond_table)/sizeof(uschar *));
+cond_type = chop_match(name, cond_table, nelem(cond_table));
 switch(cond_type)
   {
   /* def: tests for a non-empty variable, or for the existence of a header. If
@@ -2291,6 +2366,7 @@ switch(cond_type)
   case ECOND_ACL:
     /* ${if acl {{name}{arg1}{arg2}...}  {yes}{no}} */
     {
+    uschar *sub[10];
     uschar *user_msg;
     BOOL cond = FALSE;
     int size = 0;
@@ -2299,7 +2375,7 @@ switch(cond_type)
     while (isspace(*s)) s++;
     if (*s++ != '{') goto COND_FAILED_CURLY_START;     /*}*/
 
-    switch(read_subs(sub, sizeof(sub)/sizeof(*sub), 1,
+    switch(read_subs(sub, nelem(sub), 1,
       &s, yield == NULL, TRUE, US"acl", resetok))
       {
       case 1: expand_string_message = US"too few arguments or bracketing "
@@ -2308,8 +2384,8 @@ switch(cond_type)
       case 3: return NULL;
       }
 
-    *resetok = FALSE;
-    if (yield != NULL) switch(eval_acl(sub, sizeof(sub)/sizeof(*sub), &user_msg))
+    *resetok = FALSE;  /* eval_acl() might allocate; do not reclaim */
+    if (yield != NULL) switch(eval_acl(sub, nelem(sub), &user_msg))
        {
        case OK:
          cond = TRUE;
@@ -2317,7 +2393,7 @@ switch(cond_type)
           lookup_value = NULL;
          if (user_msg)
            {
-            lookup_value = string_cat(NULL, &size, &ptr, user_msg, Ustrlen(user_msg));
+            lookup_value = string_cat(NULL, &size, &ptr, user_msg);
             lookup_value[ptr] = '\0';
            }
          *yield = cond == testfor;
@@ -2325,6 +2401,7 @@ switch(cond_type)
 
        case DEFER:
           expand_string_forcedfail = TRUE;
+         /*FALLTHROUGH*/
        default:
           expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]);
          return NULL;
@@ -2341,29 +2418,32 @@ switch(cond_type)
   in their own set of braces. */
 
   case ECOND_SASLAUTHD:
-  #ifndef CYRUS_SASLAUTHD_SOCKET
-  goto COND_FAILED_NOT_COMPILED;
-  #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", resetok))
-    {
-    case 1: expand_string_message = US"too few arguments or bracketing "
-      "error for saslauthd";
-    case 2:
-    case 3: return NULL;
-    }
-  if (sub[2] == NULL) sub[3] = NULL;  /* realm if no service */
-  if (yield != NULL)
+#ifndef CYRUS_SASLAUTHD_SOCKET
+    goto COND_FAILED_NOT_COMPILED;
+#else
     {
-    int rc;
-    rc = auth_call_saslauthd(sub[0], sub[1], sub[2], sub[3],
-      &expand_string_message);
-    if (rc == ERROR || rc == DEFER) return NULL;
-    *yield = (rc == OK) == testfor;
+    uschar *sub[4];
+    while (isspace(*s)) s++;
+    if (*s++ != '{') goto COND_FAILED_CURLY_START;     /* }-for-text-editors */
+    switch(read_subs(sub, nelem(sub), 2, &s, yield == NULL, TRUE, US"saslauthd",
+                   resetok))
+      {
+      case 1: expand_string_message = US"too few arguments or bracketing "
+       "error for saslauthd";
+      case 2:
+      case 3: return NULL;
+      }
+    if (sub[2] == NULL) sub[3] = NULL;  /* realm if no service */
+    if (yield != NULL)
+      {
+      int rc = auth_call_saslauthd(sub[0], sub[1], sub[2], sub[3],
+       &expand_string_message);
+      if (rc == ERROR || rc == DEFER) return NULL;
+      *yield = (rc == OK) == testfor;
+      }
+    return s;
     }
-  return s;
-  #endif /* CYRUS_SASLAUTHD_SOCKET */
+#endif /* CYRUS_SASLAUTHD_SOCKET */
 
 
   /* symbolic operators for numeric and string comparison, and a number of
@@ -2439,19 +2519,17 @@ switch(cond_type)
     checking for them individually. */
 
     if (!isalpha(name[0]) && yield != NULL)
-      {
       if (sub[i][0] == 0)
         {
         num[i] = 0;
         DEBUG(D_expand)
-          debug_printf("empty string cast to zero for numerical comparison\n");
+          debug_printf_indent("empty string cast to zero for numerical comparison\n");
         }
       else
         {
         num[i] = expanded_string_integer(sub[i], FALSE);
         if (expand_string_message != NULL) return NULL;
         }
-      }
     }
 
   /* Result not required */
@@ -2619,7 +2697,7 @@ switch(cond_type)
       uschar digest[16];
 
       md5_start(&base);
-      md5_end(&base, (uschar *)sub[0], Ustrlen(sub[0]), digest);
+      md5_end(&base, sub[0], Ustrlen(sub[0]), digest);
 
       /* If the length that we are comparing against is 24, the MD5 digest
       is expressed as a base64 string. This is the way LDAP does it. However,
@@ -2628,7 +2706,7 @@ switch(cond_type)
 
       if (sublen == 24)
         {
-        uschar *coded = auth_b64encode((uschar *)digest, 16);
+        uschar *coded = b64encode(digest, 16);
         DEBUG(D_auth) debug_printf("crypteq: using MD5+B64 hashing\n"
           "  subject=%s\n  crypted=%s\n", coded, sub[1]+5);
         tempcond = (Ustrcmp(coded, sub[1]+5) == 0);
@@ -2654,11 +2732,11 @@ switch(cond_type)
     else if (strncmpic(sub[1], US"{sha1}", 6) == 0)
       {
       int sublen = Ustrlen(sub[1]+6);
-      sha1 base;
+      hctx h;
       uschar digest[20];
 
-      sha1_start(&base);
-      sha1_end(&base, (uschar *)sub[0], Ustrlen(sub[0]), digest);
+      sha1_start(&h);
+      sha1_end(&h, sub[0], Ustrlen(sub[0]), digest);
 
       /* If the length that we are comparing against is 28, assume the SHA1
       digest is expressed as a base64 string. If the length is 40, assume a
@@ -2666,7 +2744,7 @@ switch(cond_type)
 
       if (sublen == 28)
         {
-        uschar *coded = auth_b64encode((uschar *)digest, 20);
+        uschar *coded = b64encode(digest, 20);
         DEBUG(D_auth) debug_printf("crypteq: using SHA1+B64 hashing\n"
           "  subject=%s\n  crypted=%s\n", coded, sub[1]+6);
         tempcond = (Ustrcmp(coded, sub[1]+6) == 0);
@@ -2723,7 +2801,7 @@ switch(cond_type)
       #define XSTR(s) STR(s)
       DEBUG(D_auth) debug_printf("crypteq: using %s()\n"
         "  subject=%s\n  crypted=%s\n",
-        (which == 0)? XSTR(DEFAULT_CRYPT) : (which == 1)? "crypt" : "crypt16",
+        which == 0 ? XSTR(DEFAULT_CRYPT) : which == 1 ? "crypt" : "crypt16",
         coded, sub[1]);
       #undef STR
       #undef XSTR
@@ -2732,8 +2810,16 @@ switch(cond_type)
       salt), force failure. Otherwise we get false positives: with an empty
       string the yield of crypt() is an empty string! */
 
-      tempcond = (Ustrlen(sub[1]) < 2)? FALSE :
-        (Ustrcmp(coded, sub[1]) == 0);
+      if (coded)
+       tempcond = Ustrlen(sub[1]) < 2 ? FALSE : Ustrcmp(coded, sub[1]) == 0;
+      else if (errno == EINVAL)
+       tempcond = FALSE;
+      else
+       {
+       expand_string_message = string_sprintf("crypt error: %s\n",
+         US strerror(errno));
+       return NULL;
+       }
       }
     break;
     #endif  /* SUPPORT_CRYPTEQ */
@@ -2741,17 +2827,18 @@ switch(cond_type)
     case ECOND_INLIST:
     case ECOND_INLISTI:
       {
+      const uschar * list = sub[1];
       int sep = 0;
       uschar *save_iterate_item = iterate_item;
       int (*compare)(const uschar *, const uschar *);
 
+      DEBUG(D_expand) debug_printf_indent("condition: %s\n", name);
+
       tempcond = FALSE;
-      if (cond_type == ECOND_INLISTI)
-        compare = strcmpic;
-      else
-        compare = (int (*)(const uschar *, const uschar *)) strcmp;
+      compare = cond_type == ECOND_INLISTI
+        ? strcmpic : (int (*)(const uschar *, const uschar *)) strcmp;
 
-      while ((iterate_item = string_nextinlist(&sub[1], &sep, NULL, 0)) != NULL)
+      while ((iterate_item = string_nextinlist(&list, &sep, NULL, 0)))
         if (compare(sub[0], iterate_item) == 0)
           {
           tempcond = TRUE;
@@ -2829,9 +2916,12 @@ switch(cond_type)
   case ECOND_FORALL:
   case ECOND_FORANY:
     {
+    const uschar * list;
     int sep = 0;
     uschar *save_iterate_item = iterate_item;
 
+    DEBUG(D_expand) debug_printf_indent("condition: %s\n", name);
+
     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, resetok);
@@ -2866,9 +2956,10 @@ switch(cond_type)
       }
 
     if (yield != NULL) *yield = !testfor;
-    while ((iterate_item = string_nextinlist(&sub[0], &sep, NULL, 0)) != NULL)
+    list = sub[0];
+    while ((iterate_item = string_nextinlist(&list, &sep, NULL, 0)) != NULL)
       {
-      DEBUG(D_expand) debug_printf("%s: $item = \"%s\"\n", name, iterate_item);
+      DEBUG(D_expand) debug_printf_indent("%s: $item = \"%s\"\n", name, iterate_item);
       if (!eval_condition(sub[1], resetok, &tempcond))
         {
         expand_string_message = string_sprintf("%s inside \"%s\" condition",
@@ -2876,7 +2967,7 @@ switch(cond_type)
         iterate_item = save_iterate_item;
         return NULL;
         }
-      DEBUG(D_expand) debug_printf("%s: condition evaluated to %s\n", name,
+      DEBUG(D_expand) debug_printf_indent("%s: condition evaluated to %s\n", name,
         tempcond? "true":"false");
 
       if (yield != NULL) *yield = (tempcond == testfor);
@@ -2933,7 +3024,7 @@ switch(cond_type)
         }
       }
     DEBUG(D_expand)
-      debug_printf("considering %s: %s\n", ourname, len ? t : US"<empty>");
+      debug_printf_indent("considering %s: %s\n", ourname, len ? t : US"<empty>");
     /* logic for the lax case from expand_check_condition(), which also does
     expands, and the logic is both short and stable enough that there should
     be no maintenance burden from replicating it. */
@@ -2960,6 +3051,8 @@ switch(cond_type)
        "value \"%s\"", t);
       return NULL;
       }
+    DEBUG(D_expand) debug_printf_indent("%s: condition evaluated to %s\n", ourname,
+        boolvalue? "true":"false");
     if (yield != NULL) *yield = (boolvalue == testfor);
     return s;
     }
@@ -3074,7 +3167,8 @@ Arguments:
   yieldptr       points to the output string pointer
   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
+  type           "lookup", "if", "extract", "run", "env", "listextract" or
+                 "certextract" for error message
   resetok       if not NULL, pointer to flag - write FALSE if unsafe to reset
                the store.
 
@@ -3084,12 +3178,13 @@ Returns:         0 OK; lookup_value has been reset to save_lookup
 */
 
 static int
-process_yesno(BOOL skipping, BOOL yes, uschar *save_lookup, uschar **sptr,
+process_yesno(BOOL skipping, BOOL yes, uschar *save_lookup, const uschar **sptr,
   uschar **yieldptr, int *sizeptr, int *ptrptr, uschar *type, BOOL *resetok)
 {
 int rc = 0;
-uschar *s = *sptr;    /* Local value */
+const uschar *s = *sptr;    /* Local value */
 uschar *sub1, *sub2;
+const uschar * errwhere;
 
 /* If there are no following strings, we substitute the contents of $value for
 lookups and for extractions in the success case. For the ${if item, the string
@@ -3101,13 +3196,13 @@ if (*s == '}')
   {
   if (type[0] == 'i')
     {
-    if (yes) *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, US"true", 4);
+    if (yes && !skipping)
+      *yieldptr = string_catn(*yieldptr, sizeptr, ptrptr, US"true", 4);
     }
   else
     {
-    if (yes && lookup_value != NULL)
-      *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, lookup_value,
-        Ustrlen(lookup_value));
+    if (yes && lookup_value && !skipping)
+      *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, lookup_value);
     lookup_value = save_lookup;
     }
   s++;
@@ -3116,7 +3211,11 @@ if (*s == '}')
 
 /* The first following string must be braced. */
 
-if (*s++ != '{') goto FAILED_CURLY;
+if (*s++ != '{')
+  {
+  errwhere = US"'yes' part did not start with '{'";
+  goto FAILED_CURLY;
+  }
 
 /* Expand the first substring. Forced failures are noticed only if we actually
 want this string. Set skipping in the call in the fail case (this will always
@@ -3125,17 +3224,22 @@ be the case if we were already skipping). */
 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;
+if (*s++ != '}')
+  {
+  errwhere = US"'yes' part did not end with '}'";
+  goto FAILED_CURLY;
+  }
 
 /* If we want the first string, add it to the output */
 
 if (yes)
-  *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, sub1, Ustrlen(sub1));
+  *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, sub1);
 
-/* If this is called from a lookup or an extract, we want to restore $value to
-what it was at the start of the item, so that it has this value during the
-second string expansion. For the call from "if" or "run" to this function,
-save_lookup is set to lookup_value, so that this statement does nothing. */
+/* If this is called from a lookup/env or a (cert)extract, we want to restore
+$value to what it was at the start of the item, so that it has this value
+during the second string expansion. For the call from "if" or "run" to this
+function, save_lookup is set to lookup_value, so that this statement does
+nothing. */
 
 lookup_value = save_lookup;
 
@@ -3150,12 +3254,16 @@ if (*s == '{')
   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;
+  if (*s++ != '}')
+    {
+    errwhere = US"'no' part did not start with '{'";
+    goto FAILED_CURLY;
+    }
 
   /* If we want the second string, add it to the output */
 
   if (!yes)
-    *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, sub2, Ustrlen(sub2));
+    *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, sub2);
   }
 
 /* If there is no second string, but the word "fail" is present when the use of
@@ -3166,13 +3274,18 @@ inside another lookup or if or extract. */
 else if (*s != '}')
   {
   uschar name[256];
-  s = read_name(name, sizeof(name), s, US"_");
+  /* deconst cast ok here as source is s anyway */
+  s = US read_name(name, sizeof(name), s, US"_");
   if (Ustrcmp(name, "fail") == 0)
     {
     if (!yes && !skipping)
       {
       while (isspace(*s)) s++;
-      if (*s++ != '}') goto FAILED_CURLY;
+      if (*s++ != '}')
+        {
+       errwhere = US"did not close with '}' after forcedfail";
+       goto FAILED_CURLY;
+       }
       expand_string_message =
         string_sprintf("\"%s\" failed and \"fail\" requested", type);
       expand_string_forcedfail = TRUE;
@@ -3190,23 +3303,30 @@ else if (*s != '}')
 /* All we have to do now is to check on the final closing brace. */
 
 while (isspace(*s)) s++;
-if (*s++ == '}') goto RETURN;
-
-/* Get here if there is a bracketing failure */
-
-FAILED_CURLY:
-rc++;
-
-/* Get here for other failures */
-
-FAILED:
-rc++;
+if (*s++ != '}')
+  {
+  errwhere = US"did not close with '}'";
+  goto FAILED_CURLY;
+  }
 
-/* Update the input pointer value before returning */
 
 RETURN:
+/* Update the input pointer value before returning */
 *sptr = s;
 return rc;
+
+FAILED_CURLY:
+  /* Get here if there is a bracketing failure */
+  expand_string_message = string_sprintf(
+    "curly-bracket problem in conditional yes/no parsing: %s\n"
+    " remaining string is '%s'", errwhere, --s);
+  rc = 2;
+  goto RETURN;
+
+FAILED:
+  /* Get here for other failures */
+  rc = 1;
+  goto RETURN;
 }
 
 
@@ -3232,7 +3352,7 @@ chash_start(int type, void *base)
 if (type == HMAC_MD5)
   md5_start((md5 *)base);
 else
-  sha1_start((sha1 *)base);
+  sha1_start((hctx *)base);
 }
 
 static void
@@ -3241,7 +3361,7 @@ chash_mid(int type, void *base, uschar *string)
 if (type == HMAC_MD5)
   md5_mid((md5 *)base, string);
 else
-  sha1_mid((sha1 *)base, string);
+  sha1_mid((hctx *)base, string);
 }
 
 static void
@@ -3250,7 +3370,7 @@ chash_end(int type, void *base, uschar *string, int length, uschar *digest)
 if (type == HMAC_MD5)
   md5_end((md5 *)base, string, length, digest);
 else
-  sha1_end((sha1 *)base, string, length, digest);
+  sha1_end((hctx *)base, string, length, digest);
 }
 
 
@@ -3309,8 +3429,7 @@ prvs_hmac_sha1(uschar *address, uschar *key, uschar *key_num, uschar *daystamp)
 {
 uschar *hash_source, *p;
 int size = 0,offset = 0,i;
-sha1 sha1_base;
-void *use_base = &sha1_base;
+hctx h;
 uschar innerhash[20];
 uschar finalhash[20];
 uschar innerkey[64];
@@ -3323,12 +3442,12 @@ if (key_num == NULL)
 if (Ustrlen(key) > 64)
   return NULL;
 
-hash_source = string_cat(NULL,&size,&offset,key_num,1);
-string_cat(hash_source,&size,&offset,daystamp,3);
-string_cat(hash_source,&size,&offset,address,Ustrlen(address));
+hash_source = string_catn(NULL, &size, &offset, key_num, 1);
+hash_source = string_catn(hash_source, &size, &offset, daystamp, 3);
+hash_source = string_cat(hash_source, &size, &offset, address);
 hash_source[offset] = '\0';
 
-DEBUG(D_expand) debug_printf("prvs: hash source is '%s'\n", hash_source);
+DEBUG(D_expand) debug_printf_indent("prvs: hash source is '%s'\n", hash_source);
 
 memset(innerkey, 0x36, 64);
 memset(outerkey, 0x5c, 64);
@@ -3339,13 +3458,13 @@ for (i = 0; i < Ustrlen(key); i++)
   outerkey[i] ^= key[i];
   }
 
-chash_start(HMAC_SHA1, use_base);
-chash_mid(HMAC_SHA1, use_base, innerkey);
-chash_end(HMAC_SHA1, use_base, hash_source, offset, innerhash);
+chash_start(HMAC_SHA1, &h);
+chash_mid(HMAC_SHA1, &h, innerkey);
+chash_end(HMAC_SHA1, &h, hash_source, offset, innerhash);
 
-chash_start(HMAC_SHA1, use_base);
-chash_mid(HMAC_SHA1, use_base, outerkey);
-chash_end(HMAC_SHA1, use_base, innerhash, 20, finalhash);
+chash_start(HMAC_SHA1, &h);
+chash_mid(HMAC_SHA1, &h, outerkey);
+chash_end(HMAC_SHA1, &h, innerhash, 20, finalhash);
 
 p = finalhash_hex;
 for (i = 0; i < 3; i++)
@@ -3365,9 +3484,9 @@ return finalhash_hex;
 *        Join a file onto the output string      *
 *************************************************/
 
-/* This is used for readfile and after a run expansion. It joins the contents
-of a file onto the output string, globally replacing newlines with a given
-string (optionally). The file is closed at the end.
+/* This is used for readfile/readsock and after a run expansion.
+It joins the contents of a file onto the output string, globally replacing
+newlines with a given string (optionally).
 
 Arguments:
   f            the FILE
@@ -3382,21 +3501,18 @@ Returns:       new value of string pointer
 static uschar *
 cat_file(FILE *f, uschar *yield, int *sizep, int *ptrp, uschar *eol)
 {
-int eollen;
 uschar buffer[1024];
 
-eollen = (eol == NULL)? 0 : Ustrlen(eol);
-
-while (Ufgets(buffer, sizeof(buffer), f) != NULL)
+while (Ufgets(buffer, sizeof(buffer), f))
   {
   int len = Ustrlen(buffer);
-  if (eol != NULL && buffer[len-1] == '\n') len--;
-  yield = string_cat(yield, sizep, ptrp, buffer, len);
-  if (buffer[len] != 0)
-    yield = string_cat(yield, sizep, ptrp, eol, eollen);
+  if (eol && buffer[len-1] == '\n') len--;
+  yield = string_catn(yield, sizep, ptrp, buffer, len);
+  if (eol && buffer[len])
+    yield = string_cat(yield, sizep, ptrp, eol);
   }
 
-if (yield != NULL) yield[*ptrp] = 0;
+if (yield) yield[*ptrp] = 0;
 
 return yield;
 }
@@ -3520,7 +3636,7 @@ if (*error == NULL)
     /* SIGFPE both on div/mod by zero and on INT_MIN / -1, which would give
      * a value of INT_MAX+1. Note that INT_MIN * -1 gives INT_MIN for me, which
      * is a bug somewhere in [gcc 4.2.1, FreeBSD, amd64].  In fact, -N*-M where
-     * -N*M is INT_MIN will yielf INT_MIN.
+     * -N*M is INT_MIN will yield INT_MIN.
      * Since we don't support floating point, this is somewhat simpler.
      * Ideally, we'd return an error, but since we overflow for all other
      * arithmetic, consistency suggests otherwise, but what's the correct value
@@ -3570,13 +3686,20 @@ eval_op_sum(uschar **sptr, BOOL decimal, uschar **error)
 {
 uschar *s = *sptr;
 int_eximarith_t x = eval_op_mult(&s, decimal, error);
-if (*error == NULL)
+if (!*error)
   {
   while (*s == '+' || *s == '-')
     {
     int op = *s++;
     int_eximarith_t y = eval_op_mult(&s, decimal, error);
-    if (*error != NULL) break;
+    if (*error) break;
+    if (  (x >=   EXIM_ARITH_MAX/2  && x >=   EXIM_ARITH_MAX/2)
+       || (x <= -(EXIM_ARITH_MAX/2) && y <= -(EXIM_ARITH_MAX/2)))
+      {                        /* over-conservative check */
+      *error = op == '+'
+       ? US"overflow in sum" : US"overflow in difference";
+      break;
+      }
     if (op == '+') x += y; else x -= y;
     }
   }
@@ -3691,7 +3814,7 @@ them here in detail any more.
 We use an internal routine recursively to handle embedded substrings. The
 external function follows. The yield is NULL if the expansion failed, and there
 are two cases: if something collapsed syntactically, or if "fail" was given
-as the action on a lookup failure. These can be distinguised by looking at the
+as the action on a lookup failure. These can be distinguished by looking at the
 variable expand_string_forcedfail, which is TRUE in the latter case.
 
 The skipping flag is set true when expanding a substring that isn't actually
@@ -3735,18 +3858,22 @@ Returns:         NULL if expansion fails:
 */
 
 static uschar *
-expand_string_internal(uschar *string, BOOL ket_ends, uschar **left,
+expand_string_internal(const uschar *string, BOOL ket_ends, const uschar **left,
   BOOL skipping, BOOL honour_dollar, BOOL *resetok_p)
 {
 int ptr = 0;
 int size = Ustrlen(string)+ 64;
-int item_type;
 uschar *yield = store_get(size);
-uschar *s = string;
+int item_type;
+const uschar *s = string;
 uschar *save_expand_nstring[EXPAND_MAXN+1];
 int save_expand_nlength[EXPAND_MAXN+1];
 BOOL resetok = TRUE;
 
+expand_level++;
+DEBUG(D_expand)
+  debug_printf_indent("/%s: %s\n", skipping ? "   scanning" : "considering", string);
+
 expand_string_forcedfail = FALSE;
 expand_string_message = US"";
 
@@ -3770,9 +3897,9 @@ while (*s != 0)
 
     if (s[1] == 'N')
       {
-      uschar *t = s + 2;
+      const uschar * t = s + 2;
       for (s = t; *s != 0; s++) if (*s == '\\' && s[1] == 'N') break;
-      yield = string_cat(yield, &size, &ptr, t, s - t);
+      yield = string_catn(yield, &size, &ptr, t, s - t);
       if (*s != 0) s += 2;
       }
 
@@ -3781,7 +3908,7 @@ while (*s != 0)
       uschar ch[1];
       ch[0] = string_interpret_escape(&s);
       s++;
-      yield = string_cat(yield, &size, &ptr, ch, 1);
+      yield = string_catn(yield, &size, &ptr, ch, 1);
       }
 
     continue;
@@ -3796,7 +3923,7 @@ while (*s != 0)
 
   if (*s != '$' || !honour_dollar)
     {
-    yield = string_cat(yield, &size, &ptr, s++, 1);
+    yield = string_catn(yield, &size, &ptr, s++, 1);
     continue;
     }
 
@@ -3853,16 +3980,12 @@ while (*s != 0)
 
     /* Variable */
 
-    else
+    else if (!(value = find_variable(name, FALSE, skipping, &newsize)))
       {
-      value = find_variable(name, FALSE, skipping, &newsize);
-      if (value == NULL)
-        {
-        expand_string_message =
-          string_sprintf("unknown variable name \"%s\"", name);
-          check_variable_error_message(name);
-        goto EXPAND_FAILED;
-        }
+      expand_string_message =
+       string_sprintf("unknown variable name \"%s\"", name);
+       check_variable_error_message(name);
+      goto EXPAND_FAILED;
       }
 
     /* If the data is known to be in a new buffer, newsize will be set to the
@@ -3878,7 +4001,7 @@ while (*s != 0)
       size = newsize;
       ptr = len;
       }
-    else yield = string_cat(yield, &size, &ptr, value, len);
+    else yield = string_catn(yield, &size, &ptr, value, len);
 
     continue;
     }
@@ -3886,9 +4009,9 @@ while (*s != 0)
   if (isdigit(*s))
     {
     int n;
-    s = read_number(&n, s);
+    s = read_cnumber(&n, s);
     if (n >= 0 && n <= expand_nmax)
-      yield = string_cat(yield, &size, &ptr, expand_nstring[n],
+      yield = string_catn(yield, &size, &ptr, expand_nstring[n],
         expand_nlength[n]);
     continue;
     }
@@ -3907,14 +4030,14 @@ while (*s != 0)
   if (isdigit((*(++s))))
     {
     int n;
-    s = read_number(&n, s);            /*{*/
+    s = read_cnumber(&n, s);           /*{*/
     if (*s++ != '}')
       {                                        /*{*/
       expand_string_message = US"} expected after number";
       goto EXPAND_FAILED;
       }
     if (n >= 0 && n <= expand_nmax)
-      yield = string_cat(yield, &size, &ptr, expand_nstring[n],
+      yield = string_catn(yield, &size, &ptr, expand_nstring[n],
         expand_nlength[n]);
     continue;
     }
@@ -3930,7 +4053,7 @@ while (*s != 0)
   OK. */
 
   s = read_name(name, sizeof(name), s, US"_-");
-  item_type = chop_match(name, item_table, sizeof(item_table)/sizeof(uschar *));
+  item_type = chop_match(name, item_table, nelem(item_table));
 
   switch(item_type)
     {
@@ -3949,7 +4072,8 @@ while (*s != 0)
       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", &resetok))
+      switch(read_subs(sub, nelem(sub), 1, &s, skipping, TRUE, US"acl",
+                     &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -3958,18 +4082,19 @@ while (*s != 0)
       if (skipping) continue;
 
       resetok = FALSE;
-      switch(eval_acl(sub, sizeof(sub)/sizeof(*sub), &user_msg))
+      switch(eval_acl(sub, nelem(sub), &user_msg))
        {
        case OK:
        case FAIL:
          DEBUG(D_expand)
-           debug_printf("acl expansion yield: %s\n", user_msg);
+           debug_printf_indent("acl expansion yield: %s\n", user_msg);
          if (user_msg)
-            yield = string_cat(yield, &size, &ptr, user_msg, Ustrlen(user_msg));
+            yield = string_cat(yield, &size, &ptr, user_msg);
          continue;
 
        case DEFER:
           expand_string_forcedfail = TRUE;
+         /*FALLTHROUGH*/
        default:
           expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]);
          goto EXPAND_FAILED;
@@ -3984,17 +4109,19 @@ while (*s != 0)
     case EITEM_IF:
       {
       BOOL cond = FALSE;
-      uschar *next_s;
+      const uschar *next_s;
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
 
       while (isspace(*s)) s++;
-      next_s = eval_condition(s, &resetok, 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)
-        debug_printf("condition: %.*s\n   result: %s\n", (int)(next_s - s), s,
-          cond? "true" : "false");
+       {
+        debug_printf_indent("|__condition: %.*s\n", (int)(next_s - s), s);
+        debug_printf_indent("|_____result: %s\n", cond ? "true" : "false");
+       }
 
       s = next_s;
 
@@ -4024,6 +4151,45 @@ while (*s != 0)
       continue;
       }
 
+#ifdef SUPPORT_I18N
+    case EITEM_IMAPFOLDER:
+      {                                /* ${imapfolder {name}{sep]{specials}} */
+      uschar *sub_arg[3];
+      uschar *encoded;
+
+      switch(read_subs(sub_arg, nelem(sub_arg), 1, &s, skipping, TRUE, name,
+                     &resetok))
+        {
+        case 1: goto EXPAND_FAILED_CURLY;
+        case 2:
+        case 3: goto EXPAND_FAILED;
+        }
+
+      if (sub_arg[1] == NULL)          /* One argument */
+       {
+       sub_arg[1] = US"/";             /* default separator */
+       sub_arg[2] = NULL;
+       }
+      else if (Ustrlen(sub_arg[1]) != 1)
+       {
+       expand_string_message =
+         string_sprintf(
+               "IMAP folder separator must be one character, found \"%s\"",
+               sub_arg[1]);
+       goto EXPAND_FAILED;
+       }
+
+      if (!skipping)
+       {
+       if (!(encoded = imap_utf7_encode(sub_arg[0], headers_charset,
+                           sub_arg[1][0], sub_arg[2], &expand_string_message)))
+         goto EXPAND_FAILED;
+       yield = string_cat(yield, &size, &ptr, encoded);
+       }
+      continue;
+      }
+#endif
+
     /* Handle database lookups unless locked out. If "skipping" is TRUE, we are
     expanding an internal string that isn't actually going to be used. All we
     need to do is check the syntax, so don't do a lookup at all. Preserve the
@@ -4036,7 +4202,8 @@ while (*s != 0)
       int stype, partial, affixlen, starflags;
       int expand_setup = 0;
       int nameptr = 0;
-      uschar *key, *filename, *affix;
+      uschar *key, *filename;
+      const uschar *affix;
       uschar *save_lookup_value = lookup_value;
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
@@ -4054,8 +4221,12 @@ while (*s != 0)
       if (*s == '{')                                   /*}*/
         {
         key = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
-        if (key == NULL) goto EXPAND_FAILED;           /*{*/
-        if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+        if (!key) goto EXPAND_FAILED;                  /*{{*/
+        if (*s++ != '}')
+         {
+         expand_string_message = US"missing '}' after lookup key";
+         goto EXPAND_FAILED_CURLY;
+         }
         while (isspace(*s)) s++;
         }
       else key = NULL;
@@ -4118,10 +4289,18 @@ while (*s != 0)
       queries that also require a file name (e.g. sqlite), the file name comes
       first. */
 
-      if (*s != '{') goto EXPAND_FAILED_CURLY;
+      if (*s != '{')
+        {
+       expand_string_message = US"missing '{' for lookup file-or-query arg";
+       goto EXPAND_FAILED_CURLY;
+       }
       filename = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
       if (filename == NULL) goto EXPAND_FAILED;
-      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+      if (*s++ != '}')
+        {
+       expand_string_message = US"missing '}' closing lookup file-or-query arg";
+       goto EXPAND_FAILED_CURLY;
+       }
       while (isspace(*s)) s++;
 
       /* If this isn't a single-key+file lookup, re-arrange the variables
@@ -4129,15 +4308,13 @@ while (*s != 0)
       there is just a "key", and no file name. For the special query-style +
       file types, the query (i.e. "key") starts with a file name. */
 
-      if (key == NULL)
+      if (!key)
         {
         while (isspace(*filename)) filename++;
         key = filename;
 
         if (mac_islookup(stype, lookup_querystyle))
-          {
           filename = NULL;
-          }
         else
           {
           if (*filename != '/')
@@ -4347,14 +4524,14 @@ while (*s != 0)
       /* Now separate the domain from the local part */
       *domain++ = '\0';
 
-      yield = string_cat(yield,&size,&ptr,US"prvs=",5);
-      string_cat(yield,&size,&ptr,(sub_arg[2] != NULL) ? sub_arg[2] : US"0", 1);
-      string_cat(yield,&size,&ptr,prvs_daystamp(7),3);
-      string_cat(yield,&size,&ptr,p,6);
-      string_cat(yield,&size,&ptr,US"=",1);
-      string_cat(yield,&size,&ptr,sub_arg[0],Ustrlen(sub_arg[0]));
-      string_cat(yield,&size,&ptr,US"@",1);
-      string_cat(yield,&size,&ptr,domain,Ustrlen(domain));
+      yield = string_catn(yield, &size, &ptr, US"prvs=", 5);
+      yield = string_catn(yield, &size, &ptr, sub_arg[2] ? sub_arg[2] : US"0", 1);
+      yield = string_catn(yield, &size, &ptr, prvs_daystamp(7), 3);
+      yield = string_catn(yield, &size, &ptr, p, 6);
+      yield = string_catn(yield, &size, &ptr, US"=", 1);
+      yield = string_cat (yield, &size, &ptr, sub_arg[0]);
+      yield = string_catn(yield, &size, &ptr, US"@", 1);
+      yield = string_cat (yield, &size, &ptr, domain);
 
       continue;
       }
@@ -4401,16 +4578,16 @@ while (*s != 0)
         uschar *hash = string_copyn(expand_nstring[3],expand_nlength[3]);
         uschar *domain = string_copyn(expand_nstring[5],expand_nlength[5]);
 
-        DEBUG(D_expand) debug_printf("prvscheck localpart: %s\n", local_part);
-        DEBUG(D_expand) debug_printf("prvscheck key number: %s\n", key_num);
-        DEBUG(D_expand) debug_printf("prvscheck daystamp: %s\n", daystamp);
-        DEBUG(D_expand) debug_printf("prvscheck hash: %s\n", hash);
-        DEBUG(D_expand) debug_printf("prvscheck domain: %s\n", domain);
+        DEBUG(D_expand) debug_printf_indent("prvscheck localpart: %s\n", local_part);
+        DEBUG(D_expand) debug_printf_indent("prvscheck key number: %s\n", key_num);
+        DEBUG(D_expand) debug_printf_indent("prvscheck daystamp: %s\n", daystamp);
+        DEBUG(D_expand) debug_printf_indent("prvscheck hash: %s\n", hash);
+        DEBUG(D_expand) debug_printf_indent("prvscheck domain: %s\n", domain);
 
         /* Set up expansion variables */
-        prvscheck_address = string_cat(NULL, &mysize, &myptr, local_part, Ustrlen(local_part));
-        string_cat(prvscheck_address,&mysize,&myptr,US"@",1);
-        string_cat(prvscheck_address,&mysize,&myptr,domain,Ustrlen(domain));
+        prvscheck_address = string_cat (NULL, &mysize, &myptr, local_part);
+        prvscheck_address = string_catn(prvscheck_address, &mysize, &myptr, US"@", 1);
+        prvscheck_address = string_cat (prvscheck_address, &mysize, &myptr, domain);
         prvscheck_address[myptr] = '\0';
         prvscheck_keynum = string_copy(key_num);
 
@@ -4433,8 +4610,8 @@ while (*s != 0)
           goto EXPAND_FAILED;
           }
 
-        DEBUG(D_expand) debug_printf("prvscheck: received hash is %s\n", hash);
-        DEBUG(D_expand) debug_printf("prvscheck:      own hash is %s\n", p);
+        DEBUG(D_expand) debug_printf_indent("prvscheck: received hash is %s\n", hash);
+        DEBUG(D_expand) debug_printf_indent("prvscheck:      own hash is %s\n", p);
 
         if (Ustrcmp(p,hash) == 0)
           {
@@ -4452,18 +4629,18 @@ while (*s != 0)
           if (iexpire >= inow)
             {
             prvscheck_result = US"1";
-            DEBUG(D_expand) debug_printf("prvscheck: success, $pvrs_result set to 1\n");
+            DEBUG(D_expand) debug_printf_indent("prvscheck: success, $pvrs_result set to 1\n");
             }
             else
             {
             prvscheck_result = NULL;
-            DEBUG(D_expand) debug_printf("prvscheck: signature expired, $pvrs_result unset\n");
+            DEBUG(D_expand) debug_printf_indent("prvscheck: signature expired, $pvrs_result unset\n");
             }
           }
         else
           {
           prvscheck_result = NULL;
-          DEBUG(D_expand) debug_printf("prvscheck: hash failure, $pvrs_result unset\n");
+          DEBUG(D_expand) debug_printf_indent("prvscheck: hash failure, $pvrs_result unset\n");
           }
 
         /* Now expand the final argument. We leave this till now so that
@@ -4476,10 +4653,8 @@ while (*s != 0)
           case 3: goto EXPAND_FAILED;
           }
 
-        if (sub_arg[0] == NULL || *sub_arg[0] == '\0')
-          yield = string_cat(yield,&size,&ptr,prvscheck_address,Ustrlen(prvscheck_address));
-        else
-          yield = string_cat(yield,&size,&ptr,sub_arg[0],Ustrlen(sub_arg[0]));
+       yield = string_cat(yield, &size, &ptr,
+         !sub_arg[0] || !*sub_arg[0] ? prvscheck_address : sub_arg[0]);
 
         /* Reset the "internal" variables afterwards, because they are in
         dynamic store that will be reclaimed if the expansion succeeded. */
@@ -4488,7 +4663,6 @@ while (*s != 0)
         prvscheck_keynum = NULL;
         }
       else
-        {
         /* Does not look like a prvs encoded address, return the empty string.
            We need to make sure all subs are expanded first, so as to skip over
            the entire item. */
@@ -4499,7 +4673,6 @@ while (*s != 0)
           case 2:
           case 3: goto EXPAND_FAILED;
           }
-        }
 
       continue;
       }
@@ -4630,8 +4803,10 @@ while (*s != 0)
             port = ntohs(service_info->s_port);
             }
 
-         if ((fd = ip_connectedsocket(SOCK_STREAM, server_name, port, port,
-                 timeout, NULL, &expand_string_message)) < 0)
+         fd = ip_connectedsocket(SOCK_STREAM, server_name, port, port,
+                 timeout, NULL, &expand_string_message);
+         callout_address = NULL;
+         if (fd < 0)
               goto SOCK_FAIL;
           }
 
@@ -4668,7 +4843,7 @@ while (*s != 0)
             }
           }
 
-        DEBUG(D_expand) debug_printf("connected to socket %s\n", sub_arg[0]);
+        DEBUG(D_expand) debug_printf_indent("connected to socket %s\n", sub_arg[0]);
 
        /* Allow sequencing of test actions */
        if (running_in_test_harness) millisleep(100);
@@ -4678,7 +4853,7 @@ while (*s != 0)
         if (sub_arg[1][0] != 0)
           {
           int len = Ustrlen(sub_arg[1]);
-          DEBUG(D_expand) debug_printf("writing \"%s\" to socket\n",
+          DEBUG(D_expand) debug_printf_indent("writing \"%s\" to socket\n",
             sub_arg[1]);
           if (write(fd, sub_arg[1], len) != len)
             {
@@ -4726,10 +4901,20 @@ while (*s != 0)
         {
         if (expand_string_internal(s+1, TRUE, &s, TRUE, TRUE, &resetok) == NULL)
           goto EXPAND_FAILED;
-        if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+        if (*s++ != '}')
+         {
+         expand_string_message = US"missing '}' closing failstring for readsocket";
+         goto EXPAND_FAILED_CURLY;
+         }
         while (isspace(*s)) s++;
         }
-      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+
+    readsock_done:
+      if (*s++ != '}')
+        {
+       expand_string_message = US"missing '}' closing readsocket";
+       goto EXPAND_FAILED_CURLY;
+       }
       continue;
 
       /* Come here on failure to create socket, connect socket, write to the
@@ -4739,13 +4924,16 @@ while (*s != 0)
       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, &resetok);
-      if (arg == NULL) goto EXPAND_FAILED;
-      yield = string_cat(yield, &size, &ptr, arg, Ustrlen(arg));
-      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+      if (!(arg = expand_string_internal(s+1, TRUE, &s, FALSE, TRUE, &resetok)))
+        goto EXPAND_FAILED;
+      yield = string_cat(yield, &size, &ptr, arg);
+      if (*s++ != '}')
+        {
+       expand_string_message = US"missing '}' closing failstring for readsocket";
+       goto EXPAND_FAILED_CURLY;
+       }
       while (isspace(*s)) s++;
-      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
-      continue;
+      goto readsock_done;
       }
 
     /* Handle "run" to execute a program. */
@@ -4754,11 +4942,10 @@ while (*s != 0)
       {
       FILE *f;
       uschar *arg;
-      uschar **argv;
+      const uschar **argv;
       pid_t pid;
       int fd_in, fd_out;
-      int lsize = 0;
-      int lptr = 0;
+      int lsize = 0, lptr = 0;
 
       if ((expand_forbid & RDO_RUN) != 0)
         {
@@ -4767,16 +4954,25 @@ while (*s != 0)
         }
 
       while (isspace(*s)) s++;
-      if (*s != '{') goto EXPAND_FAILED_CURLY;
+      if (*s != '{')
+        {
+       expand_string_message = US"missing '{' for command arg of run";
+       goto EXPAND_FAILED_CURLY;
+       }
       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;
+      if (*s++ != '}')
+        {
+       expand_string_message = US"missing '}' closing command arg of run";
+       goto EXPAND_FAILED_CURLY;
+       }
 
       if (skipping)   /* Just pretend it worked when we're skipping */
-        {
+       {
         runrc = 0;
-        }
+       lookup_value = NULL;
+       }
       else
         {
         if (!transport_set_up_command(&argv,    /* anchor for arg list */
@@ -4786,15 +4982,11 @@ while (*s != 0)
             NULL,                               /* no transporting address */
             US"${run} expansion",               /* for error messages */
             &expand_string_message))            /* where to put error message */
-          {
           goto EXPAND_FAILED;
-          }
 
         /* Create the child process, making it a group leader. */
 
-        pid = child_open(argv, NULL, 0077, &fd_in, &fd_out, TRUE);
-
-        if (pid < 0)
+        if ((pid = child_open(USS argv, NULL, 0077, &fd_in, &fd_out, TRUE)) < 0)
           {
           expand_string_message =
             string_sprintf("couldn't create child process: %s", strerror(errno));
@@ -4807,12 +4999,14 @@ while (*s != 0)
 
         /* Read the pipe to get the command's output into $value (which is kept
         in lookup_value). Read during execution, so that if the output exceeds
-        the OS pipe buffer limit, we don't block forever. */
+        the OS pipe buffer limit, we don't block forever. Remember to not release
+       memory just allocated for $value. */
 
+       resetok = FALSE;
         f = fdopen(fd_out, "rb");
         sigalrm_seen = FALSE;
         alarm(60);
-        lookup_value = cat_file(f, lookup_value, &lsize, &lptr, NULL);
+        lookup_value = cat_file(f, NULL, &lsize, &lptr, NULL);
         alarm(0);
         (void)fclose(f);
 
@@ -4820,9 +5014,9 @@ while (*s != 0)
         return code for serious disasters. Simple non-zero returns are passed on.
         */
 
-        if (sigalrm_seen == TRUE || (runrc = child_close(pid, 30)) < 0)
+        if (sigalrm_seen || (runrc = child_close(pid, 30)) < 0)
           {
-          if (sigalrm_seen == TRUE || runrc == -256)
+          if (sigalrm_seen || runrc == -256)
             {
             expand_string_message = string_sprintf("command timed out");
             killpg(pid, SIGKILL);       /* Kill the whole process group */
@@ -4875,7 +5069,7 @@ while (*s != 0)
         case 3: goto EXPAND_FAILED;
         }
 
-      yield = string_cat(yield, &size, &ptr, sub[0], Ustrlen(sub[0]));
+      yield = string_cat(yield, &size, &ptr, sub[0]);
       o2m = Ustrlen(sub[2]) - 1;
 
       if (o2m >= 0) for (; oldptr < ptr; oldptr++)
@@ -4952,7 +5146,7 @@ while (*s != 0)
           extract_substr(sub[2], val[0], val[1], &len);
 
       if (ret == NULL) goto EXPAND_FAILED;
-      yield = string_cat(yield, &size, &ptr, ret, len);
+      yield = string_catn(yield, &size, &ptr, ret, len);
       continue;
       }
 
@@ -4970,7 +5164,7 @@ while (*s != 0)
       {
       uschar *sub[3];
       md5 md5_base;
-      sha1 sha1_base;
+      hctx sha1_ctx;
       void *use_base;
       int type, i;
       int hashlen;      /* Number of octets for the hash algorithm's output */
@@ -4992,79 +5186,81 @@ while (*s != 0)
         case 3: goto EXPAND_FAILED;
         }
 
-      if (Ustrcmp(sub[0], "md5") == 0)
-        {
-        type = HMAC_MD5;
-        use_base = &md5_base;
-        hashlen = 16;
-        hashblocklen = 64;
-        }
-      else if (Ustrcmp(sub[0], "sha1") == 0)
-        {
-        type = HMAC_SHA1;
-        use_base = &sha1_base;
-        hashlen = 20;
-        hashblocklen = 64;
-        }
-      else
-        {
-        expand_string_message =
-          string_sprintf("hmac algorithm \"%s\" is not recognised", sub[0]);
-        goto EXPAND_FAILED;
-        }
+      if (!skipping)
+       {
+       if (Ustrcmp(sub[0], "md5") == 0)
+         {
+         type = HMAC_MD5;
+         use_base = &md5_base;
+         hashlen = 16;
+         hashblocklen = 64;
+         }
+       else if (Ustrcmp(sub[0], "sha1") == 0)
+         {
+         type = HMAC_SHA1;
+         use_base = &sha1_ctx;
+         hashlen = 20;
+         hashblocklen = 64;
+         }
+       else
+         {
+         expand_string_message =
+           string_sprintf("hmac algorithm \"%s\" is not recognised", sub[0]);
+         goto EXPAND_FAILED;
+         }
 
-      keyptr = sub[1];
-      keylen = Ustrlen(keyptr);
+       keyptr = sub[1];
+       keylen = Ustrlen(keyptr);
 
-      /* If the key is longer than the hash block length, then hash the key
-      first */
+       /* If the key is longer than the hash block length, then hash the key
+       first */
 
-      if (keylen > hashblocklen)
-        {
-        chash_start(type, use_base);
-        chash_end(type, use_base, keyptr, keylen, keyhash);
-        keyptr = keyhash;
-        keylen = hashlen;
-        }
+       if (keylen > hashblocklen)
+         {
+         chash_start(type, use_base);
+         chash_end(type, use_base, keyptr, keylen, keyhash);
+         keyptr = keyhash;
+         keylen = hashlen;
+         }
 
-      /* Now make the inner and outer key values */
+       /* Now make the inner and outer key values */
 
-      memset(innerkey, 0x36, hashblocklen);
-      memset(outerkey, 0x5c, hashblocklen);
+       memset(innerkey, 0x36, hashblocklen);
+       memset(outerkey, 0x5c, hashblocklen);
 
-      for (i = 0; i < keylen; i++)
-        {
-        innerkey[i] ^= keyptr[i];
-        outerkey[i] ^= keyptr[i];
-        }
+       for (i = 0; i < keylen; i++)
+         {
+         innerkey[i] ^= keyptr[i];
+         outerkey[i] ^= keyptr[i];
+         }
 
-      /* Now do the hashes */
+       /* Now do the hashes */
 
-      chash_start(type, use_base);
-      chash_mid(type, use_base, innerkey);
-      chash_end(type, use_base, sub[2], Ustrlen(sub[2]), innerhash);
+       chash_start(type, use_base);
+       chash_mid(type, use_base, innerkey);
+       chash_end(type, use_base, sub[2], Ustrlen(sub[2]), innerhash);
 
-      chash_start(type, use_base);
-      chash_mid(type, use_base, outerkey);
-      chash_end(type, use_base, innerhash, hashlen, finalhash);
+       chash_start(type, use_base);
+       chash_mid(type, use_base, outerkey);
+       chash_end(type, use_base, innerhash, hashlen, finalhash);
 
-      /* Encode the final hash as a hex string */
+       /* Encode the final hash as a hex string */
 
-      p = finalhash_hex;
-      for (i = 0; i < hashlen; i++)
-        {
-        *p++ = hex_digits[(finalhash[i] & 0xf0) >> 4];
-        *p++ = hex_digits[finalhash[i] & 0x0f];
-        }
+       p = finalhash_hex;
+       for (i = 0; i < hashlen; i++)
+         {
+         *p++ = hex_digits[(finalhash[i] & 0xf0) >> 4];
+         *p++ = hex_digits[finalhash[i] & 0x0f];
+         }
 
-      DEBUG(D_any) debug_printf("HMAC[%s](%.*s,%.*s)=%.*s\n", sub[0],
-        (int)keylen, keyptr, Ustrlen(sub[2]), sub[2], hashlen*2, finalhash_hex);
+       DEBUG(D_any) debug_printf("HMAC[%s](%.*s,%s)=%.*s\n",
+         sub[0], (int)keylen, keyptr, sub[2], hashlen*2, finalhash_hex);
 
-      yield = string_cat(yield, &size, &ptr, finalhash_hex, hashlen*2);
+       yield = string_catn(yield, &size, &ptr, finalhash_hex, hashlen*2);
+       }
+      continue;
       }
 
-    continue;
-
     /* Handle global substitution for "sg" - like Perl's s/xxx/yyy/g operator.
     We have to save the numerical variables and restore them afterwards. */
 
@@ -5112,7 +5308,7 @@ while (*s != 0)
         {
         int ovector[3*(EXPAND_MAXN+1)];
         int n = pcre_exec(re, NULL, CS subject, slen, moffset + moffsetextra,
-          PCRE_EOPT | emptyopt, ovector, sizeof(ovector)/sizeof(int));
+          PCRE_EOPT | emptyopt, ovector, nelem(ovector));
         int nn;
         uschar *insert;
 
@@ -5131,7 +5327,7 @@ while (*s != 0)
             emptyopt = 0;
             continue;
             }
-          yield = string_cat(yield, &size, &ptr, subject+moffset, slen-moffset);
+          yield = string_catn(yield, &size, &ptr, subject+moffset, slen-moffset);
           break;
           }
 
@@ -5148,11 +5344,11 @@ while (*s != 0)
 
         /* Copy the characters before the match, plus the expanded insertion. */
 
-        yield = string_cat(yield, &size, &ptr, subject + moffset,
+        yield = string_catn(yield, &size, &ptr, subject + moffset,
           ovector[0] - moffset);
         insert = expand_string(sub[2]);
         if (insert == NULL) goto EXPAND_FAILED;
-        yield = string_cat(yield, &size, &ptr, insert, Ustrlen(insert));
+        yield = string_cat(yield, &size, &ptr, insert);
 
         moffset = ovector[1];
         moffsetextra = 0;
@@ -5185,7 +5381,7 @@ while (*s != 0)
     case EITEM_EXTRACT:
       {
       int i;
-      int j = 2;
+      int j;
       int field_number = 1;
       BOOL field_number_set = FALSE;
       uschar *save_lookup_value = lookup_value;
@@ -5193,16 +5389,51 @@ while (*s != 0)
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
 
-      /* Read the arguments */
+      /* While skipping we cannot rely on the data for expansions being
+      available (eg. $item) hence cannot decide on numeric vs. keyed.
+      Read a maximum of 5 arguments (including the yes/no) */
 
-      for (i = 0; i < j; i++)
-        {
+      if (skipping)
+       {
         while (isspace(*s)) s++;
-        if (*s == '{')                                                 /*}*/
-          {
+        for (j = 5; j > 0 && *s == '{'; j--)
+         {
+          if (!expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok))
+           goto EXPAND_FAILED;                                 /*{*/
+          if (*s++ != '}')
+           {
+           expand_string_message = US"missing '{' for arg of extract";
+           goto EXPAND_FAILED_CURLY;
+           }
+         while (isspace(*s)) s++;
+         }
+       if (  Ustrncmp(s, "fail", 4) == 0
+          && (s[4] == '}' || s[4] == ' ' || s[4] == '\t' || !s[4])
+          )
+         {
+         s += 4;
+         while (isspace(*s)) s++;
+         }
+       if (*s != '}')
+         {
+         expand_string_message = US"missing '}' closing extract";
+         goto EXPAND_FAILED_CURLY;
+         }
+       }
+
+      else for (i = 0, j = 2; i < j; i++) /* Read the proper number of arguments */
+        {
+        while (isspace(*s)) s++;
+        if (*s == '{')                                                 /*}*/
+          {
           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;
+          if (*s++ != '}')
+           {
+           expand_string_message = string_sprintf(
+             "missing '}' closing arg %d of extract", i+1);
+           goto EXPAND_FAILED_CURLY;
+           }
 
           /* After removal of leading and trailing white space, the first
           argument must not be empty; if it consists entirely of digits
@@ -5222,28 +5453,33 @@ while (*s != 0)
             while (len > 0 && isspace(p[len-1])) len--;
             p[len] = 0;
 
-            if (*p == 0 && !skipping)
-              {
-              expand_string_message = US"first argument of \"extract\" must "
-                "not be empty";
-              goto EXPAND_FAILED;
-              }
+           if (*p == 0)
+             {
+             expand_string_message = US"first argument of \"extract\" must "
+               "not be empty";
+             goto EXPAND_FAILED;
+             }
 
-            if (*p == '-')
-              {
-              field_number = -1;
-              p++;
-              }
-            while (*p != 0 && isdigit(*p)) x = x * 10 + *p++ - '0';
-            if (*p == 0)
-              {
-              field_number *= x;
-              j = 3;               /* Need 3 args */
-              field_number_set = TRUE;
-              }
+           if (*p == '-')
+             {
+             field_number = -1;
+             p++;
+             }
+           while (*p != 0 && isdigit(*p)) x = x * 10 + *p++ - '0';
+           if (*p == 0)
+             {
+             field_number *= x;
+             j = 3;               /* Need 3 args */
+             field_number_set = TRUE;
+             }
             }
           }
-        else goto EXPAND_FAILED_CURLY;
+        else
+         {
+         expand_string_message = string_sprintf(
+           "missing '{' for arg %d of extract", i+1);
+         goto EXPAND_FAILED_CURLY;
+         }
         }
 
       /* Extract either the numbered or the keyed substring into $value. If
@@ -5296,11 +5532,20 @@ while (*s != 0)
         {
         while (isspace(*s)) s++;
         if (*s != '{')                                 /*}*/
+         {
+         expand_string_message = string_sprintf(
+           "missing '{' for arg %d of listextract", i+1);
          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;
+       if (*s++ != '}')
+         {
+         expand_string_message = string_sprintf(
+           "missing '}' closing arg %d of listextract", i+1);
+         goto EXPAND_FAILED_CURLY;
+         }
 
        /* After removal of leading and trailing white space, the first
        argument must be numeric and nonempty. */
@@ -5357,7 +5602,7 @@ while (*s != 0)
                &yield,                       /* output pointer */
                &size,                        /* output size */
                &ptr,                         /* output current point */
-               US"extract",                  /* condition type */
+               US"listextract",              /* condition type */
               &resetok))
         {
         case 1: goto EXPAND_FAILED;          /* when all is well, the */
@@ -5383,10 +5628,17 @@ while (*s != 0)
       /* Read the field argument */
       while (isspace(*s)) s++;
       if (*s != '{')                                   /*}*/
+       {
+       expand_string_message = US"missing '{' for field arg of certextract";
        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;
+      if (*s++ != '}')
+        {
+       expand_string_message = US"missing '}' closing field arg of certextract";
+       goto EXPAND_FAILED_CURLY;
+       }
       /* strip spaces fore & aft */
       {
       int len;
@@ -5403,7 +5655,10 @@ while (*s != 0)
       /* inspect the cert argument */
       while (isspace(*s)) s++;
       if (*s != '{')                                   /*}*/
+       {
+       expand_string_message = US"missing '{' for cert variable arg of certextract";
        goto EXPAND_FAILED_CURLY;
+       }
       if (*++s != '$')
         {
        expand_string_message = US"second argument of \"certextract\" must "
@@ -5412,7 +5667,11 @@ while (*s != 0)
        }
       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 (*s++ != '}')
+        {
+       expand_string_message = US"missing '}' closing cert variable arg of certextract";
+       goto EXPAND_FAILED_CURLY;
+       }
 
       if (skipping)
        lookup_value = NULL;
@@ -5429,7 +5688,7 @@ while (*s != 0)
                &yield,                       /* output pointer */
                &size,                        /* output size */
                &ptr,                         /* output current point */
-               US"extract",                  /* condition type */
+               US"certextract",              /* condition type */
               &resetok))
         {
         case 1: goto EXPAND_FAILED;          /* when all is well, the */
@@ -5451,29 +5710,53 @@ while (*s != 0)
       int sep = 0;
       int save_ptr = ptr;
       uschar outsep[2] = { '\0', '\0' };
-      uschar *list, *expr, *temp;
+      const uschar *list, *expr, *temp;
       uschar *save_iterate_item = iterate_item;
       uschar *save_lookup_value = lookup_value;
 
       while (isspace(*s)) s++;
-      if (*s++ != '{') goto EXPAND_FAILED_CURLY;
+      if (*s++ != '{')
+        {
+       expand_string_message =
+         string_sprintf("missing '{' for first arg of %s", name);
+       goto EXPAND_FAILED_CURLY;
+       }
 
       list = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok);
       if (list == NULL) goto EXPAND_FAILED;
-      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+      if (*s++ != '}')
+        {
+       expand_string_message =
+         string_sprintf("missing '}' closing first arg of %s", name);
+       goto EXPAND_FAILED_CURLY;
+       }
 
       if (item_type == EITEM_REDUCE)
         {
+       uschar * t;
         while (isspace(*s)) s++;
-        if (*s++ != '{') goto EXPAND_FAILED_CURLY;
-        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;
+        if (*s++ != '{')
+         {
+         expand_string_message = US"missing '{' for second arg of reduce";
+         goto EXPAND_FAILED_CURLY;
+         }
+        t = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok);
+        if (!t) goto EXPAND_FAILED;
+        lookup_value = t;
+        if (*s++ != '}')
+         {
+         expand_string_message = US"missing '}' closing second arg of reduce";
+         goto EXPAND_FAILED_CURLY;
+         }
         }
 
       while (isspace(*s)) s++;
-      if (*s++ != '{') goto EXPAND_FAILED_CURLY;
+      if (*s++ != '{')
+        {
+       expand_string_message =
+         string_sprintf("missing '{' for last arg of %s", name);
+       goto EXPAND_FAILED_CURLY;
+       }
 
       expr = s;
 
@@ -5489,9 +5772,7 @@ while (*s != 0)
         if (temp != NULL) s = temp;
         }
       else
-        {
         temp = expand_string_internal(s, TRUE, &s, TRUE, TRUE, &resetok);
-        }
 
       if (temp == NULL)
         {
@@ -5520,11 +5801,12 @@ while (*s != 0)
       processing for real, we perform the iteration. */
 
       if (skipping) continue;
-      while ((iterate_item = string_nextinlist(&list, &sep, NULL, 0)) != NULL)
+      while ((iterate_item = string_nextinlist(&list, &sep, NULL, 0)))
         {
         *outsep = (uschar)sep;      /* Separator as a string */
 
-        DEBUG(D_expand) debug_printf("%s: $item = \"%s\"\n", name, iterate_item);
+       DEBUG(D_expand) debug_printf_indent("%s: $item = '%s'  $value = '%s'\n",
+                         name, iterate_item, lookup_value);
 
         if (item_type == EITEM_FILTER)
           {
@@ -5537,7 +5819,7 @@ while (*s != 0)
               expand_string_message, name);
             goto EXPAND_FAILED;
             }
-          DEBUG(D_expand) debug_printf("%s: condition is %s\n", name,
+          DEBUG(D_expand) debug_printf_indent("%s: condition is %s\n", name,
             condresult? "true":"false");
           if (condresult)
             temp = iterate_item;    /* TRUE => include this item */
@@ -5549,7 +5831,8 @@ while (*s != 0)
 
         else
           {
-          temp = expand_string_internal(expr, TRUE, NULL, skipping, TRUE, &resetok);
+         uschar * t = expand_string_internal(expr, TRUE, NULL, skipping, TRUE, &resetok);
+          temp = t;
           if (temp == NULL)
             {
             iterate_item = save_iterate_item;
@@ -5559,7 +5842,7 @@ while (*s != 0)
             }
           if (item_type == EITEM_REDUCE)
             {
-            lookup_value = temp;      /* Update the value of $value */
+            lookup_value = t;         /* Update the value of $value */
             continue;                 /* and continue the iteration */
             }
           }
@@ -5572,7 +5855,7 @@ while (*s != 0)
         separator character, or is an empty string. */
 
         if (ptr != save_ptr && (temp[0] == *outsep || temp[0] == 0))
-          yield = string_cat(yield, &size, &ptr, US" ", 1);
+          yield = string_catn(yield, &size, &ptr, US" ", 1);
 
         /* Add the string in "temp" to the output list that we are building,
         This is done in chunks by searching for the separator character. */
@@ -5580,21 +5863,22 @@ while (*s != 0)
         for (;;)
           {
           size_t seglen = Ustrcspn(temp, outsep);
-            yield = string_cat(yield, &size, &ptr, temp, seglen + 1);
+
+         yield = string_catn(yield, &size, &ptr, temp, seglen + 1);
 
           /* If we got to the end of the string we output one character
           too many; backup and end the loop. Otherwise arrange to double the
           separator. */
 
           if (temp[seglen] == '\0') { ptr--; break; }
-          yield = string_cat(yield, &size, &ptr, outsep, 1);
+          yield = string_catn(yield, &size, &ptr, outsep, 1);
           temp += seglen + 1;
           }
 
         /* Output a separator after the string: we will remove the redundant
         final one at the end. */
 
-        yield = string_cat(yield, &size, &ptr, outsep, 1);
+        yield = string_catn(yield, &size, &ptr, outsep, 1);
         }   /* End of iteration over the list loop */
 
       /* REDUCE has generated no output above: output the final value of
@@ -5602,8 +5886,7 @@ while (*s != 0)
 
       if (item_type == EITEM_REDUCE)
         {
-        yield = string_cat(yield, &size, &ptr, lookup_value,
-          Ustrlen(lookup_value));
+        yield = string_cat(yield, &size, &ptr, lookup_value);
         lookup_value = save_lookup_value;  /* Restore $value */
         }
 
@@ -5619,6 +5902,168 @@ while (*s != 0)
       continue;
       }
 
+    case EITEM_SORT:
+      {
+      int sep = 0;
+      const uschar *srclist, *cmp, *xtract;
+      uschar *srcitem;
+      const uschar *dstlist = NULL, *dstkeylist = NULL;
+      uschar * tmp;
+      uschar *save_iterate_item = iterate_item;
+
+      while (isspace(*s)) s++;
+      if (*s++ != '{')
+        {
+        expand_string_message = US"missing '{' for list arg of sort";
+       goto EXPAND_FAILED_CURLY;
+       }
+
+      srclist = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok);
+      if (!srclist) goto EXPAND_FAILED;
+      if (*s++ != '}')
+        {
+        expand_string_message = US"missing '}' closing list arg of sort";
+       goto EXPAND_FAILED_CURLY;
+       }
+
+      while (isspace(*s)) s++;
+      if (*s++ != '{')
+        {
+        expand_string_message = US"missing '{' for comparator arg of sort";
+       goto EXPAND_FAILED_CURLY;
+       }
+
+      cmp = expand_string_internal(s, TRUE, &s, skipping, FALSE, &resetok);
+      if (!cmp) goto EXPAND_FAILED;
+      if (*s++ != '}')
+        {
+        expand_string_message = US"missing '}' closing comparator arg of sort";
+       goto EXPAND_FAILED_CURLY;
+       }
+
+      while (isspace(*s)) s++;
+      if (*s++ != '{')
+        {
+        expand_string_message = US"missing '{' for extractor arg of sort";
+       goto EXPAND_FAILED_CURLY;
+       }
+
+      xtract = s;
+      tmp = expand_string_internal(s, TRUE, &s, TRUE, TRUE, &resetok);
+      if (!tmp) goto EXPAND_FAILED;
+      xtract = string_copyn(xtract, s - xtract);
+
+      if (*s++ != '}')
+        {
+        expand_string_message = US"missing '}' closing extractor arg of sort";
+       goto EXPAND_FAILED_CURLY;
+       }
+                                                       /*{*/
+      if (*s++ != '}')
+        {                                              /*{*/
+        expand_string_message = US"missing } at end of \"sort\"";
+        goto EXPAND_FAILED;
+        }
+
+      if (skipping) continue;
+
+      while ((srcitem = string_nextinlist(&srclist, &sep, NULL, 0)))
+        {
+       uschar * dstitem;
+       uschar * newlist = NULL;
+       uschar * newkeylist = NULL;
+       uschar * srcfield;
+
+        DEBUG(D_expand) debug_printf_indent("%s: $item = \"%s\"\n", name, srcitem);
+
+       /* extract field for comparisons */
+       iterate_item = srcitem;
+       if (  !(srcfield = expand_string_internal(xtract, FALSE, NULL, FALSE,
+                                         TRUE, &resetok))
+          || !*srcfield)
+         {
+         expand_string_message = string_sprintf(
+             "field-extract in sort: \"%s\"", xtract);
+         goto EXPAND_FAILED;
+         }
+
+       /* Insertion sort */
+
+       /* copy output list until new-item < list-item */
+       while ((dstitem = string_nextinlist(&dstlist, &sep, NULL, 0)))
+         {
+         uschar * dstfield;
+         uschar * expr;
+         BOOL before;
+
+         /* field for comparison */
+         if (!(dstfield = string_nextinlist(&dstkeylist, &sep, NULL, 0)))
+           goto sort_mismatch;
+
+         /* build and run condition string */
+         expr = string_sprintf("%s{%s}{%s}", cmp, srcfield, dstfield);
+
+         DEBUG(D_expand) debug_printf_indent("%s: cond = \"%s\"\n", name, expr);
+         if (!eval_condition(expr, &resetok, &before))
+           {
+           expand_string_message = string_sprintf("comparison in sort: %s",
+               expr);
+           goto EXPAND_FAILED;
+           }
+
+         if (before)
+           {
+           /* New-item sorts before this dst-item.  Append new-item,
+           then dst-item, then remainder of dst list. */
+
+           newlist = string_append_listele(newlist, sep, srcitem);
+           newkeylist = string_append_listele(newkeylist, sep, srcfield);
+           srcitem = NULL;
+
+           newlist = string_append_listele(newlist, sep, dstitem);
+           newkeylist = string_append_listele(newkeylist, sep, dstfield);
+
+           while ((dstitem = string_nextinlist(&dstlist, &sep, NULL, 0)))
+             {
+             if (!(dstfield = string_nextinlist(&dstkeylist, &sep, NULL, 0)))
+               goto sort_mismatch;
+             newlist = string_append_listele(newlist, sep, dstitem);
+             newkeylist = string_append_listele(newkeylist, sep, dstfield);
+             }
+
+           break;
+           }
+
+         newlist = string_append_listele(newlist, sep, dstitem);
+         newkeylist = string_append_listele(newkeylist, sep, dstfield);
+         }
+
+       /* If we ran out of dstlist without consuming srcitem, append it */
+       if (srcitem)
+         {
+         newlist = string_append_listele(newlist, sep, srcitem);
+         newkeylist = string_append_listele(newkeylist, sep, srcfield);
+         }
+
+       dstlist = newlist;
+       dstkeylist = newkeylist;
+
+        DEBUG(D_expand) debug_printf_indent("%s: dstlist = \"%s\"\n", name, dstlist);
+        DEBUG(D_expand) debug_printf_indent("%s: dstkeylist = \"%s\"\n", name, dstkeylist);
+       }
+
+      if (dstlist)
+       yield = string_cat(yield, &size, &ptr, dstlist);
+
+      /* Restore preserved $item */
+      iterate_item = save_iterate_item;
+      continue;
+
+      sort_mismatch:
+       expand_string_message = US"Internal error in sort (list mismatch)";
+       goto EXPAND_FAILED;
+      }
+
 
     /* If ${dlfunc } support is configured, handle calling dynamically-loaded
     functions, unless locked out at this time. Syntax is ${dlfunc{file}{func}}
@@ -5628,12 +6073,12 @@ while (*s != 0)
     #define EXPAND_DLFUNC_MAX_ARGS 8
 
     case EITEM_DLFUNC:
-    #ifndef EXPAND_DLFUNC
-    expand_string_message = US"\"${dlfunc\" encountered, but this facility "   /*}*/
-      "is not included in this binary";
-    goto EXPAND_FAILED;
+#ifndef EXPAND_DLFUNC
+      expand_string_message = US"\"${dlfunc\" encountered, but this facility " /*}*/
+       "is not included in this binary";
+      goto EXPAND_FAILED;
 
-    #else   /* EXPAND_DLFUNC */
+#else   /* EXPAND_DLFUNC */
       {
       tree_node *t;
       exim_dlfunc_t *func;
@@ -5706,7 +6151,7 @@ while (*s != 0)
       if(status == OK)
         {
         if (result == NULL) result = US"";
-        yield = string_cat(yield, &size, &ptr, result, Ustrlen(result));
+        yield = string_cat(yield, &size, &ptr, result);
         continue;
         }
       else
@@ -5719,7 +6164,43 @@ while (*s != 0)
         goto EXPAND_FAILED;
         }
       }
-    #endif /* EXPAND_DLFUNC */
+#endif /* EXPAND_DLFUNC */
+
+    case EITEM_ENV:    /* ${env {name} {val_if_found} {val_if_unfound}} */
+      {
+      uschar * key;
+      uschar *save_lookup_value = lookup_value;
+
+      while (isspace(*s)) s++;
+      if (*s != '{')                                   /*}*/
+       goto EXPAND_FAILED;
+
+      key = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
+      if (!key) goto EXPAND_FAILED;                    /*{*/
+      if (*s++ != '}')
+        {
+        expand_string_message = US"missing '{' for name arg of env";
+       goto EXPAND_FAILED_CURLY;
+       }
+
+      lookup_value = US getenv(CS key);
+
+      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"env",                      /* condition type */
+              &resetok))
+        {
+        case 1: goto EXPAND_FAILED;          /* when all is well, the */
+        case 2: goto EXPAND_FAILED_CURLY;    /* returned value is 0 */
+        }
+      continue;
+      }
     }  /* EITEM_* switch */
 
   /* Control reaches here if the name is not recognized as one of the more
@@ -5740,13 +6221,12 @@ while (*s != 0)
     the arguments and then scan the main table. */
 
     if ((c = chop_match(name, op_table_underscore,
-       sizeof(op_table_underscore)/sizeof(uschar *))) < 0)
+                       nelem(op_table_underscore))) < 0)
       {
       arg = Ustrchr(name, '_');
       if (arg != NULL) *arg = 0;
-      c = chop_match(name, op_table_main,
-        sizeof(op_table_main)/sizeof(uschar *));
-      if (c >= 0) c += sizeof(op_table_underscore)/sizeof(uschar *);
+      c = chop_match(name, op_table_main, nelem(op_table_main));
+      if (c >= 0) c += nelem(op_table_underscore);
       if (arg != NULL) *arg++ = '_';   /* Put back for error messages */
       }
 
@@ -5758,13 +6238,19 @@ while (*s != 0)
       case EOP_MD5:
       case EOP_SHA1:
       case EOP_SHA256:
+      case EOP_BASE64:
        if (s[1] == '$')
          {
-         uschar * s1 = s;
+         const 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 (*s1 != '}')
+           {
+           expand_string_message =
+             string_sprintf("missing '}' closing cert arg of %s", name);
+           goto EXPAND_FAILED_CURLY;
+           }
          if ((vp = find_var_ent(sub)) && vp->type == vtype_cert)
            {
            s = s1+1;
@@ -5793,6 +6279,47 @@ while (*s != 0)
 
     switch(c)
       {
+      case EOP_BASE32:
+       {
+        uschar *t;
+        unsigned long int n = Ustrtoul(sub, &t, 10);
+       uschar * s = NULL;
+       int sz = 0, i = 0;
+
+        if (*t != 0)
+          {
+          expand_string_message = string_sprintf("argument for base32 "
+            "operator is \"%s\", which is not a decimal number", sub);
+          goto EXPAND_FAILED;
+          }
+       for ( ; n; n >>= 5)
+         s = string_catn(s, &sz, &i, &base32_chars[n & 0x1f], 1);
+
+       while (i > 0) yield = string_catn(yield, &size, &ptr, &s[--i], 1);
+       continue;
+       }
+
+      case EOP_BASE32D:
+        {
+        uschar *tt = sub;
+        unsigned long int n = 0;
+       uschar * s;
+        while (*tt)
+          {
+          uschar * t = Ustrchr(base32_chars, *tt++);
+          if (t == NULL)
+            {
+            expand_string_message = string_sprintf("argument for base32d "
+              "operator is \"%s\", which is not a base 32 number", sub);
+            goto EXPAND_FAILED;
+            }
+          n = n * 32 + (t - base32_chars);
+          }
+        s = string_sprintf("%ld", n);
+        yield = string_cat(yield, &size, &ptr, s);
+        continue;
+        }
+
       case EOP_BASE62:
         {
         uschar *t;
@@ -5804,7 +6331,7 @@ while (*s != 0)
           goto EXPAND_FAILED;
           }
         t = string_base62(n);
-        yield = string_cat(yield, &size, &ptr, t, Ustrlen(t));
+        yield = string_cat(yield, &size, &ptr, t);
         continue;
         }
 
@@ -5828,7 +6355,7 @@ while (*s != 0)
           n = n * BASE_62 + (t - base62_chars);
           }
         (void)sprintf(CS buf, "%ld", n);
-        yield = string_cat(yield, &size, &ptr, buf, Ustrlen(buf));
+        yield = string_cat(yield, &size, &ptr, buf);
         continue;
         }
 
@@ -5842,7 +6369,7 @@ while (*s != 0)
               expand_string_message);
           goto EXPAND_FAILED;
           }
-        yield = string_cat(yield, &size, &ptr, expanded, Ustrlen(expanded));
+        yield = string_cat(yield, &size, &ptr, expanded);
         continue;
         }
 
@@ -5851,7 +6378,7 @@ while (*s != 0)
         int count = 0;
         uschar *t = sub - 1;
         while (*(++t) != 0) { *t = tolower(*t); count++; }
-        yield = string_cat(yield, &size, &ptr, sub, count);
+        yield = string_catn(yield, &size, &ptr, sub, count);
         continue;
         }
 
@@ -5860,7 +6387,7 @@ while (*s != 0)
         int count = 0;
         uschar *t = sub - 1;
         while (*(++t) != 0) { *t = toupper(*t); count++; }
-        yield = string_cat(yield, &size, &ptr, sub, count);
+        yield = string_catn(yield, &size, &ptr, sub, count);
         continue;
         }
 
@@ -5869,7 +6396,7 @@ while (*s != 0)
        if (vp && *(void **)vp->value)
          {
          uschar * cp = tls_cert_fprt_md5(*(void **)vp->value);
-         yield = string_cat(yield, &size, &ptr, cp, Ustrlen(cp));
+         yield = string_cat(yield, &size, &ptr, cp);
          }
        else
 #endif
@@ -5881,7 +6408,7 @@ while (*s != 0)
          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));
+         yield = string_cat(yield, &size, &ptr, US st);
          }
         continue;
 
@@ -5890,34 +6417,86 @@ while (*s != 0)
        if (vp && *(void **)vp->value)
          {
          uschar * cp = tls_cert_fprt_sha1(*(void **)vp->value);
-         yield = string_cat(yield, &size, &ptr, cp, Ustrlen(cp));
+         yield = string_cat(yield, &size, &ptr, cp);
          }
        else
 #endif
          {
-         sha1 base;
+         hctx h;
          uschar digest[20];
          int j;
          char st[41];
-         sha1_start(&base);
-         sha1_end(&base, sub, Ustrlen(sub), digest);
+         sha1_start(&h);
+         sha1_end(&h, 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));
+         yield = string_catn(yield, &size, &ptr, US st, 40);
          }
         continue;
 
       case EOP_SHA256:
-#ifdef SUPPORT_TLS
+#ifdef EXIM_HAVE_SHA2
        if (vp && *(void **)vp->value)
          {
          uschar * cp = tls_cert_fprt_sha256(*(void **)vp->value);
-         yield = string_cat(yield, &size, &ptr, cp, (int)Ustrlen(cp));
+         yield = string_cat(yield, &size, &ptr, cp);
          }
        else
+         {
+         hctx h;
+         blob b;
+         char st[3];
+
+         if (!exim_sha_init(&h, HASH_SHA256))
+           {
+           expand_string_message = US"unrecognised sha256 variant";
+           goto EXPAND_FAILED;
+           }
+         exim_sha_update(&h, sub, Ustrlen(sub));
+         exim_sha_finish(&h, &b);
+         while (b.len-- > 0)
+           {
+           sprintf(st, "%02X", *b.data++);
+           yield = string_catn(yield, &size, &ptr, US st, 2);
+           }
+         }
+#else
+         expand_string_message = US"sha256 only supported with TLS";
 #endif
-         expand_string_message = US"sha256 only supported for certificates";
         continue;
 
+      case EOP_SHA3:
+#ifdef EXIM_HAVE_SHA3
+       {
+       hctx h;
+       blob b;
+       char st[3];
+       hashmethod m = !arg ? HASH_SHA3_256
+         : Ustrcmp(arg, "224") == 0 ? HASH_SHA3_224
+         : Ustrcmp(arg, "256") == 0 ? HASH_SHA3_256
+         : Ustrcmp(arg, "384") == 0 ? HASH_SHA3_384
+         : Ustrcmp(arg, "512") == 0 ? HASH_SHA3_512
+         : HASH_BADTYPE;
+
+       if (m == HASH_BADTYPE || !exim_sha_init(&h, m))
+         {
+         expand_string_message = US"unrecognised sha3 variant";
+         goto EXPAND_FAILED;
+         }
+
+       exim_sha_update(&h, sub, Ustrlen(sub));
+       exim_sha_finish(&h, &b);
+       while (b.len-- > 0)
+         {
+         sprintf(st, "%02X", *b.data++);
+         yield = string_catn(yield, &size, &ptr, US st, 2);
+         }
+       }
+        continue;
+#else
+       expand_string_message = US"sha3 only supported with GnuTLS 3.5.0 +";
+       goto EXPAND_FAILED;
+#endif
+
       /* Convert hex encoding to base64 encoding */
 
       case EOP_HEX2B64:
@@ -5961,8 +6540,8 @@ while (*s != 0)
             }
           }
 
-        enc = auth_b64encode(sub, out - sub);
-        yield = string_cat(yield, &size, &ptr, enc, Ustrlen(enc));
+        enc = b64encode(sub, out - sub);
+        yield = string_cat(yield, &size, &ptr, enc);
         continue;
         }
 
@@ -5974,10 +6553,10 @@ while (*s != 0)
         while (*(++t) != 0)
           {
           if (*t < 0x21 || 0x7E < *t)
-            yield = string_cat(yield, &size, &ptr,
+            yield = string_catn(yield, &size, &ptr,
              string_sprintf("\\x%02x", *t), 4);
          else
-           yield = string_cat(yield, &size, &ptr, t, 1);
+           yield = string_catn(yield, &size, &ptr, t, 1);
           }
        continue;
        }
@@ -5991,9 +6570,9 @@ while (*s != 0)
        uschar * cp;
        uschar buffer[256];
 
-       while (string_nextinlist(&sub, &sep, buffer, sizeof(buffer)) != NULL) cnt++;
+       while (string_nextinlist(CUSS &sub, &sep, buffer, sizeof(buffer)) != NULL) cnt++;
        cp = string_sprintf("%d", cnt);
-        yield = string_cat(yield, &size, &ptr, cp, Ustrlen(cp));
+        yield = string_cat(yield, &size, &ptr, cp);
         continue;
         }
 
@@ -6003,7 +6582,7 @@ while (*s != 0)
       case EOP_LISTNAMED:
        {
        tree_node *t = NULL;
-       uschar * list;
+       const uschar * list;
        int sep = 0;
        uschar * item;
        uschar * suffix = US"";
@@ -6047,7 +6626,7 @@ while (*s != 0)
          {
          uschar * buf = US" : ";
          if (needsep)
-           yield = string_cat(yield, &size, &ptr, buf, 3);
+           yield = string_catn(yield, &size, &ptr, buf, 3);
          else
            needsep = TRUE;
 
@@ -6063,21 +6642,21 @@ while (*s != 0)
            tok[0] = sep; tok[1] = ':'; tok[2] = 0;
            while ((cp= strpbrk((const char *)item, tok)))
              {
-              yield = string_cat(yield, &size, &ptr, item, cp-(char *)item);
+              yield = string_catn(yield, &size, &ptr, item, cp-(char *)item);
              if (*cp++ == ':') /* colon in a non-colon-sep list item, needs doubling */
                {
-                yield = string_cat(yield, &size, &ptr, US"::", 2);
+                yield = string_catn(yield, &size, &ptr, US"::", 2);
                item = (uschar *)cp;
                }
              else              /* sep in item; should already be doubled; emit once */
                {
-                yield = string_cat(yield, &size, &ptr, (uschar *)tok, 1);
+                yield = string_catn(yield, &size, &ptr, (uschar *)tok, 1);
                if (*cp == sep) cp++;
                item = (uschar *)cp;
                }
              }
            }
-          yield = string_cat(yield, &size, &ptr, item, Ustrlen(item));
+          yield = string_cat(yield, &size, &ptr, item);
          }
         continue;
        }
@@ -6125,11 +6704,44 @@ while (*s != 0)
 
         /* Convert to masked textual format and add to output. */
 
-        yield = string_cat(yield, &size, &ptr, buffer,
+        yield = string_catn(yield, &size, &ptr, buffer,
           host_nmtoa(count, binary, mask, buffer, '.'));
         continue;
         }
 
+      case EOP_IPV6NORM:
+      case EOP_IPV6DENORM:
+       {
+        int type = string_is_ip_address(sub, NULL);
+       int binary[4];
+       uschar buffer[44];
+
+       switch (type)
+         {
+         case 6:
+           (void) host_aton(sub, binary);
+           break;
+
+         case 4:       /* convert to IPv4-mapped IPv6 */
+           binary[0] = binary[1] = 0;
+           binary[2] = 0x0000ffff;
+           (void) host_aton(sub, binary+3);
+           break;
+
+         case 0:
+           expand_string_message =
+             string_sprintf("\"%s\" is not an IP address", sub);
+           goto EXPAND_FAILED;
+         }
+
+       yield = string_catn(yield, &size, &ptr, buffer,
+                 c == EOP_IPV6NORM
+                   ? ipv6_nmtoa(binary, buffer)
+                   : host_nmtoa(4, binary, -1, buffer, ':')
+                 );
+       continue;
+       }
+
       case EOP_ADDRESS:
       case EOP_LOCAL_PART:
       case EOP_DOMAIN:
@@ -6143,12 +6755,12 @@ while (*s != 0)
           if (c != EOP_DOMAIN)
             {
             if (c == EOP_LOCAL_PART && domain != 0) end = start + domain - 1;
-            yield = string_cat(yield, &size, &ptr, sub+start, end-start);
+            yield = string_catn(yield, &size, &ptr, sub+start, end-start);
             }
           else if (domain != 0)
             {
             domain += start;
-            yield = string_cat(yield, &size, &ptr, sub+domain, end-domain);
+            yield = string_catn(yield, &size, &ptr, sub+domain, end-domain);
             }
           }
         continue;
@@ -6183,25 +6795,25 @@ while (*s != 0)
           if (address != NULL)
             {
             if (ptr != save_ptr && address[0] == *outsep)
-              yield = string_cat(yield, &size, &ptr, US" ", 1);
+              yield = string_catn(yield, &size, &ptr, US" ", 1);
 
             for (;;)
               {
               size_t seglen = Ustrcspn(address, outsep);
-              yield = string_cat(yield, &size, &ptr, address, seglen + 1);
+              yield = string_catn(yield, &size, &ptr, address, seglen + 1);
 
               /* If we got to the end of the string we output one character
               too many. */
 
               if (address[seglen] == '\0') { ptr--; break; }
-              yield = string_cat(yield, &size, &ptr, outsep, 1);
+              yield = string_catn(yield, &size, &ptr, outsep, 1);
               address += seglen + 1;
               }
 
             /* Output a separator after the string: we will remove the
             redundant final one at the end. */
 
-            yield = string_cat(yield, &size, &ptr, outsep, 1);
+            yield = string_catn(yield, &size, &ptr, outsep, 1);
             }
 
           if (saveend == '\0') break;
@@ -6248,24 +6860,24 @@ while (*s != 0)
 
         if (needs_quote)
           {
-          yield = string_cat(yield, &size, &ptr, US"\"", 1);
+          yield = string_catn(yield, &size, &ptr, US"\"", 1);
           t = sub - 1;
           while (*(++t) != 0)
             {
             if (*t == '\n')
-              yield = string_cat(yield, &size, &ptr, US"\\n", 2);
+              yield = string_catn(yield, &size, &ptr, US"\\n", 2);
             else if (*t == '\r')
-              yield = string_cat(yield, &size, &ptr, US"\\r", 2);
+              yield = string_catn(yield, &size, &ptr, US"\\r", 2);
             else
               {
               if (*t == '\\' || *t == '"')
-                yield = string_cat(yield, &size, &ptr, US"\\", 1);
-              yield = string_cat(yield, &size, &ptr, t, 1);
+                yield = string_catn(yield, &size, &ptr, US"\\", 1);
+              yield = string_catn(yield, &size, &ptr, t, 1);
               }
             }
-          yield = string_cat(yield, &size, &ptr, US"\"", 1);
+          yield = string_catn(yield, &size, &ptr, US"\"", 1);
           }
-        else yield = string_cat(yield, &size, &ptr, sub, Ustrlen(sub));
+        else yield = string_cat(yield, &size, &ptr, sub);
         continue;
         }
 
@@ -6297,7 +6909,7 @@ while (*s != 0)
           goto EXPAND_FAILED;
           }
 
-        yield = string_cat(yield, &size, &ptr, sub, Ustrlen(sub));
+        yield = string_cat(yield, &size, &ptr, sub);
         continue;
         }
 
@@ -6310,8 +6922,8 @@ while (*s != 0)
         while (*(++t) != 0)
           {
           if (!isalnum(*t))
-            yield = string_cat(yield, &size, &ptr, US"\\", 1);
-          yield = string_cat(yield, &size, &ptr, t, 1);
+            yield = string_catn(yield, &size, &ptr, US"\\", 1);
+          yield = string_catn(yield, &size, &ptr, t, 1);
           }
         continue;
         }
@@ -6322,9 +6934,9 @@ while (*s != 0)
       case EOP_RFC2047:
         {
         uschar buffer[2048];
-        uschar *string = parse_quote_2047(sub, Ustrlen(sub), headers_charset,
+       const uschar *string = parse_quote_2047(sub, Ustrlen(sub), headers_charset,
           buffer, sizeof(buffer), FALSE);
-        yield = string_cat(yield, &size, &ptr, string, Ustrlen(string));
+        yield = string_cat(yield, &size, &ptr, string);
         continue;
         }
 
@@ -6341,7 +6953,7 @@ while (*s != 0)
           expand_string_message = error;
           goto EXPAND_FAILED;
           }
-        yield = string_cat(yield, &size, &ptr, decoded, len);
+        yield = string_catn(yield, &size, &ptr, decoded, len);
         continue;
         }
 
@@ -6357,57 +6969,51 @@ while (*s != 0)
           GETUTF8INC(c, sub);
           if (c > 255) c = '_';
           buff[0] = c;
-          yield = string_cat(yield, &size, &ptr, buff, 1);
+          yield = string_catn(yield, &size, &ptr, buff, 1);
           }
         continue;
         }
 
          /* replace illegal UTF-8 sequences by replacement character  */
-         
+
       #define UTF8_REPLACEMENT_CHAR US"?"
 
       case EOP_UTF8CLEAN:
         {
-        int seq_len, index = 0;
+        int seq_len = 0, index = 0;
         int bytes_left = 0;
+       long codepoint = -1;
         uschar seq_buff[4];                    /* accumulate utf-8 here */
-        
+
         while (*sub != 0)
          {
-         int complete;
-         long codepoint;
-         uschar c;
+         int complete = 0;
+         uschar c = *sub++;
 
-         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 */
+                 complete = -1;        /* error (RFC3629 limit) */
                else
                  {             /* finished; output utf-8 sequence */
-                 yield = string_cat(yield, &size, &ptr, seq_buff, seq_len);
+                 yield = string_catn(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);
+             yield = string_catn(yield, &size, &ptr, &c, 1);
              continue;
              }
            if((c & 0xe0) == 0xc0)              /* 2-byte sequence */
@@ -6440,25 +7046,99 @@ while (*s != 0)
          if (complete != 0)
            {
            bytes_left = index = 0;
-           yield = string_cat(yield, &size, &ptr, UTF8_REPLACEMENT_CHAR, 1);
+           yield = string_catn(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);
-           }
+                       /* ASCII character follows incomplete sequence */
+             yield = string_catn(yield, &size, &ptr, &c, 1);
          }
         continue;
         }
 
+#ifdef SUPPORT_I18N
+      case EOP_UTF8_DOMAIN_TO_ALABEL:
+       {
+        uschar * error = NULL;
+       uschar * s = string_domain_utf8_to_alabel(sub, &error);
+       if (error)
+         {
+         expand_string_message = string_sprintf(
+           "error converting utf8 (%s) to alabel: %s",
+           string_printing(sub), error);
+         goto EXPAND_FAILED;
+         }
+       yield = string_cat(yield, &size, &ptr, s);
+        continue;
+       }
+
+      case EOP_UTF8_DOMAIN_FROM_ALABEL:
+       {
+        uschar * error = NULL;
+       uschar * s = string_domain_alabel_to_utf8(sub, &error);
+       if (error)
+         {
+         expand_string_message = string_sprintf(
+           "error converting alabel (%s) to utf8: %s",
+           string_printing(sub), error);
+         goto EXPAND_FAILED;
+         }
+       yield = string_cat(yield, &size, &ptr, s);
+        continue;
+       }
+
+      case EOP_UTF8_LOCALPART_TO_ALABEL:
+       {
+        uschar * error = NULL;
+       uschar * s = string_localpart_utf8_to_alabel(sub, &error);
+       if (error)
+         {
+         expand_string_message = string_sprintf(
+           "error converting utf8 (%s) to alabel: %s",
+           string_printing(sub), error);
+         goto EXPAND_FAILED;
+         }
+       yield = string_cat(yield, &size, &ptr, s);
+       DEBUG(D_expand) debug_printf_indent("yield: '%s'\n", yield);
+        continue;
+       }
+
+      case EOP_UTF8_LOCALPART_FROM_ALABEL:
+       {
+        uschar * error = NULL;
+       uschar * s = string_localpart_alabel_to_utf8(sub, &error);
+       if (error)
+         {
+         expand_string_message = string_sprintf(
+           "error converting alabel (%s) to utf8: %s",
+           string_printing(sub), error);
+         goto EXPAND_FAILED;
+         }
+       yield = string_cat(yield, &size, &ptr, s);
+        continue;
+       }
+#endif /* EXPERIMENTAL_INTERNATIONAL */
+
       /* escape turns all non-printing characters into escape sequences. */
 
       case EOP_ESCAPE:
         {
-        uschar *t = string_printing(sub);
-        yield = string_cat(yield, &size, &ptr, t, Ustrlen(t));
+        const uschar * t = string_printing(sub);
+        yield = string_cat(yield, &size, &ptr, t);
         continue;
         }
 
+      case EOP_ESCAPE8BIT:
+       {
+       const uschar * s = sub;
+       uschar c;
+
+       for (s = sub; (c = *s); s++)
+         yield = c < 127 && c != '\\'
+           ? string_catn(yield, &size, &ptr, s, 1)
+           : string_catn(yield, &size, &ptr, string_sprintf("\\%03o", c), 4);
+       continue;
+       }
+
       /* Handle numeric expression evaluation */
 
       case EOP_EVAL:
@@ -6475,7 +7155,7 @@ while (*s != 0)
           goto EXPAND_FAILED;
           }
         sprintf(CS var_buffer, PR_EXIM_ARITH, n);
-        yield = string_cat(yield, &size, &ptr, var_buffer, Ustrlen(var_buffer));
+        yield = string_cat(yield, &size, &ptr, var_buffer);
         continue;
         }
 
@@ -6491,7 +7171,7 @@ while (*s != 0)
           goto EXPAND_FAILED;
           }
         sprintf(CS var_buffer, "%d", n);
-        yield = string_cat(yield, &size, &ptr, var_buffer, Ustrlen(var_buffer));
+        yield = string_cat(yield, &size, &ptr, var_buffer);
         continue;
         }
 
@@ -6506,16 +7186,37 @@ while (*s != 0)
           goto EXPAND_FAILED;
           }
         t = readconf_printtime(n);
-        yield = string_cat(yield, &size, &ptr, t, Ustrlen(t));
+        yield = string_cat(yield, &size, &ptr, t);
         continue;
         }
 
       /* Convert string to base64 encoding */
 
       case EOP_STR2B64:
+      case EOP_BASE64:
+       {
+#ifdef SUPPORT_TLS
+       uschar * s = vp && *(void **)vp->value
+         ? tls_cert_der_b64(*(void **)vp->value)
+         : b64encode(sub, Ustrlen(sub));
+#else
+       uschar * s = b64encode(sub, Ustrlen(sub));
+#endif
+       yield = string_cat(yield, &size, &ptr, s);
+       continue;
+       }
+
+      case EOP_BASE64D:
         {
-        uschar *encstr = auth_b64encode(sub, Ustrlen(sub));
-        yield = string_cat(yield, &size, &ptr, encstr, Ustrlen(encstr));
+        uschar * s;
+        int len = b64decode(sub, &s);
+       if (len < 0)
+          {
+          expand_string_message = string_sprintf("string \"%s\" is not "
+            "well-formed for \"%s\" operator", sub, name);
+          goto EXPAND_FAILED;
+          }
+        yield = string_cat(yield, &size, &ptr, s);
         continue;
         }
 
@@ -6525,7 +7226,7 @@ while (*s != 0)
         {
         uschar buff[24];
         (void)sprintf(CS buff, "%d", Ustrlen(sub));
-        yield = string_cat(yield, &size, &ptr, buff, Ustrlen(buff));
+        yield = string_cat(yield, &size, &ptr, buff);
         continue;
         }
 
@@ -6616,7 +7317,7 @@ while (*s != 0)
              extract_substr(sub, value1, value2, &len);
 
         if (ret == NULL) goto EXPAND_FAILED;
-        yield = string_cat(yield, &size, &ptr, ret, len);
+        yield = string_catn(yield, &size, &ptr, ret, len);
         continue;
         }
 
@@ -6671,7 +7372,7 @@ while (*s != 0)
           (long)st.st_dev, (long)st.st_nlink, (long)st.st_uid,
           (long)st.st_gid, st.st_size, (long)st.st_atime,
           (long)st.st_mtime, (long)st.st_ctime);
-        yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
+        yield = string_cat(yield, &size, &ptr, s);
         continue;
         }
 
@@ -6686,7 +7387,7 @@ while (*s != 0)
         if (expand_string_message != NULL)
           goto EXPAND_FAILED;
         s = string_sprintf("%d", vaguely_random_number((int)max));
-        yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
+        yield = string_cat(yield, &size, &ptr, s);
         continue;
         }
 
@@ -6705,7 +7406,7 @@ while (*s != 0)
           goto EXPAND_FAILED;
           }
         invert_address(reversed, sub);
-        yield = string_cat(yield, &size, &ptr, reversed, Ustrlen(reversed));
+        yield = string_cat(yield, &size, &ptr, reversed);
         continue;
         }
 
@@ -6735,8 +7436,7 @@ while (*s != 0)
       yield = NULL;
       size = 0;
       }
-    value = find_variable(name, FALSE, skipping, &newsize);
-    if (value == NULL)
+    if (!(value = find_variable(name, FALSE, skipping, &newsize)))
       {
       expand_string_message =
         string_sprintf("unknown variable in \"${%s}\"", name);
@@ -6744,13 +7444,14 @@ while (*s != 0)
       goto EXPAND_FAILED;
       }
     len = Ustrlen(value);
-    if (yield == NULL && newsize != 0)
+    if (!yield && newsize)
       {
       yield = value;
       size = newsize;
       ptr = len;
       }
-    else yield = string_cat(yield, &size, &ptr, value, len);
+    else
+      yield = string_catn(yield, &size, &ptr, value, len);
     continue;
     }
 
@@ -6791,10 +7492,11 @@ else if (resetok_p) *resetok_p = FALSE;
 
 DEBUG(D_expand)
   {
-  debug_printf("expanding: %.*s\n   result: %s\n", (int)(s - string), string,
-    yield);
-  if (skipping) debug_printf("skipping: result is not used\n");
+  debug_printf_indent("|__expanding: %.*s\n", (int)(s - string), string);
+  debug_printf_indent("%s_____result: %s\n", skipping ? "|" : "\\", yield);
+  if (skipping) debug_printf_indent("\\___skipping: result is not used\n");
   }
+expand_level--;
 return yield;
 
 /* This is the failure exit: easiest to program with a goto. We still need
@@ -6802,10 +7504,12 @@ to update the pointer to the terminator, for cases of nested calls with "fail".
 */
 
 EXPAND_FAILED_CURLY:
-expand_string_message = malformed_header?
-  US"missing or misplaced { or } - could be header name not terminated by colon"
-  :
-  US"missing or misplaced { or }";
+if (malformed_header)
+  expand_string_message =
+    US"missing or misplaced { or } - could be header name not terminated by colon";
+
+else if (!expand_string_message || !*expand_string_message)
+  expand_string_message = US"missing or misplaced { or }";
 
 /* At one point, Exim reset the store to yield (if yield was not NULL), but
 that is a bad idea, because expand_string_message is in dynamic store. */
@@ -6814,11 +7518,13 @@ EXPAND_FAILED:
 if (left != NULL) *left = s;
 DEBUG(D_expand)
   {
-  debug_printf("failed to expand: %s\n", string);
-  debug_printf("   error message: %s\n", expand_string_message);
-  if (expand_string_forcedfail) debug_printf("failure was forced\n");
+  debug_printf_indent("|failed to expand: %s\n", string);
+  debug_printf_indent("%s___error message: %s\n",
+    expand_string_forcedfail ? "|" : "\\", expand_string_message);
+  if (expand_string_forcedfail) debug_printf_indent("\\failure was forced\n");
   }
 if (resetok_p) *resetok_p = resetok;
+expand_level--;
 return NULL;
 }
 
@@ -6842,23 +7548,35 @@ return (Ustrpbrk(string, "$\\") == NULL)? string :
 
 
 
+const uschar *
+expand_cstring(const uschar *string)
+{
+search_find_defer = FALSE;
+malformed_header = FALSE;
+return (Ustrpbrk(string, "$\\") == NULL)? string :
+  expand_string_internal(string, FALSE, NULL, FALSE, TRUE, NULL);
+}
+
+
+
 /*************************************************
 *              Expand and copy                   *
 *************************************************/
 
 /* Now and again we want to expand a string and be sure that the result is in a
 new bit of store. This function does that.
+Since we know it has been copied, the de-const cast is safe.
 
 Argument: the string to be expanded
 Returns:  the expanded string, always in a new bit of store, or NULL
 */
 
 uschar *
-expand_string_copy(uschar *string)
+expand_string_copy(const uschar *string)
 {
-uschar *yield = expand_string(string);
+const uschar *yield = expand_cstring(string);
 if (yield == string) yield = string_copy(string);
-return yield;
+return US yield;
 }
 
 
@@ -6905,7 +7623,7 @@ Returns:  the integer value, or
 */
 
 static int_eximarith_t
-expanded_string_integer(uschar *s, BOOL isplus)
+expanded_string_integer(const uschar *s, BOOL isplus)
 {
 int_eximarith_t value;
 uschar *msg = US"invalid integer \"%s\"";
@@ -6933,7 +7651,7 @@ if (isspace(*s))
   if (*s == '\0')
     {
       DEBUG(D_expand)
-       debug_printf("treating blank string as number 0\n");
+       debug_printf_indent("treating blank string as number 0\n");
       return 0;
     }
   }
@@ -6984,6 +7702,144 @@ return -2;
 }
 
 
+/* These values are usually fixed boolean values, but they are permitted to be
+expanded strings.
+
+Arguments:
+  addr       address being routed
+  mtype      the module type
+  mname      the module name
+  dbg_opt    debug selectors
+  oname      the option name
+  bvalue     the router's boolean value
+  svalue     the router's string value
+  rvalue     where to put the returned value
+
+Returns:     OK     value placed in rvalue
+             DEFER  expansion failed
+*/
+
+int
+exp_bool(address_item *addr,
+  uschar *mtype, uschar *mname, unsigned dbg_opt,
+  uschar *oname, BOOL bvalue,
+  uschar *svalue, BOOL *rvalue)
+{
+uschar *expanded;
+if (svalue == NULL) { *rvalue = bvalue; return OK; }
+
+expanded = expand_string(svalue);
+if (expanded == NULL)
+  {
+  if (expand_string_forcedfail)
+    {
+    DEBUG(dbg_opt) debug_printf("expansion of \"%s\" forced failure\n", oname);
+    *rvalue = bvalue;
+    return OK;
+    }
+  addr->message = string_sprintf("failed to expand \"%s\" in %s %s: %s",
+      oname, mname, mtype, expand_string_message);
+  DEBUG(dbg_opt) debug_printf("%s\n", addr->message);
+  return DEFER;
+  }
+
+DEBUG(dbg_opt) debug_printf("expansion of \"%s\" yields \"%s\"\n", oname,
+  expanded);
+
+if (strcmpic(expanded, US"true") == 0 || strcmpic(expanded, US"yes") == 0)
+  *rvalue = TRUE;
+else if (strcmpic(expanded, US"false") == 0 || strcmpic(expanded, US"no") == 0)
+  *rvalue = FALSE;
+else
+  {
+  addr->message = string_sprintf("\"%s\" is not a valid value for the "
+    "\"%s\" option in the %s %s", expanded, oname, mname, mtype);
+  return DEFER;
+  }
+
+return OK;
+}
+
+
+
+/* Avoid potentially exposing a password in a string about to be logged */
+
+uschar *
+expand_hide_passwords(uschar * s)
+{
+return (  (  Ustrstr(s, "failed to expand") != NULL
+         || Ustrstr(s, "expansion of ")    != NULL
+         ) 
+       && (  Ustrstr(s, "mysql")   != NULL
+         || Ustrstr(s, "pgsql")   != NULL
+         || Ustrstr(s, "redis")   != NULL
+         || Ustrstr(s, "sqlite")  != NULL
+         || Ustrstr(s, "ldap:")   != NULL
+         || Ustrstr(s, "ldaps:")  != NULL
+         || Ustrstr(s, "ldapi:")  != NULL
+         || Ustrstr(s, "ldapdn:") != NULL
+         || Ustrstr(s, "ldapm:")  != NULL
+       )  ) 
+  ? US"Temporary internal error" : s;
+}
+
+
+
+/*************************************************
+* Error-checking for testsuite                   *
+*************************************************/
+typedef struct {
+  const char * filename;
+  int          linenumber;
+  uschar *     region_start;
+  uschar *     region_end;
+  const uschar *var_name;
+  const uschar *var_data;
+} err_ctx;
+
+static void
+assert_variable_notin(uschar * var_name, uschar * var_data, void * ctx)
+{
+err_ctx * e = ctx;
+if (var_data >= e->region_start  &&  var_data < e->region_end)
+  {
+  e->var_name = CUS var_name;
+  e->var_data = CUS var_data;
+  }
+}
+
+void
+assert_no_variables(void * ptr, int len, const char * filename, int linenumber)
+{
+err_ctx e = {filename, linenumber, ptr, US ptr + len, NULL };
+int i;
+var_entry * v;
+
+/* check acl_ variables */
+tree_walk(acl_var_c, assert_variable_notin, &e);
+tree_walk(acl_var_m, assert_variable_notin, &e);
+
+/* check auth<n> variables */
+for (i = 0; i < AUTH_VARS; i++) if (auth_vars[i])
+  assert_variable_notin(US"auth<n>", auth_vars[i], &e);
+
+/* check regex<n> variables */
+for (i = 0; i < REGEX_VARS; i++) if (regex_vars[i])
+  assert_variable_notin(US"regex<n>", regex_vars[i], &e);
+
+/* check known-name variables */
+for (v = var_table; v < var_table + var_table_size; v++)
+  if (v->type == vtype_stringptr)
+    assert_variable_notin(US v->name, *(USS v->value), &e);
+
+if (e.var_name)
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+    "live variable '%s' destroyed by reset_store at %s:%d\n- value '%.64s'",
+    e.var_name, e.filename, e.linenumber, e.var_data);
+}
+
+
+
 /*************************************************
 **************************************************
 *             Stand-alone test program           *
@@ -6998,7 +7854,7 @@ regex_match_and_setup(const pcre *re, uschar *subject, int options, int setup)
 {
 int ovector[3*(EXPAND_MAXN+1)];
 int n = pcre_exec(re, NULL, subject, Ustrlen(subject), 0, PCRE_EOPT|options,
-  ovector, sizeof(ovector)/sizeof(int));
+  ovector, nelem(ovector));
 BOOL yield = n >= 0;
 if (n == 0) n = EXPAND_MAXN + 1;
 if (yield)
@@ -7039,22 +7895,22 @@ for (i = 1; i < argc; i++)
     if (Ustrspn(argv[i], "abcdefghijklmnopqrtsuvwxyz0123456789-.:/") ==
         Ustrlen(argv[i]))
       {
-      #ifdef LOOKUP_LDAP
+#ifdef LOOKUP_LDAP
       eldap_default_servers = argv[i];
-      #endif
-      #ifdef LOOKUP_MYSQL
+#endif
+#ifdef LOOKUP_MYSQL
       mysql_servers = argv[i];
-      #endif
-      #ifdef LOOKUP_PGSQL
+#endif
+#ifdef LOOKUP_PGSQL
       pgsql_servers = argv[i];
-      #endif
-      #ifdef EXPERIMENTAL_REDIS
+#endif
+#ifdef LOOKUP_REDIS
       redis_servers = argv[i];
-      #endif
+#endif
       }
-  #ifdef EXIM_PERL
+#ifdef EXIM_PERL
   else opt_perl_startup = argv[i];
-  #endif
+#endif
   }
 
 printf("Testing string expansion: debug_level = %d\n\n", debug_level);
index 7132d22..a5c3b5d 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -355,7 +355,7 @@ while (*(++ptr) != 0 && *ptr != '\"' && *ptr != '\n')
         }
       }
 
-    *bp++ = string_interpret_escape(&ptr);
+    *bp++ = string_interpret_escape(CUSS &ptr);
     }
   }
 
@@ -519,14 +519,14 @@ for (;;)
         string_item *aa;
         uschar *saveptr = ptr;
         ptr = nextword(ptr, buffer, sizeof(buffer), TRUE);
-        if (*error_pointer != NULL) break;
+        if (*error_pointer) break;
         if (Ustrcmp(buffer, "alias") != 0)
           {
           ptr = saveptr;
           break;
           }
         ptr = nextitem(ptr, buffer, sizeof(buffer), TRUE);
-        if (*error_pointer != NULL) break;
+        if (*error_pointer) break;
         aa = store_get(sizeof(string_item));
         aa->text = string_copy(buffer);
         aa->next = c->left.a;
@@ -540,7 +540,7 @@ for (;;)
     else if (Ustrcmp(buffer, "foranyaddress") == 0)
       {
       ptr = nextitem(ptr, buffer, sizeof(buffer), TRUE);
-      if (*error_pointer != NULL) break;
+      if (*error_pointer) break;
       if (*ptr != '(')
         {
         *error_pointer = string_sprintf("\"(\" expected after \"foranyaddress\" "
@@ -552,18 +552,13 @@ for (;;)
       c->left.u = string_copy(buffer);
 
       ptr = read_condition(nextsigchar(ptr+1, TRUE), &(c->right.c), FALSE);
-      if (*error_pointer != NULL) break;
+      if (*error_pointer) break;
       if (*ptr != ')')
         {
         *error_pointer = string_sprintf("expected \")\" in line %d of "
           "filter file", line_number);
         break;
         }
-      if (!testfor)
-        {
-        c->testfor = !c->testfor;
-        testfor = TRUE;
-        }
       ptr = nextsigchar(ptr+1, TRUE);
       }
 
@@ -577,7 +572,7 @@ for (;;)
 
       c->left.u = string_copy(buffer);
       ptr = nextword(ptr, buffer, sizeof(buffer), TRUE);
-      if (*error_pointer != NULL) break;
+      if (*error_pointer) break;
 
       /* Handle "does|is [not]", preserving the pointer after "is" in
       case it isn't that, but the form "is <string>". */
@@ -588,13 +583,13 @@ for (;;)
         if (buffer[0] == 'I') { c->type = cond_IS; isptr = ptr; }
 
         ptr = nextword(ptr, buffer, sizeof(buffer), TRUE);
-        if (*error_pointer != NULL) break;
+        if (*error_pointer) break;
         if (strcmpic(buffer, US"not") == 0)
           {
           c->testfor = !c->testfor;
-          if (isptr != NULL) isptr = ptr;
+          if (isptr) isptr = ptr;
           ptr = nextword(ptr, buffer, sizeof(buffer), TRUE);
-          if (*error_pointer != NULL) break;
+          if (*error_pointer) break;
           }
         }
 
@@ -612,22 +607,19 @@ for (;;)
 
       if (i >= cond_word_count)
         {
-        if (isptr != NULL)
-          {
-          ptr = isptr;
-          }
-        else
+        if (!isptr)
           {
           *error_pointer = string_sprintf("unrecognized condition word \"%s\" "
             "near line %d of filter file", buffer, line_number);
           break;
           }
+        ptr = isptr;
         }
 
       /* Get the RH argument. */
 
       ptr = nextitem(ptr, buffer, sizeof(buffer), TRUE);
-      if (*error_pointer != NULL) break;
+      if (*error_pointer) break;
       c->right.u = string_copy(buffer);
       }
     }
@@ -664,7 +656,7 @@ for (;;)
     {
     uschar *saveptr = ptr;
     ptr = nextword(ptr, buffer, sizeof(buffer), FALSE);
-    if (*error_pointer != NULL) break;
+    if (*error_pointer) break;
 
     /* "Then" terminates a toplevel condition; otherwise a closing bracket
     has been omitted. Put a string terminator at the start of "then" so
@@ -673,7 +665,7 @@ for (;;)
     if (Ustrcmp(buffer, "then") == 0)
       {
       if (toplevel) *saveptr = 0;
-        else *error_pointer = string_sprintf("missing \")\" at end of "
+      else *error_pointer = string_sprintf("missing \")\" at end of "
           "condition near line %d of filter file", line_number);
       break;
       }
@@ -707,21 +699,21 @@ for (;;)
       condition_block *orc = store_get(sizeof(condition_block));
       condition_block *or_parent = NULL;
 
-      if (current_parent != NULL)
+      if (current_parent)
         {
-        while (current_parent->parent != NULL &&
+        while (current_parent->parent &&
                current_parent->parent->type == cond_and)
           current_parent = current_parent->parent;
 
         /* If the parent has a parent, it must be an "or" parent. */
 
-        if (current_parent->parent != NULL)
+        if (current_parent->parent)
           or_parent = current_parent->parent;
         }
 
       orc->parent = or_parent;
-      if (or_parent == NULL) *cond = orc; else
-        or_parent->right.c = orc;
+      if (!or_parent) *cond = orc;
+      else or_parent->right.c = orc;
       orc->type = cond_or;
       orc->testfor = TRUE;
       orc->left.c = (current_parent == NULL)? c : current_parent;
@@ -748,7 +740,7 @@ return nextsigchar(ptr, TRUE);
 
 
 /*************************************************
-*             Ouput the current indent           *
+*             Output the current indent          *
 *************************************************/
 
 static void
@@ -1378,7 +1370,7 @@ return yield;
 *              Read a list of commands           *
 *************************************************/
 
-/* If condional is TRUE, the list must be terminated
+/* If conditional is TRUE, the list must be terminated
 by the words "else" or "endif".
 
 Arguments:
@@ -1821,7 +1813,7 @@ while (commands != NULL)
       set in a system filter and to the local address in user filters. */
 
       addr = deliver_make_addr(expargs[0], TRUE);  /* TRUE => copy s */
-      addr->p.errors_address = (s == NULL)?
+      addr->prop.errors_address = (s == NULL)?
         s : string_copy(s);                        /* Default is NULL */
       if (commands->noerror) setflag(addr, af_ignore_error);
       addr->next = *generated;
@@ -2021,7 +2013,7 @@ while (commands != NULL)
         {
         int sep = 0;
         uschar *ss;
-        uschar *list = s;
+        const uschar *list = s;
         uschar buffer[128];
         while ((ss = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))
                != NULL)
@@ -2057,7 +2049,7 @@ while (commands != NULL)
     DEFERFREEZEFAIL:
     fmsg = expargs[0];
     if (Ustrlen(fmsg) > 1024) Ustrcpy(fmsg + 1000, " ... (truncated)");
-    fmsg = string_printing(fmsg);
+    fmsg = US string_printing(fmsg);
     *error_pointer = fmsg;
 
     if (filter_test != FTEST_NONE)
@@ -2281,17 +2273,16 @@ while (commands != NULL)
 
         if (recipient != NULL)
           {
-          log_addr = string_cat(log_addr, &size, &ptr,
-            (log_addr == NULL)? US">" : US",", 1);
-          log_addr = string_cat(log_addr, &size, &ptr, recipient,
-            Ustrlen(recipient));
+          log_addr = string_catn(log_addr, &size, &ptr,
+            log_addr ? US"," : US">", 1);
+          log_addr = string_cat(log_addr, &size, &ptr, recipient);
           }
 
         /* Check size */
 
         if (ptr > 256)
           {
-          log_addr = string_cat(log_addr, &size, &ptr, US", ...", 5);
+          log_addr = string_catn(log_addr, &size, &ptr, US", ...", 5);
           break;
           }
 
@@ -2351,7 +2342,7 @@ while (commands != NULL)
     case testprint_command:
     if (filter_test != FTEST_NONE || (debug_selector & D_filter) != 0)
       {
-      uschar *s = string_printing(expargs[0]);
+      const uschar *s = string_printing(expargs[0]);
       if (filter_test == FTEST_NONE)
         debug_printf("Filter: testprint: %s\n", s);
       else
index a6257a9..ce89c0b 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2017 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -39,18 +39,24 @@ 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_der_b64(void * cert);
 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 *,
-                void *);
+                transport_instance *
+# ifdef EXPERIMENTAL_DANE
+               , dns_answer *
+# endif
+                               );
 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 void    tls_free_cert(void **);
+extern int     tls_getc(unsigned);
+extern void    tls_get_cache(void);
 extern int     tls_import_cert(const uschar *, void **);
 extern int     tls_read(BOOL, uschar *, size_t);
 extern int     tls_server_start(const uschar *);
@@ -62,10 +68,13 @@ extern void    tls_version_report(FILE *);
 # ifndef USE_GNUTLS
 extern BOOL    tls_openssl_options_parse(uschar *, long *);
 # endif
-extern uschar * tls_field_from_dn(uschar *, uschar *);
-# ifdef EXPERIMENTAL_CERTNAMES
-extern BOOL    tls_is_name_for_cert(uschar *, void *);
+extern uschar * tls_field_from_dn(uschar *, const uschar *);
+extern BOOL    tls_is_name_for_cert(const uschar *, void *);
+
+# ifdef EXPERIMENTAL_DANE
+extern int     tlsa_lookup(const host_item *, dns_answer *, BOOL);
 # endif
+
 #endif /*SUPPORT_TLS*/
 
 
@@ -77,26 +86,36 @@ extern int     acl_eval(int, uschar *, uschar **, uschar **);
 
 extern tree_node *acl_var_create(uschar *);
 extern void    acl_var_write(uschar *, uschar *, void *);
-extern uschar *auth_b64encode(uschar *, int);
-extern int     auth_b64decode(uschar *, uschar **);
-extern int     auth_call_pam(uschar *, uschar **);
+extern void    assert_no_variables(void *, int, const char *, int);
+extern int     auth_call_pam(const uschar *, uschar **);
 extern int     auth_call_pwcheck(uschar *, uschar **);
-extern int     auth_call_radius(uschar *, uschar **);
-extern int     auth_call_saslauthd(uschar *, uschar *, uschar *, uschar *,
-                 uschar **);
+extern int     auth_call_radius(const uschar *, uschar **);
+extern int     auth_call_saslauthd(const uschar *, const uschar *,
+                const uschar *, const uschar *, uschar **);
 extern int     auth_check_serv_cond(auth_instance *);
 extern int     auth_check_some_cond(auth_instance *, uschar *, uschar *, int);
 
+
 extern int     auth_get_data(uschar **, uschar *, int);
 extern int     auth_get_no64_data(uschar **, uschar *);
 extern uschar *auth_xtextencode(uschar *, int);
 extern int     auth_xtextdecode(uschar *, uschar **);
 
+extern uschar *b64encode(uschar *, int);
+extern int     b64decode(uschar *, uschar **);
+extern int     bdat_getc(unsigned);
+extern int     bdat_ungetc(int);
+extern void    bdat_flush_data(void);
+
+extern void    bits_clear(unsigned int *, size_t, int *);
+extern void    bits_set(unsigned int *, size_t, int *);
+
 extern void    cancel_cutthrough_connection(const char *);
-extern int     check_host(void *, uschar *, uschar **, uschar **);
+extern int     check_host(void *, const uschar *, const uschar **, uschar **);
 extern uschar **child_exec_exim(int, BOOL, int *, BOOL, int, ...);
-extern pid_t   child_open_uid(uschar **, uschar **, int, uid_t *, gid_t *,
-                 int *, int *, uschar *, BOOL);
+extern pid_t   child_open_uid(const uschar **, const uschar **, int,
+                uid_t *, gid_t *, int *, int *, uschar *, BOOL);
+extern BOOL    cleanup_environment(void);
 extern uschar *cutthrough_finaldot(void);
 extern BOOL    cutthrough_flush_send(void);
 extern BOOL    cutthrough_headers_send(void);
@@ -111,54 +130,70 @@ extern int     dcc_process(uschar **);
 #endif
 
 extern void    debug_logging_activate(uschar *, uschar *);
-extern void    debug_print_argv(uschar **);
+extern void    debug_logging_stop(void);
+extern void    debug_print_argv(const uschar **);
 extern void    debug_print_ids(uschar *);
+extern void    debug_printf_indent(const char *, ...) PRINTF_FUNCTION(1,2);
 extern void    debug_print_string(uschar *);
 extern void    debug_print_tree(tree_node *);
 extern void    debug_vprintf(const char *, va_list);
-extern void    decode_bits(unsigned int *, unsigned int *,
-                  int, int, uschar *, bit_table *, int, uschar *, int);
+extern void    decode_bits(unsigned int *, size_t, int *,
+                  uschar *, bit_table *, int, uschar *, int);
 extern address_item *deliver_make_addr(uschar *, BOOL);
+extern void    deliver_init(void);
 extern void    delivery_log(int, address_item *, int, uschar *);
 extern int     deliver_message(uschar *, BOOL, BOOL);
 extern void    deliver_msglog(const char *, ...) PRINTF_FUNCTION(1,2);
 extern void    deliver_set_expansions(address_item *);
 extern int     deliver_split_address(address_item *);
 extern void    deliver_succeeded(address_item *);
-#ifdef WITH_OLD_DEMIME
-extern int     demime(uschar **);
-#endif
+
+extern uschar *deliver_get_sender_address (uschar *id);
+
 extern BOOL    directory_make(const uschar *, const uschar *, int, BOOL);
 #ifndef DISABLE_DKIM
-extern BOOL    dkim_transport_write_message(address_item *, int, int,
-                   int, uschar *, uschar *, uschar *, uschar *, rewrite_rule *,
-                   int, uschar *, uschar *, uschar *, uschar *, uschar *, uschar *);
+extern BOOL    dkim_transport_write_message(int, transport_ctx *,
+                 struct ob_dkim *);
 #endif
 extern dns_address *dns_address_from_rr(dns_answer *, dns_record *);
-extern void    dns_build_reverse(uschar *, uschar *);
+extern int     dns_basic_lookup(dns_answer *, const uschar *, int);
+extern void    dns_build_reverse(const uschar *, uschar *);
 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 **);
-extern int     dns_special_lookup(dns_answer *, uschar *, int, uschar **);
-extern dns_record *dns_next_rr(dns_answer *, dns_scan *, int);
+extern BOOL    dns_is_aa(const dns_answer *);
+extern BOOL    dns_is_secure(const dns_answer *);
+extern int     dns_lookup(dns_answer *, const uschar *, int, const uschar **);
+extern void    dns_pattern_init(void);
+extern int     dns_special_lookup(dns_answer *, const uschar *, int, const uschar **);
+extern dns_record *dns_next_rr(const dns_answer *, dns_scan *, int);
 extern uschar *dns_text_type(int);
 extern void    dscp_list_to_stream(FILE *);
 extern BOOL    dscp_lookup(const uschar *, int, int *, int *, int *);
 
 extern void    enq_end(uschar *);
-extern BOOL    enq_start(uschar *);
+extern BOOL    enq_start(uschar *, unsigned);
+#ifndef DISABLE_EVENT
+extern uschar *event_raise(uschar *, const uschar *, uschar *);
+extern void    msg_event_raise(const uschar *, const address_item *);
+#endif
+extern const uschar * exim_errstr(int);
 extern void    exim_exit(int);
 extern void    exim_nullstd(void);
 extern void    exim_setugid(uid_t, gid_t, BOOL, uschar *);
 extern int     exim_tvcmp(struct timeval *, struct timeval *);
 extern void    exim_wait_tick(struct timeval *, int);
+extern int     exp_bool(address_item *addr,
+  uschar *mtype, uschar *mname, unsigned dgb_opt, uschar *oname, BOOL bvalue,
+  uschar *svalue, BOOL *rvalue);
 extern BOOL    expand_check_condition(uschar *, uschar *, uschar *);
-extern uschar *expand_string(uschar *);
-extern uschar *expand_string_copy(uschar *);
+extern uschar *expand_string(uschar *);        /* public, cannot make const */
+extern const uschar *expand_cstring(const uschar *); /* ... so use this one */
+extern uschar *expand_hide_passwords(uschar * );
+extern uschar *expand_string_copy(const uschar *);
 extern int_eximarith_t expand_string_integer(uschar *, BOOL);
 extern void    modify_variable(uschar *, void *);
 
+extern BOOL    fd_ready(int, int);
+
 extern int     filter_interpret(uschar *, int, address_item **, uschar **);
 extern BOOL    filter_personal(string_item *, BOOL);
 extern BOOL    filter_runtest(int, uschar *, BOOL, BOOL);
@@ -171,16 +206,17 @@ extern int     header_checkname(header_line *, BOOL);
 extern BOOL    header_match(uschar *, BOOL, BOOL, string_item *, int, ...);
 extern int     host_address_extract_port(uschar *);
 extern uschar *host_and_ident(BOOL);
-extern int     host_aton(uschar *, int *);
-extern void    host_build_hostlist(host_item **, uschar *, BOOL);
-extern ip_address_item *host_build_ifacelist(uschar *, uschar *);
+extern int     host_aton(const uschar *, int *);
+extern void    host_build_hostlist(host_item **, const uschar *, BOOL);
+extern ip_address_item *host_build_ifacelist(const uschar *, uschar *);
 extern void    host_build_log_info(void);
 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 *, uschar *, uschar **, BOOL *);
+extern int     host_find_byname(host_item *, const uschar *, int,
+                               const uschar **, BOOL);
+extern int     host_find_bydns(host_item *, const uschar *, int, uschar *, uschar *,
+                 uschar *, const dnssec_domains *, const uschar **, BOOL *);
 extern ip_address_item *host_find_interfaces(void);
-extern BOOL    host_is_in_net(uschar *, uschar *, int);
+extern BOOL    host_is_in_net(const uschar *, const uschar *, int);
 extern BOOL    host_is_tls_on_connect_port(int);
 extern int     host_item_get_port(host_item *);
 extern void    host_mask(int, int *, int);
@@ -189,34 +225,47 @@ extern int     host_nmtoa(int, int *, int, uschar *, int);
 extern uschar *host_ntoa(int, const void *, uschar *, int *);
 extern int     host_scan_for_local_hosts(host_item *, host_item **, BOOL *);
 
+extern uschar *imap_utf7_encode(uschar *, const uschar *,
+                                uschar, uschar *, uschar **);
+
 extern void    invert_address(uschar *, uschar *);
+extern int     ip_addr(void *, int, const uschar *, int);
 extern int     ip_bind(int, int, uschar *, int);
-extern int     ip_connect(int, int, uschar *, int, int);
+extern int     ip_connect(int, int, const uschar *, int, int, BOOL);
 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 void    ip_keepalive(int, const uschar *, BOOL);
 extern int     ip_recv(int, uschar *, int, int);
 extern int     ip_socket(int, int);
 
+extern int     ip_tcpsocket(const uschar *, uschar **, int);
+extern int     ip_unixsocket(const uschar *, uschar **);
+extern int     ip_streamsocket(const uschar *, uschar **, int);
+
+extern int     ipv6_nmtoa(int *, uschar *);
+
 extern uschar *local_part_quote(uschar *);
 extern int     log_create(uschar *);
 extern int     log_create_as_exim(uschar *);
 extern void    log_close_all(void);
 
+extern macro_item * macro_create(const uschar *, const uschar *, BOOL, BOOL);
+extern void    mainlog_close(void);
 #ifdef WITH_CONTENT_SCAN
-extern int     malware(uschar **);
+extern int     malware(const uschar *, int);
 extern int     malware_in_file(uschar *);
+extern void    malware_init(void);
 #endif
-extern int     match_address_list(uschar *, BOOL, BOOL, uschar **,
-                 unsigned int *, int, int, uschar **);
-extern int     match_check_list(uschar **, int, tree_node **, unsigned int **,
-                 int(*)(void *, uschar *, uschar **, uschar **), void *, int,
-                 uschar *, uschar **);
-extern int     match_isinlist(uschar *, uschar **, int, tree_node **,
-                 unsigned int *, int, BOOL, uschar **);
-extern int     match_check_string(uschar *, uschar *, int, BOOL, BOOL, BOOL,
-                 uschar **);
+extern int     match_address_list(const uschar *, BOOL, BOOL, const uschar **,
+                 unsigned int *, int, int, const uschar **);
+extern int     match_check_list(const uschar **, int, tree_node **, unsigned int **,
+                 int(*)(void *, const uschar *, const uschar **, uschar **), void *, int,
+                 const uschar *, const uschar **);
+extern int     match_isinlist(const uschar *, const uschar **, int, tree_node **,
+                 unsigned int *, int, BOOL, const uschar **);
+extern int     match_check_string(const uschar *, const uschar *, int, BOOL, BOOL, BOOL,
+                 const uschar **);
 extern void    md5_end(md5 *, const uschar *, int, uschar *);
 extern void    md5_mid(md5 *, const uschar *);
 extern void    md5_start(md5 *);
@@ -225,8 +274,10 @@ extern void    millisleep(int);
 struct mime_boundary_context;
 extern int     mime_acl_check(uschar *acl, FILE *f,
                  struct mime_boundary_context *, uschar **, uschar **);
-extern int     mime_decode(uschar **);
-extern int     mime_regex(uschar **);
+extern int     mime_decode(const uschar **);
+extern ssize_t mime_decode_base64(FILE *, FILE *, uschar *);
+extern int     mime_regex(const uschar **);
+extern void    mime_set_anomaly(int);
 #endif
 extern uschar *moan_check_errorcopy(uschar *);
 extern BOOL    moan_skipped_syntax_errors(uschar *, error_block *, uschar *,
@@ -238,17 +289,17 @@ extern BOOL    moan_to_sender(int, error_block *, header_line *, FILE *, BOOL);
 extern void    moan_write_from(FILE *);
 extern FILE   *modefopen(const uschar *, const char *, mode_t);
 
-extern void   open_cutthrough_connection( address_item * addr );
+extern int     open_cutthrough_connection( address_item * addr );
 
 extern uschar *parse_extract_address(uschar *, uschar **, int *, int *, int *,
                  BOOL);
 extern int     parse_forward_list(uschar *, int, address_item **, uschar **,
-                 uschar *, uschar *, error_block **);
+                 const uschar *, uschar *, error_block **);
 extern uschar *parse_find_address_end(uschar *, BOOL);
 extern uschar *parse_find_at(uschar *);
-extern uschar *parse_fix_phrase(uschar *, int, uschar *, int);
+extern const uschar *parse_fix_phrase(const uschar *, int, uschar *, int);
 extern uschar *parse_message_id(uschar *, uschar **, uschar **);
-extern uschar *parse_quote_2047(uschar *, int, uschar *, uschar *, int, BOOL);
+extern const uschar *parse_quote_2047(const uschar *, int, uschar *, uschar *, int, BOOL);
 extern uschar *parse_date_time(uschar *str, time_t *t);
 extern int     vaguely_random_number(int);
 #ifdef SUPPORT_TLS
@@ -262,9 +313,6 @@ extern void    queue_count(void);
 extern void    queue_run(uschar *, uschar *, BOOL);
 
 extern int     random_number(int);
-#ifdef WITH_CONTENT_SCAN
-extern int     recv_line(int, uschar *, int);
-#endif
 extern int     rda_interpret(redirect_block *, int, uschar *, uschar *,
                  uschar *, uschar *, uschar *, ugid_block *, address_item **,
                  uschar **, error_block **, int *, uschar *);
@@ -273,13 +321,17 @@ extern BOOL    readconf_depends(driver_instance *, uschar *);
 extern void    readconf_driver_init(uschar *, driver_instance **,
                  driver_info *, int, void *, int, optionlist *, int);
 extern uschar *readconf_find_option(void *);
-extern void    readconf_main(void);
+extern void    readconf_main(BOOL);
+extern void    readconf_options_from_list(optionlist *, unsigned, const uschar *, uschar *);
+extern void    readconf_options_routers(void);
+extern void    readconf_options_transports(void);
 extern void    readconf_print(uschar *, uschar *, BOOL);
 extern uschar *readconf_printtime(int);
 extern uschar *readconf_readname(uschar *, int, uschar *);
-extern int     readconf_readtime(uschar *, int, BOOL);
-extern void    readconf_rest();
-extern uschar *readconf_retry_error(uschar *, uschar *, int *, int *);
+extern int     readconf_readtime(const uschar *, int, BOOL);
+extern void    readconf_rest(void);
+extern uschar *readconf_retry_error(const uschar *, const uschar *, int *, int *);
+extern void    readconf_save_config(const uschar *);
 extern void    read_message_body(BOOL);
 extern void    receive_bomb_out(uschar *, uschar *);
 extern BOOL    receive_check_fs(int);
@@ -288,20 +340,21 @@ extern BOOL    receive_msg(BOOL);
 extern int     receive_statvfs(BOOL, int *);
 extern void    receive_swallow_smtp(void);
 #ifdef WITH_CONTENT_SCAN
-extern int     regex(uschar **);
+extern int     regex(const uschar **);
 #endif
-extern BOOL    regex_match_and_setup(const pcre *, uschar *, int, int);
-extern const pcre *regex_must_compile(uschar *, BOOL, BOOL);
+extern BOOL    regex_match_and_setup(const pcre *, const uschar *, int, int);
+extern const pcre *regex_must_compile(const uschar *, BOOL, BOOL);
 extern void    retry_add_item(address_item *, uschar *, int);
-extern BOOL    retry_check_address(uschar *, host_item *, uschar *, BOOL,
+extern BOOL    retry_check_address(const uschar *, host_item *, uschar *, BOOL,
                  uschar **, uschar **);
-extern retry_config *retry_find_config(uschar *, uschar *, int, int);
-extern BOOL    retry_ultimate_address_timeout(uschar *, uschar *,
+extern retry_config *retry_find_config(const uschar *, const uschar *, int, int);
+extern BOOL    retry_ultimate_address_timeout(uschar *, const uschar *,
                  dbdata_retry *, time_t);
 extern void    retry_update(address_item **, address_item **, address_item **);
 extern uschar *rewrite_address(uschar *, BOOL, BOOL, rewrite_rule *, int);
 extern uschar *rewrite_address_qualify(uschar *, BOOL);
-extern header_line *rewrite_header(header_line *, uschar *, uschar *,
+extern header_line *rewrite_header(header_line *,
+               const uschar *, const uschar *,
                rewrite_rule *, int, BOOL);
 extern uschar *rewrite_one(uschar *, int, BOOL *, BOOL, uschar *,
                  rewrite_rule *);
@@ -310,10 +363,10 @@ extern uschar *rfc2047_decode2(uschar *, BOOL, uschar *, int, int *, int *,
                  uschar **);
 extern int     route_address(address_item *, address_item **, address_item **,
                  address_item **, address_item **, int);
-extern int     route_check_prefix(uschar *, uschar *);
-extern int     route_check_suffix(uschar *, uschar *);
+extern int     route_check_prefix(const uschar *, const uschar *);
+extern int     route_check_suffix(const uschar *, const uschar *);
 extern BOOL    route_findgroup(uschar *, gid_t *);
-extern BOOL    route_finduser(uschar *, struct passwd **, uid_t *);
+extern BOOL    route_finduser(const uschar *, struct passwd **, uid_t *);
 extern BOOL    route_find_expanded_group(uschar *, uschar *, uschar *, gid_t *,
                  uschar **);
 extern BOOL    route_find_expanded_user(uschar *, uschar *, uschar *,
@@ -321,33 +374,37 @@ extern BOOL    route_find_expanded_user(uschar *, uschar *, uschar *,
 extern void    route_init(void);
 extern void    route_tidyup(void);
 
-extern uschar *search_find(void *, uschar *, uschar *, int, uschar *, int,
+extern uschar *search_find(void *, uschar *, uschar *, int, const uschar *, int,
                  int, int *);
-extern int     search_findtype(uschar *, int);
-extern int     search_findtype_partial(uschar *, int *, uschar **, int *,
+extern int     search_findtype(const uschar *, int);
+extern int     search_findtype_partial(const uschar *, int *, const uschar **, int *,
                  int *);
 extern void   *search_open(uschar *, int, int, uid_t *, gid_t *);
 extern void    search_tidyup(void);
 extern void    set_process_info(const char *, ...) PRINTF_FUNCTION(1,2);
-extern void    sha1_end(sha1 *, const uschar *, int, uschar *);
-extern void    sha1_mid(sha1 *, const uschar *);
-extern void    sha1_start(sha1 *);
+extern void    sha1_end(hctx *, const uschar *, int, uschar *);
+extern void    sha1_mid(hctx *, const uschar *);
+extern void    sha1_start(hctx *);
 extern int     sieve_interpret(uschar *, int, uschar *, uschar *, uschar *,
                  uschar *, address_item **, uschar **);
 extern void    sigalrm_handler(int);
 extern BOOL    smtp_buffered(void);
 extern void    smtp_closedown(uschar *);
-extern int     smtp_connect(host_item *, int, int, uschar *, int, BOOL, const uschar *);
+extern int     smtp_connect(host_item *, int, int, uschar *, int,
+                transport_instance *);
+extern int     smtp_sock_connect(host_item *, int, int, uschar *,
+                transport_instance * tb, int);
 extern int     smtp_feof(void);
 extern int     smtp_ferror(void);
 extern uschar *smtp_get_connection_info(void);
-extern BOOL    smtp_get_interface(uschar *, int, address_item *, BOOL *,
+extern BOOL    smtp_get_interface(uschar *, int, address_item *,
                  uschar **, uschar *);
 extern BOOL    smtp_get_port(uschar *, address_item *, int *, uschar *);
-extern int     smtp_getc(void);
+extern int     smtp_getc(unsigned);
+extern void    smtp_get_cache(void);
 extern int     smtp_handle_acl_fail(int, int, uschar *, uschar *);
 extern void    smtp_log_no_mail(void);
-extern void    smtp_message_code(uschar **, int *, uschar **, uschar **);
+extern void    smtp_message_code(uschar **, int *, uschar **, uschar **, BOOL);
 extern BOOL    smtp_read_response(smtp_inblock *, uschar *, int, int, int);
 extern void    smtp_respond(uschar *, int, BOOL, uschar *);
 extern void    smtp_notquit_exit(uschar *, uschar *, uschar *, ...);
@@ -358,82 +415,101 @@ extern int     smtp_ungetc(int);
 extern BOOL    smtp_verify_helo(void);
 extern int     smtp_write_command(smtp_outblock *, BOOL, const char *, ...) PRINTF_FUNCTION(3,4);
 #ifdef WITH_CONTENT_SCAN
-extern int     spam(uschar **);
-extern FILE   *spool_mbox(unsigned long *, uschar *);
+extern int     spam(const uschar **);
+extern FILE   *spool_mbox(unsigned long *, const uschar *);
 #endif
 extern BOOL    spool_move_message(uschar *, uschar *, uschar *, uschar *);
-extern BOOL    spool_open_datafile(uschar *);
+extern uschar *spool_dname(const uschar *, uschar *);
+extern uschar *spool_fname(const uschar *, const uschar *, const uschar *, const uschar *);
+extern uschar *spool_sname(const uschar *, uschar *);
+extern int     spool_open_datafile(uschar *);
 extern int     spool_open_temp(uschar *);
 extern int     spool_read_header(uschar *, BOOL, BOOL);
 extern int     spool_write_header(uschar *, int, uschar **);
-extern int     stdin_getc(void);
+extern int     stdin_getc(unsigned);
 extern int     stdin_feof(void);
 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_append_listele_n(uschar *, uschar, const uschar *, unsigned);
 extern uschar *string_base62(unsigned long int);
-extern uschar *string_cat(uschar *, int *, int *, const uschar *, int);
+extern uschar *string_cat(uschar *, int *, int *, const uschar *);
+extern uschar *string_catn(uschar *, int *, int *, const uschar *, int);
+extern int     string_compare_by_pointer(const void *, const void *);
 extern uschar *string_copy_dnsdomain(uschar *);
-extern uschar *string_copy_malloc(uschar *);
-extern uschar *string_copylc(uschar *);
+extern uschar *string_copy_malloc(const uschar *);
+extern uschar *string_copylc(const uschar *);
 extern uschar *string_copynlc(uschar *, int);
-extern uschar *string_dequote(uschar **);
+extern uschar *string_dequote(const uschar **);
 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_interpret_escape(const uschar **);
 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);
+#ifdef SUPPORT_I18N
+extern BOOL    string_is_utf8(const uschar *);
+#endif
+extern uschar *string_nextinlist(const uschar **, int *, uschar *, int);
 extern uschar *string_open_failed(int, const char *, ...) PRINTF_FUNCTION(2,3);
-extern uschar *string_printing2(uschar *, BOOL);
+extern const uschar *string_printing2(const uschar *, BOOL);
 extern uschar *string_split_message(uschar *);
 extern uschar *string_unprinting(uschar *);
+#ifdef SUPPORT_I18N
+extern uschar *string_address_utf8_to_alabel(const uschar *, uschar **);
+extern uschar *string_domain_alabel_to_utf8(const uschar *, uschar **);
+extern uschar *string_domain_utf8_to_alabel(const uschar *, uschar **);
+extern uschar *string_localpart_alabel_to_utf8(const uschar *, uschar **);
+extern uschar *string_localpart_utf8_to_alabel(const uschar *, uschar **);
+#endif
 extern BOOL    string_vformat(uschar *, int, const char *, va_list);
 extern int     strcmpic(const uschar *, const uschar *);
 extern int     strncmpic(const uschar *, const uschar *, int);
 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 BOOL    transport_check_waiting(const uschar *, const uschar *, int, uschar *,
+                 BOOL *, oicf, void*);
 extern void    transport_init(void);
-extern BOOL    transport_pass_socket(uschar *, uschar *, uschar *, uschar *,
+extern BOOL    transport_pass_socket(const uschar *, const uschar *, const uschar *, uschar *,
                  int);
 extern uschar *transport_rcpt_address(address_item *, BOOL);
-extern BOOL    transport_set_up_command(uschar ***, uschar *, BOOL, int,
-                 address_item *, uschar *, uschar **);
+extern BOOL    transport_set_up_command(const uschar ***, uschar *,
+                BOOL, int, address_item *, uschar *, uschar **);
 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 BOOL    transport_headers_send(int, transport_ctx *,
+                 BOOL (*)(int, transport_ctx *, uschar *, int));
+extern BOOL    transport_write_message(int, transport_ctx *, int);
 extern void    tree_add_duplicate(uschar *, address_item *);
 extern void    tree_add_nonrecipient(uschar *);
 extern void    tree_add_unusable(host_item *);
 extern int     tree_insertnode(tree_node **, tree_node *);
-extern tree_node *tree_search(tree_node *, uschar *);
+extern tree_node *tree_search(tree_node *, const uschar *);
 extern void    tree_write(tree_node *, FILE *);
 extern void    tree_walk(tree_node *, void (*)(uschar*, uschar*, void*), void *);
 
 #ifdef WITH_CONTENT_SCAN
 extern void    unspool_mbox(void);
 #endif
+#ifdef SUPPORT_I18N
+extern void    utf8_version_report(FILE *);
+#endif
 
 extern int     verify_address(address_item *, FILE *, int, int, int, int,
                  uschar *, uschar *, BOOL *);
-extern int     verify_check_dnsbl(uschar **);
+extern int     verify_check_dnsbl(int, const uschar **, uschar **);
 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*,
-                 uschar *, uschar **);
+extern int     verify_check_given_host(uschar **, host_item *);
+extern int     verify_check_this_host(const uschar **, unsigned int *,
+                const uschar*, const uschar *, const uschar **);
 extern address_item *verify_checked_sender(uschar *);
 extern void    verify_get_ident(int);
 extern BOOL    verify_sender(int *, uschar **);
index d3f9987..79ac37f 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2017 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* All the global variables are defined together in this one module, so
@@ -35,7 +35,7 @@ optionlist optionlist_auths[] = {
                  (void *)(offsetof(auth_instance, set_id)) }
 };
 
-int     optionlist_auths_size = sizeof(optionlist_auths)/sizeof(optionlist);
+int     optionlist_auths_size = nelem(optionlist_auths);
 
 /* An empty host aliases list. */
 
@@ -49,6 +49,7 @@ duplicate them here... */
 uschar *opt_perl_startup       = NULL;
 BOOL    opt_perl_at_start      = FALSE;
 BOOL    opt_perl_started       = FALSE;
+BOOL    opt_perl_taintmode     = FALSE;
 #endif
 
 #ifdef EXPAND_DLFUNC
@@ -83,7 +84,7 @@ uschar *oracle_servers         = NULL;
 uschar *pgsql_servers          = NULL;
 #endif
 
-#ifdef EXPERIMENTAL_REDIS
+#ifdef LOOKUP_REDIS
 uschar *redis_servers          = NULL;
 #endif
 
@@ -103,6 +104,10 @@ tls_support tls_in = {
  -1,   /* tls_active */
  0,    /* tls_bits */
  FALSE,/* tls_certificate_verified */
+#ifdef EXPERIMENTAL_DANE
+ FALSE,/* dane_verified */
+ 0,    /* tlsa_usage */
+#endif
  NULL, /* tls_cipher */
  FALSE,/* tls_on_connect */
  NULL, /* tls_on_connect_ports */
@@ -116,6 +121,10 @@ tls_support tls_out = {
  -1,   /* tls_active */
  0,    /* tls_bits */
  FALSE,/* tls_certificate_verified */
+#ifdef EXPERIMENTAL_DANE
+ FALSE,/* dane_verified */
+ 0,    /* tlsa_usage */
+#endif
  NULL, /* tls_cipher */
  FALSE,/* tls_on_connect */
  NULL, /* tls_on_connect_ports */
@@ -126,23 +135,17 @@ tls_support tls_out = {
  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;
 BOOL    gnutls_allow_auto_pkcs11 = FALSE;
-uschar *gnutls_require_mac     = NULL;
-uschar *gnutls_require_kx      = NULL;
-uschar *gnutls_require_proto   = NULL;
 uschar *openssl_options        = NULL;
 const pcre *regex_STARTTLS     = NULL;
-uschar *tls_advertise_hosts    = NULL;    /* This is deliberate */
+uschar *tls_advertise_hosts    = US"*";
 uschar *tls_certificate        = NULL;
 uschar *tls_crl                = NULL;
 /* This default matches NSS DH_MAX_P_BITS value at current time (2012), because
@@ -150,16 +153,18 @@ that's the interop problem which has been observed: GnuTLS suggesting a higher
 bit-count as "NORMAL" (2432) and Thunderbird dropping connection. */
 int     tls_dh_max_bits        = 2236;
 uschar *tls_dhparam            = NULL;
-#ifndef DISABLE_OCSP
+uschar *tls_eccurve            = US"auto";
+# ifndef DISABLE_OCSP
 uschar *tls_ocsp_file          = NULL;
-#endif
-BOOL    tls_offered            = FALSE;
+# endif
 uschar *tls_privatekey         = NULL;
 BOOL    tls_remember_esmtp     = FALSE;
 uschar *tls_require_ciphers    = NULL;
 uschar *tls_try_verify_hosts   = NULL;
-uschar *tls_verify_certificates= NULL;
+uschar *tls_verify_certificates= US"system";
 uschar *tls_verify_hosts       = NULL;
+#else  /*!SUPPORT_TLS*/
+uschar *tls_advertise_hosts    = NULL;
 #endif
 
 #ifndef DISABLE_PRDR
@@ -169,12 +174,19 @@ BOOL    prdr_requested         = FALSE;
 const pcre *regex_PRDR         = NULL;
 #endif
 
+#ifdef SUPPORT_I18N
+const pcre *regex_UTF8         = NULL;
+#endif
+
 /* Input-reading functions for messages, so we can use special ones for
 incoming TCP/IP. The defaults use stdin. We never need these for any
 stand-alone tests. */
 
 #ifndef STAND_ALONE
-int (*receive_getc)(void)      = stdin_getc;
+int (*lwr_receive_getc)(unsigned) = stdin_getc;
+int (*lwr_receive_ungetc)(int) = stdin_ungetc;
+int (*receive_getc)(unsigned)  = stdin_getc;
+void (*receive_get_cache)(void)= NULL;
 int (*receive_ungetc)(int)     = stdin_ungetc;
 int (*receive_feof)(void)      = stdin_feof;
 int (*receive_ferror)(void)    = stdin_ferror;
@@ -186,24 +198,24 @@ BOOL (*receive_smtp_buffered)(void) = NULL;   /* Only used for SMTP */
 when verifying one address while routing/verifying another. We have to have
 the size explicit, because it is referenced from more than one module. */
 
-uschar **address_expansions[ADDRESS_EXPANSIONS_COUNT] = {
-  &deliver_address_data,
-  &deliver_domain,
-  &deliver_domain_data,
-  &deliver_domain_orig,
-  &deliver_domain_parent,
-  &deliver_localpart,
-  &deliver_localpart_data,
-  &deliver_localpart_orig,
-  &deliver_localpart_parent,
-  &deliver_localpart_prefix,
-  &deliver_localpart_suffix,
-  (uschar **)(&deliver_recipients),
-  &deliver_host,
-  &deliver_home,
-  &address_file,
-  &address_pipe,
-  &self_hostname,
+const uschar **address_expansions[ADDRESS_EXPANSIONS_COUNT] = {
+  CUSS &deliver_address_data,
+  CUSS &deliver_domain,
+  CUSS &deliver_domain_data,
+  CUSS &deliver_domain_orig,
+  CUSS &deliver_domain_parent,
+  CUSS &deliver_localpart,
+  CUSS &deliver_localpart_data,
+  CUSS &deliver_localpart_orig,
+  CUSS &deliver_localpart_parent,
+  CUSS &deliver_localpart_prefix,
+  CUSS &deliver_localpart_suffix,
+  CUSS (uschar **)(&deliver_recipients),
+  CUSS &deliver_host,
+  CUSS &deliver_home,
+  CUSS &address_file,
+  CUSS &address_pipe,
+  CUSS &self_hostname,
   NULL };
 
 int address_expansions_count = sizeof(address_expansions)/sizeof(uschar **);
@@ -216,6 +228,8 @@ uschar *acl_arg[9]             = {NULL, NULL, NULL, NULL, NULL,
                                   NULL, NULL, NULL, NULL};
 int     acl_narg               = 0;
 
+int     acl_level             = 0;
+
 uschar *acl_not_smtp           = NULL;
 #ifdef WITH_CONTENT_SCAN
 uschar *acl_not_smtp_mime      = NULL;
@@ -226,7 +240,7 @@ uschar *acl_smtp_auth          = NULL;
 uschar *acl_smtp_connect       = NULL;
 uschar *acl_smtp_data          = NULL;
 #ifndef DISABLE_PRDR
-uschar *acl_smtp_data_prdr     = NULL;
+uschar *acl_smtp_data_prdr     = US"accept";
 #endif
 #ifndef DISABLE_DKIM
 uschar *acl_smtp_dkim          = NULL;
@@ -307,8 +321,8 @@ uschar *acl_wherecodes[]       = { US"550",     /* RCPT */
 
 BOOL    active_local_from_check = FALSE;
 BOOL    active_local_sender_retain = FALSE;
-int     body_8bitmime = 0;
 BOOL    accept_8bitmime        = TRUE; /* deliberately not RFC compliant */
+uschar *add_environment        = NULL;
 address_item  *addr_duplicate  = NULL;
 
 address_item address_defaults = {
@@ -343,21 +357,23 @@ address_item address_defaults = {
   NULL,                 /* return_filename */
   NULL,                 /* self_hostname */
   NULL,                 /* shadow_message */
-  #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
   NULL,                 /* cipher */
   NULL,                        /* ourcert */
   NULL,                        /* peercert */
   NULL,                 /* peerdn */
   OCSP_NOT_REQ,         /* ocsp */
-  #endif
+#endif
+#ifdef EXPERIMENTAL_DSN_INFO
+  NULL,                        /* smtp_greeting */
+  NULL,                        /* helo_response */
+#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 */
@@ -379,6 +395,9 @@ address_item address_defaults = {
     NULL,               /* remove_headers */
 #ifdef EXPERIMENTAL_SRS
     NULL,               /* srs_sender */
+#endif
+#ifdef SUPPORT_I18N
+    FALSE,             /* utf8 */
 #endif
   }
 };
@@ -451,46 +470,62 @@ int     bmi_deliver            = 1;
 int     bmi_run                = 0;
 uschar *bmi_verdicts           = NULL;
 #endif
+int     body_8bitmime          = 0;
 int     body_linecount         = 0;
 int     body_zerocount         = 0;
 uschar *bounce_message_file    = NULL;
 uschar *bounce_message_text    = NULL;
 uschar *bounce_recipient       = NULL;
 BOOL    bounce_return_body     = TRUE;
+int     bounce_return_linesize_limit = 998;
 BOOL    bounce_return_message  = TRUE;
 int     bounce_return_size_limit = 100*1024;
 uschar *bounce_sender_authentication = NULL;
 int     bsmtp_transaction_linecount = 0;
 
+uschar *callout_address        = NULL;
 int     callout_cache_domain_positive_expire = 7*24*60*60;
 int     callout_cache_domain_negative_expire = 3*60*60;
 int     callout_cache_positive_expire = 24*60*60;
 int     callout_cache_negative_expire = 2*60*60;
 uschar *callout_random_local_part = US"$primary_hostname-$tod_epoch-testing";
 uschar *check_dns_names_pattern= US"(?i)^(?>(?(1)\\.|())[^\\W](?>[a-z0-9/_-]*[^\\W])?)+(\\.?)$";
-int     check_log_inodes       = 0;
-int     check_log_space        = 0;
+int     check_log_inodes       = 100;
+int     check_log_space        = 10*1024;      /* 10K Kbyte == 10MB */
 BOOL    check_rfc2047_length   = TRUE;
-int     check_spool_inodes     = 0;
-int     check_spool_space      = 0;
-uschar *client_authenticator  = NULL;
-uschar *client_authenticated_id = NULL;
-uschar *client_authenticated_sender = NULL;
+int     check_spool_inodes     = 100;
+int     check_spool_space      = 10*1024;      /* 10K Kbyte == 10MB */
+
+uschar *chunking_advertise_hosts = US"*";
+unsigned chunking_datasize     = 0;
+unsigned chunking_data_left    = 0;
+BOOL    chunking_offered       = FALSE;
+chunking_state_t chunking_state= CHUNKING_NOT_OFFERED;
+const pcre *regex_CHUNKING     = NULL;
+
+uschar *client_authenticator   = NULL;
+uschar *client_authenticated_id = NULL;
+uschar *client_authenticated_sender = NULL;
 int     clmacro_count          = 0;
 uschar *clmacros[MAX_CLMACROS];
 BOOL    config_changed         = FALSE;
 FILE   *config_file            = NULL;
-uschar *config_filename        = NULL;
+const uschar *config_filename  = NULL;
 int     config_lineno          = 0;
 #ifdef CONFIGURE_GROUP
 gid_t   config_gid             = CONFIGURE_GROUP;
+#else
+gid_t   config_gid             = 0;
 #endif
 uschar *config_main_filelist   = US CONFIGURE_FILE
                          "\0<-----------Space to patch configure_filename->";
 uschar *config_main_filename   = NULL;
+uschar *config_main_directory  = NULL;
 
 #ifdef CONFIGURE_OWNER
 uid_t   config_uid             = CONFIGURE_OWNER;
+#else
+uid_t   config_uid             = 0;
 #endif
 
 int     connection_max_messages= -1;
@@ -501,8 +536,12 @@ int     continue_sequence      = 1;
 uschar *continue_transport     = NULL;
 
 uschar *csa_status             = NULL;
-BOOL    cutthrough_delivery    = FALSE;
-int     cutthrough_fd          = -1;
+cut_t   cutthrough = {
+  FALSE,                               /* delivery: when to attempt */
+  FALSE,                               /* on defer: spool locally */
+  -1,                                  /* fd: open connection */
+  0,                                   /* nrcpt: number of addresses */
+};
 
 BOOL    daemon_listen          = FALSE;
 uschar *daemon_smtp_port       = US"smtp";
@@ -520,41 +559,47 @@ uschar *dccifd_options         = US"header";
 BOOL    debug_daemon           = FALSE;
 int     debug_fd               = -1;
 FILE   *debug_file             = NULL;
-bit_table debug_options[]      = {
-  { US"acl",            D_acl },
-  { US"all",            D_all },
-  { US"auth",           D_auth },
-  { US"deliver",        D_deliver },
-  { US"dns",            D_dns },
-  { US"dnsbl",          D_dnsbl },
-  { US"exec",           D_exec },
-  { US"expand",         D_expand },
-  { US"filter",         D_filter },
-  { US"hints_lookup",   D_hints_lookup },
-  { US"host_lookup",    D_host_lookup },
-  { US"ident",          D_ident },
-  { US"interface",      D_interface },
-  { US"lists",          D_lists },
-  { US"load",           D_load },
-  { US"local_scan",     D_local_scan },
-  { US"lookup",         D_lookup },
-  { US"memory",         D_memory },
-  { US"pid",            D_pid },
-  { US"process_info",   D_process_info },
-  { US"queue_run",      D_queue_run },
-  { US"receive",        D_receive },
-  { US"resolver",       D_resolver },
-  { US"retry",          D_retry },
-  { US"rewrite",        D_rewrite },
-  { US"route",          D_route },
-  { US"timestamp",      D_timestamp },
-  { US"tls",            D_tls },
-  { US"transport",      D_transport },
-  { US"uid",            D_uid },
-  { US"verify",         D_verify }
+int     debug_notall[]         = {
+  Di_memory,
+  -1
+};
+bit_table debug_options[]      = { /* must be in alphabetical order */
+  BIT_TABLE(D, acl),
+  BIT_TABLE(D, all),
+  BIT_TABLE(D, auth),
+  BIT_TABLE(D, deliver),
+  BIT_TABLE(D, dns),
+  BIT_TABLE(D, dnsbl),
+  BIT_TABLE(D, exec),
+  BIT_TABLE(D, expand),
+  BIT_TABLE(D, filter),
+  BIT_TABLE(D, hints_lookup),
+  BIT_TABLE(D, host_lookup),
+  BIT_TABLE(D, ident),
+  BIT_TABLE(D, interface),
+  BIT_TABLE(D, lists),
+  BIT_TABLE(D, load),
+  BIT_TABLE(D, local_scan),
+  BIT_TABLE(D, lookup),
+  BIT_TABLE(D, memory),
+  BIT_TABLE(D, pid),
+  BIT_TABLE(D, process_info),
+  BIT_TABLE(D, queue_run),
+  BIT_TABLE(D, receive),
+  BIT_TABLE(D, resolver),
+  BIT_TABLE(D, retry),
+  BIT_TABLE(D, rewrite),
+  BIT_TABLE(D, route),
+  BIT_TABLE(D, timestamp),
+  BIT_TABLE(D, tls),
+  BIT_TABLE(D, transport),
+  BIT_TABLE(D, uid),
+  BIT_TABLE(D, verify),
 };
-int     debug_options_count    = sizeof(debug_options)/sizeof(bit_table);
+int     debug_options_count    = nelem(debug_options);
+
 unsigned int debug_selector    = 0;
+BOOL    debug_store            = FALSE;
 int     delay_warning[DELAY_WARNING_SIZE] = { DELAY_WARNING_SIZE, 1, 24*60*60 };
 uschar *delay_warning_condition=
   US"${if or {"
@@ -565,18 +610,19 @@ uschar *delay_warning_condition=
 BOOL    delivery_date_remove   = TRUE;
 uschar *deliver_address_data   = NULL;
 int     deliver_datafile       = -1;
-uschar *deliver_domain         = NULL;
+const uschar *deliver_domain   = NULL;
 uschar *deliver_domain_data    = NULL;
-uschar *deliver_domain_orig    = NULL;
-uschar *deliver_domain_parent  = NULL;
+const uschar *deliver_domain_orig = NULL;
+const uschar *deliver_domain_parent = NULL;
 BOOL    deliver_drop_privilege = FALSE;
 BOOL    deliver_firsttime      = FALSE;
 BOOL    deliver_force          = FALSE;
 BOOL    deliver_freeze         = FALSE;
 time_t  deliver_frozen_at      = 0;
 uschar *deliver_home           = NULL;
-uschar *deliver_host           = NULL;
-uschar *deliver_host_address   = NULL;
+const uschar *deliver_host     = NULL;
+const uschar *deliver_host_address = NULL;
+int     deliver_host_port      = 0;
 uschar *deliver_in_buffer      = NULL;
 ino_t   deliver_inode          = 0;
 uschar *deliver_localpart      = NULL;
@@ -594,11 +640,6 @@ uschar *deliver_selectstring   = NULL;
 BOOL    deliver_selectstring_regex = FALSE;
 uschar *deliver_selectstring_sender = NULL;
 BOOL    deliver_selectstring_sender_regex = FALSE;
-#ifdef WITH_OLD_DEMIME
-int     demime_errorlevel      = 0;
-int     demime_ok              = 0;
-uschar *demime_reason          = NULL;
-#endif
 BOOL    disable_callout_flush  = FALSE;
 BOOL    disable_delay_flush    = FALSE;
 #ifdef ENABLE_DISABLE_FSYNC
@@ -608,13 +649,14 @@ BOOL    disable_ipv6           = FALSE;
 BOOL    disable_logging        = FALSE;
 
 #ifndef DISABLE_DKIM
+BOOL    dkim_collect_input       = FALSE;
 uschar *dkim_cur_signer          = NULL;
+BOOL    dkim_disable_verify      = FALSE;
+int     dkim_key_length          = 0;
 uschar *dkim_signers             = NULL;
 uschar *dkim_signing_domain      = NULL;
 uschar *dkim_signing_selector    = NULL;
 uschar *dkim_verify_signers      = US"$dkim_signers";
-BOOL    dkim_collect_input       = FALSE;
-BOOL    dkim_disable_verify      = FALSE;
 #endif
 #ifdef EXPERIMENTAL_DMARC
 BOOL    dmarc_has_been_checked  = FALSE;
@@ -633,10 +675,14 @@ BOOL    dmarc_enable_forensic   = FALSE;
 uschar *dns_again_means_nonexist = NULL;
 int     dns_csa_search_limit   = 5;
 BOOL    dns_csa_use_reverse    = TRUE;
+#ifdef EXPERIMENTAL_DANE
+int     dns_dane_ok            = -1;
+#endif
 uschar *dns_ipv4_lookup        = NULL;
 int     dns_retrans            = 0;
 int     dns_retry              = 0;
 int     dns_dnssec_ok          = -1; /* <0 = not coerced */
+uschar *dns_trust_aa           = NULL;
 int     dns_use_edns0          = -1; /* <0 = not coerced */
 uschar *dnslist_domain         = NULL;
 uschar *dnslist_matched        = NULL;
@@ -656,6 +702,13 @@ uschar *errors_copy            = NULL;
 int     error_handling         = ERRORS_SENDER;
 uschar *errors_reply_to        = NULL;
 int     errors_sender_rc       = EXIT_FAILURE;
+#ifndef DISABLE_EVENT
+uschar *event_action             = NULL;       /* expansion for delivery events */
+uschar *event_data               = NULL;       /* auxiliary data variable for event */
+int     event_defer_errno        = 0;
+const uschar *event_name         = NULL;       /* event name variable */
+#endif
+
 
 gid_t   exim_gid               = EXIM_GID;
 BOOL    exim_gid_set           = TRUE;          /* This gid is always set */
@@ -663,6 +716,7 @@ uschar *exim_path              = US BIN_DIRECTORY "/exim"
                         "\0<---------------Space to patch exim_path->";
 uid_t   exim_uid               = EXIM_UID;
 BOOL    exim_uid_set           = TRUE;          /* This uid is always set */
+int     expand_level          = 0;             /* Nesting depth, indent for debug */
 int     expand_forbid          = 0;
 int     expand_nlength[EXPAND_MAXN+1];
 int     expand_nmax            = -1;
@@ -685,9 +739,6 @@ uschar *filter_test_sfile      = NULL;
 uschar *filter_test_ufile      = NULL;
 uschar *filter_thisaddress     = NULL;
 int     finduser_retries       = 0;
-#ifdef WITH_OLD_DEMIME
-uschar *found_extension        = NULL;
-#endif
 uid_t   fixed_never_users[]    = { FIXED_NEVER_USERS };
 uschar *freeze_tell            = NULL;
 uschar *freeze_tell_config     = NULL;
@@ -754,6 +805,7 @@ BOOL    ignore_fromline_local  = FALSE;
 uschar *ignore_fromline_hosts  = NULL;
 BOOL    inetd_wait_mode        = FALSE;
 int     inetd_wait_timeout     = -1;
+uschar *initial_cwd            = NULL;
 uschar *interface_address      = NULL;
 int     interface_port         = -1;
 BOOL    is_inetd               = FALSE;
@@ -761,6 +813,8 @@ uschar *iterate_item           = NULL;
 
 int     journal_fd             = -1;
 
+uschar *keep_environment       = NULL;
+
 int     keep_malformed         = 4*24*60*60;    /* 4 days */
 
 uschar *eldap_dn               = NULL;
@@ -785,84 +839,102 @@ uid_t   local_user_uid         = (uid_t)(-1);
 tree_node *localpartlist_anchor= NULL;
 int     localpartlist_count    = 0;
 uschar *log_buffer             = NULL;
-unsigned int log_extra_selector = LX_default;
+
+int     log_default[]          = { /* for initializing log_selector */
+  Li_acl_warn_skipped,
+  Li_connection_reject,
+  Li_delay_delivery,
+  Li_dnslist_defer,
+  Li_etrn,
+  Li_host_lookup_failed,
+  Li_lost_incoming_connection,
+  Li_outgoing_interface, /* see d_log_interface in deliver.c */
+  Li_queue_run,
+  Li_rejected_header,
+  Li_retry_defer,
+  Li_sender_verify_fail,
+  Li_size_reject,
+  Li_skip_delivery,
+  Li_smtp_confirmation,
+  Li_tls_certificate_verified,
+  Li_tls_cipher,
+  -1
+};
+
 uschar *log_file_path          = US LOG_FILE_PATH
                            "\0<--------------Space to patch log_file_path->";
 
-/* Those log options with L_xxx identifiers have values less than 0x800000 and
-are the ones that get put into log_write_selector. They can be used in calls to
-log_write() to test for the bit. The options with LX_xxx identifiers have
-values greater than 0x80000000 and are put into log_extra_selector (without the
-top bit). They are never used in calls to log_write(), but are tested
-independently. This separation became necessary when the number of log
-selectors was getting close to filling a 32-bit word. */
-
-/* Note that this list must be in alphabetical order. */
-
-bit_table log_options[]        = {
-  { US"8bitmime",                     LX_8bitmime },
-  { US"acl_warn_skipped",             LX_acl_warn_skipped },
-  { US"address_rewrite",              L_address_rewrite },
-  { US"all",                          L_all },
-  { US"all_parents",                  L_all_parents },
-  { US"arguments",                    LX_arguments },
-  { US"connection_reject",            L_connection_reject },
-  { US"delay_delivery",               L_delay_delivery },
-  { US"deliver_time",                 LX_deliver_time },
-  { US"delivery_size",                LX_delivery_size },
-  { US"dnslist_defer",                L_dnslist_defer },
-  { US"etrn",                         L_etrn },
-  { US"host_lookup_failed",           L_host_lookup_failed },
-  { US"ident_timeout",                LX_ident_timeout },
-  { US"incoming_interface",           LX_incoming_interface },
-  { US"incoming_port",                LX_incoming_port },
-  { 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 },
+int     log_notall[]           = {
+  -1
+};
+bit_table log_options[]        = { /* must be in alphabetical order */
+  BIT_TABLE(L, 8bitmime),
+  BIT_TABLE(L, acl_warn_skipped),
+  BIT_TABLE(L, address_rewrite),
+  BIT_TABLE(L, all),
+  BIT_TABLE(L, all_parents),
+  BIT_TABLE(L, arguments),
+  BIT_TABLE(L, connection_reject),
+  BIT_TABLE(L, delay_delivery),
+  BIT_TABLE(L, deliver_time),
+  BIT_TABLE(L, delivery_size),
+  BIT_TABLE(L, dnslist_defer),
+  BIT_TABLE(L, dnssec),
+  BIT_TABLE(L, etrn),
+  BIT_TABLE(L, host_lookup_failed),
+  BIT_TABLE(L, ident_timeout),
+  BIT_TABLE(L, incoming_interface),
+  BIT_TABLE(L, incoming_port),
+  BIT_TABLE(L, lost_incoming_connection),
+  BIT_TABLE(L, outgoing_interface),
+  BIT_TABLE(L, outgoing_port),
+  BIT_TABLE(L, pid),
+#if defined(SUPPORT_PROXY) || defined (SUPPORT_SOCKS)
+  BIT_TABLE(L, proxy),
 #endif
-  { US"queue_run",                    L_queue_run },
-  { US"queue_time",                   LX_queue_time },
-  { US"queue_time_overall",           LX_queue_time_overall },
-  { US"received_recipients",          LX_received_recipients },
-  { US"received_sender",              LX_received_sender },
-  { US"rejected_header",              LX_rejected_header },
-  { US"rejected_headers",             LX_rejected_header },
-  { US"retry_defer",                  L_retry_defer },
-  { US"return_path_on_delivery",      LX_return_path_on_delivery },
-  { US"sender_on_delivery",           LX_sender_on_delivery },
-  { US"sender_verify_fail",           LX_sender_verify_fail },
-  { US"size_reject",                  L_size_reject },
-  { US"skip_delivery",                L_skip_delivery },
-  { US"smtp_confirmation",            LX_smtp_confirmation },
-  { US"smtp_connection",              L_smtp_connection },
-  { US"smtp_incomplete_transaction",  L_smtp_incomplete_transaction },
-  { US"smtp_mailauth",                LX_smtp_mailauth },
-  { US"smtp_no_mail",                 LX_smtp_no_mail },
-  { US"smtp_protocol_error",          L_smtp_protocol_error },
-  { US"smtp_syntax_error",            L_smtp_syntax_error },
-  { US"subject",                      LX_subject },
-  { US"tls_certificate_verified",     LX_tls_certificate_verified },
-  { US"tls_cipher",                   LX_tls_cipher },
-  { US"tls_peerdn",                   LX_tls_peerdn },
-  { US"tls_sni",                      LX_tls_sni },
-  { US"unknown_in_list",              LX_unknown_in_list }
+  BIT_TABLE(L, queue_run),
+  BIT_TABLE(L, queue_time),
+  BIT_TABLE(L, queue_time_overall),
+  BIT_TABLE(L, received_recipients),
+  BIT_TABLE(L, received_sender),
+  BIT_TABLE(L, rejected_header),
+  { US"rejected_headers", Li_rejected_header },
+  BIT_TABLE(L, retry_defer),
+  BIT_TABLE(L, return_path_on_delivery),
+  BIT_TABLE(L, sender_on_delivery),
+  BIT_TABLE(L, sender_verify_fail),
+  BIT_TABLE(L, size_reject),
+  BIT_TABLE(L, skip_delivery),
+  BIT_TABLE(L, smtp_confirmation),
+  BIT_TABLE(L, smtp_connection),
+  BIT_TABLE(L, smtp_incomplete_transaction),
+  BIT_TABLE(L, smtp_mailauth),
+  BIT_TABLE(L, smtp_no_mail),
+  BIT_TABLE(L, smtp_protocol_error),
+  BIT_TABLE(L, smtp_syntax_error),
+  BIT_TABLE(L, subject),
+  BIT_TABLE(L, tls_certificate_verified),
+  BIT_TABLE(L, tls_cipher),
+  BIT_TABLE(L, tls_peerdn),
+  BIT_TABLE(L, tls_sni),
+  BIT_TABLE(L, unknown_in_list),
 };
+int     log_options_count      = nelem(log_options);
 
-int     log_options_count      = sizeof(log_options)/sizeof(bit_table);
 int     log_reject_target      = 0;
+unsigned int log_selector[log_selector_size]; /* initialized in main() */
 uschar *log_selector_string    = NULL;
 FILE   *log_stderr             = NULL;
 BOOL    log_testing_mode       = FALSE;
 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;
 
 macro_item  *macros            = NULL;
+macro_item  *mlast             = NULL;
+BOOL    macros_builtin_created = FALSE;
 uschar *mailstore_basename     = NULL;
 #ifdef WITH_CONTENT_SCAN
 uschar *malware_name           = NULL;  /* Virus Name */
@@ -887,6 +959,10 @@ int     message_linecount      = 0;
 BOOL    message_logs           = TRUE;
 int     message_size           = 0;
 uschar *message_size_limit     = US"50M";
+#ifdef SUPPORT_I18N
+BOOL    message_smtputf8       = FALSE;
+int     message_utf8_downconvert = 0;  /* -1 ifneeded; 0 never; 1 always */
+#endif
 uschar  message_subdir[2]      = { 0, 0 };
 uschar *message_reference      = NULL;
 
@@ -918,6 +994,9 @@ BOOL    no_mbox_unspool        = FALSE;
 #endif
 BOOL    no_multiline_responses = FALSE;
 
+const int on                   = 1;    /* for setsockopt */
+const int off                  = 0;
+
 uid_t   original_euid;
 gid_t   originator_gid;
 uschar *originator_login       = NULL;
@@ -941,14 +1020,14 @@ 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"";
+#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS)
+uschar *hosts_proxy            = US"";
+uschar *proxy_external_address = US"";
+int     proxy_external_port    = 0;
+uschar *proxy_local_address    = US"";
+int     proxy_local_port       = 0;
 BOOL    proxy_session          = FALSE;
 BOOL    proxy_session_failed   = FALSE;
-uschar *proxy_target_address   = US"";
-int     proxy_target_port      = 0;
 #endif
 
 uschar *prvscheck_address      = NULL;
@@ -956,12 +1035,13 @@ uschar *prvscheck_keynum       = NULL;
 uschar *prvscheck_result       = NULL;
 
 
-uschar *qualify_domain_recipient = NULL;
+const uschar *qualify_domain_recipient = NULL;
 uschar *qualify_domain_sender  = NULL;
 BOOL    queue_2stage           = FALSE;
 uschar *queue_domains          = NULL;
 int     queue_interval         = -1;
 BOOL    queue_list_requires_admin = TRUE;
+uschar *queue_name             = US"";
 BOOL    queue_only             = FALSE;
 uschar *queue_only_file        = NULL;
 int     queue_only_load        = -1;
@@ -972,7 +1052,7 @@ BOOL    queue_run_first_delivery = FALSE;
 BOOL    queue_run_force        = FALSE;
 BOOL    queue_run_in_order     = FALSE;
 BOOL    queue_run_local        = FALSE;
-int     queue_run_max          = 5;
+uschar *queue_run_max          = US"5";
 pid_t   queue_run_pid          = (pid_t)0;
 int     queue_run_pipe         = -1;
 BOOL    queue_running          = FALSE;
@@ -1038,8 +1118,9 @@ const pcre *regex_From         = NULL;
 const pcre *regex_IGNOREQUOTA  = NULL;
 const pcre *regex_PIPELINING   = NULL;
 const pcre *regex_SIZE         = NULL;
-const pcre *regex_smtp_code    = NULL;
 const pcre *regex_ismsgid      = NULL;
+const pcre *regex_smtp_code    = NULL;
+uschar *regex_vars[REGEX_VARS];
 #ifdef WHITELIST_D_MACROS
 const pcre *regex_whitelisted_macro = NULL;
 #endif
@@ -1056,8 +1137,8 @@ retry_config  *retries         = NULL;
 uschar *return_path            = NULL;
 BOOL    return_path_remove     = TRUE;
 int     rewrite_existflags     = 0;
-uschar *rfc1413_hosts          = US"*";
-int     rfc1413_query_timeout  = 5;
+uschar *rfc1413_hosts          = US"@[]";
+int     rfc1413_query_timeout  = 0;
 /* BOOL    rfc821_domains         = FALSE;  <<< on the way out */
 uid_t   root_gid               = ROOT_GID;
 uid_t   root_uid               = ROOT_UID;
@@ -1129,9 +1210,7 @@ router_instance  router_defaults = {
     TRUE,                      /* verify_sender */
     FALSE,                     /* uid_set */
     FALSE,                     /* unseen */
-#ifdef EXPERIMENTAL_DSN
     FALSE,                     /* dsn_lasthop */
-#endif
 
     self_freeze,               /* self_code */
     (uid_t)(-1),               /* uid */
@@ -1140,7 +1219,9 @@ router_instance  router_defaults = {
     NULL,                      /* fallback_hostlist */
     NULL,                      /* transport instance */
     NULL,                      /* pass_router */
-    NULL                       /* redirect_router */
+    NULL,                      /* redirect_router */
+
+    { NULL, NULL },            /* dnssec_domains {require,request} */
 };
 
 uschar *router_name            = NULL;
@@ -1171,6 +1252,7 @@ uschar *sender_address_unrewritten = NULL;
 uschar *sender_data            = NULL;
 unsigned int sender_domain_cache[(MAX_NAMED_LIST * 2)/32];
 uschar *sender_fullhost        = NULL;
+BOOL    sender_helo_dnssec     = FALSE;
 uschar *sender_helo_name       = NULL;
 uschar **sender_host_aliases   = &no_aliases;
 uschar *sender_host_address    = NULL;
@@ -1199,6 +1281,7 @@ uschar *sending_ip_address     = NULL;
 int     sending_port           = -1;
 SIGNAL_BOOL sigalrm_seen       = FALSE;
 uschar **sighup_argv           = NULL;
+int     slow_lookup_log        = 0;    /* millisecs, zero disables */
 int     smtp_accept_count      = 0;
 BOOL    smtp_accept_keepalive  = TRUE;
 int     smtp_accept_max        = 20;
@@ -1240,6 +1323,7 @@ uschar *smtp_ratelimit_mail    = NULL;
 uschar *smtp_ratelimit_rcpt    = NULL;
 uschar *smtp_read_error        = US"";
 int     smtp_receive_timeout   = 5*60;
+uschar *smtp_receive_timeout_s = NULL;
 uschar *smtp_reserve_hosts     = NULL;
 BOOL    smtp_return_error_details = FALSE;
 int     smtp_rlm_base          = 0;
@@ -1250,13 +1334,17 @@ int     smtp_rlr_base          = 0;
 double  smtp_rlr_factor        = 0.0;
 int     smtp_rlr_limit         = 0;
 int     smtp_rlr_threshold     = INT_MAX;
-BOOL    smtp_use_pipelining    = FALSE;
-BOOL    smtp_use_size          = FALSE;
+unsigned smtp_peer_options     = 0;
+unsigned smtp_peer_options_wrap= 0;
+#ifdef SUPPORT_I18N
+uschar *smtputf8_advertise_hosts = US"*";      /* overridden under test-harness */
+#endif
 
 #ifdef WITH_CONTENT_SCAN
 uschar *spamd_address          = US"127.0.0.1 783";
 uschar *spam_bar               = NULL;
 uschar *spam_report            = NULL;
+uschar *spam_action            = NULL;
 uschar *spam_score             = NULL;
 uschar *spam_score_int         = NULL;
 #endif
@@ -1300,6 +1388,7 @@ BOOL    suppress_local_fixups_default = FALSE;
 BOOL    synchronous_delivery   = FALSE;
 BOOL    syslog_duplication     = TRUE;
 int     syslog_facility        = LOG_MAIL;
+BOOL    syslog_pid             = TRUE;
 uschar *syslog_processname     = US"exim";
 BOOL    syslog_timestamp       = TRUE;
 uschar *system_filter          = NULL;
@@ -1315,6 +1404,7 @@ uid_t   system_filter_uid      = (uid_t)-1;
 BOOL    system_filter_uid_set  = FALSE;
 BOOL    system_filtering       = FALSE;
 
+BOOL    tcp_fastopen_ok        = FALSE;
 BOOL    tcp_nodelay            = TRUE;
 #ifdef USE_TCP_WRAPPERS
 uschar *tcp_wrappers_daemon_name = US TCP_WRAPPERS_DAEMON_NAME;
@@ -1324,17 +1414,6 @@ int     thismessage_size_limit = 0;
 int     timeout_frozen_after   = 0;
 BOOL    timestamps_utc         = FALSE;
 
-#ifdef EXPERIMENTAL_TPDA
-int     tpda_defer_errno        = 0;
-uschar *tpda_defer_errstr       = NULL;
-uschar *tpda_delivery_ip        = NULL;
-int     tpda_delivery_port      = 0;
-uschar *tpda_delivery_fqdn      = NULL;
-uschar *tpda_delivery_local_part= NULL;
-uschar *tpda_delivery_domain    = NULL;
-uschar *tpda_delivery_confirmation = NULL;
-#endif
-
 transport_instance  *transports = NULL;
 
 transport_instance  transport_defaults = {
@@ -1348,6 +1427,7 @@ transport_instance  transport_defaults = {
     NULL,                     /* batch_id */
     NULL,                     /* home_dir */
     NULL,                     /* current_dir */
+    NULL,                     /* expand-multi-domain */
     TRUE,                     /* multi-domain */
     FALSE,                    /* overrides_hosts */
     100,                      /* max_addresses */
@@ -1369,6 +1449,7 @@ transport_instance  transport_defaults = {
     NULL,                     /* remove_headers */
     NULL,                     /* return_path */
     NULL,                     /* debug_string */
+    NULL,                     /* max_parallel */
     NULL,                     /* message_size_limit */
     NULL,                     /* headers_rewrite */
     NULL,                     /* rewrite_rules */
@@ -1387,15 +1468,15 @@ transport_instance  transport_defaults = {
     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 */
+#ifndef DISABLE_EVENT
+   ,NULL                     /* event_action */
 #endif
 };
 
 int     transport_count;
 uschar *transport_name          = NULL;
 int     transport_newlines;
-uschar **transport_filter_argv  = NULL;
+const uschar **transport_filter_argv  = NULL;
 int     transport_filter_timeout;
 BOOL    transport_filter_timed_out = FALSE;
 int     transport_write_timeout= 0;
@@ -1440,18 +1521,19 @@ uschar *uucp_from_pattern      = US
 
 uschar *uucp_from_sender       = US"$1";
 
-uschar *warn_message_file      = NULL;
-uschar *warnmsg_delay          = NULL;
-uschar *warnmsg_recipients     = NULL;
-BOOL    write_rejectlog        = TRUE;
-
+uschar *verify_mode           = NULL;
 uschar *version_copyright      =
- US"Copyright (c) University of Cambridge, 1995 - 2014\n"
-   "(c) The Exim Maintainers and contributors in ACKNOWLEDGMENTS file, 2007 - 2014";
+ US"Copyright (c) University of Cambridge, 1995 - 2017\n"
+   "(c) The Exim Maintainers and contributors in ACKNOWLEDGMENTS file, 2007 - 2017";
 uschar *version_date           = US"?";
 uschar *version_cnumber        = US"????";
 uschar *version_string         = US"?";
 
+uschar *warn_message_file      = NULL;
 int     warning_count          = 0;
+uschar *warnmsg_delay          = NULL;
+uschar *warnmsg_recipients     = NULL;
+BOOL    write_rejectlog        = TRUE;
+
 
 /*  End of globals.c */
index 2bedcf5..340f1ae 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2017 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Almost all the global variables are defined together in this one header, so
@@ -28,6 +28,7 @@ typedef volatile sig_atomic_t SIGNAL_BOOL;
 extern uschar *opt_perl_startup;       /* Startup code for Perl interpreter */
 extern BOOL    opt_perl_at_start;      /* Start Perl interpreter at start */
 extern BOOL    opt_perl_started;       /* Set once interpreter started */
+extern BOOL    opt_perl_taintmode;     /* Enable taint mode in Perl */
 #endif
 
 #ifdef EXPAND_DLFUNC
@@ -62,7 +63,7 @@ extern uschar *oracle_servers;         /* List of servers and connect info */
 extern uschar *pgsql_servers;          /* List of servers and connect info */
 #endif
 
-#ifdef EXPERIMENTAL_REDIS
+#ifdef LOOKUP_REDIS
 extern uschar *redis_servers;          /* List of servers and connect info */
 #endif
 
@@ -82,6 +83,10 @@ typedef struct {
   int     active;             /* fd/socket when in a TLS session */
   int     bits;               /* bits used in TLS session */
   BOOL    certificate_verified; /* Client certificate verified */
+#ifdef EXPERIMENTAL_DANE
+  BOOL    dane_verified;        /* ... via DANE */
+  int     tlsa_usage;         /* TLSA record(s) usage */
+#endif
   uschar *cipher;             /* Cipher used */
   BOOL    on_connect;         /* For older MTAs that don't STARTTLS */
   uschar *on_connect_ports;   /* Ports always tls-on-connect */
@@ -103,21 +108,17 @@ extern tls_support tls_out;
 #ifdef SUPPORT_TLS
 extern BOOL    gnutls_compat_mode;     /* Less security, more compatibility */
 extern BOOL    gnutls_allow_auto_pkcs11; /* Let GnuTLS autoload PKCS11 modules */
-extern uschar *gnutls_require_mac;     /* So some can be avoided */
-extern uschar *gnutls_require_kx;      /* So some can be avoided */
-extern uschar *gnutls_require_proto;   /* So some can be avoided */
 extern uschar *openssl_options;        /* OpenSSL compatibility options */
 extern const pcre *regex_STARTTLS;     /* For recognizing STARTTLS settings */
-extern uschar *tls_advertise_hosts;    /* host for which TLS is advertised */
 extern uschar *tls_certificate;        /* Certificate file */
 extern uschar *tls_channelbinding_b64; /* string of base64 channel binding */
 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 */
-#ifndef DISABLE_OCSP
+extern uschar *tls_eccurve;            /* EC curve */
+# ifndef DISABLE_OCSP
 extern uschar *tls_ocsp_file;          /* OCSP stapling proof file */
-#endif
-extern BOOL    tls_offered;            /* Server offered TLS */
+# endif
 extern uschar *tls_privatekey;         /* Private key file */
 extern BOOL    tls_remember_esmtp;     /* For YAEB */
 extern uschar *tls_require_ciphers;    /* So some can be avoided */
@@ -125,19 +126,20 @@ extern uschar *tls_try_verify_hosts;   /* Optional client verification */
 extern uschar *tls_verify_certificates;/* Path for certificates to check */
 extern uschar *tls_verify_hosts;       /* Mandatory client verification */
 #endif
+extern uschar *tls_advertise_hosts;    /* host for which TLS is advertised */
 
-#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. */
 
-extern int (*receive_getc)(void);
+extern int (*lwr_receive_getc)(unsigned);
+extern int (*lwr_receive_ungetc)(int);
+extern int (*receive_getc)(unsigned);
+extern void (*receive_get_cache)(void);
 extern int (*receive_ungetc)(int);
 extern int (*receive_feof)(void);
 extern int (*receive_ferror)(void);
@@ -148,16 +150,17 @@ extern BOOL (*receive_smtp_buffered)(void);
 the size of this vector set explicitly, because it is referenced from more than
 one module. */
 
-extern uschar **address_expansions[ADDRESS_EXPANSIONS_COUNT];
+extern const uschar **address_expansions[ADDRESS_EXPANSIONS_COUNT];
 
 /* General global variables */
 
 extern BOOL    accept_8bitmime;        /* Allow *BITMIME incoming */
-extern int     body_8bitmime;          /* sender declared BODY= ; 7=7BIT, 8=8BITMIME */
+extern uschar *add_environment;        /* List of environment variables to add */
 extern header_line *acl_added_headers; /* Headers added by an ACL */
 extern tree_node *acl_anchor;          /* Tree of named ACLs */
 extern uschar *acl_arg[9];             /* Argument to ACL call */
 extern int     acl_narg;               /* Number of arguments to ACL call */
+extern int     acl_level;             /* Nesting depth and debug indent */
 extern uschar *acl_not_smtp;           /* ACL run for non-SMTP messages */
 #ifdef WITH_CONTENT_SCAN
 extern uschar *acl_not_smtp_mime;      /* For MIME parts of ditto */
@@ -190,7 +193,7 @@ extern uschar *acl_smtp_starttls;      /* ACL run for STARTTLS */
 extern uschar *acl_smtp_vrfy;          /* ACL run for VRFY */
 extern BOOL    acl_temp_details;       /* TRUE to give details for 4xx error */
 extern tree_node *acl_var_c;           /* ACL connection variables */
-extern tree_node *acl_var_m;           /* ACL messsage variables */
+extern tree_node *acl_var_m;           /* ACL message variables */
 extern uschar *acl_verify_message;     /* User message for verify failure */
 extern string_item *acl_warn_logged;   /* Logged lines */
 extern uschar *acl_wherecodes[];       /* Response codes for ACL fails */
@@ -243,15 +246,18 @@ extern int     bmi_deliver;            /* Flag that determines if the message sh
 extern int     bmi_run;                /* Flag that determines if message should be run through Brightmail server */
 extern uschar *bmi_verdicts;           /* BASE64-encoded verdicts with recipient lists */
 #endif
+extern int     body_8bitmime;          /* sender declared BODY= ; 7=7BIT, 8=8BITMIME */
 extern uschar *bounce_message_file;    /* Template file */
 extern uschar *bounce_message_text;    /* One-liner */
 extern uschar *bounce_recipient;       /* When writing an errmsg */
 extern BOOL    bounce_return_body;     /* Include body in returned message */
+extern int     bounce_return_linesize_limit; /* Max line length in return */
 extern BOOL    bounce_return_message;  /* Include message in bounce */
 extern int     bounce_return_size_limit; /* Max amount to return */
 extern uschar *bounce_sender_authentication; /* AUTH address for bounces */
 extern int     bsmtp_transaction_linecount; /* Start of last transaction */
 
+extern uschar *callout_address;         /* Address used for a malware/spamd/verify etc. callout */
 extern int     callout_cache_domain_positive_expire; /* Time for positive domain callout cache records to expire */
 extern int     callout_cache_domain_negative_expire; /* Time for negative domain callout cache records to expire */
 extern int     callout_cache_positive_expire; /* Time for positive callout cache records to expire */
@@ -263,6 +269,11 @@ extern int     check_log_space;        /* Minimum for message acceptance */
 extern BOOL    check_rfc2047_length;   /* Check RFC 2047 encoded string length */
 extern int     check_spool_inodes;     /* Minimum for message acceptance */
 extern int     check_spool_space;      /* Minimum for message acceptance */
+extern uschar *chunking_advertise_hosts;    /* RFC 3030 CHUNKING */
+extern unsigned chunking_datasize;
+extern unsigned chunking_data_left;
+extern BOOL    chunking_offered;
+extern chunking_state_t chunking_state;
 extern uschar *client_authenticator;        /* Authenticator name used for smtp delivery */
 extern uschar *client_authenticated_id;     /* "login" name used for SMTP AUTH */
 extern uschar *client_authenticated_sender; /* AUTH option to SMTP MAIL FROM (not yet used) */
@@ -271,16 +282,13 @@ extern uschar *clmacros[];             /* Copy of them, for re-exec */
 extern int     connection_max_messages;/* Max down one SMTP connection */
 extern BOOL    config_changed;         /* True if -C used */
 extern FILE   *config_file;            /* Configuration file */
-extern uschar *config_filename;        /* Configuration file name */
-#ifdef CONFIGURE_GROUP
+extern const uschar *config_filename;  /* Configuration file name */
 extern gid_t   config_gid;             /* Additional group owner */
-#endif
 extern int     config_lineno;          /* Line number */
 extern uschar *config_main_filelist;   /* List of possible config files */
 extern uschar *config_main_filename;   /* File name actually used */
-#ifdef CONFIGURE_OWNER
+extern uschar *config_main_directory;  /* Directory where the main config file was found */
 extern uid_t   config_uid;             /* Additional owner */
-#endif
 extern uschar *continue_hostname;      /* Host for continued delivery */
 extern uschar *continue_host_address;  /* IP address for ditto */
 extern BOOL    continue_more;          /* Flag more addresses waiting */
@@ -288,8 +296,17 @@ extern int     continue_sequence;      /* Sequence num for continued delivery */
 extern uschar *continue_transport;     /* Transport for continued delivery */
 
 extern uschar *csa_status;             /* Client SMTP Authorization result */
-extern BOOL    cutthrough_delivery;    /* Deliver in foreground */
-extern int     cutthrough_fd;          /* Connection for ditto */
+
+typedef struct {
+  unsigned     delivery:1;             /* When to attempt */
+  unsigned     defer_pass:1;           /* Pass 4xx to caller rather than spooling */
+  int          fd;                     /* Open connection */
+  int          nrcpt;                  /* Count of addresses */
+  uschar *     interface;              /* (address of) */
+  host_item    host;                   /* Host used */
+  address_item addr;                   /* (Chain of) addresses */
+} cut_t;
+extern cut_t cutthrough;               /* Deliver-concurrently */
 
 extern BOOL    daemon_listen;          /* True if listening required */
 extern uschar *daemon_smtp_port;       /* Can be a list of ports */
@@ -307,27 +324,30 @@ extern uschar *dccifd_options;         /* options for the dccifd daemon */
 extern BOOL    debug_daemon;           /* Debug the daemon process only */
 extern int     debug_fd;               /* The fd for debug_file */
 extern FILE   *debug_file;             /* Where to write debugging info */
+extern int     debug_notall[];         /* Debug options excluded from +all */
 extern bit_table debug_options[];      /* Table of debug options */
 extern int     debug_options_count;    /* Size of table */
+extern BOOL    debug_store;           /* Do extra checks on store_reset */
 extern int     delay_warning[];        /* Times between warnings */
 extern uschar *delay_warning_condition; /* Condition string for warnings */
 extern BOOL    delivery_date_remove;   /* Remove delivery-date headers */
 
 extern uschar *deliver_address_data;   /* Arbitrary data for an address */
 extern int     deliver_datafile;       /* FD for data part of message */
-extern uschar *deliver_domain;         /* The local domain for delivery */
+extern const uschar *deliver_domain;   /* The local domain for delivery */
 extern uschar *deliver_domain_data;    /* From domain lookup */
-extern uschar *deliver_domain_orig;    /* The original local domain for delivery */
-extern uschar *deliver_domain_parent;  /* The parent domain for delivery */
+extern const uschar *deliver_domain_orig; /* The original local domain for delivery */
+extern const uschar *deliver_domain_parent; /* The parent domain for delivery */
 extern BOOL    deliver_drop_privilege; /* TRUE for unprivileged delivery */
 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 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 */
+extern const uschar *deliver_host;     /* (First) host for routed local deliveries */
                                        /* Remote host for filter */
-extern uschar *deliver_host_address;   /* Address for remote delivery filter */
+extern const uschar *deliver_host_address; /* Address for remote delivery filter */
+extern int     deliver_host_port;      /* Address for remote delivery filter */
 extern uschar *deliver_in_buffer;      /* Buffer for copying file */
 extern ino_t   deliver_inode;          /* Inode for appendfile */
 extern uschar *deliver_localpart;      /* The local part for delivery */
@@ -345,11 +365,6 @@ extern uschar *deliver_selectstring;   /* For selecting by recipient */
 extern BOOL    deliver_selectstring_regex; /* String is regex */
 extern uschar *deliver_selectstring_sender; /* For selecting by sender */
 extern BOOL    deliver_selectstring_sender_regex; /* String is regex */
-#ifdef WITH_OLD_DEMIME
-extern int     demime_errorlevel;      /* Severity of MIME error */
-extern int     demime_ok;              /* Nonzero if message has been demimed */
-extern uschar *demime_reason;          /* Reason for broken MIME container */
-#endif
 extern BOOL    disable_callout_flush;  /* Don't flush before callouts */
 extern BOOL    disable_delay_flush;    /* Don't flush before "delay" in ACL */
 #ifdef ENABLE_DISABLE_FSYNC
@@ -359,13 +374,14 @@ extern BOOL    disable_ipv6;           /* Don't do any IPv6 things */
 extern BOOL    disable_logging;        /* Disables log writing when TRUE */
 
 #ifndef DISABLE_DKIM
+extern BOOL    dkim_collect_input;     /* Runtime flag that tracks wether SMTP input is fed to DKIM validation */
 extern uschar *dkim_cur_signer;        /* Expansion variable, holds the current "signer" domain or identity during a acl_smtp_dkim run */
+extern BOOL    dkim_disable_verify;    /* Set via ACL control statement. When set, DKIM verification is disabled for the current message */
+extern int     dkim_key_length;        /* Expansion variable, length of signing key in bits */
 extern uschar *dkim_signers;           /* Expansion variable, holds colon-separated list of domains and identities that have signed a message */
 extern uschar *dkim_signing_domain;    /* Expansion variable, domain used for signing a message. */
 extern uschar *dkim_signing_selector;  /* Expansion variable, selector used for signing a message. */
 extern uschar *dkim_verify_signers;    /* Colon-separated list of domains for each of which we call the DKIM ACL */
-extern BOOL    dkim_collect_input;     /* Runtime flag that tracks wether SMTP input is fed to DKIM validation */
-extern BOOL    dkim_disable_verify;    /* Set via ACL control statement. When set, DKIM verification is disabled for the current message */
 #endif
 #ifdef EXPERIMENTAL_DMARC
 extern BOOL    dmarc_has_been_checked; /* Global variable to check if test has been called yet */
@@ -385,9 +401,13 @@ extern uschar *dns_again_means_nonexist; /* Domains that are badly set up */
 extern int     dns_csa_search_limit;   /* How deep to search for CSA SRV records */
 extern BOOL    dns_csa_use_reverse;    /* Check CSA in reverse DNS? (non-standard) */
 extern uschar *dns_ipv4_lookup;        /* For these domains, don't look for AAAA (or A6) */
+#ifdef EXPERIMENTAL_DANE
+extern int     dns_dane_ok;            /* Ok to use DANE when checking TLS authenticity */
+#endif
 extern int     dns_retrans;            /* Retransmission time setting */
 extern int     dns_retry;              /* Number of retries */
 extern int     dns_dnssec_ok;          /* When constructing DNS query, set DO flag */
+extern uschar *dns_trust_aa;           /* DNSSEC trust AA as AD */
 extern int     dns_use_edns0;          /* Coerce EDNS0 support on/off in resolver. */
 extern uschar *dnslist_domain;         /* DNS (black) list domain */
 extern uschar *dnslist_matched;        /* DNS (black) list matched key */
@@ -404,18 +424,27 @@ extern BOOL    drop_cr;                /* For broken local MUAs */
 extern uschar *dsn_from;               /* From: string for DSNs */
 
 extern BOOL    enable_dollar_recipients; /* Make $recipients available */
-extern int     envelope_to_remove;     /* Remove envelope_to_headers */
+extern BOOL    envelope_to_remove;     /* Remove envelope_to_headers */
 extern int     errno_quota;            /* Quota errno in this OS */
 extern int     error_handling;         /* Error handling style */
 extern uschar *errors_copy;            /* For taking copies of errors */
 extern uschar *errors_reply_to;        /* Reply-to for error messages */
 extern int     errors_sender_rc;       /* Return after message to sender*/
+
+#ifndef DISABLE_EVENT
+extern uschar *event_action;           /* expansion for delivery events */
+extern uschar *event_data;            /* event data */
+extern int     event_defer_errno;      /* error number set when a remote delivery is deferred with a host error */
+extern const uschar *event_name;       /* event classification */
+#endif
+
 extern gid_t   exim_gid;               /* To be used with exim_uid */
 extern BOOL    exim_gid_set;           /* TRUE if exim_gid set */
 extern uschar *exim_path;              /* Path to exec exim */
 extern const uschar *exim_sieve_extension_list[]; /* list of sieve extensions */
 extern uid_t   exim_uid;               /* Non-root uid for exim */
 extern BOOL    exim_uid_set;           /* TRUE if exim_uid set */
+extern int     expand_level;          /* Nesting depth; indent for debug */
 extern int     expand_forbid;          /* RDO flags for forbidding things */
 extern int     expand_nlength[];       /* Lengths of numbered strings */
 extern int     expand_nmax;            /* Max numerical value */
@@ -435,9 +464,6 @@ extern uschar *filter_test_ufile;      /* User filter test file */
 extern uschar *filter_thisaddress;     /* For address looping */
 extern int     finduser_retries;       /* Retry count for getpwnam() */
 extern uid_t   fixed_never_users[];    /* Can't be overridden */
-#ifdef WITH_OLD_DEMIME
-extern uschar *found_extension;        /* demime acl condition: file extension found */
-#endif
 extern uschar *freeze_tell;            /* Message on (some) freezings */
 extern uschar *freeze_tell_config;     /* The configured setting */
 extern uschar *fudged_queue_times;     /* For use in test harness */
@@ -482,11 +508,13 @@ extern BOOL    ignore_fromline_local;  /* Local SMTP ignore fromline */
 extern uschar *ignore_fromline_hosts;  /* Hosts permitted to send "From " */
 extern BOOL    inetd_wait_mode;        /* Whether running in inetd wait mode */
 extern int     inetd_wait_timeout;     /* Timeout for inetd wait mode */
+extern uschar *initial_cwd;            /* The directory we where in at startup */
 extern BOOL    is_inetd;               /* True for inetd calls */
 extern uschar *iterate_item;           /* Item from iterate list */
 
 extern int     journal_fd;             /* Fd for journal file */
 
+extern uschar *keep_environment;       /* Whitelist for environment variables */
 extern int     keep_malformed;         /* Time to keep malformed messages */
 
 extern uschar *eldap_dn;               /* Where LDAP DNs are left */
@@ -506,16 +534,17 @@ extern uid_t   local_user_uid;         /* As it says; may be set in routers */
 extern tree_node *localpartlist_anchor;/* Tree of defined localpart lists */
 extern int     localpartlist_count;    /* Number defined */
 extern uschar *log_buffer;             /* For constructing log entries */
-extern unsigned int log_extra_selector;/* Bit map of logging options other than used by log_write() */
+extern int     log_default[];          /* Initialization list for log_selector */
 extern uschar *log_file_path;          /* If unset, use default */
+extern int     log_notall[];           /* Log options excluded from +all */
 extern bit_table log_options[];        /* Table of options */
 extern int     log_options_count;      /* Size of table */
 extern int     log_reject_target;      /* Target log for ACL rejections */
+extern unsigned int log_selector[];    /* Bit map of logging options */
 extern uschar *log_selector_string;    /* As supplied in the config */
 extern FILE   *log_stderr;             /* Copy of stderr for log use, or NULL */
 extern BOOL    log_testing_mode;       /* TRUE in various testing modes */
 extern BOOL    log_timezone;           /* TRUE to include the timezone in log lines */
-extern unsigned int log_write_selector;/* Bit map of logging options for log_write() */
 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 */
@@ -524,6 +553,8 @@ extern int     lookup_open_max;        /* Max lookup files to cache */
 extern uschar *lookup_value;           /* Value looked up from file */
 
 extern macro_item *macros;             /* Configuration macros */
+extern macro_item *mlast;              /* Last item in macro list */
+extern BOOL    macros_builtin_created; /* Flag for lazy-create */
 extern uschar *mailstore_basename;     /* For mailstore deliveries */
 #ifdef WITH_CONTENT_SCAN
 extern uschar *malware_name;           /* Name of virus or malware ("W32/Klez-H") */
@@ -547,6 +578,11 @@ extern int     message_linecount;      /* As it says */
 extern BOOL    message_logs;           /* TRUE to write message logs */
 extern int     message_size;           /* Size of message */
 extern uschar *message_size_limit;     /* As it says */
+#ifdef SUPPORT_I18N
+extern BOOL    message_smtputf8;       /* Internationalized mail handling */
+extern int     message_utf8_downconvert; /* convert from utf8 */
+const extern pcre *regex_UTF8;         /* For recognizing SMTPUTF8 settings */
+#endif
 extern uschar  message_subdir[];       /* Subdirectory for messages */
 extern uschar *message_reference;      /* Reference for error messages */
 
@@ -578,6 +614,9 @@ extern BOOL    no_mbox_unspool;        /* don't unlink files in /scan directory
 #endif
 extern BOOL    no_multiline_responses; /* For broken clients */
 
+extern const int on;                   /* For setsockopt */
+extern const int off;
+
 extern optionlist optionlist_auths[];      /* These option lists are made */
 extern int     optionlist_auths_size;      /* global so that readconf can */
 extern optionlist optionlist_routers[];    /* see them for printing out   */
@@ -611,21 +650,21 @@ 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 */
+#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS)
+extern uschar *hosts_proxy;            /* Hostlist which (require) use proxy protocol */
+extern uschar *proxy_external_address; /* IP of remote interface of proxy */
+extern int     proxy_external_port;    /* Port on remote interface of proxy */
+extern uschar *proxy_local_address;    /* IP of local interface of proxy */
+extern int     proxy_local_port;       /* Port on local interface of proxy */
 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 */
 
-extern uschar *qualify_domain_recipient; /* Domain to qualify recipients with */
+extern const uschar *qualify_domain_recipient; /* Domain to qualify recipients with */
 extern uschar *qualify_domain_sender;  /* Domain to qualify senders with */
 extern BOOL    queue_2stage;           /* Run queue in 2-stage manner */
 extern uschar *queue_domains;          /* Queue these domains */
@@ -638,6 +677,7 @@ extern BOOL    queue_running;          /* TRUE for queue running process and */
 extern pid_t   queue_run_pid;          /* PID of the queue running process or 0 */
 extern int     queue_run_pipe;         /* Pipe for synchronizing */
 extern int     queue_interval;         /* Queue running interval */
+extern uschar *queue_name;             /* Name of queue, if nondefault spooling */
 extern BOOL    queue_only;             /* TRUE to disable immediate delivery */
 extern int     queue_only_load;        /* Max load before auto-queue */
 extern BOOL    queue_only_load_latch;  /* Latch queue_only_load TRUE */
@@ -645,8 +685,8 @@ extern uschar *queue_only_file;        /* Queue if file exists/not-exists */
 extern BOOL    queue_only_override;    /* Allow override from command line */
 extern BOOL    queue_only_policy;      /* ACL or local_scan wants queue_only */
 extern BOOL    queue_run_in_order;     /* As opposed to random */
-extern int     queue_run_max;          /* Max queue runners */
-extern BOOL    queue_smtp;             /* Disable all immediate STMP (-odqs)*/
+extern uschar *queue_run_max;          /* Max queue runners */
+extern BOOL    queue_smtp;             /* Disable all immediate SMTP (-odqs)*/
 extern uschar *queue_smtp_domains;     /* Ditto, for these domains */
 
 extern unsigned int random_seed;       /* Seed for random numbers */
@@ -678,15 +718,17 @@ extern uschar *recipient_verify_failure; /* What went wrong */
 extern BOOL    recipients_discarded;   /* By an ACL */
 extern int     recipients_list_max;    /* Maximum number fitting in list */
 extern int     recipients_max;         /* Max permitted */
-extern int     recipients_max_reject;  /* If TRUE, reject whole message */
+extern BOOL    recipients_max_reject;  /* If TRUE, reject whole message */
 extern const pcre *regex_AUTH;         /* For recognizing AUTH settings */
 extern const pcre  *regex_check_dns_names; /* For DNS name checking */
 extern const pcre  *regex_From;        /* For recognizing "From_" lines */
+extern const pcre  *regex_CHUNKING;    /* For recognizing CHUNKING (RFC 3030) */
 extern const pcre  *regex_IGNOREQUOTA; /* For recognizing IGNOREQUOTA (LMTP) */
 extern const pcre  *regex_PIPELINING;  /* For recognizing PIPELINING */
 extern const pcre  *regex_SIZE;        /* For recognizing SIZE settings */
-extern const pcre  *regex_smtp_code;   /* For recognizing SMTP codes */
 extern const pcre  *regex_ismsgid;     /* Compiled r.e. for message it */
+extern const pcre  *regex_smtp_code;   /* For recognizing SMTP codes */
+extern uschar *regex_vars[];           /* $regexN variables */
 #ifdef WHITELIST_D_MACROS
 extern const pcre  *regex_whitelisted_macro; /* For -D macro values */
 #endif
@@ -727,6 +769,7 @@ extern uschar *sender_address_unrewritten; /* Set if rewritten by verify */
 extern uschar *sender_data;            /* lookup result for senders */
 extern unsigned int sender_domain_cache[(MAX_NAMED_LIST * 2)/32]; /* Cache bits for sender domain */
 extern uschar *sender_fullhost;        /* Sender host name + address */
+extern BOOL    sender_helo_dnssec;     /* True if HELO verify used DNS and was DNSSEC */
 extern uschar *sender_helo_name;       /* Host name from HELO/EHLO */
 extern uschar **sender_host_aliases;   /* Points to list of alias names */
 extern unsigned int sender_host_cache[(MAX_NAMED_LIST * 2)/32]; /* Cache bits for incoming host */
@@ -749,6 +792,7 @@ extern uschar *sending_ip_address;     /* Address of outgoing (SMTP) interface *
 extern int     sending_port;           /* Port of outgoing interface */
 extern SIGNAL_BOOL sigalrm_seen;       /* Flag for sigalrm_handler */
 extern uschar **sighup_argv;           /* Args for re-execing after SIGHUP */
+extern int     slow_lookup_log;        /* Log DNS lookups taking longer than N millisecs */
 extern int     smtp_accept_count;      /* Count of connections */
 extern BOOL    smtp_accept_keepalive;  /* Set keepalive on incoming */
 extern int     smtp_accept_max;        /* Max SMTP connections */
@@ -786,6 +830,7 @@ extern uschar *smtp_ratelimit_mail;    /* Parameters for MAIL limiting */
 extern uschar *smtp_ratelimit_rcpt;    /* Parameters for RCPT limiting */
 extern uschar *smtp_read_error;        /* Message for SMTP input error */
 extern int     smtp_receive_timeout;   /* Applies to each received line */
+extern uschar *smtp_receive_timeout_s; /* ... expandable version */
 extern uschar *smtp_reserve_hosts;     /* Hosts for reserved slots */
 extern BOOL    smtp_return_error_details; /* TRUE to return full info */
 extern int     smtp_rlm_base;          /* Base interval for MAIL rate limit */
@@ -796,13 +841,17 @@ extern int     smtp_rlr_base;          /* Base interval for RCPT rate limit */
 extern double  smtp_rlr_factor;        /* Factor for RCPT rate limit */
 extern int     smtp_rlr_limit;         /* Max delay */
 extern int     smtp_rlr_threshold;     /* Threshold for RCPT rate limit */
-extern BOOL    smtp_use_pipelining;    /* Global for passed connections */
-extern BOOL    smtp_use_size;          /* Global for passed connections */
+extern unsigned smtp_peer_options;     /* Global flags for passed connections */
+extern unsigned smtp_peer_options_wrap; /* stacked version hidden by TLS */
+#ifdef SUPPORT_I18N
+extern uschar *smtputf8_advertise_hosts; /* ingress control */
+#endif
 
 #ifdef WITH_CONTENT_SCAN
 extern uschar *spamd_address;          /* address for the spamassassin daemon */
 extern uschar *spam_bar;               /* the spam "bar" (textual representation of spam_score) */
 extern uschar *spam_report;            /* the spamd report (multiline) */
+extern uschar *spam_action;            /* the spamd recommended-action */
 extern uschar *spam_score;             /* the spam score (float) */
 extern uschar *spam_score_int;         /* spam_score * 10 (int) */
 #endif
@@ -844,6 +893,7 @@ extern BOOL    suppress_local_fixups_default; /* former is reset to this; overri
 extern BOOL    synchronous_delivery;   /* TRUE if -odi is set */
 extern BOOL    syslog_duplication;     /* FALSE => no duplicate logging */
 extern int     syslog_facility;        /* As defined by Syslog.h */
+extern BOOL    syslog_pid;             /* TRUE if PID on syslogs */
 extern uschar *syslog_processname;     /* 'ident' param to openlog() */
 extern BOOL    syslog_timestamp;       /* TRUE if time on syslogs */
 extern uschar *system_filter;          /* Name of system filter file */
@@ -859,6 +909,7 @@ extern uid_t   system_filter_uid;      /* Uid for running system filter */
 extern BOOL    system_filter_uid_set;  /* TRUE if uid set */
 extern BOOL    system_filtering;       /* TRUE when running system filter */
 
+extern BOOL    tcp_fastopen_ok;               /* appears to be supported by kernel */
 extern BOOL    tcp_nodelay;            /* Controls TCP_NODELAY on daemon */
 #ifdef USE_TCP_WRAPPERS
 extern uschar *tcp_wrappers_daemon_name; /* tcpwrappers daemon lookup name */
@@ -868,21 +919,10 @@ extern int     thismessage_size_limit; /* Limit for this message */
 extern int     timeout_frozen_after;   /* Max time to keep frozen messages */
 extern BOOL    timestamps_utc;         /* Use UTC for all times */
 
-#ifdef EXPERIMENTAL_TPDA
-extern int     tpda_defer_errno;        /* error number set when a remote delivery is deferred with a host error */
-extern uschar *tpda_defer_errstr;       /* error string set when a remote delivery is deferred with a host error */
-extern uschar *tpda_delivery_ip;        /* IP of host, which has accepted delivery */
-extern int     tpda_delivery_port;       /* port of host, which has accepted delivery */
-extern uschar *tpda_delivery_fqdn;      /* FQDN of host, which has accepted delivery */
-extern uschar *tpda_delivery_local_part;/* local part of address being delivered */
-extern uschar *tpda_delivery_domain;    /* domain part of address being delivered */
-extern uschar *tpda_delivery_confirmation; /* SMTP confirmation message */
-#endif
-
 extern uschar *transport_name;         /* Name of transport last started */
 extern int     transport_count;        /* Count of bytes transported */
 extern int     transport_newlines;     /* Accurate count of number of newline chars transported */
-extern uschar **transport_filter_argv; /* For on-the-fly filtering */
+extern const uschar **transport_filter_argv; /* For on-the-fly filtering */
 extern int     transport_filter_timeout; /* Timeout for same */
 extern BOOL    transport_filter_timed_out; /* True if it did */
 
@@ -914,6 +954,7 @@ extern uschar *warnmsg_delay;          /* String form of delay time */
 extern uschar *warnmsg_recipients;     /* Recipients of warning message */
 extern BOOL    write_rejectlog;        /* Control of reject logging */
 
+extern uschar *verify_mode;           /* Running a router in verify mode */
 extern uschar *version_copyright;      /* Copyright notice */
 extern uschar *version_date;           /* Date of compilation */
 extern uschar *version_cnumber;        /* Compile number */
similarity index 88%
rename from src/auths/sha1.c
rename to src/hash.c
index 67a1191..7590d55 100644 (file)
-/*************************************************
-*     Exim - an Internet mail transport agent    *
-*************************************************/
-
-/* Copyright (c) University of Cambridge 1995 - 2009 */
-/* See the file NOTICE for conditions of use and distribution. */
+/*
+ *  Exim - an Internet mail transport agent
+ *
+ *  Copyright (C) 2016  Exim maintainers
+ *  Copyright (c) University of Cambridge 1995 - 2016
+ *
+ *  Hash interface functions
+ */
 
 #ifndef STAND_ALONE
-#include "../exim.h"
+# include "exim.h"
+
+#else
 
 /* For stand-alone testing, we need to have the structure defined, and
 to be able to do I/O */
 
-#else
-#include <stdio.h>
-#include <stdlib.h>
+# include <stdio.h>
+# include <stdlib.h>
 typedef unsigned char uschar;
 typedef struct sha1 {
   unsigned int H[5];
   unsigned int length;
   }
 sha1;
+#endif /*STAND_ALONE*/
+
+#include <assert.h>
+
+/******************************************************************************/
+#ifdef SHA_OPENSSL
+
+BOOL
+exim_sha_init(hctx * h, hashmethod m)
+{
+switch (h->method = m)
+  {
+  case HASH_SHA1:   h->hashlen = 20; SHA1_Init  (&h->u.sha1); break;
+  case HASH_SHA256: h->hashlen = 32; SHA256_Init(&h->u.sha2); break;
+  default:         h->hashlen = 0; return FALSE;
+  }
+return TRUE;
+}
+
+
+void
+exim_sha_update(hctx * h, const uschar * data, int len)
+{
+switch (h->method)
+  {
+  case HASH_SHA1:   SHA1_Update  (&h->u.sha1, data, len); break;
+  case HASH_SHA256: SHA256_Update(&h->u.sha2, data, len); break;
+  /* should be blocked by init not handling these, but be explicit to
+   * guard against accidents later (and hush up clang -Wswitch) */
+  default: assert(0);
+  }
+}
+
+
+void
+exim_sha_finish(hctx * h, blob * b)
+{
+b->data = store_get(b->len = h->hashlen);
+switch (h->method)
+  {
+  case HASH_SHA1:   SHA1_Final  (b->data, &h->u.sha1); break;
+  case HASH_SHA256: SHA256_Final(b->data, &h->u.sha2); break;
+  default: assert(0);
+  }
+}
+
+
+
+#elif defined(SHA_GNUTLS)
+/******************************************************************************/
+
+BOOL
+exim_sha_init(hctx * h, hashmethod m)
+{
+switch (h->method = m)
+  {
+  case HASH_SHA1:     h->hashlen = 20; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA1); break;
+  case HASH_SHA256:   h->hashlen = 32; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA256); break;
+#ifdef EXIM_HAVE_SHA3
+  case HASH_SHA3_256: h->hashlen = 32; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA3_256); break;
 #endif
+  default: h->hashlen = 0; return FALSE;
+  }
+return TRUE;
+}
 
 
+void
+exim_sha_update(hctx * h, const uschar * data, int len)
+{
+gnutls_hash(h->sha, data, len);
+}
+
+
+void
+exim_sha_finish(hctx * h, blob * b)
+{
+b->data = store_get(b->len = h->hashlen);
+gnutls_hash_output(h->sha, b->data);
+}
+
+
+
+#elif defined(SHA_GCRYPT)
+/******************************************************************************/
+
+BOOL
+exim_sha_init(hctx * h, hashmethod m)
+{
+switch (h->method = m)
+  {
+  case HASH_SHA1:   h->hashlen = 20; gcry_md_open(&h->sha, GCRY_MD_SHA1, 0); break;
+  case HASH_SHA256: h->hashlen = 32; gcry_md_open(&h->sha, GCRY_MD_SHA256, 0); break;
+  default:         h->hashlen = 0; return FALSE;
+  }
+return TRUE;
+}
+
+
+void
+exim_sha_update(hctx * h, const uschar * data, int len)
+{
+gcry_md_write(h->sha, data, len);
+}
+
+
+void
+exim_sha_finish(hctx * h, blob * b)
+{
+b->data = store_get(b->len = h->hashlen);
+memcpy(b->data, gcry_md_read(h->sha, 0), h->hashlen);
+}
+
+
+
+
+#elif defined(SHA_POLARSSL)
+/******************************************************************************/
+
+BOOL
+exim_sha_init(hctx * h, hashmethod m)
+{
+switch (h->method = m)
+  {
+  case HASH_SHA1:   h->hashlen = 20; sha1_starts(&h->u.sha1);    break;
+  case HASH_SHA256: h->hashlen = 32; sha2_starts(&h->u.sha2, 0); break;
+  default:         h->hashlen = 0; return FALSE;
+  }
+return TRUE;
+}
+
+
+void
+exim_sha_update(hctx * h, const uschar * data, int len)
+{
+switch (h->method)
+  {
+  case HASH_SHA1:   sha1_update(h->u.sha1, US data, len); break;
+  case HASH_SHA256: sha2_update(h->u.sha2, US data, len); break;
+  }
+}
+
+
+void
+exim_sha_finish(hctx * h, blob * b)
+{
+b->data = store_get(b->len = h->hashlen);
+switch (h->method)
+  {
+  case HASH_SHA1:   sha1_finish(h->u.sha1, b->data); break;
+  case HASH_SHA256: sha2_finish(h->u.sha2, b->data); break;
+  }
+}
+
+
+
+
+#elif defined(SHA_NATIVE)
+/******************************************************************************/
+/* Only sha-1 supported */
 
 /*************************************************
 *        Start off a new SHA-1 computation.      *
@@ -33,8 +193,8 @@ Argument:  pointer to sha1 storage structure
 Returns:   nothing
 */
 
-void
-sha1_start(sha1 *base)
+static void
+native_sha1_start(sha1 *base)
 {
 base->H[0] = 0x67452301;
 base->H[1] = 0xefcdab89;
@@ -59,18 +219,18 @@ Arguments:
 Returns:     nothing
 */
 
-void
-sha1_mid(sha1 *base, const uschar *text)
+static void
+native_sha1_mid(sha1 *base, const uschar *text)
 {
-register int i;
-unsigned int A, B, C, D, E;
-unsigned int W[80];
+int i;
+uint A, B, C, D, E;
+uint W[80];
 
 base->length += 64;
 
 for (i = 0; i < 16; i++)
   {
-  W[i] = (text[0] << 24) | (text[1] << 16) | (text[2] << 8) | text[3];
+  W[i] = ((uint)text[0] << 24) | (text[1] << 16) | (text[2] << 8) | text[3];
   text += 4;
   }
 
@@ -158,8 +318,8 @@ Arguments:
 Returns:    nothing
 */
 
-void
-sha1_end(sha1 *base, const uschar *text, int length, uschar *digest)
+static void
+native_sha1_end(sha1 *base, const uschar *text, int length, uschar *digest)
 {
 int i;
 uschar work[64];
@@ -168,7 +328,7 @@ uschar work[64];
 
 while (length >= 64)
   {
-  sha1_mid(base, text);
+  native_sha1_mid(base, text);
   text += 64;
   length -= 64;
   }
@@ -184,7 +344,7 @@ work[length] = 0x80;
 if (length > 55)
   {
   memset(work+length+1, 0, 63-length);
-  sha1_mid(base, work);
+  native_sha1_mid(base, work);
   base->length -= 64;
   memset(work, 0, 56);
   }
@@ -210,7 +370,7 @@ memset(work+56, 0, 4);
 
 /* Process the final 64-byte chunk */
 
-sha1_mid(base, work);
+native_sha1_mid(base, work);
 
 /* Pass back the result, high-order byte first in each word. */
 
@@ -226,13 +386,113 @@ for (i = 0; i < 5; i++)
 
 
 
+
+
+
+# ifdef notdef
+BOOL
+exim_sha_init(hctx * h, hashmethod m)
+{
+h->hashlen = 20;
+native_sha1_start(&h->sha1);
+return TRUE;
+}
+
+
+void
+exim_sha_update(hctx * h, const uschar * data, int len)
+{
+native_sha1_mid(&h->sha1, US data);    /* implicit size always 64 */
+}
+
+
+void
+exim_sha_finish(hctx * h, blob * b)
+{
+b->data = store_get(b->len = h->hashlen);
+
+native_sha1_end(&h->sha1, NULL, 0, b->data);
+}
+# endif
+
+
+#endif
+/******************************************************************************/
+
+/* Common to all library versions */
+int
+exim_sha_hashlen(hctx * h)
+{
+return h->method == HASH_SHA1 ? 20
+     : h->method == HASH_SHA256 ? 32
+     : 0;
+}
+
+
+/******************************************************************************/
+/******************************************************************************/
+/******************************************************************************/
+/******************************************************************************/
+/* Original sha-1 interface used by crypteq{shal1},
+${sha1:} ${hmac:} and ${prvs:} */
+
+#ifdef SHA_NATIVE
+
+void
+sha1_start(hctx * h)
+{
+native_sha1_start(&h->sha1);
+}
+
+void
+sha1_mid(hctx * h, const uschar * data)
+{
+native_sha1_mid(&h->sha1, data);
+}
+
+void
+sha1_end(hctx * h, const uschar * data, int len, uschar *digest)
+{
+native_sha1_end(&h->sha1, data, len, digest);
+}
+
+#else
+
+void
+sha1_start(hctx * h)
+{
+(void) exim_sha_init(h, HASH_SHA1);
+}
+
+void
+sha1_mid(hctx * h, const uschar * data)
+{
+exim_sha_update(h, data, 64);
+}
+
+void
+sha1_end(hctx * h, const uschar * data, int len, uschar *digest)
+{
+blob b;
+exim_sha_update(h, data, len);
+exim_sha_finish(h, &b);
+memcpy(digest, b.data, 20);
+}
+
+#endif
+
+
+
+
+
+
 /*************************************************
 **************************************************
 *             Stand-alone test program           *
 **************************************************
 *************************************************/
 
-#ifdef STAND_ALONE
+# ifdef STAND_ALONE
 
 /* Test values. The first 128 may contain binary zeros and have increasing
 length. */
@@ -525,8 +785,8 @@ printf("Checking sha1: %s-endian\n\n", (ctest[0] == 0x04)? "little" : "big");
 for (i = 0; i < sizeof(tests)/sizeof(uschar *); i ++)
   {
   printf("%d.\nShould be: %s\n", i, hashes[i]);
-  sha1_start(&base);
-  sha1_end(&base, tests[i], (i <= 128)? i : strlen(tests[i]), digest);
+  native_sha1_start(&base);
+  native_sha1_end(&base, tests[i], (i <= 128)? i : strlen(tests[i]), digest);
   for (j = 0; j < 20; j++) sprintf(s+2*j, "%02X", digest[j]);
   printf("Computed:  %s\n", s);
   if (strcmp(s, hashes[i]) != 0) printf("*** No match ***\n");
@@ -540,13 +800,13 @@ memset(ctest, 'a', 1000000);
 
 printf("1 000 000 repetitions of 'a'\n");
 printf("Should be: %s\n", atest);
-sha1_start(&base);
-sha1_end(&base, ctest, 1000000, digest);
+native_sha1_start(&base);
+native_sha1_end(&base, ctest, 1000000, digest);
 for (j = 0; j < 20; j++) sprintf(s+2*j, "%02X", digest[j]);
 printf("Computed:  %s\n", s);
 if (strcmp(s, atest) != 0) printf("*** No match ***\n");
 
 }
-#endif
+# endif        /*STAND_ALONE*/
 
-/* End of sha1.c */
+/* End of File */
diff --git a/src/hash.h b/src/hash.h
new file mode 100644 (file)
index 0000000..09b6594
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ *  Exim - an Internet mail transport agent
+ *
+ *  Copyright (C) 2016  Exim maintainers
+ *
+ *  Hash interface functions
+ */
+
+#include "exim.h"
+
+#if !defined(HASH_H)   /* entire file */
+#define HASH_H
+
+#include "sha_ver.h"
+#include "blob.h"
+
+#ifdef SHA_OPENSSL
+# include <openssl/sha.h>
+#elif defined SHA_GNUTLS
+# include <gnutls/crypto.h>
+#elif defined(SHA_GCRYPT)
+# include <gcrypt.h>
+#elif defined(SHA_POLARSSL)
+# include "pdkim/pdkim.h"              /*XXX ugly */
+# include "pdkim/polarssl/sha1.h"
+# include "pdkim/polarssl/sha2.h"
+#endif
+
+
+/* Hash context for the exim_sha_* routines */
+
+typedef enum hashmethod {
+  HASH_BADTYPE,
+  HASH_SHA1,
+  HASH_SHA256,
+  HASH_SHA3_224,
+  HASH_SHA3_256,
+  HASH_SHA3_384,
+  HASH_SHA3_512,
+} hashmethod;
+
+typedef struct {
+  hashmethod   method;
+  int          hashlen;
+
+#ifdef SHA_OPENSSL
+  union {
+    SHA_CTX      sha1;       /* SHA1 block                                */
+    SHA256_CTX   sha2;       /* SHA256 block                              */
+  } u;
+
+#elif defined(SHA_GNUTLS)
+  gnutls_hash_hd_t sha;      /* Either SHA1 or SHA256 block               */
+
+#elif defined(SHA_GCRYPT)
+  gcry_md_hd_t sha;          /* Either SHA1 or SHA256 block               */
+
+#elif defined(SHA_POLARSSL)
+  union {
+    sha1_context sha1;       /* SHA1 block                                */
+    sha2_context sha2;       /* SHA256 block                              */
+  } u;
+
+#elif defined(SHA_NATIVE)
+  sha1 sha1;
+#endif
+
+} hctx;
+
+extern BOOL     exim_sha_init(hctx *, hashmethod);
+extern void     exim_sha_update(hctx *, const uschar *a, int);
+extern void     exim_sha_finish(hctx *, blob *);
+extern int      exim_sha_hashlen(hctx *);
+
+#endif
+/* End of File */
index 8136c69..decd0cc 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -119,7 +119,7 @@ if (name == NULL)
     hptr = &header_list;
 
     /* header_list->text can be NULL if we get here between when the new
-    received header is allocated and when it is acutally filled in. We want
+    received header is allocated and when it is actually filled in. We want
     that header to be first, so skip it for now. */
 
     if (header_list->text == NULL)
@@ -132,7 +132,7 @@ else
   {
   int len = Ustrlen(name);
 
-  /* Find the first non-deleted header witht the correct name. */
+  /* Find the first non-deleted header with the correct name. */
 
   for (hptr = &header_list; (h = *hptr) != NULL; hptr = &(h->next))
     {
@@ -450,10 +450,11 @@ for (s = strings; s != NULL; s = s->next)
 
 va_start(ap, count);
 for (i = 0; i < count; i++)
-  {
   if (one_pattern_match(name, slen, has_addresses, va_arg(ap, uschar *)))
+    {
+    va_end(ap);
     return cond;
-  }
+    }
 va_end(ap);
 
 return !cond;
index 00524f4..b3b8b18 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for finding hosts, either by gethostbyname(), gethostbyaddr(), or
@@ -94,6 +94,53 @@ random_seed = 1103515245 * random_seed + 12345;
 return (unsigned int)(random_seed >> 16) % limit;
 }
 
+/*************************************************
+*      Wrappers for logging lookup times         *
+*************************************************/
+
+/* When the 'slow_lookup_log' variable is enabled, these wrappers will
+write to the log file all (potential) dns lookups that take more than
+slow_lookup_log milliseconds
+*/
+
+static void
+log_long_lookup(const uschar * type, const uschar * data, unsigned long msec)
+{
+log_write(0, LOG_MAIN, "Long %s lookup for '%s': %lu msec",
+  type, data, msec);
+}
+
+
+/* returns the current system epoch time in milliseconds. */
+static unsigned long
+get_time_in_ms()
+{
+struct timeval tmp_time;
+unsigned long seconds, microseconds;
+
+gettimeofday(&tmp_time, NULL);
+seconds = (unsigned long) tmp_time.tv_sec;
+microseconds = (unsigned long) tmp_time.tv_usec;
+return seconds*1000 + microseconds/1000;
+}
+
+
+static int
+dns_lookup_timerwrap(dns_answer *dnsa, const uschar *name, int type,
+  const uschar **fully_qualified_name)
+{
+int retval;
+unsigned long time_msec;
+
+if (!slow_lookup_log)
+  return dns_lookup(dnsa, name, type, fully_qualified_name);
+
+time_msec = get_time_in_ms();
+retval = dns_lookup(dnsa, name, type, fully_qualified_name);
+if ((time_msec = get_time_in_ms() - time_msec) > slow_lookup_log)
+  log_long_lookup(US"name", name, time_msec);
+return retval;
+}
 
 
 /*************************************************
@@ -101,8 +148,7 @@ return (unsigned int)(random_seed >> 16) % limit;
 *************************************************/
 
 /* This function is called instead of gethostbyname(), gethostbyname2(), or
-getipnodebyname() when running in the test harness. It recognizes the name
-"manyhome.test.ex" and generates a humungous number of IP addresses. It also
+getipnodebyname() when running in the test harness. . It also
 recognizes an unqualified "localhost" and forces it to the appropriate loopback
 address. IP addresses are treated as literals. For other names, it uses the DNS
 to find the host name. In the test harness, this means it will access only the
@@ -118,7 +164,7 @@ Returns:        a hostent structure or NULL for an error
 */
 
 static struct hostent *
-host_fake_gethostbyname(uschar *name, int af, int *error_num)
+host_fake_gethostbyname(const uschar *name, int af, int *error_num)
 {
 #if HAVE_IPV6
 int alen = (af == AF_INET)? sizeof(struct in_addr):sizeof(struct in6_addr);
@@ -127,7 +173,7 @@ int alen = sizeof(struct in_addr);
 #endif
 
 int ipa;
-uschar *lname = name;
+const uschar *lname = name;
 uschar *adds;
 uschar **alist;
 struct hostent *yield;
@@ -139,34 +185,6 @@ DEBUG(D_host_lookup)
   debug_printf("using host_fake_gethostbyname for %s (%s)\n", name,
     (af == AF_INET)? "IPv4" : "IPv6");
 
-/* Handle the name that needs a vast number of IP addresses */
-
-if (Ustrcmp(name, "manyhome.test.ex") == 0 && af == AF_INET)
-  {
-  int i, j;
-  yield = store_get(sizeof(struct hostent));
-  alist = store_get(2049 * sizeof(char *));
-  adds  = store_get(2048 * alen);
-  yield->h_name = CS name;
-  yield->h_aliases = NULL;
-  yield->h_addrtype = af;
-  yield->h_length = alen;
-  yield->h_addr_list = CSS alist;
-  for (i = 104; i <= 111; i++)
-    {
-    for (j = 0; j <= 255; j++)
-      {
-      *alist++ = adds;
-      *adds++ = 10;
-      *adds++ = 250;
-      *adds++ = i;
-      *adds++ = j;
-      }
-    }
-  *alist = NULL;
-  return yield;
-  }
-
 /* Handle unqualified "localhost" */
 
 if (Ustrcmp(name, "localhost") == 0)
@@ -217,7 +235,7 @@ if (ipa != 0)
 else
   {
   int type = (af == AF_INET)? T_A:T_AAAA;
-  int rc = dns_lookup(&dnsa, lname, type, NULL);
+  int rc = dns_lookup_timerwrap(&dnsa, lname, type, NULL);
   int count = 0;
 
   lookup_dnssec_authenticated = NULL;
@@ -233,14 +251,13 @@ else
     }
 
   for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
-       rr != NULL;
+       rr;
        rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
-    {
-    if (rr->type == type) count++;
-    }
+    if (rr->type == type)
+      count++;
 
   yield = store_get(sizeof(struct hostent));
-  alist = store_get((count + 1) * sizeof(char **));
+  alist = store_get((count + 1) * sizeof(char *));
   adds  = store_get(count *alen);
 
   yield->h_name = CS name;
@@ -250,14 +267,14 @@ else
   yield->h_addr_list = CSS alist;
 
   for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
-       rr != NULL;
+       rr;
        rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
     {
     int i, n;
     int x[4];
     dns_address *da;
     if (rr->type != type) continue;
-    da = dns_address_from_rr(&dnsa, rr);
+    if (!(da = dns_address_from_rr(&dnsa, rr))) break;
     *alist++ = adds;
     n = host_aton(da->address, x);
     for (i = 0; i < n; i++)
@@ -295,19 +312,18 @@ Returns:      nothing
 */
 
 void
-host_build_hostlist(host_item **anchor, uschar *list, BOOL randomize)
+host_build_hostlist(host_item **anchor, const uschar *list, BOOL randomize)
 {
 int sep = 0;
 int fake_mx = MX_NONE;          /* This value is actually -1 */
 uschar *name;
-uschar buffer[1024];
 
 if (list == NULL) return;
 if (randomize) fake_mx--;       /* Start at -2 for randomizing */
 
 *anchor = NULL;
 
-while ((name = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
+while ((name = string_nextinlist(&list, &sep, NULL, 0)) != NULL)
   {
   host_item *h;
 
@@ -318,7 +334,7 @@ while ((name = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
     }
 
   h = store_get(sizeof(host_item));
-  h->name = string_copy(name);
+  h->name = name;
   h->address = NULL;
   h->port = PORT_NONE;
   h->mx = fake_mx;
@@ -444,7 +460,7 @@ Returns:    a port number or PORT_NONE
 int
 host_item_get_port(host_item *h)
 {
-uschar *p;
+const uschar *p;
 int port, x;
 int len = Ustrlen(h->name);
 
@@ -528,7 +544,7 @@ use this directly as the first item for Received: because it ain't an RFC 2822
 domain. Sigh. */
 
 address = string_sprintf("[%s]:%d", sender_host_address, sender_host_port);
-if ((log_extra_selector & LX_incoming_port) == 0 || sender_host_port <= 0)
+if (!LOGGING(incoming_port) || sender_host_port <= 0)
   *(Ustrrchr(address, ':')) = 0;
 
 /* If there's no EHLO/HELO data, we can't show it. */
@@ -538,7 +554,7 @@ if (sender_helo_name == NULL) show_helo = FALSE;
 /* If HELO/EHLO was followed by an IP literal, it's messy because of two
 features of IPv6. Firstly, there's the "IPv6:" prefix (Exim is liberal and
 doesn't require this, for historical reasons). Secondly, IPv6 addresses may not
-be given in canonical form, so we have to canonicize them before comparing. As
+be given in canonical form, so we have to canonicalize them before comparing. As
 it happens, the code works for both IPv4 and IPv6. */
 
 else if (sender_helo_name[0] == '[' &&
@@ -581,12 +597,12 @@ if (sender_host_name == NULL)
   sender_fullhost = (sender_helo_name == NULL)? address :
     string_sprintf("(%s) %s", sender_helo_name, address);
 
-  sender_rcvhost = string_cat(NULL, &size, &ptr, address, adlen);
+  sender_rcvhost = string_catn(NULL, &size, &ptr, address, adlen);
 
   if (sender_ident != NULL || show_helo || portptr != NULL)
     {
     int firstptr;
-    sender_rcvhost = string_cat(sender_rcvhost, &size, &ptr, US" (", 2);
+    sender_rcvhost = string_catn(sender_rcvhost, &size, &ptr, US" (", 2);
     firstptr = ptr;
 
     if (portptr != NULL)
@@ -601,7 +617,7 @@ if (sender_host_name == NULL)
       sender_rcvhost = string_append(sender_rcvhost, &size, &ptr, 2,
         (firstptr == ptr)? US"ident=" : US" ident=", sender_ident);
 
-    sender_rcvhost = string_cat(sender_rcvhost, &size, &ptr, US")", 1);
+    sender_rcvhost = string_catn(sender_rcvhost, &size, &ptr, US")", 1);
     }
 
   sender_rcvhost[ptr] = 0;   /* string_cat() always leaves room */
@@ -679,8 +695,7 @@ else
   {
   uschar *flag = useflag? US"H=" : US"";
   uschar *iface = US"";
-  if ((log_extra_selector & LX_incoming_interface) != 0 &&
-       interface_address != NULL)
+  if (LOGGING(incoming_interface) && interface_address != NULL)
     iface = string_sprintf(" I=[%s]:%d", interface_address, interface_port);
   if (sender_ident == NULL)
     (void)string_format(big_buffer, big_buffer_size, "%s%s%s",
@@ -718,7 +733,7 @@ Returns:      a chain of ip_address_items, each containing to a textual
 */
 
 ip_address_item *
-host_build_ifacelist(uschar *list, uschar *name)
+host_build_ifacelist(const uschar *list, uschar *name)
 {
 int sep = 0;
 uschar *s;
@@ -811,9 +826,9 @@ ip_address_item *running_interfaces = NULL;
 if (local_interface_data == NULL)
   {
   void *reset_item = store_get(0);
-  ip_address_item *dlist = host_build_ifacelist(local_interfaces,
+  ip_address_item *dlist = host_build_ifacelist(CUS local_interfaces,
     US"local_interfaces");
-  ip_address_item *xlist = host_build_ifacelist(extra_local_interfaces,
+  ip_address_item *xlist = host_build_ifacelist(CUS extra_local_interfaces,
     US"extra_local_interfaces");
   ip_address_item *ipa;
 
@@ -972,7 +987,7 @@ Returns:     the number of ints used
 */
 
 int
-host_aton(uschar *address, int *bin)
+host_aton(const uschar *address, int *bin)
 {
 int x[4];
 int v4offset = 0;
@@ -984,8 +999,8 @@ supported. */
 
 if (Ustrchr(address, ':') != NULL)
   {
-  uschar *p = address;
-  uschar *component[8];
+  const uschar *p = address;
+  const uschar *component[8];
   BOOL ipv4_ends = FALSE;
   int ci = 0;
   int nulloffset = 0;
@@ -1052,7 +1067,7 @@ if (Ustrchr(address, ':') != NULL)
 /* Handle IPv4 address */
 
 (void)sscanf(CS address, "%d.%d.%d.%d", x, x+1, x+2, x+3);
-bin[v4offset] = (x[0] << 24) + (x[1] << 16) + (x[2] << 8) + x[3];
+bin[v4offset] = ((uint)x[0] << 24) + (x[1] << 16) + (x[2] << 8) + x[3];
 return v4offset+1;
 }
 
@@ -1083,7 +1098,7 @@ for (i = 0; i < count; i++)
   if (mask == 0) wordmask = 0;
   else if (mask < 32)
     {
-    wordmask = (-1) << (32 - mask);
+    wordmask = (uint)(-1) << (32 - mask);
     mask = 0;
     }
   else
@@ -1131,20 +1146,14 @@ if (count == 1)
   {
   j = binary[0];
   for (i = 24; i >= 0; i -= 8)
-    {
-    sprintf(CS tt, "%d.", (j >> i) & 255);
-    while (*tt) tt++;
-    }
+    tt += sprintf(CS tt, "%d.", (j >> i) & 255);
   }
 else
-  {
   for (i = 0; i < 4; i++)
     {
     j = binary[i];
-    sprintf(CS tt, "%04x%c%04x%c", (j >> 16) & 0xffff, sep, j & 0xffff, sep);
-    while (*tt) tt++;
+    tt += sprintf(CS tt, "%04x%c%04x%c", (j >> 16) & 0xffff, sep, j & 0xffff, sep);
     }
-  }
 
 tt--;   /* lose final separator */
 
@@ -1160,6 +1169,63 @@ return tt - buffer;
 }
 
 
+/* Like host_nmtoa() but: ipv6-only, canonical output, no mask
+
+Arguments:
+  binary      points to the ints
+  buffer      big enough to hold the result
+
+Returns:      the number of characters placed in buffer, not counting
+             the final nul.
+*/
+
+int
+ipv6_nmtoa(int * binary, uschar * buffer)
+{
+int i, j, k;
+uschar * c = buffer;
+uschar * d = NULL;     /* shut insufficiently "clever" compiler up */
+
+for (i = 0; i < 4; i++)
+  {                    /* expand to text */
+  j = binary[i];
+  c += sprintf(CS c, "%x:%x:", (j >> 16) & 0xffff, j & 0xffff);
+  }
+
+for (c = buffer, k = -1, i = 0; i < 8; i++)
+  {                    /* find longest 0-group sequence */
+  if (*c == '0')       /* must be "0:" */
+    {
+    uschar * s = c;
+    j = i;
+    while (c[2] == '0') i++, c += 2;
+    if (i-j > k)
+      {
+      k = i-j;         /* length of sequence */
+      d = s;           /* start of sequence */
+      }
+    }
+  while (*++c != ':') ;
+  c++;
+  }
+
+c[-1] = '\0';  /* drop trailing colon */
+
+/* debug_printf("%s: D k %d <%s> <%s>\n", __FUNCTION__, k, d, d + 2*(k+1)); */
+if (k >= 0)
+  {                    /* collapse */
+  c = d + 2*(k+1);
+  if (d == buffer) c--;        /* need extra colon */
+  *d++ = ':';  /* 1st 0 */
+  while ((*d++ = *c++)) ;
+  }
+else
+  d = c;
+
+return d - buffer;
+}
+
+
 
 /*************************************************
 *        Check port for tls_on_connect           *
@@ -1179,7 +1245,7 @@ host_is_tls_on_connect_port(int port)
 {
 int sep = 0;
 uschar buffer[32];
-uschar *list = tls_in.on_connect_ports;
+const uschar *list = tls_in.on_connect_ports;
 uschar *s;
 uschar *end;
 
@@ -1214,7 +1280,7 @@ Returns:
 */
 
 BOOL
-host_is_in_net(uschar *host, uschar *net, int maskoffset)
+host_is_in_net(const uschar *host, const uschar *net, int maskoffset)
 {
 int i;
 int address[4];
@@ -1255,7 +1321,7 @@ for (i = 0; i < size; i++)
   if (mlen == 0) mask = 0;
   else if (mlen < 32)
     {
-    mask = (-1) << (32 - mlen);
+    mask = (uint)(-1) << (32 - mlen);
     mlen = 0;
     }
   else
@@ -1329,9 +1395,9 @@ for (h = host; h != last->next; h = h->next)
   if (hosts_treat_as_local != NULL)
     {
     int rc;
-    uschar *save = deliver_domain;
+    const uschar *save = deliver_domain;
     deliver_domain = h->name;   /* set $domain */
-    rc = match_isinlist(string_copylc(h->name), &hosts_treat_as_local, 0,
+    rc = match_isinlist(string_copylc(h->name), CUSS &hosts_treat_as_local, 0,
       &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL);
     deliver_domain = save;
     if (rc == OK) goto FOUND_LOCAL;
@@ -1455,6 +1521,9 @@ int len;
 uschar *s, *t;
 struct hostent *hosts;
 struct in_addr addr;
+unsigned long time_msec = 0;   /* init to quieten dumb static analysis */
+
+if (slow_lookup_log) time_msec = get_time_in_ms();
 
 /* Lookup on IPv6 system */
 
@@ -1490,6 +1559,11 @@ addr.s_addr = (S_ADDR_TYPE)inet_addr(CS sender_host_address);
 hosts = gethostbyaddr(CS(&addr), sizeof(addr), AF_INET);
 #endif
 
+if (  slow_lookup_log
+   && (time_msec = get_time_in_ms() - time_msec) > slow_lookup_log
+   )
+  log_long_lookup(US"name", sender_host_address, time_msec);
+
 /* Failed to look up the host. */
 
 if (hosts == NULL)
@@ -1572,7 +1646,7 @@ Returns:      OK on success, the answer being placed in the global variable
               FAIL if no host name can be found
               DEFER if a temporary error was encountered
 
-The variable host_lookup_msg is set to an empty string on sucess, or to a
+The variable host_lookup_msg is set to an empty string on success, or to a
 reason for the failure otherwise, in a form suitable for tagging onto an error
 message, and also host_lookup_failed is set TRUE if the lookup failed. If there
 was a defer, host_lookup_deferred is set TRUE.
@@ -1590,7 +1664,7 @@ uschar *hname, *save_hostname;
 uschar **aliases;
 uschar buffer[256];
 uschar *ordername;
-uschar *list = host_lookup_order;
+const uschar *list = host_lookup_order;
 dns_record *rr;
 dns_answer dnsa;
 dns_scan dnss;
@@ -1615,14 +1689,13 @@ if (running_in_test_harness &&
 /* Do lookups directly in the DNS or via gethostbyaddr() (or equivalent), in
 the order specified by the host_lookup_order option. */
 
-while ((ordername = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))
-        != NULL)
+while ((ordername = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
   {
   if (strcmpic(ordername, US"bydns") == 0)
     {
     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);
+    rc = dns_lookup_timerwrap(&dnsa, buffer, T_PTR, NULL);
 
     /* The first record we come across is used for the name; others are
     considered to be aliases. We have to scan twice, in order to find out the
@@ -1637,8 +1710,6 @@ while ((ordername = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))
       int count = 0;
       int old_pool = store_pool;
 
-      /* Ideally we'd check DNSSEC both forward and reverse, but we use the
-      gethost* routines for forward, so can't do that unless/until we rewrite. */
       sender_host_dnssec = dns_is_secure(&dnsa);
       DEBUG(D_dns)
         debug_printf("Reverse DNS security status: %s\n",
@@ -1647,11 +1718,10 @@ while ((ordername = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))
       store_pool = POOL_PERM;        /* Save names in permanent storage */
 
       for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
-           rr != NULL;
+           rr;
            rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
-        {
-        if (rr->type == T_PTR) count++;
-        }
+        if (rr->type == T_PTR)
+         count++;
 
       /* Get store for the list of aliases. For compatibility with
       gethostbyaddr, we make an empty list if there are none. */
@@ -1661,7 +1731,7 @@ while ((ordername = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))
       /* Re-scan and extract the names */
 
       for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
-           rr != NULL;
+           rr;
            rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
         {
         uschar *s = NULL;
@@ -1686,8 +1756,8 @@ while ((ordername = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))
             "empty name: treated as non-existent host name\n");
           continue;
           }
-        if (sender_host_name == NULL) sender_host_name = s;
-          else *aptr++ = s;
+        if (!sender_host_name) sender_host_name = s;
+       else *aptr++ = s;
         while (*s != 0) { *s = tolower(*s); s++; }
         }
 
@@ -1742,8 +1812,8 @@ if (sender_host_name == NULL)
 HDEBUG(D_host_lookup)
   {
   uschar **aliases = sender_host_aliases;
-  debug_printf("IP address lookup yielded %s\n", sender_host_name);
-  while (*aliases != NULL) debug_printf("  alias %s\n", *aliases++);
+  debug_printf("IP address lookup yielded \"%s\"\n", sender_host_name);
+  while (*aliases != NULL) debug_printf("  alias \"%s\"\n", *aliases++);
   }
 
 /* We need to verify that a forward lookup on the name we found does indeed
@@ -1766,21 +1836,30 @@ for (hname = sender_host_name; hname != NULL; hname = *aliases++)
   int rc;
   BOOL ok = FALSE;
   host_item h;
+  dnssec_domains d;
+
   h.next = NULL;
   h.name = hname;
   h.mx = MX_NONE;
   h.address = NULL;
+  d.request = sender_host_dnssec ? US"*" : NULL;;
+  d.require = NULL;
 
-  /* When called with the last argument FALSE, host_find_byname() won't return
-  HOST_FOUND_LOCAL. If the incoming address is an IPv4 address expressed in
-  IPv6 format, we must compare the IPv4 part to any IPv4 addresses. */
-
-  if ((rc = host_find_byname(&h, NULL, 0, NULL, FALSE)) == HOST_FOUND)
+  if (  (rc = host_find_bydns(&h, NULL, HOST_FIND_BY_A,
+         NULL, NULL, NULL, &d, NULL, NULL)) == HOST_FOUND
+     || rc == HOST_FOUND_LOCAL
+     )
     {
     host_item *hh;
     HDEBUG(D_host_lookup) debug_printf("checking addresses for %s\n", hname);
+
+    /* If the forward lookup was not secure we cancel the is-secure variable */
+
+    DEBUG(D_dns) debug_printf("Forward DNS security status: %s\n",
+         h.dnssec == DS_YES ? "DNSSEC verified (AD)" : "unverified");
+    if (h.dnssec != DS_YES) sender_host_dnssec = FALSE;
+
     for (hh = &h; hh != NULL; hh = hh->next)
-      {
       if (host_is_in_net(hh->address, sender_host_address, 0))
         {
         HDEBUG(D_host_lookup) debug_printf("  %s OK\n", hh->address);
@@ -1788,10 +1867,8 @@ for (hname = sender_host_name; hname != NULL; hname = *aliases++)
         break;
         }
       else
-        {
         HDEBUG(D_host_lookup) debug_printf("  %s\n", hh->address);
-        }
-      }
+
     if (!ok) HDEBUG(D_host_lookup)
       debug_printf("no IP address for %s matched %s\n", hname,
         sender_host_address);
@@ -1804,9 +1881,7 @@ for (hname = sender_host_name; hname != NULL; hname = *aliases++)
     return DEFER;
     }
   else
-    {
     HDEBUG(D_host_lookup) debug_printf("no IP addresses found for %s\n", hname);
-    }
 
   /* If this name is no good, and it's the sender name, set it null pro tem;
   if it's an alias, just remove it from the list. */
@@ -1892,8 +1967,8 @@ Returns:                 HOST_FIND_FAILED  Failed to find the host or domain
 */
 
 int
-host_find_byname(host_item *host, uschar *ignore_target_hosts, int flags,
-  uschar **fully_qualified_name, BOOL local_host_check)
+host_find_byname(host_item *host, const uschar *ignore_target_hosts, int flags,
+  const uschar **fully_qualified_name, BOOL local_host_check)
 {
 int i, yield, times;
 uschar **addrlist;
@@ -1903,22 +1978,12 @@ BOOL temp_error = FALSE;
 int af;
 #endif
 
-/* If we are in the test harness, a name ending in .test.again.dns always
-forces a temporary error response, unless the name is in
-dns_again_means_nonexist. */
-
-if (running_in_test_harness)
-  {
-  uschar *endname = host->name + Ustrlen(host->name);
-  if (Ustrcmp(endname - 14, "test.again.dns") == 0) goto RETURN_AGAIN;
-  }
-
 /* Make sure DNS options are set as required. This appears to be necessary in
 some circumstances when the get..byname() function actually calls the DNS. */
 
 dns_init((flags & HOST_FIND_QUALIFY_SINGLE) != 0,
          (flags & HOST_FIND_SEARCH_PARENTS) != 0,
-        FALSE);        /*XXX dnssec? */
+        FALSE);                /* Cannot retrieve dnssec status so do not request */
 
 /* 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
@@ -1932,8 +1997,8 @@ lookups here (except when testing standalone). */
   #else
   if (disable_ipv6 ||
     (dns_ipv4_lookup != NULL &&
-        match_isinlist(host->name, &dns_ipv4_lookup, 0, NULL, NULL, MCL_DOMAIN,
-          TRUE, NULL) == OK))
+        match_isinlist(host->name, CUSS &dns_ipv4_lookup, 0, NULL, NULL,
+         MCL_DOMAIN, TRUE, NULL) == OK))
   #endif
 
     { af = AF_INET; times = 1; }
@@ -1962,11 +2027,14 @@ for (i = 1; i <= times;
   BOOL ipv4_addr;
   int error_num = 0;
   struct hostent *hostdata;
+  unsigned long time_msec = 0; /* compiler quietening */
 
   #ifdef STAND_ALONE
   printf("Looking up: %s\n", host->name);
   #endif
 
+  if (slow_lookup_log) time_msec = get_time_in_ms();
+
   #if HAVE_IPV6
   if (running_in_test_harness)
     hostdata = host_fake_gethostbyname(host->name, af, &error_num);
@@ -1990,17 +2058,21 @@ for (i = 1; i <= times;
     }
   #endif   /* HAVE_IPV6 */
 
+  if (   slow_lookup_log
+      && (time_msec = get_time_in_ms() - time_msec) > slow_lookup_log)
+    log_long_lookup(US"name", host->name, time_msec);
+
   if (hostdata == NULL)
     {
     uschar *error;
     switch (error_num)
       {
       case HOST_NOT_FOUND: error = US"HOST_NOT_FOUND"; break;
-      case TRY_AGAIN: error = US"TRY_AGAIN"; break;
-      case NO_RECOVERY: error = US"NO_RECOVERY"; break;
-      case NO_DATA: error = US"NO_DATA"; break;
+      case TRY_AGAIN:      error = US"TRY_AGAIN"; break;
+      case NO_RECOVERY:    error = US"NO_RECOVERY"; break;
+      case NO_DATA:        error = US"NO_DATA"; break;
       #if NO_DATA != NO_ADDRESS
-      case NO_ADDRESS: error = US"NO_ADDRESS"; break;
+      case NO_ADDRESS:     error = US"NO_ADDRESS"; break;
       #endif
       default: error = US"?"; break;
       }
@@ -2116,7 +2188,7 @@ yield = local_host_check?
 
 HDEBUG(D_host_lookup)
   {
-  host_item *h;
+  const host_item *h;
   if (fully_qualified_name != NULL)
     debug_printf("fully qualified name = %s\n", *fully_qualified_name);
   debug_printf("%s looked up these IP addresses:\n",
@@ -2146,9 +2218,9 @@ RETURN_AGAIN:
   {
   #ifndef STAND_ALONE
   int rc;
-  uschar *save = deliver_domain;
+  const uschar *save = deliver_domain;
   deliver_domain = host->name;  /* set $domain */
-  rc = match_isinlist(host->name, &dns_again_means_nonexist, 0, NULL, NULL,
+  rc = match_isinlist(host->name, CUSS &dns_again_means_nonexist, 0, NULL, NULL,
     MCL_DOMAIN, TRUE, NULL);
   deliver_domain = save;
   if (rc == OK)
@@ -2196,7 +2268,8 @@ Arguments:
   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
+  dnssec_request       if TRUE request the AD bit
+  dnssec_require       if TRUE require the AD bit
 
 Returns:       HOST_FIND_FAILED     couldn't find A record
                HOST_FIND_AGAIN      try again later
@@ -2206,8 +2279,9 @@ Returns:       HOST_FIND_FAILED     couldn't find A record
 
 static int
 set_address_from_dns(host_item *host, host_item **lastptr,
-  uschar *ignore_target_hosts, BOOL allow_ip, uschar **fully_qualified_name,
-  BOOL dnssec_requested, BOOL dnssec_require)
+  const uschar *ignore_target_hosts, BOOL allow_ip,
+  const uschar **fully_qualified_name,
+  BOOL dnssec_request, BOOL dnssec_require)
 {
 dns_record *rr;
 host_item *thishostlast = NULL;    /* Indicates not yet filled in anything */
@@ -2231,27 +2305,22 @@ if (allow_ip && string_is_ip_address(host->name, NULL) != 0)
   return HOST_FOUND;
   }
 
-/* On an IPv6 system, unless IPv6 is disabled, go round the loop up to three
-times, looking for A6 and AAAA records the first two times. However, unless
+/* On an IPv6 system, unless IPv6 is disabled, go round the loop up to twice,
+looking for AAAA records the first time. However, unless
 doing standalone testing, we force an IPv4 lookup if the domain matches
-dns_ipv4_lookup is set. Since A6 records look like being abandoned, support
-them only if explicitly configured to do so. On an IPv4 system, go round the
+dns_ipv4_lookup is set.  On an IPv4 system, go round the
 loop once only, looking only for A records. */
 
 #if HAVE_IPV6
   #ifndef STAND_ALONE
     if (disable_ipv6 || (dns_ipv4_lookup != NULL &&
-        match_isinlist(host->name, &dns_ipv4_lookup, 0, NULL, NULL, MCL_DOMAIN,
-        TRUE, NULL) == OK))
+        match_isinlist(host->name, CUSS &dns_ipv4_lookup, 0, NULL, NULL,
+         MCL_DOMAIN, TRUE, NULL) == OK))
       i = 0;    /* look up A records only */
     else
   #endif        /* STAND_ALONE */
 
-  #ifdef SUPPORT_A6
-  i = 2;        /* look up A6 and AAAA and A records */
-  #else
   i = 1;        /* look up AAAA and A records */
-  #endif        /* SUPPORT_A6 */
 
 /* The IPv4 world */
 
@@ -2261,17 +2330,24 @@ loop once only, looking only for A records. */
 
 for (; i >= 0; i--)
   {
-  static int types[] = { T_A, T_AAAA, T_A6 };
+  static int types[] = { T_A, T_AAAA };
   int type = types[i];
   int randoffset = (i == 0)? 500 : 0;  /* Ensures v6 sorts before v4 */
   dns_answer dnsa;
   dns_scan dnss;
 
-  int rc = dns_lookup(&dnsa, host->name, type, fully_qualified_name);
-  lookup_dnssec_authenticated = !dnssec_requested ? NULL
+  int rc = dns_lookup_timerwrap(&dnsa, host->name, type, fully_qualified_name);
+  lookup_dnssec_authenticated = !dnssec_request ? 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
+  DEBUG(D_dns)
+    if (  (dnssec_request || dnssec_require)
+       && !dns_is_secure(&dnsa)
+       && dns_is_aa(&dnsa)
+       )
+      debug_printf("DNS lookup of %.256s (A/AAAA) requested AD, but got AA\n", host->name);
+
+  /* We want to return HOST_FIND_AGAIN if one of the A or AAAA lookups
   fails or times out, but not if another one succeeds. (In the early
   IPv6 days there are name servers that always fail on AAAA, but are happy
   to give out an A record. We want to proceed with that A record.) */
@@ -2280,51 +2356,68 @@ for (; i >= 0; i--)
     {
     if (i == 0)  /* Just tried for an A record, i.e. end of loop */
       {
-      if (host->address != NULL) return HOST_FOUND;  /* A6 or AAAA was found */
+      if (host->address != NULL) return HOST_FOUND;  /* AAAA was found */
       if (rc == DNS_AGAIN || rc == DNS_FAIL || v6_find_again)
         return HOST_FIND_AGAIN;
       return HOST_FIND_FAILED;    /* DNS_NOMATCH or DNS_NODATA */
       }
 
-    /* Tried for an A6 or AAAA record: remember if this was a temporary
+    /* Tried for an AAAA record: remember if this was a temporary
     error, and look for the next record type. */
 
     if (rc != DNS_NOMATCH && rc != DNS_NODATA) v6_find_again = TRUE;
     continue;
     }
-  if (dnssec_require && !dns_is_secure(&dnsa))
+
+  if (dnssec_request)
     {
-    log_write(L_host_lookup_failed, LOG_MAIN, "dnssec fail on %s for %.256s",
-               i>1 ? "A6" : i>0 ? "AAAA" : "A", host->name);
-    continue;
+    if (dns_is_secure(&dnsa))
+      {
+      DEBUG(D_host_lookup) debug_printf("%s A DNSSEC\n", host->name);
+      if (host->dnssec == DS_UNK) /* set in host_find_bydns() */
+       host->dnssec = DS_YES;
+      }
+    else
+      {
+      if (dnssec_require)
+       {
+       log_write(L_host_lookup_failed, LOG_MAIN,
+               "dnssec fail on %s for %.256s",
+               i>0 ? "AAAA" : "A", host->name);
+       continue;
+       }
+      if (host->dnssec == DS_YES) /* set in host_find_bydns() */
+       {
+       DEBUG(D_host_lookup) debug_printf("%s A cancel DNSSEC\n", host->name);
+       host->dnssec = DS_NO;
+       lookup_dnssec_authenticated = US"no";
+       }
+      }
     }
 
   /* 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
-  may generate more than one address. */
+  may generate more than one address.  The lookup had a chance to update the
+  fqdn; we do not want any later times round the loop to do so. */
+
+  fully_qualified_name = NULL;
 
   for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
-       rr != NULL;
+       rr;
        rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
     {
     if (rr->type == type)
       {
-      /* dns_address *da = dns_address_from_rr(&dnsa, rr); */
-
-      dns_address *da;
-      da = dns_address_from_rr(&dnsa, rr);
+      dns_address *da = dns_address_from_rr(&dnsa, rr);
 
       DEBUG(D_host_lookup)
-        {
-        if (da == NULL)
-          debug_printf("no addresses extracted from A6 RR for %s\n",
+        if (!da) debug_printf("no addresses extracted from A6 RR for %s\n",
             host->name);
-        }
 
       /* This loop runs only once for A and AAAA records, but may run
       several times for an A6 record that generated multiple addresses. */
 
-      for (; da != NULL; da = da->next)
+      for (; da; da = da->next)
         {
         #ifndef STAND_ALONE
         if (ignore_target_hosts != NULL &&
@@ -2411,10 +2504,10 @@ for (; i >= 0; i--)
     }
   }
 
-/* Control gets here only if the third lookup (the A record) succeeded.
+/* Control gets here only if the econdookup (the A record) succeeded.
 However, the address may not be filled in if it was ignored. */
 
-return (host->address == NULL)? HOST_IGNORED : HOST_FOUND;
+return host->address ? HOST_FOUND : HOST_IGNORED;
 }
 
 
@@ -2444,8 +2537,8 @@ Arguments:
   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
+  dnssec_d.request =>   make dnssec request: domainlist
+  dnssec_d.require =>   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
 
@@ -2461,10 +2554,10 @@ Returns:                HOST_FIND_FAILED  Failed to find the host or domain;
 */
 
 int
-host_find_bydns(host_item *host, uschar *ignore_target_hosts, int whichrrs,
+host_find_bydns(host_item *host, const 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)
+  const dnssec_domains *dnssec_d,
+  const uschar **fully_qualified_name, BOOL *removed)
 {
 host_item *h, *last;
 dns_record *rr;
@@ -2473,11 +2566,13 @@ int ind_type = 0;
 int yield;
 dns_answer dnsa;
 dns_scan dnss;
-BOOL dnssec_require = match_isinlist(host->name, &dnssec_require_domains,
+BOOL dnssec_require = dnssec_d
+                   && match_isinlist(host->name, CUSS &dnssec_d->require,
                                    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_d
+                      && match_isinlist(host->name, CUSS &dnssec_d->request,
+                                   0, NULL, NULL, MCL_DOMAIN, TRUE, NULL) == OK);
 dnssec_status_t dnssec;
 
 /* Set the default fully qualified name to the incoming name, initialize the
@@ -2487,12 +2582,11 @@ that gets set for DNS syntax check errors. */
 if (fully_qualified_name != NULL) *fully_qualified_name = host->name;
 dns_init((whichrrs & HOST_FIND_QUALIFY_SINGLE) != 0,
          (whichrrs & HOST_FIND_SEARCH_PARENTS) != 0,
-        dnssec_request
-        );
+        dnssec_request);
 host_find_failed_syntax = FALSE;
 
 /* First, if requested, look for SRV records. The service name is given; we
-assume TCP progocol. DNS domain names are constrained to a maximum of 256
+assume TCP protocol. DNS domain names are constrained to a maximum of 256
 characters, so the code below should be safe. */
 
 if ((whichrrs & HOST_FIND_BY_SRV) != 0)
@@ -2511,7 +2605,13 @@ if ((whichrrs & HOST_FIND_BY_SRV) != 0)
 
   dnssec = DS_UNK;
   lookup_dnssec_authenticated = NULL;
-  rc = dns_lookup(&dnsa, buffer, ind_type, &temp_fully_qualified_name);
+  rc = dns_lookup_timerwrap(&dnsa, buffer, ind_type, CUSS &temp_fully_qualified_name);
+
+  DEBUG(D_dns)
+    if ((dnssec_request || dnssec_require)
+       & !dns_is_secure(&dnsa)
+       & dns_is_aa(&dnsa))
+      debug_printf("DNS lookup of %.256s (SRV) requested AD, but got AA\n", host->name);
 
   if (dnssec_request)
     {
@@ -2536,8 +2636,8 @@ if ((whichrrs & HOST_FIND_BY_SRV) != 0)
   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)
+    if (match_isinlist(host->name, CUSS &srv_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 "
@@ -2557,14 +2657,25 @@ 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);
+  rc = dns_lookup_timerwrap(&dnsa, host->name, ind_type, fully_qualified_name);
+
+  DEBUG(D_dns)
+    if ((dnssec_request || dnssec_require)
+       & !dns_is_secure(&dnsa)
+       & dns_is_aa(&dnsa))
+      debug_printf("DNS lookup of %.256s (MX) requested AD, but got AA\n", host->name);
 
   if (dnssec_request)
     {
     if (dns_is_secure(&dnsa))
-      { dnssec = DS_YES; lookup_dnssec_authenticated = US"yes"; }
+      {
+      DEBUG(D_host_lookup) debug_printf("%s MX DNSSEC\n", host->name);
+      dnssec = DS_YES; lookup_dnssec_authenticated = US"yes";
+      }
     else
-      { dnssec = DS_NO; lookup_dnssec_authenticated = US"no"; }
+      {
+      dnssec = DS_NO; lookup_dnssec_authenticated = US"no";
+      }
     }
 
   switch (rc)
@@ -2578,13 +2689,13 @@ if (rc != DNS_SUCCEED && (whichrrs & HOST_FIND_BY_MX) != 0)
       log_write(L_host_lookup_failed, LOG_MAIN,
                  "dnssec fail on MX for %.256s", host->name);
       rc = DNS_FAIL;
-      /*FALLTRHOUGH*/
+      /*FALLTHROUGH*/
 
     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)
+      if (match_isinlist(host->name, CUSS &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 "
@@ -2609,19 +2720,11 @@ if (rc != DNS_SUCCEED)
   last = host;        /* End of local chainlet */
   host->mx = MX_NONE;
   host->port = PORT_NONE;
-  dnssec = DS_UNK;
+  host->dnssec = DS_UNK;
   lookup_dnssec_authenticated = NULL;
   rc = set_address_from_dns(host, &last, ignore_target_hosts, FALSE,
     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
   inserted, host_scan_for_local_hosts() can only return HOST_FOUND or
@@ -2674,17 +2777,15 @@ host which is not the primary hostname. */
 last = NULL;    /* Indicates that not even the first item is filled yet */
 
 for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
-     rr != NULL;
-     rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
+     rr;
+     rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT)) if (rr->type == ind_type)
   {
   int precedence;
   int weight = 0;        /* For SRV records */
   int port = PORT_NONE;
-  uschar *s;             /* MUST be unsigned for GETSHORT */
+  const uschar * s = rr->data; /* MUST be unsigned for GETSHORT */
   uschar data[256];
 
-  if (rr->type != ind_type) continue;
-  s = rr->data;
   GETSHORT(precedence, s);      /* Pointer s is advanced */
 
   /* For MX records, we use a random "weight" which causes multiple records of
@@ -2817,6 +2918,12 @@ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
   NEXT_MX_RR: continue;
   }
 
+if (!last)     /* No rr of correct type; give up */
+  {
+  yield = HOST_FIND_FAILED;
+  goto out;
+  }
+
 /* If the list of hosts was obtained from SRV records, there are two things to
 do. First, if there is only one host, and it's name is ".", it means there is
 no SMTP service at this domain. Otherwise, we have to sort the hosts of equal
@@ -2843,7 +2950,7 @@ if (ind_type == T_SRV)
       debug_printf("  %s P=%d W=%d\n", h->name, h->mx, h->sort_key % 1000);
     }
 
-  for (pptr = &host, h = host; h != last; pptr = &(h->next), h = h->next)
+  for (pptr = &host, h = host; h != last; pptr = &h->next, h = h->next)
     {
     int sum = 0;
     host_item *hh;
@@ -2948,7 +3055,8 @@ dns_init(FALSE, FALSE,       /* Disable qualify_single and search_parents */
 
 for (h = host; h != last->next; h = h->next)
   {
-  if (h->address != NULL) continue;  /* Inserted by a multihomed host */
+  if (h->address) continue;  /* Inserted by a multihomed host */
+
   rc = set_address_from_dns(h, &last, ignore_target_hosts, allow_mx_to_ip,
     NULL, dnssec_request, dnssec_require);
   if (rc != HOST_FOUND)
@@ -2960,7 +3068,7 @@ for (h = host; h != last->next; h = h->next)
       h->why = hwhy_deferred;
       }
     else
-      h->why = (rc == HOST_IGNORED)? hwhy_ignored : hwhy_failed;
+      h->why = rc == HOST_IGNORED ? hwhy_ignored : hwhy_failed;
     }
   }
 
@@ -3051,8 +3159,9 @@ DEBUG(D_host_lookup)
     yield);
   for (h = host; h != last->next; h = h->next)
     {
-    debug_printf("  %s %s MX=%d ", h->name,
-      (h->address == NULL)? US"<null>" : h->address, h->mx);
+    debug_printf("  %s %s MX=%d %s", h->name,
+      !h->address ? US"<null>" : h->address, h->mx,
+      h->dnssec == DS_YES ? US"DNSSEC " : US"");
     if (h->port != PORT_NONE) debug_printf("port=%d ", h->port);
     if (h->status >= hstatus_unusable) debug_printf("*");
     debug_printf("\n");
@@ -3065,9 +3174,6 @@ dns_init(FALSE, FALSE, FALSE);    /* clear the dnssec bit for getaddrbyname */
 return yield;
 }
 
-
-
-
 /*************************************************
 **************************************************
 *             Stand-alone test program           *
@@ -3137,7 +3243,7 @@ while (Ufgets(buffer, 256, stdin) != NULL)
   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, "no_require_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;
@@ -3158,6 +3264,7 @@ while (Ufgets(buffer, 256, stdin) != NULL)
   else
     {
     int flags = whichrrs;
+    dnssec_domains d;
 
     h.name = buffer;
     h.next = NULL;
@@ -3170,12 +3277,13 @@ while (Ufgets(buffer, 256, stdin) != NULL)
     if (qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE;
     if (search_parents) flags |= HOST_FIND_SEARCH_PARENTS;
 
+    d.request = request_dnssec ? &h.name : NULL;
+    d.require = require_dnssec ? &h.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);
+                       &d, &fully_qualified_name, NULL);
 
     if (rc == HOST_FIND_FAILED) printf("Failed\n");
       else if (rc == HOST_FIND_AGAIN) printf("Again\n");
diff --git a/src/imap_utf7.c b/src/imap_utf7.c
new file mode 100644 (file)
index 0000000..0c3d5a2
--- /dev/null
@@ -0,0 +1,209 @@
+#include "exim.h"
+
+#ifdef SUPPORT_I18N
+
+uschar *
+imap_utf7_encode(uschar *string, const uschar *charset, uschar sep,
+  uschar *specials, uschar **error)
+{
+static uschar encode_base64[64] =
+  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
+int ptr = 0;
+int size = 0;
+size_t slen;
+uschar *sptr, *yield = NULL;
+int i = 0, j;  /* compiler quietening */
+uschar c = 0;  /* compiler quietening */
+BOOL base64mode = FALSE;
+BOOL lastsep = FALSE;
+uschar utf16buf[256];
+uschar *utf16ptr;
+uschar *s;
+uschar outbuf[256];
+uschar *outptr = outbuf;
+#if HAVE_ICONV
+iconv_t icd;
+#endif
+
+if (!specials) specials = US"";
+
+/* Pass over the string. If it consists entirely of "normal" characters
+   (possibly with leading seps), return it as is. */
+for (s = string; *s; s++)
+  {
+  if (s == string && *s == sep)
+    string++;
+  if (  *s >= 0x7f
+     || *s < 0x20
+     || strchr("./&", *s)
+     || *s == sep
+     || Ustrchr(specials, *s)
+     )
+    break;
+  }
+
+if (!*s)
+  return string;
+
+sptr = string;
+slen = Ustrlen(string);
+
+#if HAVE_ICONV
+if ((icd = iconv_open("UTF-16BE", CCS charset)) == (iconv_t)-1)
+  {
+  *error = string_sprintf(
+       "imapfolder: iconv_open(\"UTF-16BE\", \"%s\") failed: %s%s",
+    charset, strerror(errno),
+    errno == EINVAL ? " (maybe unsupported conversion)" : "");
+  return NULL;
+  }
+#endif
+
+while (slen > 0)
+  {
+#if HAVE_ICONV
+  size_t left = sizeof(utf16buf);
+  utf16ptr = utf16buf;
+
+  if (  iconv(icd, (ICONV_ARG2_TYPE)&sptr, &slen, CSS &utf16ptr, &left)
+               == (size_t)-1
+     && errno != E2BIG
+        )
+    {
+    *error = string_sprintf("imapfolder: iconv() failed to convert from %s: %s",
+                             charset, strerror(errno));
+    iconv_close(icd);
+    return NULL;
+    }
+#else
+  for (utf16ptr = utf16buf;
+       slen > 0 && (utf16ptr - utf16buf) < sizeof(utf16buf);
+       utf16ptr += 2, slen--, sptr++)
+    {
+    *utf16ptr = *sptr;
+    *(utf16ptr+1) = '\0';
+    }
+#endif
+
+  s = utf16buf;
+  while (s < utf16ptr)
+    {
+    /* Now encode utf16buf as modified UTF-7 */
+    if (  s[0] != 0
+       || s[1] >= 0x7f
+       || s[1] < 0x20
+       || (Ustrchr(specials, s[1]) && s[1] != sep)
+       )
+      {
+      lastsep = FALSE;
+      /* Encode as modified BASE64 */
+      if (!base64mode)
+        {
+        *outptr++ = '&';
+        base64mode = TRUE;
+        i = 0;
+        }
+
+      for (j = 0; j < 2; j++, s++) switch (i++)
+       {
+       case 0:
+         /* Top 6 bits of the first octet */
+         *outptr++ = encode_base64[(*s >> 2) & 0x3F];
+         c = (*s & 0x03); break;
+       case 1:
+         /* Bottom 2 bits of the first octet, and top 4 bits of the second */
+         *outptr++ = encode_base64[(c << 4) | ((*s >> 4) & 0x0F)];
+         c = (*s & 0x0F); break;
+       case 2:
+         /* Bottom 4 bits of the second octet and top 2 bits of the third */
+         *outptr++ = encode_base64[(c << 2) | ((*s >> 6) & 0x03)];
+         /* Bottom 6 bits of the third octet */
+         *outptr++ = encode_base64[*s & 0x3F];
+         i = 0;
+       }
+      }
+
+    else if (  (s[1] != '.' && s[1] != '/')
+           || s[1] == sep
+           )
+      {
+      /* Encode as self (almost) */
+      if (base64mode)
+        {
+        switch (i)
+          {
+          case 1:
+               /* Remaining bottom 2 bits of the last octet */
+               *outptr++ = encode_base64[c << 4];
+               break;
+         case 2:
+               /* Remaining bottom 4 bits of the last octet */
+               *outptr++ = encode_base64[c << 2];
+         }
+       *outptr++ = '-';
+       base64mode = FALSE;
+       }
+
+      if (*++s == sep)
+       {
+       if (!lastsep)
+         {
+         *outptr++ = '.';
+         lastsep = TRUE;
+         }
+       }
+      else
+        {
+        *outptr++ = *s;
+        if (*s == '&')
+         *outptr++ = '-';
+       lastsep = FALSE;
+        }
+
+      s++;
+      }
+    else
+      {
+      *error = string_sprintf("imapfolder: illegal character '%c'", s[1]);
+      return NULL;
+      }
+
+    if (outptr > outbuf + sizeof(outbuf) - 3)
+      {
+      yield = string_catn(yield, &size, &ptr, outbuf, outptr - outbuf);
+      outptr = outbuf;
+      }
+
+    }
+  } /* End of input string */
+
+if (base64mode)
+  {
+  switch (i)
+    {
+    case 1:
+      /* Remaining bottom 2 bits of the last octet */
+      *outptr++ = encode_base64[c << 4];
+      break;
+    case 2:
+      /* Remaining bottom 4 bits of the last octet */
+      *outptr++ = encode_base64[c << 2];
+    }
+  *outptr++ = '-';
+  }
+
+#if HAVE_ICONV
+iconv_close(icd);
+#endif
+
+yield = string_catn(yield, &size, &ptr, outbuf, outptr - outbuf);
+if (yield[ptr-1] == '.')
+  ptr--;
+yield[ptr] = '\0';
+
+return yield;
+}
+
+#endif /* whole file */
+/* vi: aw ai sw=2
+*/
index b492b9d..bf56466 100644 (file)
--- a/src/ip.c
+++ b/src/ip.c
@@ -2,12 +2,12 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2017 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for doing things with sockets. With the advent of IPv6 this has
 got messier, so that it's worth pulling out the code into separate functions
-that other parts of Exim can call, expecially as there are now several
+that other parts of Exim can call, especially as there are now several
 different places in the code where sockets are used. */
 
 
@@ -64,11 +64,11 @@ Returns:      nothing - failure provokes a panic-die
 */
 
 static void
-ip_addrinfo(uschar *address, struct sockaddr_in6 *saddr)
+ip_addrinfo(const uschar *address, struct sockaddr_in6 *saddr)
 {
 #ifdef IPV6_USE_INET_PTON
 
-  if (inet_pton(AF_INET6, CS address, &saddr->sin6_addr) != 1)
+  if (inet_pton(AF_INET6, CCS address, &saddr->sin6_addr) != 1)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "unable to parse \"%s\" as an "
       "IP address", address);
   saddr->sin6_family = AF_INET6;
@@ -81,7 +81,7 @@ ip_addrinfo(uschar *address, struct sockaddr_in6 *saddr)
   hints.ai_family = AF_INET6;
   hints.ai_socktype = SOCK_STREAM;
   hints.ai_flags = AI_NUMERICHOST;
-  if ((rc = getaddrinfo(CS address, NULL, &hints, &res)) != 0 || res == NULL)
+  if ((rc = getaddrinfo(CCS address, NULL, &hints, &res)) != 0 || res == NULL)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "unable to parse \"%s\" as an "
       "IP address: %s", address,
       (rc == 0)? "NULL result returned" : gai_strerror(rc));
@@ -97,24 +97,11 @@ ip_addrinfo(uschar *address, struct sockaddr_in6 *saddr)
 *         Bind socket to interface and port      *
 *************************************************/
 
-/* This function binds a socket to a local interface address and port. For a
-wildcard IPv6 bind, the address is ":".
-
-Arguments:
-  sock           the socket
-  af             AF_INET or AF_INET6 - the socket type
-  address        the IP address, in text form
-  port           the IP port (host order)
-
-Returns:         the result of bind()
-*/
-
 int
-ip_bind(int sock, int af, uschar *address, int port)
+ip_addr(void * sin_, int af, const uschar * address, int port)
 {
-int s_len;
-union sockaddr_46 sin;
-memset(&sin, 0, sizeof(sin));
+union sockaddr_46 * sin = sin_;
+memset(sin, 0, sizeof(*sin));
 
 /* Setup code when using an IPv6 socket. The wildcard address is ":", to
 ensure an IPv6 socket is used. */
@@ -124,15 +111,13 @@ if (af == AF_INET6)
   {
   if (address[0] == ':' && address[1] == 0)
     {
-    sin.v6.sin6_family = AF_INET6;
-    sin.v6.sin6_addr = in6addr_any;
+    sin->v6.sin6_family = AF_INET6;
+    sin->v6.sin6_addr = in6addr_any;
     }
   else
-    {
-    ip_addrinfo(address, &sin.v6);  /* Panic-dies on error */
-    }
-  sin.v6.sin6_port = htons(port);
-  s_len = sizeof(sin.v6);
+    ip_addrinfo(address, &sin->v6);  /* Panic-dies on error */
+  sin->v6.sin6_port = htons(port);
+  return sizeof(sin->v6);
   }
 else
 #else     /* HAVE_IPv6 */
@@ -142,17 +127,34 @@ af = af;  /* Avoid compiler warning */
 /* Setup code when using IPv4 socket. The wildcard address is "". */
 
   {
-  sin.v4.sin_family = AF_INET;
-  sin.v4.sin_port = htons(port);
-  s_len = sizeof(sin.v4);
-  if (address[0] == 0)
-    sin.v4.sin_addr.s_addr = (S_ADDR_TYPE)INADDR_ANY;
-  else
-    sin.v4.sin_addr.s_addr = (S_ADDR_TYPE)inet_addr(CS address);
+  sin->v4.sin_family = AF_INET;
+  sin->v4.sin_port = htons(port);
+  sin->v4.sin_addr.s_addr = address[0] == 0
+    ? (S_ADDR_TYPE)INADDR_ANY
+    : (S_ADDR_TYPE)inet_addr(CS address);
+  return sizeof(sin->v4);
   }
+}
+
+
+
+/* This function binds a socket to a local interface address and port. For a
+wildcard IPv6 bind, the address is ":".
+
+Arguments:
+  sock           the socket
+  af             AF_INET or AF_INET6 - the socket type
+  address        the IP address, in text form
+  port           the IP port (host order)
 
-/* Now we can call the bind() function */
+Returns:         the result of bind()
+*/
 
+int
+ip_bind(int sock, int af, uschar *address, int port)
+{
+union sockaddr_46 sin;
+int s_len = ip_addr(&sin, af, address, port);
 return bind(sock, (struct sockaddr *)&sin, s_len);
 }
 
@@ -173,12 +175,14 @@ Arguments:
   address     the remote address, in text form
   port        the remote port
   timeout     a timeout (zero for indefinite timeout)
+  fastopen    TRUE iff TCP_FASTOPEN can be used
 
 Returns:      0 on success; -1 on failure, with errno set
 */
 
 int
-ip_connect(int sock, int af, uschar *address, int port, int timeout)
+ip_connect(int sock, int af, const uschar *address, int port, int timeout,
+  BOOL fastopen)
 {
 struct sockaddr_in s_in4;
 struct sockaddr *s_ptr;
@@ -208,7 +212,7 @@ IPv6 support. */
   memset(&s_in4, 0, sizeof(s_in4));
   s_in4.sin_family = AF_INET;
   s_in4.sin_port = htons(port);
-  s_in4.sin_addr.s_addr = (S_ADDR_TYPE)inet_addr(CS address);
+  s_in4.sin_addr.s_addr = (S_ADDR_TYPE)inet_addr(CCS address);
   s_ptr = (struct sockaddr *)&s_in4;
   s_len = sizeof(s_in4);
   }
@@ -216,9 +220,34 @@ IPv6 support. */
 /* If no connection timeout is set, just call connect() without setting a
 timer, thereby allowing the inbuilt OS timeout to operate. */
 
+callout_address = string_sprintf("[%s]:%d", address, port);
 sigalrm_seen = FALSE;
 if (timeout > 0) alarm(timeout);
-rc = connect(sock, s_ptr, s_len);
+
+#if defined(TCP_FASTOPEN) && defined(MSG_FASTOPEN)
+/* TCP Fast Open, if the system has a cookie from a previous call to
+this peer, can send data in the SYN packet.  The peer can send data
+before it gets our ACK of its SYN,ACK - the latter is useful for
+the SMTP banner.  Is there any usage where the former might be?
+We might extend the ip_connect() args for data if so.  For now,
+connect in FASTOPEN mode but with zero data.
+*/
+
+if (fastopen)
+  {
+  if (  (rc = sendto(sock, NULL, 0, MSG_FASTOPEN, s_ptr, s_len)) < 0
+     && errno == EOPNOTSUPP
+     )
+    {
+    DEBUG(D_transport)
+      debug_printf("Tried TCP Fast Open but apparently not enabled by sysctl\n");
+    rc = connect(sock, s_ptr, s_len);
+    }
+  }
+else
+#endif
+  rc = connect(sock, s_ptr, s_len);
+
 save_errno = errno;
 alarm(0);
 
@@ -226,24 +255,22 @@ alarm(0);
 can't think of any other way of doing this. It converts a connection refused
 into a timeout if the timeout is set to 999999. */
 
-if (running_in_test_harness)
+if (running_in_test_harness  && save_errno == ECONNREFUSED && timeout == 999999)
   {
-  if (save_errno == ECONNREFUSED && timeout == 999999)
-    {
-    rc = -1;
-    save_errno = EINTR;
-    sigalrm_seen = TRUE;
-    }
+  rc = -1;
+  save_errno = EINTR;
+  sigalrm_seen = TRUE;
   }
 
 /* Success */
 
-if (rc >= 0) return 0;
+if (rc >= 0)
+  return 0;
 
 /* A failure whose error code is "Interrupted system call" is in fact
 an externally applied timeout if the signal handler has been run. */
 
-errno = (save_errno == EINTR && sigalrm_seen)? ETIMEDOUT : save_errno;
+errno = save_errno == EINTR && sigalrm_seen ? ETIMEDOUT : save_errno;
 return -1;
 }
 
@@ -276,6 +303,7 @@ int namelen, port;
 host_item shost;
 host_item *h;
 int af = 0, fd, fd4 = -1, fd6 = -1;
+BOOL fastopen = tcp_fastopen_ok && type == SOCK_STREAM;
 
 shost.next = NULL;
 shost.address = NULL;
@@ -289,9 +317,7 @@ namelen = Ustrlen(hostname);
 if (hostname[0] == '[' &&
     hostname[namelen - 1] == ']')
   {
-  uschar * host = string_copy(hostname);
-  host[namelen - 1] = 0;
-  host++;
+  uschar * host = string_copyn(hostname+1, namelen-2);
   if (string_is_ip_address(host, NULL) == 0)
     {
     *errstr = string_sprintf("malformed IP address \"%s\"", hostname);
@@ -303,15 +329,15 @@ if (hostname[0] == '[' &&
 /* Otherwise check for an unadorned IP address */
 
 else if (string_is_ip_address(hostname, NULL) != 0)
-  shost.name = shost.address = string_copy(hostname);
+  shost.name = shost.address = string_copyn(hostname, namelen);
 
 /* 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)
+  shost.name = string_copyn(hostname, namelen);
+  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;
@@ -320,11 +346,11 @@ else
 
 /* Try to connect to the server - test each IP till one works */
 
-for (h = &shost; h != NULL; h = h->next)
+for (h = &shost; h; 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;
+  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)
     {
@@ -333,11 +359,12 @@ for (h = &shost; h != NULL; h = h->next)
     }
 
   for(port = portlo; port <= porthi; port++)
-    if (ip_connect(fd, af, h->address, port, timeout) == 0)
+    if (ip_connect(fd, af, h->address, port, timeout, fastopen) == 0)
       {
       if (fd != fd6) close(fd6);
       if (fd != fd4) close(fd4);
-      if (connhost) {
+      if (connhost)
+       {
        h->port = port;
        *connhost = *h;
        connhost->next = NULL;
@@ -346,14 +373,71 @@ for (h = &shost; h != NULL; h = h->next)
       }
   }
 
-*errstr = string_sprintf("failed to connect to %s: "
-  "couldn't connect to any host: %s", hostname, strerror(errno));
+*errstr = string_sprintf("failed to connect to any address for %s: %s",
+  hostname, strerror(errno));
 
 bad:
   close(fd4); close(fd6); return -1;
 }
 
 
+int
+ip_tcpsocket(const uschar * hostport, uschar ** errstr, int tmo)
+{
+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,
+                         tmo, NULL, errstr);
+}
+
+int
+ip_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;
+  }
+
+callout_address = string_copy(path);
+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;
+}
+
+int
+ip_streamsocket(const uschar * spec, uschar ** errstr, int tmo)
+{
+return *spec == '/'
+  ? ip_unixsocket(spec, errstr) : ip_tcpsocket(spec, errstr, tmo);
+}
+
 /*************************************************
 *         Set keepalive on a socket              *
 *************************************************/
@@ -369,7 +453,7 @@ Returns:     nothing
 */
 
 void
-ip_keepalive(int sock, uschar *address, BOOL torf)
+ip_keepalive(int sock, const uschar *address, BOOL torf)
 {
 int fodder = 1;
 if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE,
@@ -384,39 +468,36 @@ if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE,
 *         Receive from a socket with timeout     *
 *************************************************/
 
-/* The timeout is implemented using select(), and we loop to cover select()
-getting interrupted, and the possibility of select() returning with a positive
-result but no ready descriptor. Is this in fact possible?
-
+/*
 Arguments:
-  sock        the socket
-  buffer      to read into
-  bufsize     the buffer size
-  timeout     the timeout
-
-Returns:      > 0 => that much data read
-              <= 0 on error or EOF; errno set - zero for EOF
+  fd          the file descriptor
+  timeout     the timeout, seconds
+Returns:      TRUE => ready for i/o
+              FALSE => timed out, or other error
 */
-
-int
-ip_recv(int sock, uschar *buffer, int buffsize, int timeout)
+BOOL
+fd_ready(int fd, int timeout)
 {
 fd_set select_inset;
-struct timeval tv;
 time_t start_recv = time(NULL);
+int time_left = timeout;
 int rc;
 
+if (time_left <= 0)
+  {
+  errno = ETIMEDOUT;
+  return FALSE;
+  }
 /* Wait until the socket is ready */
 
-for (;;)
+do
   {
+  struct timeval tv = { time_left, 0 };
   FD_ZERO (&select_inset);
-  FD_SET (sock, &select_inset);
-  tv.tv_sec = timeout;
-  tv.tv_usec = 0;
+  FD_SET (fd, &select_inset);
 
-  DEBUG(D_transport) debug_printf("waiting for data on socket\n");
-  rc = select(sock + 1, (SELECT_ARG2_TYPE *)&select_inset, NULL, NULL, &tv);
+  /*DEBUG(D_transport) debug_printf("waiting for data on fd\n");*/
+  rc = select(fd + 1, (SELECT_ARG2_TYPE *)&select_inset, NULL, NULL, &tv);
 
   /* If some interrupt arrived, just retry. We presume this to be rare,
   but it can happen (e.g. the SIGUSR1 signal sent by exiwhat causes
@@ -425,28 +506,52 @@ for (;;)
   Aug 2004: Somebody set up a cron job that ran exiwhat every 2 minutes, making
   the interrupt not at all rare. Since the timeout is typically more than 2
   minutes, the effect was to block the timeout completely. To prevent this
-  happening again, we do an explicit time test. */
+  happening again, we do an explicit time test and adjust the timeout
+  accordingly */
 
   if (rc < 0 && errno == EINTR)
     {
     DEBUG(D_transport) debug_printf("EINTR while waiting for socket data\n");
-    if (time(NULL) - start_recv < timeout) continue;
-    DEBUG(D_transport) debug_printf("total wait time exceeds timeout\n");
-    }
 
-  /* Handle a timeout, and treat any other select error as a timeout, including
-  an EINTR when we have been in this loop for longer than timeout. */
+    /* Watch out, 'continue' jumps to the condition, not to the loops top */
+    time_left = timeout - (time(NULL) - start_recv);
+    if (time_left > 0) continue;
+    }
 
   if (rc <= 0)
     {
     errno = ETIMEDOUT;
-    return -1;
+    return FALSE;
     }
 
-  /* If the socket is ready, break out of the loop. */
-
-  if (FD_ISSET(sock, &select_inset)) break;
+  /* Checking the FD_ISSET is not enough, if we're interrupted, the
+  select_inset may still contain the 'input'. */
   }
+while (rc < 0 || !FD_ISSET(fd, &select_inset));
+return TRUE;
+}
+
+/* The timeout is implemented using select(), and we loop to cover select()
+getting interrupted, and the possibility of select() returning with a positive
+result but no ready descriptor. Is this in fact possible?
+
+Arguments:
+  sock        the socket
+  buffer      to read into
+  bufsize     the buffer size
+  timeout     the timeout
+
+Returns:      > 0 => that much data read
+              <= 0 on error or EOF; errno set - zero for EOF
+*/
+
+int
+ip_recv(int sock, uschar *buffer, int buffsize, int timeout)
+{
+int rc;
+
+if (!fd_ready(sock, timeout))
+  return -1;
 
 /* The socket is ready, read from it (via TLS if it's active). On EOF (i.e.
 close down of the connection), set errno to zero; otherwise leave it alone. */
@@ -625,13 +730,9 @@ while (last > first)
     return TRUE;
     }
   else if (c > 0)
-    {
     first = middle + 1;
-    }
   else
-    {
     last = middle;
-    }
   }
 return FALSE;
 }
@@ -646,3 +747,5 @@ for (i=0; i < dscp_table_size; ++i)
 
 
 /* End of ip.c */
+/* vi: aw ai sw=2
+*/
index 770348a..bc4fc8e 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* This file is the header that is the only Exim header to be included in the
@@ -98,8 +98,8 @@ ABI is changed in a non backward compatible way. The minor number is increased
 each time a new feature is added (in a way that doesn't break backward
 compatibility). */
 
-#define LOCAL_SCAN_ABI_VERSION_MAJOR 1
-#define LOCAL_SCAN_ABI_VERSION_MINOR 1
+#define LOCAL_SCAN_ABI_VERSION_MAJOR 2
+#define LOCAL_SCAN_ABI_VERSION_MINOR 0
 #define LOCAL_SCAN_ABI_VERSION \
   LOCAL_SCAN_ABI_VERSION_MAJOR.LOCAL_SCAN_ABI_VERSION_MINOR
 
@@ -115,7 +115,7 @@ typedef struct header_line {
 /* Entries in lists options are in this form. */
 
 typedef struct {
-  const char   *name;
+  const char   *name; /* should have been uschar but too late now */
   int           type;
   void         *value;
 } optionlist;
@@ -128,10 +128,8 @@ typedef struct recipient_item {
   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
@@ -191,7 +189,7 @@ extern int     smtp_fflush(void);
 extern void    smtp_printf(const char *, ...) PRINTF_FUNCTION(1,2);
 extern void    smtp_vprintf(const char *, va_list);
 extern uschar *string_copy(const uschar *);
-extern uschar *string_copyn(uschar *, int);
+extern uschar *string_copyn(const uschar *, int);
 extern uschar *string_sprintf(const char *, ...) ALMOST_PRINTF(1,2);
 
 /* End of local_scan.h */
index c80c347..ddd7137 100644 (file)
--- a/src/log.c
+++ b/src/log.c
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for writing log files. The code for maintaining datestamped
@@ -47,8 +47,80 @@ static BOOL   path_inspected = FALSE;
 static int    logging_mode = LOG_MODE_FILE;
 static uschar *file_path = US"";
 
-
-
+static size_t pid_position[2];
+
+
+/* These should be kept in-step with the private delivery error
+number definitions in macros.h */
+
+static const uschar * exim_errstrings[] = {
+  US"",
+  US"unknown error",
+  US"user slash",
+  US"exist race",
+  US"not regular",
+  US"not directory",
+  US"bad ugid",
+  US"bad mode",
+  US"inode changed",
+  US"lock failed",
+  US"bad address2",
+  US"forbid pipe",
+  US"forbid file",
+  US"forbid reply",
+  US"missing pipe",
+  US"missing file",
+  US"missing reply",
+  US"bad redirect",
+  US"smtp closed",
+  US"smtp format",
+  US"spool format",
+  US"not absolute",
+  US"Exim-imposed quota",
+  US"held",
+  US"Delivery filter process failure",
+  US"Delivery add/remove header failure",
+  US"Delivery write incomplete error",
+  US"Some expansion failed",
+  US"Failed to get gid",
+  US"Failed to get uid",
+  US"Unset or non-existent transport",
+  US"MBX length mismatch",
+  US"Lookup failed routing or in smtp tpt",
+  US"Can't match format in appendfile",
+  US"Creation outside home in appendfile",
+  US"Can't check a list; lookup defer",
+  US"DNS lookup defer",
+  US"Failed to start TLS session",
+  US"Mandatory TLS session not started",
+  US"Failed to chown a file",
+  US"Failed to create a pipe",
+  US"When verifying",
+  US"When required by client",
+  US"Used internally in smtp transport",
+  US"RCPT gave 4xx error",
+  US"MAIL gave 4xx error",
+  US"DATA gave 4xx error",
+  US"Negotiation failed for proxy configured host",
+  US"Authenticator 'other' failure",
+  US"target not supporting SMTPUTF8",
+  US"",
+
+  US"Not time for routing",
+  US"Not time for local delivery",
+  US"Not time for any remote host",
+  US"Local-only delivery",
+  US"Domain in queue_domains",
+  US"Transport concurrency limit",
+};
+
+
+/************************************************/
+const uschar *
+exim_errstr(int err)
+{
+return err < 0 ? exim_errstrings[-err] : CUS strerror(err);
+}
 
 /*************************************************
 *              Write to syslog                   *
@@ -62,7 +134,7 @@ can get here if there is a failure to open the panic log.)
 
 Arguments:
   priority       syslog priority
-  s              the string to be written
+  s              the string to be written, the string may be modified!
 
 Returns:         nothing
 */
@@ -76,6 +148,8 @@ int linecount = 0;
 if (running_in_test_harness) return;
 
 if (!syslog_timestamp) s += log_timezone? 26 : 20;
+if (!syslog_pid && LOGGING(pid))
+    memmove(s + pid_position[0], s + pid_position[1], pid_position[1] - pid_position[0]);
 
 len = Ustrlen(s);
 
@@ -182,7 +256,11 @@ Returns:       a file descriptor, or < 0 on failure (errno set)
 int
 log_create(uschar *name)
 {
-int fd = Uopen(name, O_CREAT|O_APPEND|O_WRONLY, LOG_MODE);
+int fd = Uopen(name,
+#ifdef O_CLOEXEC
+       O_CLOEXEC |
+#endif
+       O_CREAT|O_APPEND|O_WRONLY, LOG_MODE);
 
 /* If creation failed, attempt to build a log directory in case that is the
 problem. */
@@ -196,7 +274,11 @@ if (fd < 0 && errno == ENOENT)
   DEBUG(D_any) debug_printf("%s log directory %s\n",
     created? "created" : "failed to create", name);
   *lastslash = '/';
-  if (created) fd = Uopen(name, O_CREAT|O_APPEND|O_WRONLY, LOG_MODE);
+  if (created) fd = Uopen(name,
+#ifdef O_CLOEXEC
+                       O_CLOEXEC |
+#endif
+                       O_CREAT|O_APPEND|O_WRONLY, LOG_MODE);
   }
 
 return fd;
@@ -246,7 +328,11 @@ if (pid == 0)
 /* If we created a subprocess, wait for it. If it succeeded, try the open. */
 
 while (pid > 0 && waitpid(pid, &status, 0) != pid);
-if (status == 0) fd = Uopen(name, O_APPEND|O_WRONLY, LOG_MODE);
+if (status == 0) fd = Uopen(name,
+#ifdef O_CLOEXEC
+                       O_CLOEXEC |
+#endif
+                       O_APPEND|O_WRONLY, LOG_MODE);
 
 /* If we failed to create a subprocess, we are in a bad way. We return
 with fd still < 0, and errno set, letting the caller handle the error. */
@@ -368,11 +454,17 @@ if (!ok)
 /* We now have the file name. Try to open an existing file. After a successful
 open, arrange for automatic closure on exec(), and then return. */
 
-*fd = Uopen(buffer, O_APPEND|O_WRONLY, LOG_MODE);
+*fd = Uopen(buffer,
+#ifdef O_CLOEXEC
+               O_CLOEXEC |
+#endif
+               O_APPEND|O_WRONLY, LOG_MODE);
 
 if (*fd >= 0)
   {
+#ifndef O_CLOEXEC
   (void)fcntl(*fd, F_SETFD, fcntl(*fd, F_GETFD) | FD_CLOEXEC);
+#endif
   return;
   }
 
@@ -399,7 +491,9 @@ else if (euid == root_uid) *fd = log_create_as_exim(buffer);
 
 if (*fd >= 0)
   {
+#ifndef O_CLOEXEC
   (void)fcntl(*fd, F_SETFD, fcntl(*fd, F_GETFD) | FD_CLOEXEC);
+#endif
   return;
   }
 
@@ -420,12 +514,9 @@ log. If possible, save a copy of the original line that was being logged. If we
 are recursing (can't open the panic log either), the pointer will already be
 set. */
 
-if (panic_save_buffer == NULL)
-  {
-  panic_save_buffer = (uschar *)malloc(LOG_BUFFER_SIZE);
-  if (panic_save_buffer != NULL)
+if (!panic_save_buffer)
+  if ((panic_save_buffer = US malloc(LOG_BUFFER_SIZE)))
     memcpy(panic_save_buffer, log_buffer, LOG_BUFFER_SIZE);
-  }
 
 log_write(0, LOG_PANIC_DIE, "Cannot open %s log file \"%s\": %s: "
   "euid=%d egid=%d", log_names[type], buffer, strerror(errno), euid, getegid());
@@ -433,6 +524,13 @@ log_write(0, LOG_PANIC_DIE, "Cannot open %s log file \"%s\": %s: "
 }
 
 
+static void
+unlink_log(int type)
+{
+if (type == lt_debug) unlink(CS debuglog_name);
+}
+
+
 
 /*************************************************
 *     Add configuration file info to log line    *
@@ -498,12 +596,9 @@ log_write_failed(uschar *name, int length, int rc)
 {
 int save_errno = errno;
 
-if (panic_save_buffer == NULL)
-  {
-  panic_save_buffer = (uschar *)malloc(LOG_BUFFER_SIZE);
-  if (panic_save_buffer != NULL)
+if (!panic_save_buffer)
+  if ((panic_save_buffer = US malloc(LOG_BUFFER_SIZE)))
     memcpy(panic_save_buffer, log_buffer, LOG_BUFFER_SIZE);
-  }
 
 log_write(0, LOG_PANIC_DIE, "failed to write to %s: length=%d result=%d "
   "errno=%d (%s)", name, length, rc, save_errno,
@@ -556,6 +651,31 @@ return total_written;
 }
 
 
+
+static void
+set_file_path(void)
+{
+int sep = ':';              /* Fixed separator - outside use */
+uschar *t;
+const uschar *tt = US LOG_FILE_PATH;
+while ((t = string_nextinlist(&tt, &sep, log_buffer, LOG_BUFFER_SIZE)))
+  {
+  if (Ustrcmp(t, "syslog") == 0 || t[0] == 0) continue;
+  file_path = string_copy(t);
+  break;
+  }
+}
+
+
+void
+mainlog_close(void)
+{
+if (mainlogfd < 0) return;
+(void)close(mainlogfd);
+mainlogfd = -1;
+mainlog_inode = 0;
+}
+
 /*************************************************
 *            Write message to log file           *
 *************************************************/
@@ -584,7 +704,7 @@ If it is not, don't try to write to the log because permission will probably be
 denied.
 
 Avoid actually writing to the logs when exim is called with -bv or -bt to
-test an address, but take other actions, such as panicing.
+test an address, but take other actions, such as panicking.
 
 In Exim proper, the buffer for building the message is got at start-up, so that
 nothing gets done if it can't be got. However, some functions that are also
@@ -596,7 +716,7 @@ If a message_id exists, we include it after the timestamp.
 
 Arguments:
   selector  write to main log or LOG_INFO only if this value is zero, or if
-              its bit is set in log_write_selector
+              its bit is set in log_selector[0]
   flags     each bit indicates some independent action:
               LOG_SENDER      add raw sender to the message
               LOG_RECIPIENTS  add raw recipients list to message
@@ -642,15 +762,12 @@ if (panic_recurseflag)
 /* Ensure we have a buffer (see comment above); this should never be obeyed
 when running Exim proper, only when running utilities. */
 
-if (log_buffer == NULL)
-  {
-  log_buffer = (uschar *)malloc(LOG_BUFFER_SIZE);
-  if (log_buffer == NULL)
+if (!log_buffer)
+  if (!(log_buffer = US malloc(LOG_BUFFER_SIZE)))
     {
     fprintf(stderr, "exim: failed to get store for log buffer\n");
     exim_exit(EXIT_FAILURE);
     }
-  }
 
 /* If we haven't already done so, inspect the setting of log_file_path to
 determine whether to log to files and/or to syslog. Bits in logging_mode
@@ -668,13 +785,13 @@ if (!path_inspected)
   /* If nothing has been set, don't waste effort... the default values for the
   statics are file_path="" and logging_mode = LOG_MODE_FILE. */
 
-  if (log_file_path[0] != 0)
+  if (*log_file_path)
     {
     int sep = ':';              /* Fixed separator - outside use */
     uschar *s;
-    uschar *ss = log_file_path;
+    const uschar *ss = log_file_path;
     logging_mode = 0;
-    while ((s = string_nextinlist(&ss,&sep,log_buffer,LOG_BUFFER_SIZE)) != NULL)
+    while ((s = string_nextinlist(&ss, &sep, log_buffer, LOG_BUFFER_SIZE)))
       {
       if (Ustrcmp(s, "syslog") == 0)
         logging_mode |= LOG_MODE_SYSLOG;
@@ -685,10 +802,8 @@ if (!path_inspected)
 
         /* If a non-empty path is given, use it */
 
-        if (s[0] != 0)
-          {
+        if (*s)
           file_path = string_copy(s);
-          }
 
         /* If the path is empty, we want to use the first non-empty, non-
         syslog item in LOG_FILE_PATH, if there is one, since the value of
@@ -696,17 +811,7 @@ if (!path_inspected)
         use the ultimate default in the spool directory. */
 
         else
-          {
-          uschar *t;
-          uschar *tt = US LOG_FILE_PATH;
-          while ((t = string_nextinlist(&tt,&sep,log_buffer,LOG_BUFFER_SIZE))
-                != NULL)
-            {
-            if (Ustrcmp(t, "syslog") == 0 || t[0] == 0) continue;
-            file_path = string_copy(t);
-            break;
-            }
-          }  /* Empty item in log_file_path */
+         set_file_path();  /* Empty item in log_file_path */
         }    /* First non-syslog item in log_file_path */
       }      /* Scan of log_file_path */
     }
@@ -729,10 +834,8 @@ if (!path_inspected)
   should work since we have now set up the routing. */
 
   if (multiple)
-    {
     log_write(0, LOG_MAIN|LOG_PANIC,
       "More than one path given in log_file_path: using %s", file_path);
-    }
   }
 
 /* If debugging, show all log entries, but don't show headers. Do it all
@@ -746,15 +849,12 @@ DEBUG(D_any|D_v)
   Ustrcpy(ptr, "LOG:");
   ptr += 4;
 
-  /* Show the options that were passed into the call. These are those whose
-  flag values do not have the 0x80000000 bit in them. Note that this
-  automatically exclude the "all" setting. */
+  /* Show the selector that was passed into the call. */
 
   for (i = 0; i < log_options_count; i++)
     {
-    unsigned int bit = log_options[i].bit;
-    if ((bit & 0x80000000) != 0) continue;
-    if ((selector & bit) != 0)
+    unsigned int bitnum = log_options[i].bit;
+    if (bitnum < BITWORDSIZE && selector == BIT(bitnum))
       {
       *ptr++ = ' ';
       Ustrcpy(ptr, log_options[i].name);
@@ -806,10 +906,12 @@ ptr = log_buffer;
 sprintf(CS ptr, "%s ", tod_stamp(tod_log));
 while(*ptr) ptr++;
 
-if ((log_extra_selector & LX_pid) != 0)
+if (LOGGING(pid))
   {
   sprintf(CS ptr, "[%d] ", (int)getpid());
+  if (!syslog_pid) pid_position[0] = ptr - log_buffer; /* remember begin â€¦ */
   while (*ptr) ptr++;
+  if (!syslog_pid) pid_position[1] = ptr - log_buffer; /*  â€¦ and end+1 of the PID */
   }
 
 if (really_exim && message_id[0] != 0)
@@ -866,7 +968,7 @@ or unless there is no log_stderr (expn called from daemon, for example). */
 if (!really_exim || log_testing_mode)
   {
   if (debug_selector == 0 && log_stderr != NULL &&
-      (selector == 0 || (selector & log_write_selector) != 0))
+      (selector == 0 || (selector & log_selector[0]) != 0))
     {
     if (host_checking)
       fprintf(log_stderr, "LOG: %s", CS(log_buffer + 20));  /* no timestamp */
@@ -883,14 +985,14 @@ been opened, but we don't want to keep on writing to it for too long after it
 has been renamed. Therefore, do a stat() and see if the inode has changed, and
 if so, re-open. */
 
-if ((flags & LOG_MAIN) != 0 &&
-    (selector == 0 || (selector & log_write_selector) != 0))
+if (  flags & LOG_MAIN
+   && (!selector ||  selector & log_selector[0]))
   {
-  if ((logging_mode & LOG_MODE_SYSLOG) != 0 &&
-      (syslog_duplication || (flags & (LOG_REJECT|LOG_PANIC)) == 0))
+  if (  logging_mode & LOG_MODE_SYSLOG
+     && (syslog_duplication || !(flags & (LOG_REJECT|LOG_PANIC))))
     write_syslog(LOG_INFO, log_buffer);
 
-  if ((logging_mode & LOG_MODE_FILE) != 0)
+  if (logging_mode & LOG_MODE_FILE)
     {
     struct stat statbuf;
 
@@ -916,14 +1018,8 @@ if ((flags & LOG_MAIN) != 0 &&
     happening. */
 
     if (mainlogfd >= 0)
-      {
       if (Ustat(mainlog_name, &statbuf) < 0 || statbuf.st_ino != mainlog_inode)
-        {
-        (void)close(mainlogfd);
-        mainlogfd = -1;
-        mainlog_inode = 0;
-        }
-      }
+       mainlog_close();
 
     /* If the log is closed, open it. Then write the line. */
 
@@ -953,7 +1049,7 @@ if ((flags & LOG_REJECT) != 0)
   {
   header_line *h;
 
-  if (header_list != NULL && (log_extra_selector & LX_rejected_header) != 0)
+  if (header_list != NULL && LOGGING(rejected_header))
     {
     if (recipients_count > 0)
       {
@@ -1138,6 +1234,35 @@ syslog_open = FALSE;
 
 
 
+/*************************************************
+*             Multi-bit set or clear             *
+*************************************************/
+
+/* These functions take a list of bit indexes (terminated by -1) and
+clear or set the corresponding bits in the selector.
+
+Arguments:
+  selector       address of the bit string
+  selsize        number of words in the bit string
+  bits           list of bits to set
+*/
+
+void
+bits_clear(unsigned int *selector, size_t selsize, int *bits)
+{
+for(; *bits != -1; ++bits)
+  BIT_CLEAR(selector, selsize, *bits);
+}
+
+void
+bits_set(unsigned int *selector, size_t selsize, int *bits)
+{
+for(; *bits != -1; ++bits)
+  BIT_SET(selector, selsize, *bits);
+}
+
+
+
 /*************************************************
 *         Decode bit settings for log/debug      *
 *************************************************/
@@ -1148,13 +1273,9 @@ also recognizes a numeric setting of the form =<number>, but this is not
 intended for user use. It's an easy way for Exim to pass the debug settings
 when it is re-exec'ed.
 
-The log options are held in two unsigned ints (because there became too many
-for one). The top bit in the table means "put in 2nd selector". This does not
-yet apply to debug options, so the "=" facility sets only the first selector.
-
-The "all" selector, which must be equal to 0xffffffff, is recognized specially.
-It sets all the bits in both selectors. However, there is a facility for then
-unsetting certain bits, because we want to turn off "memory" in the debug case.
+The option table is a list of names and bit indexes. The index -1
+means "set all bits, except for those listed in notall". The notall
+list is terminated by -1.
 
 The action taken for bad values varies depending upon why we're here.
 For log messages, or if the debugging is triggered from config, then we write
@@ -1162,10 +1283,9 @@ to the log on the way out.  For debug setting triggered from the command-line,
 we treat it as an unknown option: error message to stderr and die.
 
 Arguments:
-  selector1      address of the first bit string
-  selector2      address of the second bit string, or NULL
-  notall1        bits to exclude from "all" for selector1
-  notall2        bits to exclude from "all" for selector2
+  selector       address of the bit string
+  selsize        number of words in the bit string
+  notall         list of bits to exclude from "all"
   string         the configured string
   options        the table of option names
   count          size of table
@@ -1176,9 +1296,8 @@ Returns:         nothing on success - bomb out on failure
 */
 
 void
-decode_bits(unsigned int *selector1, unsigned int *selector2, int notall1,
-  int notall2, uschar *string, bit_table *options, int count, uschar *which,
-  int flags)
+decode_bits(unsigned int *selector, size_t selsize, int *notall,
+  uschar *string, bit_table *options, int count, uschar *which, int flags)
 {
 uschar *errmsg;
 if (string == NULL) return;
@@ -1186,7 +1305,8 @@ if (string == NULL) return;
 if (*string == '=')
   {
   char *end;    /* Not uschar */
-  *selector1 = strtoul(CS string+1, &end, 0);
+  memset(selector, 0, sizeof(*selector)*selsize);
+  *selector = strtoul(CS string+1, &end, 0);
   if (*end == 0) return;
   errmsg = string_sprintf("malformed numeric %s_selector setting: %s", which,
     string);
@@ -1229,40 +1349,22 @@ else for(;;)
       if (middle->name[len] != 0) c = -1; else
         {
         unsigned int bit = middle->bit;
-        unsigned int *selector;
-
-        /* The value with all bits set means "force all bits in both selectors"
-        in the case where two are being handled. However, the top bit in the
-        second selector is never set. When setting, some bits can be excluded.
-        */
-
-        if (bit == 0xffffffff)
-          {
-          if (adding)
-            {
-            *selector1 = 0xffffffff ^ notall1;
-            if (selector2 != NULL) *selector2 = 0x7fffffff ^ notall2;
-            }
-          else
-            {
-            *selector1 = 0;
-            if (selector2 != NULL) *selector2 = 0;
-            }
-          }
-
-        /* Otherwise, the 0x80000000 bit means "this value, without the top
-        bit, belongs in the second selector". */
 
-        else
-          {
-          if ((bit & 0x80000000) != 0)
-            {
-            selector = selector2;
-            bit &= 0x7fffffff;
-            }
-          else selector = selector1;
-          if (adding) *selector |= bit; else *selector &= ~bit;
-          }
+       if (bit == -1)
+         {
+         if (adding)
+           {
+           memset(selector, -1, sizeof(*selector)*selsize);
+           bits_clear(selector, selsize, notall);
+           }
+         else
+           memset(selector, 0, sizeof(*selector)*selsize);
+         }
+       else if (adding)
+         BIT_SET(selector, selsize, bit);
+       else
+         BIT_CLEAR(selector, selsize, bit);
+
         break;  /* Out of loop to match selector name */
         }
       }
@@ -1319,7 +1421,7 @@ int fd = -1;
 if (debug_file)
   {
   debug_printf("DEBUGGING ACTIVATED FROM WITHIN CONFIG.\n"
-      "DEBUG: Tag=\"%s\" Opts=\"%s\"\n", tag_name, opts ? opts : US"");
+      "DEBUG: Tag=\"%s\" opts=\"%s\"\n", tag_name, opts ? opts : US"");
   return;
   }
 
@@ -1332,10 +1434,14 @@ if (tag_name != NULL && (Ustrchr(tag_name, '/') != NULL))
 
 debug_selector = D_default;
 if (opts)
-  {
-  decode_bits(&debug_selector, NULL, D_memory, 0, opts,
+  decode_bits(&debug_selector, 1, debug_notall, opts,
       debug_options, debug_options_count, US"debug", DEBUG_FROM_CONFIG);
-  }
+
+/* When activating from a transport process we may never have logged at all
+resulting in certain setup not having been done.  Hack this for now so we
+do not segfault; note that nondefault log locations will not work */
+
+if (!*file_path) set_file_path();
 
 open_log(&fd, lt_debug, tag_name);
 
@@ -1346,4 +1452,16 @@ else
 }
 
 
+void
+debug_logging_stop(void)
+{
+if (!debug_file || !debuglog_name[0]) return;
+
+debug_selector = 0;
+fclose(debug_file);
+debug_file = NULL;
+unlink_log(lt_debug);
+}
+
+
 /* End of log.c */
index 2a27071..9055f52 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -30,11 +30,11 @@ typedef struct lookup_info {
   int (*find)(                    /* find function */
     void *,                       /* handle */
     uschar *,                     /* file name or NULL */
-    uschar *,                     /* key or query */
+    const uschar *,               /* key or query */
     int,                          /* length of key or query */
     uschar **,                    /* for returning answer */
     uschar **,                    /* for error message */
-    BOOL *);                      /* to request cache cleanup */
+    uint *);                      /* cache TTL, seconds */
   void (*close)(                  /* close function */
     void *);                      /* handle */
   void (*tidy)(void);             /* tidy function */
@@ -46,9 +46,10 @@ typedef struct lookup_info {
 } lookup_info;
 
 /* This magic number is used by the following lookup_module_info structure
-   for checking API compatibility. It's equivalent to the string"LMM2" */
-#define LOOKUP_MODULE_INFO_MAGIC 0x4c4d4d32
+   for checking API compatibility. It used to be equivalent to the string"LMM3" */
+#define LOOKUP_MODULE_INFO_MAGIC 0x4c4d4933
 /* Version 2 adds: version_report */
+/* Version 3 change: non/cache becomes TTL in seconds */
 
 typedef struct lookup_module_info {
   uint magic;
index 6ba0cb1..7c97006 100644 (file)
@@ -34,6 +34,7 @@ dnsdb.o:         $(PHDRS) dnsdb.c
 dsearch.o:       $(PHDRS) dsearch.c
 ibase.o:         $(PHDRS) ibase.c
 ldap.o:          $(PHDRS) ldap.c
+lmdb.o:          $(PHDRS) lmdb.c
 lsearch.o:       $(PHDRS) lsearch.c
 mysql.o:         $(PHDRS) mysql.c
 nis.o:           $(PHDRS) nis.c
@@ -53,6 +54,7 @@ dnsdb.so:         $(PHDRS) dnsdb.c
 dsearch.so:       $(PHDRS) dsearch.c
 ibase.so:         $(PHDRS) ibase.c
 ldap.so:          $(PHDRS) ldap.c
+lmdb.so:          $(PHDRS) lmdb.c
 lsearch.so:       $(PHDRS) lsearch.c
 mysql.so:         $(PHDRS) mysql.c
 nis.so:           $(PHDRS) nis.c
index 98905dc..31fea64 100644 (file)
@@ -122,12 +122,15 @@ DEFER. The arguments are:
   uschar **errmsg     where to put an error message on failure;
                       this is initially set to "", and should be left
                       as that for a standard "entry not found" error
-  BOOL *do_cache      the lookup should set this to FALSE when it changes data.
-                      This is TRUE by default. When set to FALSE the cache tree
+  uint *do_cache      the lookup should set this to 0 when it changes data.
+                      This is MAXINT by default. When set to 0 the cache tree
                       of the current search handle will be cleaned and the
                       current result will NOT be cached. Currently the mysql
                       and pgsql lookups use this when UPDATE/INSERT queries are
                       executed.
+                      If set to a nonzero number of seconds, the cached value
+                      becomes unusable after this time. Currently the dnsdb
+                      lookup uses this to support the TTL value.
 
 Even though the key is zero-terminated, the length is passed because in the
 common case it has been computed already and is often needed.
index fda14fb..bc61046 100644 (file)
@@ -18,6 +18,9 @@
  *   Changed over to using unsigned chars
  *   Makes use of lf_check_file() for file checking
  * --------------------------------------------------------------
+ * Modified by The Exim Maintainers 2015:
+ *   const propagation
+ * --------------------------------------------------------------
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -40,7 +43,7 @@
  * cdb.[ch] it does *not* link against an external cdb library.
  *
  *
- * There are 2 varients included within this code.  One uses MMAP and
+ * There are 2 variants included within this code.  One uses MMAP and
  * should give better performance especially for multiple lookups on a
  * modern machine.  The other is the default implementation which is
  * used in the case where the MMAP fails or if MMAP was not compiled
@@ -91,7 +94,7 @@ typedef unsigned int uint32;
  * Internal function to make hash value */
 
 static uint32
-cdb_hash(uschar *buf, unsigned int len)
+cdb_hash(const uschar *buf, unsigned int len)
 {
   uint32 h;
 
@@ -128,7 +131,7 @@ cdb_bread(int fd,
 
 /*
  * cdb_bread()
- * Internal function to parse 4 byte number (endian independant) */
+ * Internal function to parse 4 byte number (endian independent) */
 
 static uint32
 cdb_unpack(uschar *buf)
@@ -272,141 +275,177 @@ cdb_check(void *handle,
 static int
 cdb_find(void *handle,
         uschar *filename,
-        uschar *keystring,
+        const uschar *keystring,
         int  key_len,
         uschar **result,
         uschar **errmsg,
-        BOOL *do_cache)
+        uint *do_cache)
 {
-  struct cdb_state * cdbp = handle;
-  uint32 item_key_len,
-    item_dat_len,
-    key_hash,
-    item_hash,
-    item_posn,
-    cur_offset,
-    end_offset,
-    hash_offset_entry,
-    hash_offset,
-    hash_offlen,
-    hash_slotnm;
-  int loop;
-
-  /* Keep picky compilers happy */
-  do_cache = do_cache;
-
-  key_hash = cdb_hash((uschar *)keystring, key_len);
-
-  hash_offset_entry = CDB_HASH_ENTRY * (key_hash & CDB_HASH_MASK);
-  hash_offset = cdb_unpack(cdbp->cdb_offsets + hash_offset_entry);
-  hash_offlen = cdb_unpack(cdbp->cdb_offsets + hash_offset_entry + 4);
-
-  /* If the offset length is zero this key cannot be in the file */
-  if (hash_offlen == 0) {
-    return FAIL;
-  }
-  hash_slotnm = (key_hash >> 8) % hash_offlen;
-
-  /* check to ensure that the file is not corrupt
-   * if the hash_offset + (hash_offlen * CDB_HASH_ENTRY) is longer
-   * than the file, then we have problems.... */
-  if ((hash_offset + (hash_offlen * CDB_HASH_ENTRY)) > cdbp->filelen) {
-    *errmsg = string_sprintf("cdb: corrupt cdb file %s (too short)",
-                            filename);
-    DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
-    return DEFER;
+struct cdb_state * cdbp = handle;
+uint32 item_key_len,
+item_dat_len,
+key_hash,
+item_hash,
+item_posn,
+cur_offset,
+end_offset,
+hash_offset_entry,
+hash_offset,
+hash_offlen,
+hash_slotnm;
+int loop;
+
+/* Keep picky compilers happy */
+do_cache = do_cache;
+
+key_hash = cdb_hash(keystring, key_len);
+
+hash_offset_entry = CDB_HASH_ENTRY * (key_hash & CDB_HASH_MASK);
+hash_offset = cdb_unpack(cdbp->cdb_offsets + hash_offset_entry);
+hash_offlen = cdb_unpack(cdbp->cdb_offsets + hash_offset_entry + 4);
+
+/* If the offset length is zero this key cannot be in the file */
+
+if (hash_offlen == 0)
+  return FAIL;
+
+hash_slotnm = (key_hash >> 8) % hash_offlen;
+
+/* check to ensure that the file is not corrupt
+ * if the hash_offset + (hash_offlen * CDB_HASH_ENTRY) is longer
+ * than the file, then we have problems.... */
+
+if ((hash_offset + (hash_offlen * CDB_HASH_ENTRY)) > cdbp->filelen)
+  {
+  *errmsg = string_sprintf("cdb: corrupt cdb file %s (too short)",
+                     filename);
+  DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
+  return DEFER;
   }
 
-  cur_offset = hash_offset + (hash_slotnm * CDB_HASH_ENTRY);
-  end_offset = hash_offset + (hash_offlen * CDB_HASH_ENTRY);
-  /* if we are allowed to we use mmap here.... */
+cur_offset = hash_offset + (hash_slotnm * CDB_HASH_ENTRY);
+end_offset = hash_offset + (hash_offlen * CDB_HASH_ENTRY);
+
+/* if we are allowed to we use mmap here.... */
+
 #ifdef HAVE_MMAP
-  /* make sure the mmap was OK */
-  if (cdbp->cdb_map != NULL) {
-    uschar * cur_pos = cur_offset + cdbp->cdb_map;
-    uschar * end_pos = end_offset + cdbp->cdb_map;
-    for (loop = 0; (loop < hash_offlen); ++loop) {
-      item_hash = cdb_unpack(cur_pos);
-      cur_pos += 4;
-      item_posn = cdb_unpack(cur_pos);
-      cur_pos += 4;
-      /* if the position is zero then we have a definite miss */
-      if (item_posn == 0)
-       return FAIL;
-
-      if (item_hash == key_hash) {
-       /* matching hash value */
-       uschar * item_ptr = cdbp->cdb_map + item_posn;
-       item_key_len = cdb_unpack(item_ptr);
-       item_ptr += 4;
-       item_dat_len = cdb_unpack(item_ptr);
-       item_ptr += 4;
-       /* check key length matches */
-       if (item_key_len == key_len) {
-         /* finally check if key matches */
-         if (Ustrncmp(keystring, item_ptr, key_len) == 0) {
-           /* we have a match....
-            * make item_ptr point to data */
-           item_ptr += item_key_len;
-           /* ... and the returned result */
-           *result = store_get(item_dat_len + 1);
-           memcpy(*result, item_ptr, item_dat_len);
-           (*result)[item_dat_len] = 0;
-           return OK;
-         }
-       }
-      }
-      /* handle warp round of table */
-      if (cur_pos == end_pos)
-       cur_pos = cdbp->cdb_map + hash_offset;
-    }
-    /* looks like we failed... */
-    return FAIL;
-  }
-#endif /* HAVE_MMAP */
-  for (loop = 0; (loop < hash_offlen); ++loop) {
-    uschar packbuf[8];
-    if (lseek(cdbp->fileno, (off_t) cur_offset,SEEK_SET) == -1) return DEFER;
-    if (cdb_bread(cdbp->fileno, packbuf,8) == -1) return DEFER;
-    item_hash = cdb_unpack(packbuf);
-    item_posn = cdb_unpack(packbuf + 4);
+/* make sure the mmap was OK */
+if (cdbp->cdb_map != NULL)
+  {
+  uschar * cur_pos = cur_offset + cdbp->cdb_map;
+  uschar * end_pos = end_offset + cdbp->cdb_map;
+
+  for (loop = 0; (loop < hash_offlen); ++loop)
+    {
+    item_hash = cdb_unpack(cur_pos);
+    cur_pos += 4;
+    item_posn = cdb_unpack(cur_pos);
+    cur_pos += 4;
+
     /* if the position is zero then we have a definite miss */
+
     if (item_posn == 0)
       return FAIL;
 
-    if (item_hash == key_hash) {
-      /* matching hash value */
-      if (lseek(cdbp->fileno, (off_t) item_posn, SEEK_SET) == -1) return DEFER;
-      if (cdb_bread(cdbp->fileno, packbuf, 8) == -1) return DEFER;
-      item_key_len = cdb_unpack(packbuf);
+    if (item_hash == key_hash)
+      {                                        /* matching hash value */
+      uschar * item_ptr = cdbp->cdb_map + item_posn;
+
+      item_key_len = cdb_unpack(item_ptr);
+      item_ptr += 4;
+      item_dat_len = cdb_unpack(item_ptr);
+      item_ptr += 4;
+
       /* check key length matches */
-      if (item_key_len == key_len) {
-       /* finally check if key matches */
-       uschar * item_key = store_get(key_len);
-       if (cdb_bread(cdbp->fileno, item_key, key_len) == -1) return DEFER;
-       if (Ustrncmp(keystring, item_key, key_len) == 0) {
-         /* Reclaim some store */
-         store_reset(item_key);
-         /* matches - get data length */
-         item_dat_len = cdb_unpack(packbuf + 4);
-         /* then we build a new result string */
-         *result = store_get(item_dat_len + 1);
-         if (cdb_bread(cdbp->fileno, *result, item_dat_len) == -1)
-           return DEFER;
-         (*result)[item_dat_len] = 0;
-         return OK;
-       }
+
+      if (item_key_len == key_len)
+       {
+        /* finally check if key matches */
+        if (Ustrncmp(keystring, item_ptr, key_len) == 0)
+          {
+          /* we have a match....  * make item_ptr point to data */
+
+          item_ptr += item_key_len;
+
+          /* ... and the returned result */
+
+          *result = store_get(item_dat_len + 1);
+          memcpy(*result, item_ptr, item_dat_len);
+          (*result)[item_dat_len] = 0;
+          return OK;
+          }
+       }
+      }
+    /* handle warp round of table */
+    if (cur_pos == end_pos)
+    cur_pos = cdbp->cdb_map + hash_offset;
+    }
+  /* looks like we failed... */
+  return FAIL;
+  }
+
+#endif /* HAVE_MMAP */
+
+for (loop = 0; (loop < hash_offlen); ++loop)
+  {
+  uschar packbuf[8];
+
+  if (lseek(cdbp->fileno, (off_t) cur_offset,SEEK_SET) == -1) return DEFER;
+  if (cdb_bread(cdbp->fileno, packbuf,8) == -1) return DEFER;
+
+  item_hash = cdb_unpack(packbuf);
+  item_posn = cdb_unpack(packbuf + 4);
+
+  /* if the position is zero then we have a definite miss */
+
+  if (item_posn == 0)
+    return FAIL;
+
+  if (item_hash == key_hash)
+    {                                          /* matching hash value */
+    if (lseek(cdbp->fileno, (off_t) item_posn, SEEK_SET) == -1) return DEFER;
+    if (cdb_bread(cdbp->fileno, packbuf, 8) == -1) return DEFER;
+
+    item_key_len = cdb_unpack(packbuf);
+
+    /* check key length matches */
+
+    if (item_key_len == key_len)
+      {                                        /* finally check if key matches */
+      uschar * item_key = store_get(key_len);
+
+      if (cdb_bread(cdbp->fileno, item_key, key_len) == -1) return DEFER;
+      if (Ustrncmp(keystring, item_key, key_len) == 0) {
+
        /* Reclaim some store */
        store_reset(item_key);
+
+       /* matches - get data length */
+       item_dat_len = cdb_unpack(packbuf + 4);
+
+       /* then we build a new result string.  We know we have enough
+       memory so disable Coverity errors about the tainted item_dat_ken */
+
+       *result = store_get(item_dat_len + 1);
+       /* coverity[tainted_data] */
+       if (cdb_bread(cdbp->fileno, *result, item_dat_len) == -1)
+        return DEFER;
+
+       /* coverity[tainted_data] */
+       (*result)[item_dat_len] = 0;
+       return OK;
+      }
+      /* Reclaim some store */
+      store_reset(item_key);
       }
     }
-    cur_offset += 8;
+  cur_offset += 8;
 
-    /* handle warp round of table */
-    if (cur_offset == end_offset)
-      cur_offset = hash_offset;
+  /* handle warp round of table */
+  if (cur_offset == end_offset)
+  cur_offset = hash_offset;
   }
-  return FAIL;
+return FAIL;
 }
 
 
index 3dec7a7..b8c42d5 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -86,8 +86,8 @@ return rc == 0;
 the keylength in order to include the terminating zero. */
 
 static int
-dbmdb_find(void *handle, uschar *filename, uschar *keystring, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+dbmdb_find(void *handle, uschar *filename, const uschar *keystring, int length,
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 EXIM_DB *d = (EXIM_DB *)handle;
 EXIM_DATUM key, data;
@@ -119,8 +119,8 @@ return FAIL;
 /* See local README for interface description */
 
 int
-static dbmnz_find(void *handle, uschar *filename, uschar *keystring, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+static dbmnz_find(void *handle, uschar *filename, const uschar *keystring, int length,
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 return dbmdb_find(handle, filename, keystring, length-1, result, errmsg,
   do_cache);
@@ -139,11 +139,11 @@ return dbmdb_find(handle, filename, keystring, length-1, result, errmsg,
  */
 
 static int
-dbmjz_find(void *handle, uschar *filename, uschar *keystring, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+dbmjz_find(void *handle, uschar *filename, const uschar *keystring, int length,
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 uschar *key_item, *key_buffer, *key_p;
-uschar *key_elems = keystring;
+const uschar *key_elems = keystring;
 int buflen, bufleft, key_item_len, sep = 0;
 
 /* To a first approximation, the size of the lookup key needs to be about,
index fde98b9..c4b5b53 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -34,9 +34,6 @@ static const char *type_names[] = {
 #if HAVE_IPV6
   "a+",
   "aaaa",
-  #ifdef SUPPORT_A6
-  "a6",
-  #endif
 #endif
   "cname",
   "csa",
@@ -44,6 +41,7 @@ static const char *type_names[] = {
   "mxh",
   "ns",
   "ptr",
+  "soa",
   "spf",
   "srv",
   "tlsa",
@@ -56,9 +54,6 @@ static int type_values[] = {
 #if HAVE_IPV6
   T_ADDRESSES,     /* Private type for AAAA + A */
   T_AAAA,
-  #ifdef SUPPORT_A6
-  T_A6,
-  #endif
 #endif
   T_CNAME,
   T_CSA,     /* Private type for "Client SMTP Authorization". */
@@ -66,6 +61,7 @@ static int type_values[] = {
   T_MXH,     /* Private type for "MX hostnames" */
   T_NS,
   T_PTR,
+  T_SOA,
   T_SPF,
   T_SRV,
   T_TLSA,
@@ -108,27 +104,34 @@ no separator. With neither of these options specified, only the first item
 is output.  Similarly for "SPF" records, but the default for joining multiple
 items in one SPF record is the empty string, for direct concatenation.
 
-(c) If the next sequence of characters is 'defer_FOO' followed by a comma,
-the defer behaviour is set to FOO. The possible behaviours are: 'strict', where
-any defer causes the whole lookup to defer; 'lax', where a defer causes the
-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'.
+(c) Options, all comma-terminated, in any order.  Any unrecognised option
+terminates option processing.  Recognised options are:
 
-(d) Another optional comma-sep field: 'dnssec_FOO', with 'strict', 'lax'
-and 'never' (default); can appear before or after (c).  The meanings are
+- 'defer_FOO':  set the defer behaviour to FOO.  The possible behaviours are:
+'strict', where any defer causes the whole lookup to defer; 'lax', where a defer
+causes the 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'.
+
+- 'dnssec_FOO', with 'strict', 'lax' and 'never' (default).  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
+- 'retrans_VAL', set the timeout value.  VAL is an Exim time specification
+(eg "5s").  The default is set by the main configuration option 'dns_retrans'.
+
+- 'retry_VAL', set the retry count on timeouts.  VAL is an integer.  The
+default is set by the main configuration option "dns_retry".
+
+(d) 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".
 
-(f) Then there follows list of domain names. This is a generalized Exim list,
+(e) 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. */
 
 static int
-dnsdb_find(void *handle, uschar *filename, uschar *keystring, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+dnsdb_find(void *handle, uschar *filename, const uschar *keystring, int length,
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 int rc;
 int size = 256;
@@ -136,12 +139,13 @@ int ptr = 0;
 int sep = 0;
 int defer_mode = PASS;
 int dnssec_mode = OK;
+int save_retrans = dns_retrans;
+int save_retry =   dns_retry;
 int type;
 int failrc = FAIL;
-uschar *outsep = US"\n";
-uschar *outsep2 = NULL;
+const uschar *outsep = CUS"\n";
+const uschar *outsep2 = NULL;
 uschar *equals, *domain, *found;
-uschar buffer[256];
 
 /* Because we're the working in the search pool, we try to reclaim as much
 store as possible later, so we preallocate the result here */
@@ -180,58 +184,62 @@ if (*keystring == '>')
 
 /* Check for a modifier keyword. */
 
-while (  strncmpic(keystring, US"defer_", 6) == 0
-      || strncmpic(keystring, US"dnssec_", 7) == 0
-      )
+for (;;)
   {
   if (strncmpic(keystring, US"defer_", 6) == 0)
     {
     keystring += 6;
     if (strncmpic(keystring, US"strict", 6) == 0)
-      {
-      defer_mode = DEFER;
-      keystring += 6;
-      }
+      { defer_mode = DEFER; keystring += 6; }
     else if (strncmpic(keystring, US"lax", 3) == 0)
-      {
-      defer_mode = PASS;
-      keystring += 3;
-      }
+      { defer_mode = PASS; keystring += 3; }
     else if (strncmpic(keystring, US"never", 5) == 0)
-      {
-      defer_mode = OK;
-      keystring += 5;
-      }
+      { defer_mode = OK; keystring += 5; }
     else
       {
       *errmsg = US"unsupported dnsdb defer behaviour";
       return DEFER;
       }
     }
-  else
+  else if (strncmpic(keystring, US"dnssec_", 7) == 0)
     {
     keystring += 7;
     if (strncmpic(keystring, US"strict", 6) == 0)
-      {
-      dnssec_mode = DEFER;
-      keystring += 6;
-      }
+      { 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
       {
-      dnssec_mode = PASS;
-      keystring += 3;
+      *errmsg = US"unsupported dnsdb dnssec behaviour";
+      return DEFER;
       }
-    else if (strncmpic(keystring, US"never", 5) == 0)
+    }
+  else if (strncmpic(keystring, US"retrans_", 8) == 0)
+    {
+    int timeout_sec;
+    if ((timeout_sec = readconf_readtime(keystring += 8, ',', FALSE)) <= 0)
       {
-      dnssec_mode = OK;
-      keystring += 5;
+      *errmsg = US"unsupported dnsdb timeout value";
+      return DEFER;
       }
-    else
+    dns_retrans = timeout_sec;
+    while (*keystring != ',') keystring++;
+    }
+  else if (strncmpic(keystring, US"retry_", 6) == 0)
+    {
+    int retries;
+    if ((retries = (int)strtol(CCS keystring + 6, CSS &keystring, 0)) < 0)
       {
-      *errmsg = US"unsupported dnsdb dnssec behaviour";
+      *errmsg = US"unsupported dnsdb retry count";
       return DEFER;
       }
+    dns_retry = retries;
     }
+  else
+    break;
+
   while (isspace(*keystring)) keystring++;
   if (*keystring++ != ',')
     {
@@ -253,17 +261,15 @@ if ((equals = Ustrchr(keystring, '=')) != NULL)
   while (tend > keystring && isspace(tend[-1])) tend--;
   len = tend - keystring;
 
-  for (i = 0; i < sizeof(type_names)/sizeof(uschar *); i++)
-    {
+  for (i = 0; i < nelem(type_names); i++)
     if (len == Ustrlen(type_names[i]) &&
         strncmpic(keystring, US type_names[i], len) == 0)
       {
       type = type_values[i];
       break;
       }
-    }
 
-  if (i >= sizeof(type_names)/sizeof(uschar *))
+  if (i >= nelem(type_names))
     {
     *errmsg = US"unsupported DNS record type";
     return DEFER;
@@ -295,15 +301,19 @@ if (type == T_PTR && keystring[0] != '<' &&
 /* SPF strings should be concatenated without a separator, thus make
 it the default if not defined (see RFC 4408 section 3.1.3).
 Multiple SPF records are forbidden (section 3.1.2) but are currently
-not handled specially, thus they are concatenated with \n by default. */
+not handled specially, thus they are concatenated with \n by default.
+MX priority and value are space-separated by default.
+SRV and TLSA record parts are space-separated by default. */
 
-if (type == T_SPF && outsep2 == NULL)
-  outsep2 = US"";
+if (!outsep2) switch(type)
+  {
+  case T_SPF:                         outsep2 = US"";  break;
+  case T_SRV: case T_MX: case T_TLSA: outsep2 = US" "; break;
+  }
 
 /* Now scan the list and do a lookup for each item */
 
-while ((domain = string_nextinlist(&keystring, &sep, buffer, sizeof(buffer)))
-        != NULL)
+while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))
   {
   uschar rbuffer[256];
   int searchtype = (type == T_CSA)? T_SRV :         /* record type we want */
@@ -340,19 +350,13 @@ while ((domain = string_nextinlist(&keystring, &sep, buffer, sizeof(buffer)))
 #if HAVE_IPV6
     if (type == T_ADDRESSES)           /* NB cannot happen unless HAVE_IPV6 */
       {
-      if (searchtype == T_ADDRESSES)
-# if defined(SUPPORT_A6)
-                                     searchtype = T_A6;
-# else
-                                     searchtype = T_AAAA;
-# endif
-      else if (searchtype == T_A6)   searchtype = T_AAAA;
+      if (searchtype == T_ADDRESSES) searchtype = T_AAAA;
       else if (searchtype == T_AAAA) searchtype = T_A;
-      rc = dns_special_lookup(&dnsa, domain, searchtype, &found);
+      rc = dns_special_lookup(&dnsa, domain, searchtype, CUSS &found);
       }
     else
 #endif
-      rc = dns_special_lookup(&dnsa, domain, type, &found);
+      rc = dns_special_lookup(&dnsa, domain, type, CUSS &found);
 
     lookup_dnssec_authenticated = dnssec_mode==OK ? NULL
       : dns_is_secure(&dnsa) ? US"yes" : US"no";
@@ -364,6 +368,8 @@ while ((domain = string_nextinlist(&keystring, &sep, buffer, sizeof(buffer)))
       {
       if (defer_mode == DEFER)
        {
+       dns_retrans = save_retrans;
+       dns_retry = save_retry;
        dns_init(FALSE, FALSE, FALSE);                  /* clr dnssec bit */
        return DEFER;                                   /* always defer */
        }
@@ -380,23 +386,16 @@ while ((domain = string_nextinlist(&keystring, &sep, buffer, sizeof(buffer)))
       {
       if (rr->type != searchtype) continue;
 
-      /* There may be several addresses from an A6 record. Put the configured
-      separator between them, just as for between several records. However, A6
-      support is not normally configured these days. */
+      if (*do_cache > rr->ttl)
+       *do_cache = rr->ttl;
 
-      if (type == T_A ||
-          #ifdef SUPPORT_A6
-          type == T_A6 ||
-          #endif
-          type == T_AAAA ||
-          type == T_ADDRESSES)
+      if (type == T_A || type == T_AAAA || type == T_ADDRESSES)
         {
         dns_address *da;
-        for (da = dns_address_from_rr(&dnsa, rr); da != NULL; da = da->next)
+        for (da = dns_address_from_rr(&dnsa, rr); da; da = da->next)
           {
-          if (ptr != 0) yield = string_cat(yield, &size, &ptr, outsep, 1);
-          yield = string_cat(yield, &size, &ptr, da->address,
-            Ustrlen(da->address));
+          if (ptr != 0) yield = string_catn(yield, &size, &ptr, outsep, 1);
+          yield = string_cat(yield, &size, &ptr, da->address);
           }
         continue;
         }
@@ -404,14 +403,14 @@ while ((domain = string_nextinlist(&keystring, &sep, buffer, sizeof(buffer)))
       /* Other kinds of record just have one piece of data each, but there may be
       several of them, of course. */
 
-      if (ptr != 0) yield = string_cat(yield, &size, &ptr, outsep, 1);
+      if (ptr != 0) yield = string_catn(yield, &size, &ptr, outsep, 1);
 
       if (type == T_TXT || type == T_SPF)
         {
         if (outsep2 == NULL)
           {
           /* output only the first item of data */
-          yield = string_cat(yield, &size, &ptr, (uschar *)(rr->data+1),
+          yield = string_catn(yield, &size, &ptr, (uschar *)(rr->data+1),
             (rr->data)[0]);
           }
         else
@@ -422,9 +421,9 @@ while ((domain = string_nextinlist(&keystring, &sep, buffer, sizeof(buffer)))
             {
             uschar chunk_len = (rr->data)[data_offset++];
             if (outsep2[0] != '\0' && data_offset != 1)
-              yield = string_cat(yield, &size, &ptr, outsep2, 1);
-            yield = string_cat(yield, &size, &ptr,
-                             (uschar *)((rr->data)+data_offset), chunk_len);
+              yield = string_catn(yield, &size, &ptr, outsep2, 1);
+            yield = string_catn(yield, &size, &ptr,
+                             US ((rr->data)+data_offset), chunk_len);
             data_offset += chunk_len;
             }
           }
@@ -435,83 +434,88 @@ while ((domain = string_nextinlist(&keystring, &sep, buffer, sizeof(buffer)))
         uint16_t i, payload_length;
         uschar s[MAX_TLSA_EXPANDED_SIZE];
        uschar * sp = s;
-        uschar *p = (uschar *)(rr->data);
+        uschar * p = US rr->data;
 
         usage = *p++;
         selector = *p++;
         matching_type = *p++;
         /* What's left after removing the first 3 bytes above */
         payload_length = rr->size - 3;
-        sp += sprintf(CS s, "%d %d %d ", usage, selector, matching_type);
+        sp += sprintf(CS s, "%d%c%d%c%d%c", usage, *outsep2,
+               selector, *outsep2, matching_type, *outsep2);
         /* Now append the cert/identifier, one hex char at a time */
         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));
+
+        yield = string_cat(yield, &size, &ptr, s);
         }
-      else   /* T_CNAME, T_CSA, T_MX, T_MXH, T_NS, T_PTR, T_SRV */
+      else   /* T_CNAME, T_CSA, T_MX, T_MXH, T_NS, T_PTR, T_SOA, T_SRV */
         {
         int priority, weight, port;
         uschar s[264];
-        uschar *p = (uschar *)(rr->data);
-
-        if (type == T_MXH)
-          {
-          /* mxh ignores the priority number and includes only the hostnames */
-          GETSHORT(priority, p);
-          }
-        else if (type == T_MX)
-          {
-          GETSHORT(priority, p);
-          sprintf(CS s, "%d ", priority);
-          yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
-          }
-        else if (type == T_SRV)
-          {
-          GETSHORT(priority, p);
-          GETSHORT(weight, p);
-          GETSHORT(port, p);
-          sprintf(CS s, "%d %d %d ", priority, weight, port);
-          yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
-          }
-        else if (type == T_CSA)
-          {
-          /* See acl_verify_csa() for more comments about CSA. */
-
-          GETSHORT(priority, p);
-          GETSHORT(weight, p);
-          GETSHORT(port, p);
-
-          if (priority != 1) continue;      /* CSA version must be 1 */
-
-          /* If the CSA record we found is not the one we asked for, analyse
-          the subdomain assertions in the port field, else analyse the direct
-          authorization status in the weight field. */
-
-          if (found != domain)
-            {
-            if (port & 1) *s = 'X';         /* explicit authorization required */
-            else *s = '?';                  /* no subdomain assertions here */
-            }
-          else
-            {
-            if (weight < 2) *s = 'N';       /* not authorized */
-            else if (weight == 2) *s = 'Y'; /* authorized */
-            else if (weight == 3) *s = '?'; /* unauthorizable */
-            else continue;                  /* invalid */
-            }
-
-          s[1] = ' ';
-          yield = string_cat(yield, &size, &ptr, s, 2);
-          }
+        uschar * p = US rr->data;
+
+       switch (type)
+         {
+         case T_MXH:
+           /* mxh ignores the priority number and includes only the hostnames */
+           GETSHORT(priority, p);
+           break;
+
+         case T_MX:
+           GETSHORT(priority, p);
+           sprintf(CS s, "%d%c", priority, *outsep2);
+           yield = string_cat(yield, &size, &ptr, s);
+           break;
+
+         case T_SRV:
+           GETSHORT(priority, p);
+           GETSHORT(weight, p);
+           GETSHORT(port, p);
+           sprintf(CS s, "%d%c%d%c%d%c", priority, *outsep2,
+                             weight, *outsep2, port, *outsep2);
+           yield = string_cat(yield, &size, &ptr, s);
+           break;
+
+         case T_CSA:
+           /* See acl_verify_csa() for more comments about CSA. */
+           GETSHORT(priority, p);
+           GETSHORT(weight, p);
+           GETSHORT(port, p);
+
+           if (priority != 1) continue;      /* CSA version must be 1 */
+
+           /* If the CSA record we found is not the one we asked for, analyse
+           the subdomain assertions in the port field, else analyse the direct
+           authorization status in the weight field. */
+
+           if (Ustrcmp(found, domain) != 0)
+             {
+             if (port & 1) *s = 'X';         /* explicit authorization required */
+             else *s = '?';                  /* no subdomain assertions here */
+             }
+           else
+             {
+             if (weight < 2) *s = 'N';       /* not authorized */
+             else if (weight == 2) *s = 'Y'; /* authorized */
+             else if (weight == 3) *s = '?'; /* unauthorizable */
+             else continue;                  /* invalid */
+             }
+
+           s[1] = ' ';
+           yield = string_catn(yield, &size, &ptr, s, 2);
+           break;
+
+         default:
+           break;
+         }
 
         /* GETSHORT() has advanced the pointer to the target domain. */
 
         rc = dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen, p,
-          (DN_EXPAND_ARG4_TYPE)(s), sizeof(s));
+          (DN_EXPAND_ARG4_TYPE)s, sizeof(s));
 
         /* If an overlong response was received, the data will have been
         truncated and dn_expand may fail. */
@@ -522,11 +526,37 @@ while ((domain = string_nextinlist(&keystring, &sep, buffer, sizeof(buffer)))
             "domain=%s", dns_text_type(type), domain);
           break;
           }
-        else yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
+        else yield = string_cat(yield, &size, &ptr, s);
+
+       if (type == T_SOA && outsep2 != NULL)
+         {
+         unsigned long serial, refresh, retry, expire, minimum;
+
+         p += rc;
+         yield = string_catn(yield, &size, &ptr, outsep2, 1);
+
+         rc = dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen, p,
+           (DN_EXPAND_ARG4_TYPE)s, sizeof(s));
+         if (rc < 0)
+           {
+           log_write(0, LOG_MAIN, "responsible-mailbox truncated: type=%s "
+             "domain=%s", dns_text_type(type), domain);
+           break;
+           }
+         else yield = string_cat(yield, &size, &ptr, s);
+
+         p += rc;
+         GETLONG(serial, p); GETLONG(refresh, p);
+         GETLONG(retry,  p); GETLONG(expire,  p); GETLONG(minimum, p);
+         sprintf(CS s, "%c%lu%c%lu%c%lu%c%lu%c%lu",
+           *outsep2, serial, *outsep2, refresh,
+           *outsep2, retry,  *outsep2, expire,  *outsep2, minimum);
+         yield = string_cat(yield, &size, &ptr, s);
+         }
         }
       }    /* Loop for list of returned records */
 
-           /* Loop for set of A-lookupu types */
+           /* Loop for set of A-lookup types */
     } while (type == T_ADDRESSES && searchtype != T_A);
 
   }        /* Loop for list of domains */
@@ -538,6 +568,8 @@ store_reset(yield + ptr + 1);
 /* If ptr == 0 we have not found anything. Otherwise, insert the terminating
 zero and return the result. */
 
+dns_retrans = save_retrans;
+dns_retry = save_retry;
 dns_init(FALSE, FALSE, FALSE); /* clear the dnssec bit for getaddrbyname */
 
 if (ptr == 0) return failrc;
index 990b69c..9f7dd8d 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* The idea for this code came from Matthew Byng-Maddick, but his original has
@@ -66,8 +66,8 @@ scanning the directory, as it is hopefully faster to let the OS do the scanning
 for us. */
 
 int
-static dsearch_find(void *handle, uschar *dirname, uschar *keystring, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+static dsearch_find(void *handle, uschar *dirname, const uschar *keystring, int length,
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 struct stat statbuf;
 int save_errno;
index 23e1dea..b29fccc 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* The code in this module was contributed by Ard Biesheuvel. */
@@ -353,14 +353,14 @@ has the password removed. This copy is also used for debugging output. */
         }
 
         if (result != NULL)
-            result = string_cat(result, &ssize, &offset, US "\n", 1);
+            result = string_catn(result, &ssize, &offset, US "\n", 1);
 
         /* Find the number of fields returned. If this is one, we don't add field
            names to the data. Otherwise we do. */
         if (out_sqlda->sqld == 1) {
             if (out_sqlda->sqlvar[0].sqlind == NULL || *out_sqlda->sqlvar[0].sqlind != -1)     /* NULL value yields nothing */
                 result =
-                    string_cat(result, &ssize, &offset, US buffer,
+                    string_catn(result, &ssize, &offset, US buffer,
                                fetch_field(buffer, sizeof(buffer),
                                            &out_sqlda->sqlvar[0]));
         }
@@ -374,19 +374,19 @@ has the password removed. This copy is also used for debugging output. */
                     string_cat(result, &ssize, &offset,
                                US out_sqlda->sqlvar[i].aliasname,
                                out_sqlda->sqlvar[i].aliasname_length);
-                result = string_cat(result, &ssize, &offset, US "=", 1);
+                result = string_catn(result, &ssize, &offset, US "=", 1);
 
                 /* Quote the value if it contains spaces or is empty */
 
                 if (*out_sqlda->sqlvar[i].sqlind == -1) {       /* NULL value */
                     result =
-                        string_cat(result, &ssize, &offset, US "\"\"", 2);
+                        string_catn(result, &ssize, &offset, US "\"\"", 2);
                 }
 
                 else if (buffer[0] == 0 || Ustrchr(buffer, ' ') != NULL) {
                     int j;
                     result =
-                        string_cat(result, &ssize, &offset, US "\"", 1);
+                        string_catn(result, &ssize, &offset, US "\"", 1);
                     for (j = 0; j < len; j++) {
                         if (buffer[j] == '\"' || buffer[j] == '\\')
                             result =
@@ -397,13 +397,12 @@ has the password removed. This copy is also used for debugging output. */
                                        US buffer + j, 1);
                     }
                     result =
-                        string_cat(result, &ssize, &offset, US "\"", 1);
+                        string_catn(result, &ssize, &offset, US "\"", 1);
                 } else {
                     result =
-                        string_cat(result, &ssize, &offset, US buffer,
-                                   len);
+                        string_catn(result, &ssize, &offset, US buffer, len);
                 }
-                result = string_cat(result, &ssize, &offset, US " ", 1);
+                result = string_catn(result, &ssize, &offset, US " ", 1);
             }
     }
 
@@ -427,7 +426,7 @@ always leaves enough room for a terminating zero. */
     if (stmth != NULL)
         isc_dsql_free_statement(status, &stmth, DSQL_drop);
 
-/* Non-NULL result indicates a sucessful result */
+/* Non-NULL result indicates a successful result */
 
     if (result != NULL) {
         *resultptr = result;
@@ -451,7 +450,7 @@ deferred with a retryable error. */
 
 static int
 ibase_find(void *handle, uschar * filename, uschar * query, int length,
-           uschar ** result, uschar ** errmsg, BOOL *do_cache)
+           uschar ** result, uschar ** errmsg, uint *do_cache)
 {
     int sep = 0;
     uschar *server;
@@ -577,7 +576,7 @@ static lookup_info _lookup_info = {
 #ifdef DYNLOOKUP
 #define ibase_lookup_module_info _lookup_module_info
 #endif
+
 static lookup_info *_lookup_list[] = { &_lookup_info };
 lookup_module_info ibase_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 };
 
index f77229d..b52ef22 100644 (file)
@@ -2,11 +2,11 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Many thanks to Stuart Lynne for contributing the original code for this
-driver. Further contibutions from Michael Haardt, Brian Candler, Barry
+driver. Further contributions from Michael Haardt, Brian Candler, Barry
 Pederson, Peter Savitch and Christian Kellner. Particular thanks to Brian for
 researching how to handle the different kinds of error. */
 
@@ -130,9 +130,10 @@ Returns:        OK or FAIL or DEFER
 */
 
 static int
-perform_ldap_search(uschar *ldap_url, uschar *server, int s_port, int search_type,
-  uschar **res, uschar **errmsg, BOOL *defer_break, uschar *user, uschar *password,
-  int sizelimit, int timelimit, int tcplimit, int dereference, void *referrals)
+perform_ldap_search(const uschar *ldap_url, uschar *server, int s_port,
+  int search_type, uschar **res, uschar **errmsg, BOOL *defer_break,
+  uschar *user, uschar *password, int sizelimit, int timelimit, int tcplimit,
+  int dereference, void *referrals)
 {
 LDAPURLDesc     *ludp = NULL;
 LDAPMessage     *result = NULL;
@@ -155,7 +156,7 @@ uschar *error1 = NULL;   /* string representation of errcode (static) */
 uschar *error2 = NULL;   /* error message from the server */
 uschar *matched = NULL;  /* partially matched DN */
 
-int    attr_count = 0;
+int    attrs_requested = 0;
 int    error_yield = DEFER;
 int    msgid;
 int    rc, ldap_rc, ldap_parse_rc;
@@ -247,7 +248,7 @@ if (host != NULL)
 /* Count the attributes; we need this later to tell us how to format results */
 
 for (attrp = USS ludp->lud_attrs; attrp != NULL && *attrp != NULL; attrp++)
-  attr_count++;
+  attrs_requested++;
 
 /* See if we can find a cached connection to this host. The port is not
 relevant for ldapi. The host name pointer is set to NULL if no host was given
@@ -579,7 +580,7 @@ if (!lcp->bound ||
   {
   DEBUG(D_lookup) debug_printf("%sbinding with user=%s password=%s\n",
     (lcp->bound)? "re-" : "", user, password);
-  if (eldap_start_tls && !lcp->is_start_tls_called)
+  if (eldap_start_tls && !lcp->is_start_tls_called && !ldapi)
     {
 #if defined(LDAP_OPT_X_TLS) && !defined(LDAP_LIB_SOLARIS)
     /* The Oracle LDAP libraries (LDAP_LIB_TYPE=SOLARIS) don't support this.
@@ -712,10 +713,15 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
         LDAP_RES_SEARCH_ENTRY)
   {
   LDAPMessage  *e;
+  int valuecount;   /* We can see an attr spread across several
+                    entries. If B is derived from A and we request
+                    A and the directory contains both, A and B,
+                    then we get two entries, one for A and one for B.
+                    Here we just count the values per entry */
 
-  DEBUG(D_lookup) debug_printf("ldap_result loop\n");
+  DEBUG(D_lookup) debug_printf("LDAP result loop\n");
 
-  for(e = ldap_first_entry(lcp->ld, result);
+  for(e = ldap_first_entry(lcp->ld, result), valuecount = 0;
       e != NULL;
       e = ldap_next_entry(lcp->ld, e))
     {
@@ -728,7 +734,7 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
 
     /* Results for multiple entries values are separated by newlines. */
 
-    if (data != NULL) data = string_cat(data, &size, &ptr, US"\n", 1);
+    if (data != NULL) data = string_catn(data, &size, &ptr, US"\n", 1);
 
     /* Get the DN from the last result. */
 
@@ -756,7 +762,7 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
       {                                  /* condition, because of the else */
       if (new_dn != NULL)                /* below, that's for the first only */
         {
-        data = string_cat(data, &size, &ptr, new_dn, Ustrlen(new_dn));
+        data = string_cat(data, &size, &ptr, new_dn);
         data[ptr] = 0;
         attribute_found = TRUE;
         }
@@ -764,15 +770,21 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
 
     /* Otherwise, loop through the entry, grabbing attribute values. If there's
     only one attribute being retrieved, no attribute name is given, and the
-    result is not quoted. Multiple values are separated by (comma, space).
+    result is not quoted. Multiple values are separated by (comma).
     If more than one attribute is being retrieved, the data is given as a
-    sequence of name=value pairs, with the value always in quotes. If there are
-    multiple values, they are given within the quotes, comma separated. */
+    sequence of name=value pairs, separated by (space), with the value always in quotes.
+    If there are multiple values, they are given within the quotes, comma separated. */
 
     else for (attr = US ldap_first_attribute(lcp->ld, e, &ber);
               attr != NULL;
               attr = US ldap_next_attribute(lcp->ld, e, ber))
       {
+      DEBUG(D_lookup) debug_printf("LDAP attr loop\n");
+
+      /* In case of attrs_requested == 1 we just count the values, in all other cases
+      (0, >1) we count the values per attribute */
+      if (attrs_requested != 1) valuecount = 0;
+
       if (attr[0] != 0)
         {
         /* Get array of values for this attribute. */
@@ -780,43 +792,51 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
         if ((firstval = values = USS ldap_get_values(lcp->ld, e, CS attr))
              != NULL)
           {
-          if (attr_count != 1)
+
+          if (attrs_requested != 1)
             {
             if (insert_space)
-              data = string_cat(data, &size, &ptr, US" ", 1);
+              data = string_catn(data, &size, &ptr, US" ", 1);
             else
               insert_space = TRUE;
-            data = string_cat(data, &size, &ptr, attr, Ustrlen(attr));
-            data = string_cat(data, &size, &ptr, US"=\"", 2);
+            data = string_cat(data, &size, &ptr, attr);
+            data = string_catn(data, &size, &ptr, US"=\"", 2);
             }
 
           while (*values != NULL)
             {
             uschar *value = *values;
             int len = Ustrlen(value);
+            ++valuecount;
+
+            DEBUG(D_lookup) debug_printf("LDAP value loop %s:%s\n", attr, value);
 
-            DEBUG(D_lookup) debug_printf("LDAP attr loop %s:%s\n", attr, value);
+            /* In case we requested one attribute only but got several times
+            into that attr loop, we need to append the additional values.
+            (This may happen if you derive attributeTypes B and C from A and
+            then query for A.) In all other cases we detect the different
+            attribute and append only every non first value. */
 
-            if (values != firstval)
-              data = string_cat(data, &size, &ptr, US",", 1);
+            if (data && valuecount > 1)
+              data = string_catn(data, &size, &ptr, US",", 1);
 
             /* For multiple attributes, the data is in quotes. We must escape
             internal quotes, backslashes, newlines, and must double commas. */
 
-            if (attr_count != 1)
+            if (attrs_requested != 1)
               {
               int j;
               for (j = 0; j < len; j++)
                 {
                 if (value[j] == '\n')
-                  data = string_cat(data, &size, &ptr, US"\\n", 2);
+                  data = string_catn(data, &size, &ptr, US"\\n", 2);
                 else if (value[j] == ',')
-                  data = string_cat(data, &size, &ptr, US",,", 2);
+                  data = string_catn(data, &size, &ptr, US",,", 2);
                 else
                   {
                   if (value[j] == '\"' || value[j] == '\\')
-                    data = string_cat(data, &size, &ptr, US"\\", 1);
-                  data = string_cat(data, &size, &ptr, value+j, 1);
+                    data = string_catn(data, &size, &ptr, US"\\", 1);
+                  data = string_catn(data, &size, &ptr, value+j, 1);
                   }
                 }
               }
@@ -827,12 +847,10 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
              {
              int j;
              for (j = 0; j < len; j++)
-               {
                if (value[j] == ',')
-                 data = string_cat(data, &size, &ptr, US",,", 2);
+                 data = string_catn(data, &size, &ptr, US",,", 2);
                else
-                 data = string_cat(data, &size, &ptr, value+j, 1);
-               }
+                 data = string_catn(data, &size, &ptr, value+j, 1);
              }
 
 
@@ -844,8 +862,8 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
 
           /* Closing quote at the end of the data for a named attribute. */
 
-          if (attr_count != 1)
-            data = string_cat(data, &size, &ptr, US"\"", 1);
+          if (attrs_requested != 1)
+            data = string_catn(data, &size, &ptr, US"\"", 1);
 
           /* Free the values */
 
@@ -1119,7 +1137,7 @@ Returns:        OK or FAIL or DEFER
 */
 
 static int
-control_ldap_search(uschar *ldap_url, int search_type, uschar **res,
+control_ldap_search(const uschar *ldap_url, int search_type, uschar **res,
   uschar **errmsg)
 {
 BOOL defer_break = FALSE;
@@ -1129,12 +1147,13 @@ int tcplimit = 0;
 int sep = 0;
 int dereference = LDAP_DEREF_NEVER;
 void* referrals = LDAP_OPT_ON;
-uschar *url = ldap_url;
-uschar *p;
+const uschar *url = ldap_url;
+const uschar *p;
 uschar *user = NULL;
 uschar *password = NULL;
 uschar *local_servers = NULL;
-uschar *server, *list;
+uschar *server;
+const uschar *list;
 uschar buffer[512];
 
 while (isspace(*url)) url++;
@@ -1146,7 +1165,7 @@ NAME has the value "ldap". */
 
 while (strncmpic(url, US"ldap", 4) != 0)
   {
-  uschar *name = url;
+  const uschar *name = url;
   while (*url != 0 && *url != '=') url++;
   if (*url == '=')
     {
@@ -1330,8 +1349,8 @@ are handled by a common function, with a flag to differentiate between them.
 The handle and filename arguments are not used. */
 
 static int
-eldap_find(void *handle, uschar *filename, uschar *ldap_url, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+eldap_find(void *handle, uschar *filename, const uschar *ldap_url, int length,
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 /* Keep picky compilers happy */
 do_cache = do_cache;
@@ -1339,8 +1358,8 @@ return(control_ldap_search(ldap_url, SEARCH_LDAP_SINGLE, result, errmsg));
 }
 
 static int
-eldapm_find(void *handle, uschar *filename, uschar *ldap_url, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+eldapm_find(void *handle, uschar *filename, const uschar *ldap_url, int length,
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 /* Keep picky compilers happy */
 do_cache = do_cache;
@@ -1348,8 +1367,8 @@ return(control_ldap_search(ldap_url, SEARCH_LDAP_MULTIPLE, result, errmsg));
 }
 
 static int
-eldapdn_find(void *handle, uschar *filename, uschar *ldap_url, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+eldapdn_find(void *handle, uschar *filename, const uschar *ldap_url, int length,
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 /* Keep picky compilers happy */
 do_cache = do_cache;
@@ -1357,8 +1376,8 @@ return(control_ldap_search(ldap_url, SEARCH_LDAP_DN, result, errmsg));
 }
 
 int
-eldapauth_find(void *handle, uschar *filename, uschar *ldap_url, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+eldapauth_find(void *handle, uschar *filename, const uschar *ldap_url, int length,
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 /* Keep picky compilers happy */
 do_cache = do_cache;
index 5ce756d..ddfda85 100644 (file)
@@ -2,12 +2,12 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Header for eldapauth_find */
 
-extern int     eldapauth_find(void *, uschar *, uschar *, int, uschar **,
+extern int     eldapauth_find(void *, uschar *, const uschar *, int, uschar **,
                  uschar **, BOOL *);
 
 /* End of lookups/ldap.h */
index 9f7c7a2..d2487d3 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Header for the functions that are shared by the lookups */
@@ -10,8 +10,9 @@
 extern int     lf_check_file(int, uschar *, int, int, uid_t *, gid_t *,
                  const char *, uschar **);
 extern uschar *lf_quote(uschar *, uschar *, int, uschar *, int *, int *);
-extern int     lf_sqlperform(uschar *, uschar *, uschar *, uschar *, uschar **,
-                 uschar **, BOOL *, int(*)(uschar *, uschar *, uschar **,
-                 uschar **, BOOL *, BOOL *));
+extern int     lf_sqlperform(const uschar *, const uschar *, const uschar *,
+                const uschar *, uschar **,
+                 uschar **, uint *, int(*)(const uschar *, uschar *, uschar **,
+                 uschar **, BOOL *, uint *));
 
 /* End of lf_functions.h */
index 60c0a76..2a76756 100644 (file)
@@ -37,7 +37,11 @@ result = string_append(result, asize, aoffset, 2, name, US"=");
 
 /* NULL is handled as an empty string */
 
-if (value == NULL) value = US"";
+if (!value)
+  {
+  value = US"";
+  vlength = 0;
+  }
 
 /* Quote the value if it is empty, contains white space, or starts with a quote
 character. */
@@ -45,21 +49,19 @@ character. */
 if (value[0] == 0 || Ustrpbrk(value, " \t\n\r") != NULL || value[0] == '\"')
   {
   int j;
-  result = string_cat(result, asize, aoffset, US"\"", 1);
+  result = string_catn(result, asize, aoffset, US"\"", 1);
   for (j = 0; j < vlength; j++)
     {
     if (value[j] == '\"' || value[j] == '\\')
-      result = string_cat(result, asize, aoffset, US"\\", 1);
-    result = string_cat(result, asize, aoffset, US value+j, 1);
+      result = string_catn(result, asize, aoffset, US"\\", 1);
+    result = string_catn(result, asize, aoffset, US value+j, 1);
     }
-  result = string_cat(result, asize, aoffset, US"\"", 1);
+  result = string_catn(result, asize, aoffset, US"\"", 1);
   }
 else
-  {
-  result = string_cat(result, asize, aoffset, US value, vlength);
-  }
+  result = string_catn(result, asize, aoffset, US value, vlength);
 
-return string_cat(result, asize, aoffset, US" ", 1);
+return string_catn(result, asize, aoffset, US" ", 1);
 }
 
 /* End of lf_quote.c */
index d430cd4..6d4f7a7 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -27,20 +27,21 @@ Arguments:
   query          the query
   result         where to pass back the result
   errmsg         where to pass back an error message
-  do_cache       to be set FALSE if data is changed
+  do_cache       to be set zero if data is changed
   func           the lookup function to call
 
 Returns:         the return from the lookup function, or DEFER
 */
 
 int
-lf_sqlperform(uschar *name, uschar *optionname, uschar *optserverlist,
-  uschar *query, uschar **result, uschar **errmsg, BOOL *do_cache,
-  int(*fn)(uschar *, uschar *, uschar **, uschar **, BOOL *, BOOL *))
+lf_sqlperform(const uschar *name, const uschar *optionname,
+  const uschar *optserverlist, const uschar *query,
+  uschar **result, uschar **errmsg, uint *do_cache,
+  int(*fn)(const uschar *, uschar *, uschar **, uschar **, BOOL *, uint *))
 {
 int sep, rc;
 uschar *server;
-uschar *serverlist;
+const uschar *serverlist;
 uschar buffer[512];
 BOOL defer_break = FALSE;
 
@@ -68,8 +69,8 @@ if (Ustrncmp(query, "servers", 7) != 0)
 else
   {
   int qsep;
-  uschar *s, *ss;
-  uschar *qserverlist;
+  const uschar *s, *ss;
+  const uschar *qserverlist;
   uschar *qserver;
   uschar qbuffer[512];
 
diff --git a/src/lookups/lmdb.c b/src/lookups/lmdb.c
new file mode 100644 (file)
index 0000000..a6888d5
--- /dev/null
@@ -0,0 +1,160 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 2016 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+#include "../exim.h"
+
+#ifdef EXPERIMENTAL_LMDB
+
+#include <lmdb.h>
+
+typedef struct lmdbstrct
+{
+MDB_txn *txn;
+MDB_dbi db_dbi;
+} Lmdbstrct;
+
+
+/*************************************************
+*              Open entry point                  *
+*************************************************/
+
+static void *
+lmdb_open(uschar * filename, uschar ** errmsg)
+{
+MDB_env * db_env = NULL;
+Lmdbstrct * lmdb_p;
+int ret, save_errno;
+const uschar * errstr;
+
+lmdb_p = store_get(sizeof(Lmdbstrct));
+lmdb_p->txn = NULL;
+
+if ((ret = mdb_env_create(&db_env)))
+  {
+  errstr = US"create environment";
+  goto bad;
+  }
+
+if ((ret = mdb_env_open(db_env, CS filename, MDB_NOSUBDIR|MDB_RDONLY, 0660)))
+  {
+  errstr = string_sprintf("open environment with %s", filename);
+  goto bad;
+  }
+
+if ((ret = mdb_txn_begin(db_env, NULL, MDB_RDONLY, &lmdb_p->txn)))
+  {
+  errstr = US"start transaction";
+  goto bad;
+  }
+
+if ((ret = mdb_open(lmdb_p->txn, NULL, 0, &lmdb_p->db_dbi)))
+  {
+  errstr = US"open database";
+  goto bad;
+  }
+
+return lmdb_p;
+
+bad:
+  save_errno = errno;
+  if (lmdb_p->txn) mdb_txn_abort(lmdb_p->txn);
+  if (db_env) mdb_env_close(db_env);
+  *errmsg = string_sprintf("LMDB: Unable to %s: %s", errstr,  mdb_strerror(ret));
+  errno = save_errno;
+  return NULL;
+}
+
+
+/*************************************************
+*              Find entry point                  *
+*************************************************/
+
+static int
+lmdb_find(void * handle, uschar * filename,
+    const uschar * keystring, int length, uschar ** result, uschar ** errmsg,
+    uint * do_cache)
+{
+int ret;
+MDB_val dbkey, data;
+Lmdbstrct * lmdb_p = handle;
+
+dbkey.mv_data = CS keystring;
+dbkey.mv_size = length;
+
+DEBUG(D_lookup) debug_printf("LMDB: lookup key: %s\n", (char *)keystring);
+
+if ((ret = mdb_get(lmdb_p->txn, lmdb_p->db_dbi, &dbkey, &data)) == 0)
+  {
+  *result = string_copyn(US data.mv_data, data.mv_size);
+  DEBUG(D_lookup) debug_printf("LMDB: lookup result: %s\n", *result);
+  return OK;
+  }
+else if (ret == MDB_NOTFOUND)
+  {
+  *errmsg = US"LMDB: lookup, no data found";
+  DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
+  return FAIL;
+  }
+else
+  {
+  *errmsg = string_sprintf("LMDB: lookup error: %s", mdb_strerror(ret));
+  DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
+  return DEFER;
+  }
+}
+
+
+/*************************************************
+*              Close entry point                 *
+*************************************************/
+
+static void
+lmdb_close(void * handle)
+{
+Lmdbstrct * lmdb_p = handle;
+MDB_env * db_env = mdb_txn_env(lmdb_p->txn);
+mdb_txn_abort(lmdb_p->txn);
+mdb_env_close(db_env);
+}
+
+
+/*************************************************
+*         Version reporting entry point          *
+*************************************************/
+
+#include "../version.h"
+
+void
+lmdb_version_report(FILE * f)
+{
+fprintf(f, "Library version: LMDB: Compile: %d.%d.%d\n",
+    MDB_VERSION_MAJOR, MDB_VERSION_MINOR, MDB_VERSION_PATCH);
+#ifdef DYNLOOKUP
+fprintf(f, "                        Exim version %s\n", EXIM_VERSION_STR);
+#endif
+}
+
+static lookup_info lmdb_lookup_info = {
+  US"lmdb",                     /* lookup name */
+  lookup_absfile,               /* query-style lookup */
+  lmdb_open,                    /* open function */
+  NULL,                         /* no check function */
+  lmdb_find,                    /* find function */
+  lmdb_close,                   /* close function */
+  NULL,                         /* tidy function */
+  NULL,                         /* quoting function */
+  lmdb_version_report           /* version reporting */
+};
+
+#ifdef DYNLOOKUP
+# define lmdb_lookup_module_info _lookup_module_info
+#endif /* DYNLOOKUP */
+
+static lookup_info *_lookup_list[] = { &lmdb_lookup_info };
+lookup_module_info lmdb_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 };
+
+#endif /* EXPERIMENTAL_LMDB */
index 537cac7..6101d00 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -71,7 +71,7 @@ fit into the fixed sized buffer. Most of the time this will never be exercised,
 but people do occasionally do weird things. */
 
 static int
-internal_lsearch_find(void *handle, uschar *filename, uschar *keystring,
+internal_lsearch_find(void *handle, uschar *filename, const uschar *keystring,
   int length, uschar **result, uschar **errmsg, int type)
 {
 FILE *f = (FILE *)handle;
@@ -146,7 +146,7 @@ for (last_was_eol = TRUE;
     uschar *t = s++;
     while (*s != 0 && *s != '\"')
       {
-      if (*s == '\\') *t++ = string_interpret_escape(&s);
+      if (*s == '\\') *t++ = string_interpret_escape(CUSS &s);
         else *t++ = *s;
       s++;
       }
@@ -181,7 +181,7 @@ for (last_was_eol = TRUE;
       {
       int rc;
       int save = buffer[linekeylength];
-      uschar *list = buffer;
+      const uschar *list = buffer;
       buffer[linekeylength] = 0;
       rc = match_isinlist(keystring,
         &list,
@@ -258,7 +258,7 @@ for (last_was_eol = TRUE;
   ptr = 0;
   yield = store_get(size);
   if (*s != 0)
-    yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
+    yield = string_cat(yield, &size, &ptr, s);
 
   /* Now handle continuations */
 
@@ -294,7 +294,7 @@ for (last_was_eol = TRUE;
 
     /* Join a physical or logical line continuation onto the result string. */
 
-    yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
+    yield = string_cat(yield, &size, &ptr, s);
     }
 
   yield[ptr] = 0;
@@ -322,8 +322,8 @@ return FAIL;
 /* See local README for interface description */
 
 static int
-lsearch_find(void *handle, uschar *filename, uschar *keystring, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+lsearch_find(void *handle, uschar *filename, const uschar *keystring, int length,
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 do_cache = do_cache;  /* Keep picky compilers happy */
 return internal_lsearch_find(handle, filename, keystring, length, result,
@@ -339,8 +339,8 @@ return internal_lsearch_find(handle, filename, keystring, length, result,
 /* See local README for interface description */
 
 static int
-wildlsearch_find(void *handle, uschar *filename, uschar *keystring, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+wildlsearch_find(void *handle, uschar *filename, const uschar *keystring, int length,
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 do_cache = do_cache;  /* Keep picky compilers happy */
 return internal_lsearch_find(handle, filename, keystring, length, result,
@@ -356,8 +356,8 @@ return internal_lsearch_find(handle, filename, keystring, length, result,
 /* See local README for interface description */
 
 static int
-nwildlsearch_find(void *handle, uschar *filename, uschar *keystring, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+nwildlsearch_find(void *handle, uschar *filename, const uschar *keystring, int length,
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 do_cache = do_cache;  /* Keep picky compilers happy */
 return internal_lsearch_find(handle, filename, keystring, length, result,
@@ -374,8 +374,8 @@ return internal_lsearch_find(handle, filename, keystring, length, result,
 /* See local README for interface description */
 
 static int
-iplsearch_find(void *handle, uschar *filename, uschar *keystring, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+iplsearch_find(void *handle, uschar *filename, const uschar *keystring, int length,
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 do_cache = do_cache;  /* Keep picky compilers happy */
 if ((length == 1 && keystring[0] == '*') ||
index 9511d2a..5cf15af 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Thanks to Paul Kelly for contributing the original code for these
@@ -74,7 +74,7 @@ Arguments:
   resultptr    where to store the result
   errmsg       where to point an error message
   defer_break  TRUE if no more servers are to be tried after DEFER
-  do_cache     set false if data is changed
+  do_cache     set zero if data is changed
 
 The server string is of the form "host/dbname/user/password". The host can be
 host:port. This string is in a nextinlist temporary buffer, so can be
@@ -84,8 +84,8 @@ Returns:       OK, FAIL, or DEFER
 */
 
 static int
-perform_mysql_search(uschar *query, uschar *server, uschar **resultptr,
-  uschar **errmsg, BOOL *defer_break, BOOL *do_cache)
+perform_mysql_search(const uschar *query, uschar *server, uschar **resultptr,
+  uschar **errmsg, BOOL *defer_break, uint *do_cache)
 {
 MYSQL *mysql_handle = NULL;        /* Keep compilers happy */
 MYSQL_RES *mysql_result = NULL;
@@ -125,42 +125,50 @@ sdata[0] = server;   /* What's left at the start */
 
 /* See if we have a cached connection to the server */
 
-for (cn = mysql_connections; cn != NULL; cn = cn->next)
-  {
+for (cn = mysql_connections; cn; cn = cn->next)
   if (Ustrcmp(cn->server, server_copy) == 0)
     {
     mysql_handle = cn->handle;
     break;
     }
-  }
 
 /* If no cached connection, we must set one up. Mysql allows for a host name
 and port to be specified. It also allows the name of a Unix socket to be used.
 Unfortunately, this contains slashes, but its use is expected to be rare, so
 the rather cumbersome syntax shouldn't inconvenience too many people. We use
-this:  host:port(socket)  where all the parts are optional. */
+this:  host:port(socket)[group]  where all the parts are optional.
+The "group" parameter specifies an option group from a MySQL option file. */
 
-if (cn == NULL)
+if (!cn)
   {
   uschar *p;
   uschar *socket = NULL;
   int port = 0;
+  uschar *group = US"exim";
+
+  if ((p = Ustrchr(sdata[0], '[')))
+    {
+    *p++ = 0;
+    group = p;
+    while (*p && *p != ']') p++;
+    *p = 0;
+    }
 
-  if ((p = Ustrchr(sdata[0], '(')) != NULL)
+  if ((p = Ustrchr(sdata[0], '(')))
     {
     *p++ = 0;
     socket = p;
-    while (*p != 0 && *p != ')') p++;
+    while (*p && *p != ')') p++;
     *p = 0;
     }
 
-  if ((p = Ustrchr(sdata[0], ':')) != NULL)
+  if ((p = Ustrchr(sdata[0], ':')))
     {
     *p++ = 0;
     port = Uatoi(p);
     }
 
-  if (Ustrchr(sdata[0], '/') != NULL)
+  if (Ustrchr(sdata[0], '/'))
     {
     *errmsg = string_sprintf("unexpected slash in MySQL server hostname: %s",
       sdata[0]);
@@ -181,6 +189,7 @@ if (cn == NULL)
 
   mysql_handle = store_get(sizeof(MYSQL));
   mysql_init(mysql_handle);
+  mysql_options(mysql_handle, MYSQL_READ_DEFAULT_GROUP, CS group);
   if (mysql_real_connect(mysql_handle,
       /*  host        user         passwd     database */
       CS sdata[0], CS sdata[2], CS sdata[3], CS sdata[1],
@@ -225,7 +234,7 @@ can be detected by calling mysql_field_count(). If its result is zero, no data
 was expected (this is all explained clearly in the MySQL manual). In this case,
 we return the number of rows affected by the command. In this event, we do NOT
 want to cache the result; also the whole cache for the handle must be cleaned
-up. Setting do_cache FALSE requests this. */
+up. Setting do_cache zero requests this. */
 
 if ((mysql_result = mysql_use_result(mysql_handle)) == NULL)
   {
@@ -233,7 +242,7 @@ if ((mysql_result = mysql_use_result(mysql_handle)) == NULL)
     {
     DEBUG(D_lookup) debug_printf("MYSQL: query was not one that returns data\n");
     result = string_sprintf("%d", mysql_affected_rows(mysql_handle));
-    *do_cache = FALSE;
+    *do_cache = 0;
     goto MYSQL_EXIT;
     }
   *errmsg = string_sprintf("MYSQL: lookup result failed: %s\n",
@@ -257,12 +266,12 @@ while ((mysql_row_data = mysql_fetch_row(mysql_result)) != NULL)
   unsigned long *lengths = mysql_fetch_lengths(mysql_result);
 
   if (result != NULL)
-      result = string_cat(result, &ssize, &offset, US"\n", 1);
+      result = string_catn(result, &ssize, &offset, US"\n", 1);
 
   if (num_fields == 1)
     {
     if (mysql_row_data[0] != NULL)    /* NULL value yields nothing */
-      result = string_cat(result, &ssize, &offset, US mysql_row_data[0],
+      result = string_catn(result, &ssize, &offset, US mysql_row_data[0],
         lengths[0]);
     }
 
@@ -313,7 +322,7 @@ it is cached. */
 
 if (mysql_result != NULL) mysql_free_result(mysql_result);
 
-/* Non-NULL result indicates a sucessful result */
+/* Non-NULL result indicates a successful result */
 
 if (result != NULL)
   {
@@ -340,8 +349,8 @@ query is deferred with a retryable error is now in a separate function that is
 shared with other SQL lookups. */
 
 static int
-mysql_find(void *handle, uschar *filename, uschar *query, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+mysql_find(void *handle, uschar *filename, const uschar *query, int length,
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 return lf_sqlperform(US"MySQL", US"mysql_servers", mysql_servers, query,
   result, errmsg, do_cache, perform_mysql_search);
index 7b012b1..278ee09 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -42,7 +42,7 @@ code. */
 
 static int
 nis_find(void *handle, uschar *filename, uschar *keystring, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 int rc;
 uschar *nis_data;
@@ -68,7 +68,7 @@ return (rc == YPERR_KEY || rc == YPERR_MAP)? FAIL : DEFER;
 
 static int
 nis0_find(void *handle, uschar *filename, uschar *keystring, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 int rc;
 uschar *nis_data;
index 8895cee..ff632a1 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -43,7 +43,7 @@ equals sign. */
 
 static int
 nisplus_find(void *handle, uschar *filename, uschar *query, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 int i;
 int ssize = 0;
@@ -156,27 +156,26 @@ for (i = 0; i < eo->en_cols.en_cols_len; i++)
 
   if (field_name == NULL)
     {
-    yield = string_cat(yield, &ssize, &offset,US  tc->tc_name,
-      Ustrlen(tc->tc_name));
-    yield = string_cat(yield, &ssize, &offset, US"=", 1);
+    yield = string_cat(yield, &ssize, &offset,US  tc->tc_name);
+    yield = string_catn(yield, &ssize, &offset, US"=", 1);
 
     /* Quote the value if it contains spaces or is empty */
 
     if (value[0] == 0 || Ustrchr(value, ' ') != NULL)
       {
       int j;
-      yield = string_cat(yield, &ssize, &offset, US"\"", 1);
+      yield = string_catn(yield, &ssize, &offset, US"\"", 1);
       for (j = 0; j < len; j++)
         {
         if (value[j] == '\"' || value[j] == '\\')
-          yield = string_cat(yield, &ssize, &offset, US"\\", 1);
-        yield = string_cat(yield, &ssize, &offset, value+j, 1);
+          yield = string_catn(yield, &ssize, &offset, US"\\", 1);
+        yield = string_catn(yield, &ssize, &offset, value+j, 1);
         }
-      yield = string_cat(yield, &ssize, &offset, US"\"", 1);
+      yield = string_catn(yield, &ssize, &offset, US"\"", 1);
       }
-    else yield = string_cat(yield, &ssize, &offset, value, len);
+    else yield = string_catn(yield, &ssize, &offset, value, len);
 
-    yield = string_cat(yield, &ssize, &offset, US" ", 1);
+    yield = string_catn(yield, &ssize, &offset, US" ", 1);
     }
 
   /* When the specified field is found, grab its data and finish */
index 1f2520a..eca15f1 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Interface to an Oracle database. This code was originally supplied by
@@ -400,12 +400,12 @@ while (cda->rc != NO_DATA_FOUND)  /* Loop for each row */
   ofetch(cda);
   if(cda->rc == NO_DATA_FOUND) break;
 
-  if (result != NULL) result = string_cat(result, &ssize, &offset, "\n", 1);
+  if (result) result = string_catn(result, &ssize, &offset, "\n", 1);
 
   /* Single field - just add on the data */
 
   if (num_fields == 1)
-    result = string_cat(result, &ssize, &offset, def[0].buf, def[0].col_retlen);
+    result = string_catn(result, &ssize, &offset, def[0].buf, def[0].col_retlen);
 
   /* Multiple fields - precede by file name, removing {lead,trail}ing WS */
 
@@ -417,8 +417,8 @@ while (cda->rc != NO_DATA_FOUND)  /* Loop for each row */
     while (*s != 0 && isspace(*s)) s++;
     slen = Ustrlen(s);
     while (slen > 0 && isspace(s[slen-1])) slen--;
-    result = string_cat(result, &ssize, &offset, s, slen);
-    result = string_cat(result, &ssize, &offset, US"=", 1);
+    result = string_catn(result, &ssize, &offset, s, slen);
+    result = string_catn(result, &ssize, &offset, US"=", 1);
 
     /* int and float type wont ever need escaping. Otherwise, quote the value
     if it contains spaces or is empty. */
@@ -427,30 +427,30 @@ while (cda->rc != NO_DATA_FOUND)  /* Loop for each row */
        (def[i].buf[0] == 0 || strchr(def[i].buf, ' ') != NULL))
       {
       int j;
-      result = string_cat(result, &ssize, &offset, "\"", 1);
+      result = string_catn(result, &ssize, &offset, "\"", 1);
       for (j = 0; j < def[i].col_retlen; j++)
         {
         if (def[i].buf[j] == '\"' || def[i].buf[j] == '\\')
-          result = string_cat(result, &ssize, &offset, "\\", 1);
-        result = string_cat(result, &ssize, &offset, def[i].buf+j, 1);
+          result = string_catn(result, &ssize, &offset, "\\", 1);
+        result = string_catn(result, &ssize, &offset, def[i].buf+j, 1);
         }
-      result = string_cat(result, &ssize, &offset, "\"", 1);
+      result = string_catn(result, &ssize, &offset, "\"", 1);
       }
 
     else switch(desc[i].dbtype)
       {
       case INT_TYPE:
       sprintf(CS tmp, "%d", def[i].int_buf);
-      result = string_cat(result, &ssize, &offset, tmp, Ustrlen(tmp));
+      result = string_cat(result, &ssize, &offset, tmp);
       break;
 
       case FLOAT_TYPE:
       sprintf(CS tmp, "%f", def[i].flt_buf);
-      result = string_cat(result, &ssize, &offset, tmp, Ustrlen(tmp));
+      result = string_cat(result, &ssize, &offset, tmp);
       break;
 
       case STRING_TYPE:
-      result = string_cat(result, &ssize, &offset, def[i].buf,
+      result = string_catn(result, &ssize, &offset, def[i].buf,
         def[i].col_retlen);
       break;
 
@@ -461,7 +461,7 @@ while (cda->rc != NO_DATA_FOUND)  /* Loop for each row */
       goto ORACLE_EXIT;
       }
 
-    result = string_cat(result, &ssize, &offset, " ", 1);
+    result = string_catn(result, &ssize, &offset, " ", 1);
     }
   }
 
@@ -490,7 +490,7 @@ oclose(cda);
 
 ORACLE_EXIT_NO_VALS:
 
-/* Non-NULL result indicates a sucessful result */
+/* Non-NULL result indicates a successful result */
 
 if (result != NULL)
   {
@@ -517,7 +517,7 @@ deferred with a retryable error. */
 
 static int
 oracle_find(void *handle, uschar *filename, uschar *query, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 int sep = 0;
 uschar *server;
index 4690cbf..315677f 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -33,8 +33,8 @@ return (void *)(-1);     /* Just return something non-null */
 /* See local README for interface description */
 
 static int
-passwd_find(void *handle, uschar *filename, uschar *keystring, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+passwd_find(void *handle, uschar *filename, const uschar *keystring, int length,
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 struct passwd *pw;
 
index 95b1b8c..d71f97b 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Thanks to Petr Cech for contributing the original code for these
@@ -97,7 +97,7 @@ configuration line for PostgreSQL via Unix domain sockets looks like this:
 hide pgsql_servers = (/tmp/.s.PGSQL.5432)/db/user/password[:<nextserver>]
 
 We enclose the path name in parentheses so that its slashes aren't visually
-confused with the delimeters for the other pgsql_server settings.
+confused with the delimiters for the other pgsql_server settings.
 
 For TCP/IP connections, the server is a host name and optional port (with a
 colon separator).
@@ -118,18 +118,18 @@ Returns:       OK, FAIL, or DEFER
 */
 
 static int
-perform_pgsql_search(uschar *query, uschar *server, uschar **resultptr,
-  uschar **errmsg, BOOL *defer_break, BOOL *do_cache)
+perform_pgsql_search(const uschar *query, uschar *server, uschar **resultptr,
+  uschar **errmsg, BOOL *defer_break, uint *do_cache)
 {
 PGconn *pg_conn = NULL;
 PGresult *pg_result = NULL;
 
 int i;
+uschar *result = NULL;
 int ssize = 0;
 int offset = 0;
 int yield = DEFER;
 unsigned int num_fields, num_tuples;
-uschar *result = NULL;
 pgsql_connection *cn;
 uschar *server_copy = NULL;
 uschar *sdata[3];
@@ -290,10 +290,10 @@ else
     /* The command was successful but did not return any data since it was
      * not SELECT but either an INSERT, UPDATE or DELETE statement. Tell the
      * high level code to not cache this query, and clean the current cache for
-     * this handle by setting *do_cache FALSE. */
+     * this handle by setting *do_cache zero. */
     result = string_copy(US PQcmdTuples(pg_result));
     offset = Ustrlen(result);
-    *do_cache = FALSE;
+    *do_cache = 0;
     DEBUG(D_lookup) debug_printf("PGSQL: command does not return any data "
       "but was successful. Rows affected: %s\n", result);
 
@@ -327,11 +327,11 @@ row, we insert '\n' between them. */
 for (i = 0; i < num_tuples; i++)
   {
   if (result != NULL)
-    result = string_cat(result, &ssize, &offset, US"\n", 1);
+    result = string_catn(result, &ssize, &offset, US"\n", 1);
 
    if (num_fields == 1)
     {
-    result = string_cat(result, &ssize, &offset,
+    result = string_catn(result, &ssize, &offset,
       US PQgetvalue(pg_result, i, 0), PQgetlength(pg_result, i, 0));
     }
 
@@ -371,7 +371,7 @@ it is cached. */
 
 if (pg_result != NULL) PQclear(pg_result);
 
-/* Non-NULL result indicates a sucessful result */
+/* Non-NULL result indicates a successful result */
 
 if (result != NULL)
   {
@@ -398,8 +398,8 @@ query is deferred with a retryable error is now in a separate function that is
 shared with other SQL lookups. */
 
 static int
-pgsql_find(void *handle, uschar *filename, uschar *query, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+pgsql_find(void *handle, uschar *filename, const uschar *query, int length,
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 return lf_sqlperform(US"PostgreSQL", US"pgsql_servers", pgsql_servers, query,
   result, errmsg, do_cache, perform_pgsql_search);
@@ -413,12 +413,6 @@ return lf_sqlperform(US"PostgreSQL", US"pgsql_servers", pgsql_servers, query,
 
 /* The characters that always need to be quoted (with backslash) are newline,
 tab, carriage return, backspace, backslash itself, and the quote characters.
-Percent and underscore are only special in contexts where they can be wild
-cards, and this isn't usually the case for data inserted from messages, since
-that isn't likely to be treated as a pattern of any kind. However, pgsql seems
-to allow escaping "on spec". If you use something like "where id="ab\%cd" it
-does treat the string as "ab%cd". So we can safely quote percent and
-underscore. [This is different to MySQL, where you can't do this.]
 
 The original code quoted single quotes as \' which is documented as valid in
 the O'Reilly book "Practical PostgreSQL" (first edition) as an alternative to
@@ -448,7 +442,7 @@ uschar *quoted;
 if (opt != NULL) return NULL;     /* No options recognized */
 
 while ((c = *t++) != 0)
-  if (Ustrchr("\n\t\r\b\'\"\\%_", c) != NULL) count++;
+  if (Ustrchr("\n\t\r\b\'\"\\", c) != NULL) count++;
 
 if (count == 0) return s;
 t = quoted = store_get(Ustrlen(s) + count + 1);
@@ -460,7 +454,7 @@ while ((c = *s++) != 0)
     *t++ = '\'';
     *t++ = '\'';
     }
-  else if (Ustrchr("\n\t\r\b\"\\%_", c) != NULL)
+  else if (Ustrchr("\n\t\r\b\"\\", c) != NULL)
     {
     *t++ = '\\';
     switch(c)
dissimilarity index 84%
index 87cc9fd..3a96f5e 100644 (file)
-/*************************************************
-*     Exim - an Internet mail transport agent    *
-*************************************************/
-
-/* Copyright (c) University of Cambridge 1995 - 2009 */
-/* See the file NOTICE for conditions of use and distribution. */
-
-#include "../exim.h"
-
-#ifdef EXPERIMENTAL_REDIS
-
-#include "lf_functions.h"
-
-#include <hiredis/hiredis.h>
-
-/* Structure and anchor for caching connections. */
-typedef struct redis_connection {
-       struct redis_connection *next;
-       uschar  *server;
-       redisContext    *handle;
-} redis_connection;
-
-static redis_connection *redis_connections = NULL;
-
-static void *
-redis_open(uschar *filename, uschar **errmsg)
-{
-       return (void *)(1);
-}
-
-void
-redis_tidy(void)
-{
-       redis_connection *cn;
-
-       /*
-        * XXX: Not sure how often this is called!
-        * Guess its called after every lookup which probably would mean to just
-        * not use the _tidy() function at all and leave with exim exiting to
-        * GC connections!
-        */
-       while ((cn = redis_connections) != NULL) {
-               redis_connections = cn->next;
-               DEBUG(D_lookup) debug_printf("close REDIS connection: %s\n", cn->server);
-               redisFree(cn->handle);
-       }
-}
-
-/* This function is called from the find entry point to do the search for a
- * single server.
- *
- *     Arguments:
- *       query        the query string
- *       server       the server string
- *       resultptr    where to store the result
- *       errmsg       where to point an error message
- *       defer_break  TRUE if no more servers are to be tried after DEFER
- *       do_cache     set false if data is changed
- *
- *     The server string is of the form "host/dbnumber/password". The host can be
- *     host:port. This string is in a nextinlist temporary buffer, so can be
- *     overwritten.
- *
- *     Returns:       OK, FAIL, or DEFER
- */
-static int
-perform_redis_search(uschar *command, uschar *server, uschar **resultptr,
-  uschar **errmsg, BOOL *defer_break, BOOL *do_cache)
-{
-       redisContext *redis_handle = NULL;        /* Keep compilers happy */
-       redisReply *redis_reply = NULL;
-       redisReply *entry = NULL;
-       redisReply *tentry = NULL;
-       redis_connection *cn;
-       int ssize = 0;
-       int offset = 0;
-       int yield = DEFER;
-       int i, j;
-       uschar *result = NULL;
-       uschar *server_copy = NULL;
-       uschar *tmp, *ttmp;
-       uschar *sdata[3];
-
-       /*
-        * Disaggregate the parameters from the server argument.
-        * The order is host:port(socket)
-        * We can write to the string, since it is in a nextinlist temporary buffer.
-        * This copy is also used for debugging output.
-        */
-        memset(sdata, 0, sizeof(sdata)) /* Set all to NULL */;
-                for (i = 2; i > 0; i--) {
-                        uschar *pp = Ustrrchr(server, '/');
-                        if (pp == NULL) {
-                                *errmsg = string_sprintf("incomplete Redis server data: %s", (i == 2) ? server : server_copy);
-                                *defer_break = TRUE;
-                                return DEFER;
-                        }
-                        *pp++ = 0;
-                        sdata[i] = pp;
-                        if (i == 2) server_copy = string_copy(server);  /* sans password */
-                }
-        sdata[0] = server;   /* What's left at the start */
-
-        /* If the database or password is an empty string, set it NULL */
-        if (sdata[1][0] == 0) sdata[1] = NULL;
-        if (sdata[2][0] == 0) sdata[2] = NULL;
-
-       /* See if we have a cached connection to the server */
-       for (cn = redis_connections; cn != NULL; cn = cn->next) {
-               if (Ustrcmp(cn->server, server_copy) == 0) {
-                       redis_handle = cn->handle;
-                       break;
-               }
-       }
-
-       if (cn == NULL) {
-               uschar *p;
-               uschar *socket = NULL;
-               int port = 0;
-               /* int redis_err = REDIS_OK; */
-
-               if ((p = Ustrchr(sdata[0], '(')) != NULL) {
-                       *p++ = 0;
-                       socket = p;
-                       while (*p != 0 && *p != ')')
-                               p++;
-                       *p = 0;
-               }
-
-               if ((p = Ustrchr(sdata[0], ':')) != NULL) {
-                       *p++ = 0;
-                       port = Uatoi(p);
-               } else {
-                       port = Uatoi("6379");
-               }
-
-               if (Ustrchr(server, '/') != NULL) {
-                       *errmsg = string_sprintf("unexpected slash in Redis server hostname: %s", sdata[0]);
-                       *defer_break = TRUE;
-                       return DEFER;
-               }
-
-               DEBUG(D_lookup)
-               debug_printf("REDIS new connection: host=%s port=%d socket=%s database=%s\n", sdata[0], port, socket, sdata[1]);
-
-               /* Get store for a new handle, initialize it, and connect to the server */
-               /* XXX: Use timeouts ? */
-               if (socket != NULL)
-                       redis_handle = redisConnectUnix(CCS socket);
-               else
-                       redis_handle = redisConnect(CCS server, port);
-               if (redis_handle == NULL) {
-                       *errmsg = string_sprintf("REDIS connection failed");
-                       *defer_break = FALSE;
-                       goto REDIS_EXIT;
-               }
-
-               /* Add the connection to the cache */
-               cn = store_get(sizeof(redis_connection));
-               cn->server = server_copy;
-               cn->handle = redis_handle;
-               cn->next = redis_connections;
-               redis_connections = cn;
-       } else {
-               DEBUG(D_lookup)
-               debug_printf("REDIS using cached connection for %s\n", server_copy);
-       }
-
-       /* Authenticate if there is a password */
-       if(sdata[2] != NULL) {
-               if ((redis_reply = redisCommand(redis_handle, "AUTH %s", sdata[2])) == NULL) {
-                       *errmsg = string_sprintf("REDIS Authentication failed: %s\n", redis_handle->errstr);
-                       *defer_break = FALSE;
-                       goto REDIS_EXIT;
-               }
-       }
-
-       /* Select the database if there is a dbnumber passed */
-       if(sdata[1] != NULL) {
-               if ((redis_reply = redisCommand(redis_handle, "SELECT %s", sdata[1])) == NULL) {
-                       *errmsg = string_sprintf("REDIS: Selecting database=%s failed: %s\n", sdata[1], redis_handle->errstr);
-                       *defer_break = FALSE;
-                       goto REDIS_EXIT;
-               } else {
-                       DEBUG(D_lookup) debug_printf("REDIS: Selecting database=%s\n", sdata[1]);
-               }
-       }
-
-       /* Run the command */
-       if ((redis_reply = redisCommand(redis_handle, CS command)) == NULL) {
-               *errmsg = string_sprintf("REDIS: query failed: %s\n", redis_handle->errstr);
-               *defer_break = FALSE;
-               goto REDIS_EXIT;
-       }
-
-       switch (redis_reply->type) {
-       case REDIS_REPLY_ERROR:
-               *errmsg = string_sprintf("REDIS: lookup result failed: %s\n", redis_reply->str);
-               *defer_break = FALSE;
-               *do_cache = FALSE;
-               goto REDIS_EXIT;
-               /* NOTREACHED */
-
-               break;
-       case REDIS_REPLY_NIL:
-               DEBUG(D_lookup) debug_printf("REDIS: query was not one that returned any data\n");
-               result = string_sprintf("");
-               *do_cache = FALSE;
-               goto REDIS_EXIT;
-               /* NOTREACHED */
-
-               break;
-       case REDIS_REPLY_INTEGER:
-               ttmp = (redis_reply->integer == 1) ? US"true" : US"false";
-               result = string_cat(result, &ssize, &offset, US ttmp, Ustrlen(ttmp));
-               break;
-       case REDIS_REPLY_STRING:
-       case REDIS_REPLY_STATUS:
-               result = string_cat(result, &ssize, &offset, US redis_reply->str, redis_reply->len);
-               break;
-       case REDIS_REPLY_ARRAY:
-
-               /* NOTE: For now support 1 nested array result. If needed a limitless result can be parsed */
-               for (i = 0; i < redis_reply->elements; i++) {
-                       entry = redis_reply->element[i];
-
-                       if (result != NULL)
-                               result = string_cat(result, &ssize, &offset, US"\n", 1);
-
-                       switch (entry->type) {
-                       case REDIS_REPLY_INTEGER:
-                               tmp = string_sprintf("%d", entry->integer);
-                               result = string_cat(result, &ssize, &offset, US tmp, Ustrlen(tmp));
-                               break;
-                       case REDIS_REPLY_STRING:
-                               result = string_cat(result, &ssize, &offset, US entry->str, entry->len);
-                               break;
-                       case REDIS_REPLY_ARRAY:
-                               for (j = 0; j < entry->elements; j++) {
-                                       tentry = entry->element[j];
-
-                                       if (result != NULL)
-                                               result = string_cat(result, &ssize, &offset, US"\n", 1);
-
-                                       switch (tentry->type) {
-                                       case REDIS_REPLY_INTEGER:
-                                               ttmp = string_sprintf("%d", tentry->integer);
-                                               result = string_cat(result, &ssize, &offset, US ttmp, Ustrlen(ttmp));
-                                               break;
-                                       case REDIS_REPLY_STRING:
-                                               result = string_cat(result, &ssize, &offset, US tentry->str, tentry->len);
-                                               break;
-                                       case REDIS_REPLY_ARRAY:
-                                               DEBUG(D_lookup) debug_printf("REDIS: result has nesting of arrays which is not supported. Ignoring!\n");
-                                               break;
-                                       default:
-                                               DEBUG(D_lookup) debug_printf("REDIS: result has unsupported type. Ignoring!\n");
-                                               break;
-                                       }
-                               }
-                               break;
-                       default:
-                               DEBUG(D_lookup) debug_printf("REDIS: query returned unsupported type\n");
-                               break;
-                       }
-               }
-               break;
-       }
-
-
-       if (result == NULL) {
-               yield = FAIL;
-               *errmsg = US"REDIS: no data found";
-       } else {
-               result[offset] = 0;
-               store_reset(result + offset + 1);
-       }
-
-    REDIS_EXIT:
-       /* Free store for any result that was got; don't close the connection, as it is cached. */
-       if (redis_reply != NULL)
-               freeReplyObject(redis_reply);
-
-       /* Non-NULL result indicates a sucessful result */
-       if (result != NULL) {
-               *resultptr = result;
-               return OK;
-       } else {
-               DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
-               /* NOTE: Required to close connection since it needs to be reopened */
-               return yield;      /* FAIL or DEFER */
-       }
-}
-
-/*************************************************
-*               Find entry point                 *
-*************************************************/
-/*
- * See local README for interface description. The handle and filename
- * arguments are not used. The code to loop through a list of servers while the
- * query is deferred with a retryable error is now in a separate function that is
- * shared with other noSQL lookups.
- */
-
-static int
-redis_find(void *handle __attribute__((unused)), uschar *filename __attribute__((unused)),
-           uschar *command, int length, uschar **result, uschar **errmsg, BOOL *do_cache)
-{
-       return lf_sqlperform(US"Redis", US"redis_servers", redis_servers, command,
-         result, errmsg, do_cache, perform_redis_search);
-}
-
-/*************************************************
-*         Version reporting entry point          *
-*************************************************/
-#include "../version.h"
-
-void
-redis_version_report(FILE *f)
-{
-       fprintf(f, "Library version: REDIS: Compile: %d [%d]\n",
-               HIREDIS_MAJOR, HIREDIS_MINOR);
-#ifdef DYNLOOKUP
-       fprintf(f, "                        Exim version %s\n", EXIM_VERSION_STR);
-#endif
-}
-
-/* These are the lookup_info blocks for this driver */
-static lookup_info redis_lookup_info = {
-  US"redis",                     /* lookup name */
-  lookup_querystyle,             /* query-style lookup */
-  redis_open,                    /* open function */
-  NULL,                          /* no check function */
-  redis_find,                    /* find function */
-  NULL,                          /* no close function */
-  redis_tidy,                    /* tidy function */
-  NULL,                                /* quoting function */
-  redis_version_report           /* version reporting */
-};
-
-#ifdef DYNLOOKUP
-#define redis_lookup_module_info _lookup_module_info
-#endif /* DYNLOOKUP */
-
-static lookup_info *_lookup_list[] = { &redis_lookup_info };
-lookup_module_info redis_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 };
-
-#endif /* EXPERIMENTAL_REDIS */
-/* End of lookups/redis.c */
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+#include "../exim.h"
+
+#ifdef LOOKUP_REDIS
+
+#include "lf_functions.h"
+
+#include <hiredis/hiredis.h>
+
+#ifndef nele
+# define nele(arr) (sizeof(arr) / sizeof(*arr))
+#endif
+
+/* Structure and anchor for caching connections. */
+typedef struct redis_connection {
+  struct redis_connection *next;
+  uschar  *server;
+  redisContext    *handle;
+} redis_connection;
+
+static redis_connection *redis_connections = NULL;
+
+
+static void *
+redis_open(uschar *filename, uschar **errmsg)
+{
+return (void *)(1);
+}
+
+
+void
+redis_tidy(void)
+{
+redis_connection *cn;
+
+/* XXX: Not sure how often this is called!
+ Guess its called after every lookup which probably would mean to just
+ not use the _tidy() function at all and leave with exim exiting to
+ GC connections!  */
+
+while ((cn = redis_connections))
+  {
+  redis_connections = cn->next;
+  DEBUG(D_lookup) debug_printf("close REDIS connection: %s\n", cn->server);
+  redisFree(cn->handle);
+  }
+}
+
+
+/* This function is called from the find entry point to do the search for a
+single server.
+
+    Arguments:
+      query        the query string
+      server       the server string
+      resultptr    where to store the result
+      errmsg       where to point an error message
+      defer_break  TRUE if no more servers are to be tried after DEFER
+      do_cache     set false if data is changed
+
+    The server string is of the form "host/dbnumber/password". The host can be
+    host:port. This string is in a nextinlist temporary buffer, so can be
+    overwritten.
+
+    Returns:       OK, FAIL, or DEFER 
+*/
+
+static int
+perform_redis_search(const uschar *command, uschar *server, uschar **resultptr,
+  uschar **errmsg, BOOL *defer_break, uint *do_cache)
+{
+redisContext *redis_handle = NULL;        /* Keep compilers happy */
+redisReply *redis_reply = NULL;
+redisReply *entry = NULL;
+redisReply *tentry = NULL;
+redis_connection *cn;
+int ssize = 0;
+int offset = 0;
+int yield = DEFER;
+int i, j;
+uschar *result = NULL;
+uschar *server_copy = NULL;
+uschar *tmp, *ttmp;
+uschar *sdata[3];
+
+/* Disaggregate the parameters from the server argument.
+The order is host:port(socket)
+We can write to the string, since it is in a nextinlist temporary buffer.
+This copy is also used for debugging output.  */
+
+memset(sdata, 0, sizeof(sdata)) /* Set all to NULL */;
+for (i = 2; i > 0; i--)
+  {
+  uschar *pp = Ustrrchr(server, '/');
+
+  if (!pp)
+    {
+    *errmsg = string_sprintf("incomplete Redis server data: %s",
+      i == 2 ? server : server_copy);
+    *defer_break = TRUE;
+    return DEFER;
+    }
+  *pp++ = 0;
+  sdata[i] = pp;
+  if (i == 2) server_copy = string_copy(server);  /* sans password */
+  }
+sdata[0] = server;   /* What's left at the start */
+
+/* If the database or password is an empty string, set it NULL */
+if (sdata[1][0] == 0) sdata[1] = NULL;
+if (sdata[2][0] == 0) sdata[2] = NULL;
+
+/* See if we have a cached connection to the server */
+
+for (cn = redis_connections; cn; cn = cn->next)
+  if (Ustrcmp(cn->server, server_copy) == 0)
+    {
+    redis_handle = cn->handle;
+    break;
+    }
+
+if (!cn)
+  {
+  uschar *p;
+  uschar *socket = NULL;
+  int port = 0;
+  /* int redis_err = REDIS_OK; */
+
+  if ((p = Ustrchr(sdata[0], '(')))
+    {
+    *p++ = 0;
+    socket = p;
+    while (*p != 0 && *p != ')') p++;
+    *p = 0;
+    }
+
+  if ((p = Ustrchr(sdata[0], ':')))
+    {
+    *p++ = 0;
+    port = Uatoi(p);
+    }
+  else
+    port = Uatoi("6379");
+
+  if (Ustrchr(server, '/'))
+    {
+    *errmsg = string_sprintf("unexpected slash in Redis server hostname: %s",
+      sdata[0]);
+    *defer_break = TRUE;
+    return DEFER;
+    }
+
+  DEBUG(D_lookup)
+    debug_printf("REDIS new connection: host=%s port=%d socket=%s database=%s\n",
+      sdata[0], port, socket, sdata[1]);
+
+  /* Get store for a new handle, initialize it, and connect to the server */
+  /* XXX: Use timeouts ? */
+  redis_handle =
+    socket ? redisConnectUnix(CCS socket) : redisConnect(CCS server, port);
+  if (!redis_handle)
+    {
+    *errmsg = string_sprintf("REDIS connection failed");
+    *defer_break = FALSE;
+    goto REDIS_EXIT;
+    }
+
+  /* Add the connection to the cache */
+  cn = store_get(sizeof(redis_connection));
+  cn->server = server_copy;
+  cn->handle = redis_handle;
+  cn->next = redis_connections;
+  redis_connections = cn;
+  }
+else
+  {
+  DEBUG(D_lookup)
+    debug_printf("REDIS using cached connection for %s\n", server_copy);
+}
+
+/* Authenticate if there is a password */
+if(sdata[2])
+  if (!(redis_reply = redisCommand(redis_handle, "AUTH %s", sdata[2])))
+    {
+    *errmsg = string_sprintf("REDIS Authentication failed: %s\n", redis_handle->errstr);
+    *defer_break = FALSE;
+    goto REDIS_EXIT;
+    }
+
+/* Select the database if there is a dbnumber passed */
+if(sdata[1])
+  {
+  if (!(redis_reply = redisCommand(redis_handle, "SELECT %s", sdata[1])))
+    {
+    *errmsg = string_sprintf("REDIS: Selecting database=%s failed: %s\n", sdata[1], redis_handle->errstr);
+    *defer_break = FALSE;
+    goto REDIS_EXIT;
+    }
+  DEBUG(D_lookup) debug_printf("REDIS: Selecting database=%s\n", sdata[1]);
+  }
+
+/* split string on whitespace into argv */
+  {
+  uschar * argv[32];
+  int i;
+  const uschar * s = command;
+  int siz, ptr;
+  uschar c;
+
+  while (isspace(*s)) s++;
+
+  for (i = 0; *s && i < nele(argv); i++)
+    {
+    for (argv[i] = NULL, siz = ptr = 0; (c = *s) && !isspace(c); s++)
+      if (c != '\\' || *++s)           /* backslash protects next char */
+       argv[i] = string_catn(argv[i], &siz, &ptr, s, 1);
+    *(argv[i]+ptr) = '\0';
+    DEBUG(D_lookup) debug_printf("REDIS: argv[%d] '%s'\n", i, argv[i]);
+    while (isspace(*s)) s++;
+    }
+
+  /* Run the command. We use the argv form rather than plain as that parses
+  into args by whitespace yet has no escaping mechanism. */
+
+  redis_reply = redisCommandArgv(redis_handle, i, (const char **) argv, NULL);
+  if (!redis_reply)
+    {
+    *errmsg = string_sprintf("REDIS: query failed: %s\n", redis_handle->errstr);
+    *defer_break = FALSE;
+    goto REDIS_EXIT;
+    }
+  }
+
+switch (redis_reply->type)
+  {
+  case REDIS_REPLY_ERROR:
+    *errmsg = string_sprintf("REDIS: lookup result failed: %s\n", redis_reply->str);
+    *defer_break = FALSE;
+    *do_cache = 0;
+    goto REDIS_EXIT;
+    /* NOTREACHED */
+
+  case REDIS_REPLY_NIL:
+    DEBUG(D_lookup)
+      debug_printf("REDIS: query was not one that returned any data\n");
+    result = string_sprintf("");
+    *do_cache = 0;
+    goto REDIS_EXIT;
+    /* NOTREACHED */
+
+  case REDIS_REPLY_INTEGER:
+    ttmp = (redis_reply->integer != 0) ? US"true" : US"false";
+    result = string_cat(result, &ssize, &offset, US ttmp);
+    break;
+
+  case REDIS_REPLY_STRING:
+  case REDIS_REPLY_STATUS:
+    result = string_catn(result, &ssize, &offset,
+                       US redis_reply->str, redis_reply->len);
+    break;
+
+  case REDIS_REPLY_ARRAY:
+    /* NOTE: For now support 1 nested array result. If needed a limitless
+    result can be parsed */
+
+    for (i = 0; i < redis_reply->elements; i++)
+      {
+      entry = redis_reply->element[i];
+
+      if (result)
+       result = string_catn(result, &ssize, &offset, US"\n", 1);
+
+      switch (entry->type)
+       {
+       case REDIS_REPLY_INTEGER:
+         tmp = string_sprintf("%d", entry->integer);
+         result = string_cat(result, &ssize, &offset, US tmp);
+         break;
+       case REDIS_REPLY_STRING:
+         result = string_catn(result, &ssize, &offset,
+                             US entry->str, entry->len);
+         break;
+       case REDIS_REPLY_ARRAY:
+         for (j = 0; j < entry->elements; j++)
+           {
+           tentry = entry->element[j];
+
+           if (result)
+             result = string_catn(result, &ssize, &offset, US"\n", 1);
+
+           switch (tentry->type)
+             {
+             case REDIS_REPLY_INTEGER:
+               ttmp = string_sprintf("%d", tentry->integer);
+               result = string_cat(result, &ssize, &offset, US ttmp);
+               break;
+             case REDIS_REPLY_STRING:
+               result = string_catn(result, &ssize, &offset,
+                                   US tentry->str, tentry->len);
+               break;
+             case REDIS_REPLY_ARRAY:
+               DEBUG(D_lookup)
+                 debug_printf("REDIS: result has nesting of arrays which"
+                   " is not supported. Ignoring!\n");
+               break;
+             default:
+               DEBUG(D_lookup) debug_printf(
+                         "REDIS: result has unsupported type. Ignoring!\n");
+               break;
+             }
+           }
+           break;
+         default:
+           DEBUG(D_lookup) debug_printf("REDIS: query returned unsupported type\n");
+           break;
+         }
+       }
+      break;
+  }
+
+
+if (result)
+  {
+  result[offset] = 0;
+  store_reset(result + offset + 1);
+  }
+else
+  {
+  yield = FAIL;
+  *errmsg = US"REDIS: no data found";
+  }
+
+REDIS_EXIT:
+
+/* Free store for any result that was got; don't close the connection,
+as it is cached. */
+
+if (redis_reply) freeReplyObject(redis_reply);
+
+/* Non-NULL result indicates a successful result */
+
+if (result)
+  {
+  *resultptr = result;
+  return OK;
+  }
+else
+  {
+  DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
+  /* NOTE: Required to close connection since it needs to be reopened */
+  return yield;      /* FAIL or DEFER */
+  }
+}
+
+
+
+/*************************************************
+*               Find entry point                 *
+*************************************************/
+/*
+ * See local README for interface description. The handle and filename
+ * arguments are not used. The code to loop through a list of servers while the
+ * query is deferred with a retryable error is now in a separate function that is
+ * shared with other noSQL lookups.
+ */
+
+static int
+redis_find(void *handle __attribute__((unused)),
+  uschar *filename __attribute__((unused)),
+  const uschar *command, int length, uschar **result, uschar **errmsg,
+  uint *do_cache)
+{
+return lf_sqlperform(US"Redis", US"redis_servers", redis_servers, command,
+  result, errmsg, do_cache, perform_redis_search);
+}
+
+
+
+/*************************************************
+*               Quote entry point                *
+*************************************************/
+
+/* Prefix any whitespace, or backslash, with a backslash.
+This is not a Redis thing but instead to let the argv splitting
+we do to split on whitespace, yet provide means for getting
+whitespace into an argument.
+
+Arguments:
+  s          the string to be quoted
+  opt        additional option text or NULL if none
+
+Returns:     the processed string or NULL for a bad option
+*/
+
+static uschar *
+redis_quote(uschar *s, uschar *opt)
+{
+register int c;
+int count = 0;
+uschar *t = s;
+uschar *quoted;
+
+if (opt) return NULL;     /* No options recognized */
+
+while ((c = *t++) != 0)
+  if (isspace(c) || c == '\\') count++;
+
+if (count == 0) return s;
+t = quoted = store_get(Ustrlen(s) + count + 1);
+
+while ((c = *s++) != 0)
+  {
+  if (isspace(c) || c == '\\') *t++ = '\\';
+  *t++ = c;
+  }
+
+*t = 0;
+return quoted;
+}
+
+
+/*************************************************
+*         Version reporting entry point          *
+*************************************************/
+#include "../version.h"
+
+void
+redis_version_report(FILE *f)
+{
+fprintf(f, "Library version: REDIS: Compile: %d [%d]\n",
+               HIREDIS_MAJOR, HIREDIS_MINOR);
+#ifdef DYNLOOKUP
+fprintf(f, "                        Exim version %s\n", EXIM_VERSION_STR);
+#endif
+}
+
+
+
+/* These are the lookup_info blocks for this driver */
+static lookup_info redis_lookup_info = {
+  US"redis",                     /* lookup name */
+  lookup_querystyle,             /* query-style lookup */
+  redis_open,                    /* open function */
+  NULL,                          /* no check function */
+  redis_find,                    /* find function */
+  NULL,                          /* no close function */
+  redis_tidy,                    /* tidy function */
+  redis_quote,                   /* quoting function */
+  redis_version_report           /* version reporting */
+};
+
+#ifdef DYNLOOKUP
+# define redis_lookup_module_info _lookup_module_info
+#endif /* DYNLOOKUP */
+
+static lookup_info *_lookup_list[] = { &redis_lookup_info };
+lookup_module_info redis_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 };
+
+#endif /* LOOKUP_REDIS */
+/* End of lookups/redis.c */
index 23ad2ad..ad56a2a 100644 (file)
@@ -13,6 +13,7 @@
  * as published by the Free Software Foundation; either version 2
  * of the License, or (at your option) any later version.
  *
+ * Copyright (c) The Exim Maintainers 2016
  */
 
 #include "../exim.h"
@@ -31,7 +32,9 @@ static void dummy(int x) { dummy2(x-1); }
 #include <spf2/spf_dns_resolv.h>
 #include <spf2/spf_dns_cache.h>
 
-static void *spf_open(uschar *filename, uschar **errmsg) {
+static void *
+spf_open(uschar *filename, uschar **errmsg)
+{
   SPF_server_t *spf_server = NULL;
   spf_server = SPF_server_new(SPF_DNS_CACHE, 0);
   if (spf_server == NULL) {
@@ -41,13 +44,17 @@ static void *spf_open(uschar *filename, uschar **errmsg) {
   return (void *) spf_server;
 }
 
-static void spf_close(void *handle) {
+static void
+spf_close(void *handle)
+{
   SPF_server_t *spf_server = handle;
   if (spf_server) SPF_server_free(spf_server);
 }
 
-static int spf_find(void *handle, uschar *filename, uschar *keystring, int key_len,
-             uschar **result, uschar **errmsg, BOOL *do_cache) {
+static int
+spf_find(void *handle, uschar *filename, const uschar *keystring, int key_len,
+             uschar **result, uschar **errmsg, uint *do_cache)
+{
   SPF_server_t *spf_server = handle;
   SPF_request_t *spf_request = NULL;
   SPF_response_t *spf_response = NULL;
index c0fc891..6e7b015 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -55,7 +55,7 @@ int i;
 /* For second and subsequent results, insert \n */
 
 if (res->string != NULL)
-  res->string = string_cat(res->string, &res->size, &res->len, US"\n", 1);
+  res->string = string_catn(res->string, &res->size, &res->len, US"\n", 1);
 
 if (argc > 1)
   {
@@ -80,8 +80,8 @@ return 0;
 
 
 static int
-sqlite_find(void *handle, uschar *filename, uschar *query, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+sqlite_find(void *handle, uschar *filename, const uschar *query, int length,
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 int ret;
 struct strbuf res = { NULL, 0, 0 };
@@ -93,7 +93,7 @@ if (ret != SQLITE_OK)
   return FAIL;
   }
 
-if (res.string == NULL) *do_cache = FALSE;
+if (res.string == NULL) *do_cache = 0;
 
 *result = res.string;
 return OK;
index 7a81795..401f7c8 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -37,8 +37,8 @@ return (void *)(1);    /* Just return something non-null */
 /* See local README for interface description. */
 
 static int
-testdb_find(void *handle, uschar *filename, uschar *query, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+testdb_find(void *handle, uschar *filename, const uschar *query, int length,
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 handle = handle;          /* Keep picky compilers happy */
 filename = filename;
@@ -57,7 +57,7 @@ if (Ustrcmp(query, "defer") == 0)
   return DEFER;
   }
 
-if (Ustrcmp(query, "nocache") == 0) *do_cache = FALSE;
+if (Ustrcmp(query, "nocache") == 0) *do_cache = 0;
 
 *result = string_copy(query);
 return OK;
index 4166089..8f065e6 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* This code originally came from Robert Wal. */
@@ -36,7 +36,7 @@ return (void *)(1);    /* Just return something non-null */
 
 static int
 whoson_find(void *handle, uschar *filename, uschar *query, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 uschar buffer[80];
 handle = handle;          /* Keep picky compilers happy */
index ab358d9..59cbd7f 100644 (file)
--- a/src/lss.c
+++ b/src/lss.c
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Support functions for calling from local_scan(). These are mostly just
@@ -27,7 +27,7 @@ Returns:         OK/FAIL/DEFER
 int
 lss_match_domain(uschar *domain, uschar *list)
 {
-return match_isinlist(domain, &list, 0, &domainlist_anchor, NULL, MCL_DOMAIN,
+return match_isinlist(CUS domain, CUSS &list, 0, &domainlist_anchor, NULL, MCL_DOMAIN,
   TRUE, NULL);
 }
 
@@ -49,7 +49,7 @@ Returns:         OK/FAIL/DEFER
 int
 lss_match_local_part(uschar *local_part, uschar *list, BOOL caseless)
 {
-return match_isinlist(local_part, &list, 0, &localpartlist_anchor, NULL,
+return match_isinlist(CUS local_part, CUSS &list, 0, &localpartlist_anchor, NULL,
   MCL_LOCALPART, caseless, NULL);
 }
 
@@ -71,7 +71,7 @@ Returns:         OK/FAIL/DEFER
 int
 lss_match_address(uschar *address, uschar *list, BOOL caseless)
 {
-return match_address_list(address, caseless, TRUE, &list, NULL, -1, 0, NULL);
+return match_address_list(CUS address, caseless, TRUE, CUSS &list, NULL, -1, 0, NULL);
 }
 
 
@@ -95,7 +95,7 @@ Returns:         OK/FAIL/DEFER
 int
 lss_match_host(uschar *host_name, uschar *host_address, uschar *list)
 {
-return verify_check_this_host(&list, NULL, host_name, host_address, NULL);
+return verify_check_this_host(CUSS &list, NULL, host_name, host_address, NULL);
 }
 
 
@@ -117,7 +117,7 @@ Returns:      a pointer to the zero-terminated base 64 string, which
 uschar *
 lss_b64encode(uschar *clear, int len)
 {
-return auth_b64encode(clear, len);
+return b64encode(clear, len);
 }
 
 /*
@@ -135,7 +135,7 @@ be interpreted as text. This is not included in the count. */
 int
 lss_b64decode(uschar *code, uschar **ptr)
 {
-return auth_b64decode(code, ptr);
+return b64decode(code, ptr);
 }
 
 
index b7dd337..2692714 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -12,6 +12,14 @@ a string as a text string. This is sometimes useful for debugging output. */
 #define mac_string(s) # s
 #define mac_expanded_string(s) mac_string(s)
 
+/* Number of elements of an array */
+#define nelem(arr) (sizeof(arr) / sizeof(*arr))
+
+/* Maximum of two items */
+#ifndef MAX
+# define MAX(a,b) ((a) > (b) ? (a) : (b))
+#endif
+
 
 /* When running in the test harness, the load average is fudged. */
 
@@ -106,6 +114,13 @@ don't make the file descriptors two-way. */
 #define DEBUG(x)      if ((debug_selector & (x)) != 0)
 #define HDEBUG(x)     if (host_checking || (debug_selector & (x)) != 0)
 
+#define PTR_CHK(ptr) \
+do { \
+if ((void *)ptr > (void *)store_get(0)) \
+  debug_printf("BUG: ptr '%s' beyond arena at %s:%d\n", \
+               mac_expanded_string(ptr), __FUNCTION__, __LINE__); \
+} while(0)
+
 /* The default From: text for DSNs */
 
 #define DEFAULT_DSN_FROM "Mail Delivery System <Mailer-Daemon@$qualify_domain>"
@@ -149,13 +164,17 @@ into big_buffer_size and in some circumstances increased. It should be at least
 as long as the maximum path length. */
 
 #if defined PATH_MAX && PATH_MAX > 16384
-#define BIG_BUFFER_SIZE PATH_MAX
+# define BIG_BUFFER_SIZE PATH_MAX
 #elif defined MAXPATHLEN && MAXPATHLEN > 16384
-#define BIG_BUFFER_SIZE MAXPATHLEN
+# define BIG_BUFFER_SIZE MAXPATHLEN
 #else
-#define BIG_BUFFER_SIZE 16384
+# define BIG_BUFFER_SIZE 16384
 #endif
 
+/* header size of pipe content
+   currently: char id, char subid, char[5] length */
+#define PIPE_HEADER_SIZE 7
+
 /* This limits the length of data returned by local_scan(). Because it is
 written on the spool, it gets read into big_buffer. */
 
@@ -180,7 +199,7 @@ record. */
 /* 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
+#ifdef SUPPORT_PROXY
 #define PROXY_NEGOTIATION_TIMEOUT_SEC 3
 #define PROXY_NEGOTIATION_TIMEOUT_USEC 0
 #endif
@@ -306,47 +325,92 @@ for having to swallow the rest of an SMTP message is whether the value is
 #define END_NOTENDED   3    /* Message reading not yet ended */
 #define END_SIZE       4    /* Reading ended because message too big */
 #define END_WERROR     5    /* Write error while reading the message */
+#define END_PROTOCOL   6    /* Protocol error in CHUNKING sequence */
+
+/* result codes for bdat_getc() (which can also return EOF) */
+
+#define EOD (-2)
+#define ERR (-3)
+
+
+/* Bit masks for debug and log selectors */
+
+/* Assume words are 32 bits wide. Tiny waste of space on 64 bit
+platforms, but this ensures bit vectors always work the same way. */
+#define BITWORDSIZE 32
+
+/* This macro is for single-word bit vectors: the debug selector,
+and the first word of the log selector. */
+#define BIT(n) (1 << (n))
+
+/* And these are for multi-word vectors. */
+#define BITWORD(n) (     (n) / BITWORDSIZE)
+#define BITMASK(n) (1 << (n) % BITWORDSIZE)
+
+#define BIT_CLEAR(s,z,n) ((s)[BITWORD(n)] &= ~BITMASK(n))
+#define BIT_SET(s,z,n)   ((s)[BITWORD(n)] |=  BITMASK(n))
+#define BIT_TEST(s,z,n) (((s)[BITWORD(n)] &   BITMASK(n)) != 0)
+
+/* Used in globals.c for initializing bit_table structures. T will be either
+D or L corresponding to the debug and log selector bits declared below. */
+
+#define BIT_TABLE(T,name) { US #name, T##i_##name }
+
+/* IOTA allows us to keep an implicit sequential count, like a simple enum,
+but we can have sequentially numbered identifiers which are not declared
+sequentially. We use this for more compact declarations of bit indexes and
+masks, alternating between sequential bit index and corresponding mask. */
+
+#define IOTA(iota)      (__LINE__ - iota)
+#define IOTA_INIT(zero) (__LINE__ - zero + 1)
+
+/* Options bits for debugging. DEBUG_BIT() declares both a bit index and the
+corresponding mask. Di_all is a special value recognized by decode_bits().
+
+Exim's code assumes in a number of places that the debug_selector is one
+word, and this is exposed in the local_scan ABI. The D_v and D_local_scan bit
+masks are part of the local_scan API so are #defined in local_scan.h */
 
-/* Options bits for debugging; D_v and D_local_scan are also in local_scan.h */
-
-#define D_v                          0x00000001
-#define D_local_scan                 0x00000002
-
-#define D_acl                        0x00000004
-#define D_auth                       0x00000008
-#define D_deliver                    0x00000010
-#define D_dns                        0x00000020
-#define D_dnsbl                      0x00000040
-#define D_exec                       0x00000080
-#define D_expand                     0x00000100
-#define D_filter                     0x00000200
-#define D_hints_lookup               0x00000400
-#define D_host_lookup                0x00000800
-#define D_ident                      0x00001000
-#define D_interface                  0x00002000
-#define D_lists                      0x00004000
-#define D_load                       0x00008000
-#define D_lookup                     0x00010000
-#define D_memory                     0x00020000
-#define D_pid                        0x00040000
-#define D_process_info               0x00080000
-#define D_queue_run                  0x00100000
-#define D_receive                    0x00200000
-#define D_resolver                   0x00400000
-#define D_retry                      0x00800000
-#define D_rewrite                    0x01000000
-#define D_route                      0x02000000
-#define D_timestamp                  0x04000000
-#define D_tls                        0x08000000
-#define D_transport                  0x10000000
-#define D_uid                        0x20000000
-#define D_verify                     0x40000000
-
-/* The D_all value must always have all bits set, as it is recognized specially
-by the function that decodes debug and log selectors. This is to enable it to
-set all the bits in a multi-word selector. Debug doesn't use this yet, but we
-are getting close. In fact, we want to omit "memory" for -d+all, but can't
-handle this here. It is fudged externally. */
+#define DEBUG_BIT(name) Di_##name = IOTA(Di_iota), D_##name = BIT(Di_##name)
+
+enum {
+  Di_all        = -1,
+  Di_v          = 0,
+  Di_local_scan = 1,
+
+  Di_iota = IOTA_INIT(2),
+  DEBUG_BIT(acl),
+  DEBUG_BIT(auth),
+  DEBUG_BIT(deliver),
+  DEBUG_BIT(dns),
+  DEBUG_BIT(dnsbl),
+  DEBUG_BIT(exec),
+  DEBUG_BIT(expand),
+  DEBUG_BIT(filter),
+  DEBUG_BIT(hints_lookup),
+  DEBUG_BIT(host_lookup),
+  DEBUG_BIT(ident),
+  DEBUG_BIT(interface),
+  DEBUG_BIT(lists),
+  DEBUG_BIT(load),
+  DEBUG_BIT(lookup),
+  DEBUG_BIT(memory),
+  DEBUG_BIT(pid),
+  DEBUG_BIT(process_info),
+  DEBUG_BIT(queue_run),
+  DEBUG_BIT(receive),
+  DEBUG_BIT(resolver),
+  DEBUG_BIT(retry),
+  DEBUG_BIT(rewrite),
+  DEBUG_BIT(route),
+  DEBUG_BIT(timestamp),
+  DEBUG_BIT(tls),
+  DEBUG_BIT(transport),
+  DEBUG_BIT(uid),
+  DEBUG_BIT(verify),
+};
+
+/* Multi-bit debug masks */
 
 #define D_all                        0xffffffff
 
@@ -366,83 +430,73 @@ handle this here. It is fudged externally. */
                                          D_timestamp   | \
                                          D_resolver))
 
-/* Options bits for logging. Those that will end up in log_write_selector have
-values < 0x80000000. They can be used in calls to log_write(). The others have
-values > 0x80000000 and are put into log_extra_selector (without the top bit).
-These are only ever tested independently. "All" is a magic value that is used
-only in the name table to set all options in both bit maps. */
-
-/* The L_all value must always have all bits set, as it is recognized specially
-by the function that decodes debug and log selectors. This is to enable it to
-set all the bits in a multi-word selector. */
-
-#define L_all                          0xffffffff
-
-#define L_address_rewrite              0x00000001
-#define L_all_parents                  0x00000002
-#define L_connection_reject            0x00000004
-#define L_delay_delivery               0x00000008
-#define L_dnslist_defer                0x00000010
-#define L_etrn                         0x00000020
-#define L_host_lookup_failed           0x00000040
-#define L_lost_incoming_connection     0x00000080
-#define L_queue_run                    0x00000100
-#define L_retry_defer                  0x00000200
-#define L_size_reject                  0x00000400
-#define L_skip_delivery                0x00000800
-#define L_smtp_connection              0x00001000
-#define L_smtp_incomplete_transaction  0x00002000
-#define L_smtp_protocol_error          0x00004000
-#define L_smtp_syntax_error            0x00008000
-
-#define LX_acl_warn_skipped            0x80000001
-#define LX_arguments                   0x80000002
-#define LX_deliver_time                0x80000004
-#define LX_delivery_size               0x80000008
-#define LX_ident_timeout               0x80000010
-#define LX_incoming_interface          0x80000020
-#define LX_incoming_port               0x80000040
-#define LX_outgoing_port               0x80000080
-#define LX_pid                         0x80000100
-#define LX_queue_time                  0x80000200
-#define LX_queue_time_overall          0x80000400
-#define LX_received_sender             0x80000800
-#define LX_received_recipients         0x80001000
-#define LX_rejected_header             0x80002000
-#define LX_return_path_on_delivery     0x80004000
-#define LX_sender_on_delivery          0x80008000
-#define LX_sender_verify_fail          0x80010000
-#define LX_smtp_confirmation           0x80020000
-#define LX_smtp_no_mail                0x80040000
-#define LX_subject                     0x80080000
-#define LX_tls_certificate_verified    0x80100000
-#define LX_tls_cipher                  0x80200000
-#define LX_tls_peerdn                  0x80400000
-#define LX_tls_sni                     0x80800000
-#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           | \
-                       L_dnslist_defer            | \
-                       L_etrn                     | \
-                       L_host_lookup_failed       | \
-                       L_lost_incoming_connection | \
-                       L_queue_run                | \
-                       L_retry_defer              | \
-                       L_size_reject              | \
-                       L_skip_delivery)
-
-#define LX_default   ((LX_acl_warn_skipped        | \
-                       LX_rejected_header         | \
-                       LX_sender_verify_fail      | \
-                       LX_smtp_confirmation       | \
-                       LX_tls_cipher) & 0x7fffffff)
+/* Options bits for logging. Those that have values < BITWORDSIZE can be used
+in calls to log_write(). The others are put into later words in log_selector
+and are only ever tested independently, so they do not need bit mask
+declarations. The Li_all value is recognized specially by decode_bits(). */
+
+#define LOG_BIT(name) Li_##name = IOTA(Li_iota), L_##name = BIT(Li_##name)
+
+enum {
+  Li_all = -1,
+
+  Li_iota = IOTA_INIT(0),
+  LOG_BIT(address_rewrite),
+  LOG_BIT(all_parents),
+  LOG_BIT(connection_reject),
+  LOG_BIT(delay_delivery),
+  LOG_BIT(dnslist_defer),
+  LOG_BIT(etrn),
+  LOG_BIT(host_lookup_failed),
+  LOG_BIT(lost_incoming_connection),
+  LOG_BIT(queue_run),
+  LOG_BIT(retry_defer),
+  LOG_BIT(size_reject),
+  LOG_BIT(skip_delivery),
+  LOG_BIT(smtp_connection),
+  LOG_BIT(smtp_incomplete_transaction),
+  LOG_BIT(smtp_protocol_error),
+  LOG_BIT(smtp_syntax_error),
+
+  Li_8bitmime = BITWORDSIZE,
+  Li_acl_warn_skipped,
+  Li_arguments,
+  Li_deliver_time,
+  Li_delivery_size,
+  Li_dnssec,
+  Li_ident_timeout,
+  Li_incoming_interface,
+  Li_incoming_port,
+  Li_outgoing_interface,
+  Li_outgoing_port,
+  Li_pid,
+  Li_proxy,
+  Li_queue_time,
+  Li_queue_time_overall,
+  Li_received_sender,
+  Li_received_recipients,
+  Li_rejected_header,
+  Li_return_path_on_delivery,
+  Li_sender_on_delivery,
+  Li_sender_verify_fail,
+  Li_smtp_confirmation,
+  Li_smtp_mailauth,
+  Li_smtp_no_mail,
+  Li_subject,
+  Li_tls_certificate_verified,
+  Li_tls_cipher,
+  Li_tls_peerdn,
+  Li_tls_sni,
+  Li_unknown_in_list,
+
+  log_selector_size = BITWORD(Li_unknown_in_list) + 1
+};
+
+#define LOGGING(opt) BIT_TEST(log_selector, log_selector_size, Li_##opt)
 
 /* Private error numbers for delivery failures, set negative so as not
-to conflict with system errno values. */
+to conflict with system errno values.  Take care to maintain the string
+table exim_errstrings[] in log.c */
 
 #define ERRNO_UNKNOWNERROR    (-1)
 #define ERRNO_USERSLASH       (-2)
@@ -475,7 +529,7 @@ to conflict with system errno values. */
 #define ERRNO_UIDFAIL        (-29)   /* Failed to get uid */
 #define ERRNO_BADTRANSPORT   (-30)   /* Unset or non-existent transport */
 #define ERRNO_MBXLENGTH      (-31)   /* MBX length mismatch */
-#define ERRNO_UNKNOWNHOST    (-32)   /* Lookup failed in smtp transport */
+#define ERRNO_UNKNOWNHOST    (-32)   /* Lookup failed routing or in smtp tpt */
 #define ERRNO_FORMATUNKNOWN  (-33)   /* Can't match format in appendfile */
 #define ERRNO_BADCREATE      (-34)   /* Creation outside home in appendfile */
 #define ERRNO_LISTDEFER      (-35)   /* Can't check a list; lookup defer */
@@ -491,6 +545,11 @@ to conflict with system errno values. */
 #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 */
+#define ERRNO_AUTHPROB       (-48)   /* Authenticator "other" failure */
+
+#ifdef SUPPORT_I18N
+# define ERRNO_UTF8_FWD      (-49)   /* target not supporting SMTPUTF8 */
+#endif
 
 /* These must be last, so all retry deferments can easily be identified */
 
@@ -500,6 +559,9 @@ to conflict with system errno values. */
 #define ERRNO_HRETRY         (-53)   /* Not time for any remote host */
 #define ERRNO_LOCAL_ONLY     (-54)   /* Local-only delivery */
 #define ERRNO_QUEUE_DOMAIN   (-55)   /* Domain in queue_domains */
+#define ERRNO_TRETRY         (-56)   /* Transport concurrency limit */
+
+
 
 /* Special actions to take after failure or deferment. */
 
@@ -623,7 +685,7 @@ can be easily tested as a group. That is the only use of opt_bool_last. */
 enum { opt_bit = 32, opt_bool_verify, opt_bool_set, opt_expand_bool,
   opt_bool_last,
   opt_rewrite, opt_timelist, opt_uid, opt_gid, opt_uidlist, opt_gidlist,
-  opt_expand_uid, opt_expand_gid, opt_void };
+  opt_expand_uid, opt_expand_gid, opt_func, opt_void };
 
 /* There's a high-ish bit which is used to flag duplicate options, kept
 for compatibility, which shouldn't be output. Also used for hidden options
@@ -737,7 +799,8 @@ most recent SMTP commands. Must be kept in step with the list of names in
 smtp_in.c that is used for creating the smtp_no_mail logging action. SCH_NONE
 is "empty". */
 
-enum { SCH_NONE, SCH_AUTH, SCH_DATA, SCH_EHLO, SCH_ETRN, SCH_EXPN, SCH_HELO,
+enum { SCH_NONE, SCH_AUTH, SCH_DATA, SCH_BDAT,
+       SCH_EHLO, SCH_ETRN, SCH_EXPN, SCH_HELO,
        SCH_HELP, SCH_MAIL, SCH_NOOP, SCH_QUIT, SCH_RCPT, SCH_RSET, SCH_STARTTLS,
        SCH_VRFY };
 
@@ -783,12 +846,12 @@ enum {
 #define topt_add_delivery_date  0x002
 #define topt_add_envelope_to    0x004
 #define topt_use_crlf           0x008  /* Terminate lines with CRLF */
-#define topt_end_dot            0x010  /* Send terminting dot line */
+#define topt_end_dot            0x010  /* Send terminating dot line */
 #define topt_no_headers         0x020  /* Omit headers */
 #define topt_no_body            0x040  /* Omit body */
 #define topt_escape_headers     0x080  /* Apply escape check to headers */
+#define topt_use_bdat          0x100  /* prepend chunks with RFC3030 BDAT header */
 
-#ifdef EXPERIMENTAL_DSN
 /* Flags for recipient_block, used in DSN support */
 
 #define rf_dsnlasthop           0x01  /* Do not propagate DSN any further */
@@ -809,7 +872,6 @@ enum {
 #define dsn_support_yes         1
 #define dsn_support_no          2
 
-#endif
 
 /* Codes for the host_find_failed and host_all_ignored options. */
 
@@ -895,4 +957,20 @@ explicit port number. */
 
 enum { FILTER_UNSET, FILTER_FORWARD, FILTER_EXIM, FILTER_SIEVE };
 
+/* Codes for ESMTP facilities offered by peer */
+
+#define PEER_OFFERED_TLS       BIT(0)
+#define PEER_OFFERED_IGNQ      BIT(1)
+#define PEER_OFFERED_PRDR      BIT(2)
+#define PEER_OFFERED_UTF8      BIT(3)
+#define PEER_OFFERED_DSN       BIT(4)
+#define PEER_OFFERED_PIPE      BIT(5)
+#define PEER_OFFERED_SIZE      BIT(6)
+#define PEER_OFFERED_CHUNKING  BIT(7)
+
+/* Argument for *_getc */
+
+#define GETC_BUFFER_UNLIMITED  UINT_MAX
+
+
 /* End of macros.h */
dissimilarity index 83%
index 04b5887..547bc26 100644 (file)
-/*************************************************
-*     Exim - an Internet mail transport agent    *
-*************************************************/
-
-/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003-2014 */
-/* License: GPL */
-
-/* Code for calling virus (malware) scanners. Called from acl.c. */
-
-#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"
-/* Maximum length of the hostname that can be specified in the clamd address list */
-#define MAX_CLAMD_ADDRESS_LENGTH 64
-#define MAX_CLAMD_ADDRESS_LENGTH_S "64"
-
-typedef struct clamd_address_container {
-  uschar tcp_addr[MAX_CLAMD_ADDRESS_LENGTH+1];
-  unsigned int tcp_port;
-} clamd_address_container;
-
-/* declaration of private routines */
-static int mksd_scan_packed(struct scan * scanent, int sock, uschar *scan_filename);
-static int malware_internal(uschar **listptr, uschar *eml_filename, BOOL faking);
-
-#ifndef nelements
-# define nelements(arr) (sizeof(arr) / sizeof(arr[0]))
-#endif
-
-
-#define        MALWARE_TIMEOUT             120
-
-
-#define DRWEBD_SCAN_CMD             (1)     /* scan file, buffer or diskfile */
-#define DRWEBD_RETURN_VIRUSES       (1<<0)   /* ask daemon return to us viruses names from report */
-#define DRWEBD_IS_MAIL              (1<<19)  /* say to daemon that format is "archive MAIL" */
-
-#define DERR_READ_ERR               (1<<0)   /* read error */
-#define DERR_NOMEMORY               (1<<2)   /* no memory */
-#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 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
-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);
-}
-
-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
-needed header variables if not already set on the command-line? */
-extern int spool_mbox_ok;
-extern uschar spooled_message_id[17];
-
-/*************************************************
-*          Scan an email for malware             *
-*************************************************/
-
-/* This is the normal interface for scanning an email, which doesn't need a
-filename; it's a wrapper around the malware_file function.
-
-Arguments:
-  listptr     the list of options to the "malware = ..." ACL condition
-
-Returns:      Exim message processing code (OK, FAIL, DEFER, ...)
-              where true means malware was found (condition applies)
-*/
-int
-malware(uschar **listptr)
-{
-  uschar * scan_filename;
-  int ret;
-
-  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;
-
-  return ret;
-}
-
-
-/*************************************************
-*          Scan a file for malware               *
-*************************************************/
-
-/* This is a test wrapper for scanning an email, which is not used in
-normal processing.  Scan any file, using the Exim scanning interface.
-This function tampers with various global variables so is unsafe to use
-in any other context.
-
-Arguments:
-  eml_filename  a file holding the message to be scanned
-
-Returns:        Exim message processing code (OK, FAIL, DEFER, ...)
-                where true means malware was found (condition applies)
-*/
-int
-malware_in_file(uschar *eml_filename)
-{
-  uschar *scan_options[2];
-  uschar message_id_buf[64];
-  int ret;
-
-  scan_options[0] = US"*";
-  scan_options[1] = NULL;
-
-  /* spool_mbox() assumes various parameters exist, when creating
-  the relevant directory and the email within */
-  (void) string_format(message_id_buf, sizeof(message_id_buf),
-      "dummy-%d", vaguely_random_number(INT_MAX));
-  message_id = message_id_buf;
-  sender_address = US"malware-sender@example.net";
-  return_path = US"";
-  recipients_list = NULL;
-  receive_add_recipient(US"malware-victim@example.net", -1);
-  enable_dollar_recipients = TRUE;
-
-  ret = malware_internal(scan_options, eml_filename, TRUE);
-
-  Ustrncpy(spooled_message_id, message_id, sizeof(spooled_message_id));
-  spool_mbox_ok = 1;
-  /* don't set no_mbox_unspool; at present, there's no way for it to become
-  set, but if that changes, then it should apply to these tests too */
-  unspool_mbox();
-
-  /* silence static analysis tools */
-  message_id = NULL;
-
-  return ret;
-}
-
-
-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              *
-*************************************************/
-
-/* This is an internal interface for scanning an email; the normal interface
-is via malware(), or there's malware_in_file() used for testing/debugging.
-
-Arguments:
-  listptr       the list of options to the "malware = ..." ACL condition
-  eml_filename  the file holding the email to be scanned
-  faking        whether or not we're faking this up for the -bmalware test
-
-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)
-{
-  int sep = 0;
-  uschar *list = *listptr;
-  uschar *av_scanner_work = av_scanner;
-  uschar *scanner_name;
-  uschar *malware_regex;
-  uschar malware_regex_default[] = ".+";
-  unsigned long mbox_size;
-  FILE *mbox_file;
-  const pcre *re;
-  uschar * errstr;
-  struct scan * scanent;
-  const uschar * scanner_options;
-  int sock = -1;
-
-  /* make sure the eml mbox file is spooled up */
-  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, NULL, 0)))
-    return FAIL;               /* empty means "don't match anything" */
-
-  /* parse 1st option */
-    if ( (strcmpic(malware_regex,US"false") == 0) ||
-       (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 */
-  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 == '$') {
-    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 (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, NULL, 0)))
-      return malware_errlog_defer(US"av_scanner configuration variable is empty");
-
-    for (scanent = m_scans; ; scanent++) {
-      if (!scanent->name)
-       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)
-      {
-      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;
-    }
-    DEBUG(D_lookup) debug_printf("Malware scan: %s\n", scanner_name);
-
-    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);
-
-       /* 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"<detected type=\"") != NULL )
-             detected = 1;
-           else if ( detected && (strhelper = Ustrstr(buf, US"<name>")) ) {
-             if ((strhelper2 = Ustrstr(buf, US"</name>")) != NULL) {
-               *strhelper2 = '\0';
-               malware_name_internal = string_copy(strhelper+6);
-             }
-           } else if ( Ustrstr(buf, US"<summary code=\"") )
-               malware_name = Ustrstr(buf, US"<summary code=\"11\">")
-                 ? malware_name_internal : NULL;
-         }
-       }
-       break;
-      }        /* f-protd */
-
-    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);
-       }
-
-       /* 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<drweb_vnum;i++)
-         {
-           int size = 0, off = 0, ovector[10*3];
-           /* read the size of report */
-           if ((bread = recv(sock, &drweb_slen, sizeof(drweb_slen), 0) != sizeof(drweb_slen)))
-             return m_errlog_defer_3(scanent,
-                               US"cannot read report size", sock);
-           drweb_slen = ntohl(drweb_slen);
-           tmpbuf = store_get(drweb_slen);
-
-           /* read report body */
-           if ((bread = recv(sock, tmpbuf, drweb_slen, 0)) != drweb_slen)
-             return m_errlog_defer_3(scanent,
-                               US"cannot read report string", sock);
-           tmpbuf[drweb_slen] = '\0';
-
-           /* try matcher on the line, grab substring */
-           result = pcre_exec(drweb_re, NULL, CS tmpbuf, Ustrlen(tmpbuf), 0, 0,
-                                   ovector, nelements(ovector));
-           if (result >= 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 (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);
-
-         /* no virus found */
-         malware_name = NULL;
-       }
-       break;
-      }        /* drweb */
-
-    case M_AVES: /* "aveserver" scanner type -------------------------------- */
-      {
-       uschar buf[32768];
-       int result;
-
-       /* 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);
-
-       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]);
-         }
-       }
-
-       /* 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 */
-
-    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" };
-
-       malware_name = NULL;
-
-       DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan [%s]\n",
-           scanner_name, scanner_options);
-
-       /* 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<bread;j++)
-           if((av_buffer[j]=='\r')||(av_buffer[j]=='\n'))
-             av_buffer[j] ='@';
-       }
-
-       /* pass the mailfile to fsecure */
-       file_name = string_sprintf("SCAN\t%s\n", eml_filename);
-
-       if (m_sock_send(sock, file_name, Ustrlen(file_name), &errstr) < 0)
-         return m_errlog_defer(scanent, errstr);
-
-       /* 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 */
-
-    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);
-       }
-
-       /* 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 */
-         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;
-
-       break;
-      }
-
-    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)));
-       }
-
-       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)));
-       }
-
-       /* 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;
-       }
-
-       (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 */
-
-    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;
-
-       break;
-      }
-
-    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.");
-       }
-
-       /* 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);
-       }
-
-       /* 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>\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 <length><data>
-         chunks, <n> 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);
-
-         /* 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. */
-
-       /* 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: -> "<filename>: <virusname> FOUND"
-         not-infected: -> "<filename>: OK"
-         error: -> "<filename>: <errcode> ERROR
-       If the ExtendedDetectionInfo option has been turned on, then we get:
-         "<filename>: <virusname>(<virushash>:<virussize>) 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;
-
-       /* 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;
-      }
-
-    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;
-
-       DEBUG(D_acl) debug_printf("Malware scan: issuing %s scan\n", scanner_name);
-
-       if ((retval = mksd_scan_packed(scanent, sock, eml_filename)) != OK) {
-         close (sock);
-         return retval;
-       }
-       break;
-      }
-    }
-
-    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 && (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
-    return FAIL;
-}
-
-
-/* simple wrapper for reading lines from sockets */
-int
-recv_line(int sock, uschar *buffer, int size)
-{
-  uschar *p = buffer;
-
-  memset(buffer,0,size);
-  /* read until \n */
-  while(recv(sock,p,1,0) > -1) {
-    if ((p-buffer) > (size-2)) break;
-    if (*p == '\n') break;
-    if (*p != '\r') p++;
-  }
-  *p = '\0';
-
-  return (p-buffer);
-}
-
-
-/* ============= private routines for the "mksd" scanner type ============== */
-
-#include <sys/uio.h>
-
-static inline int
-mksd_writev (int sock, struct iovec *iov, int iovcnt)
-{
-  int i;
-
-  for (;;) {
-    do
-      i = writev (sock, iov, iovcnt);
-    while ((i < 0) && (errno == EINTR));
-    if (i <= 0) {
-      (void) malware_errlog_defer(US"unable to write to mksd UNIX socket (/var/run/mksd/socket)");
-      return -1;
-    }
-
-    for (;;)
-      if (i >= iov->iov_len) {
-        if (--iovcnt == 0)
-          return 0;
-        i -= iov->iov_len;
-        iov++;
-      } else {
-        iov->iov_len -= i;
-        iov->iov_base = CS iov->iov_base + i;
-        break;
-      }
-  }
-}
-
-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) {
-      (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) {
-      (void) malware_errlog_defer(US"malformed reply received from mksd");
-      return -1;
-    }
-  } while (av_buffer[offset-1] != '\n');
-
-  av_buffer[offset] = '\0';
-  return offset;
-}
-
-static inline int
-mksd_parse_line(struct scan * scanent, char *line)
-{
-  char *p;
-
-  switch (*line) {
-    case 'O': /* OK */
-      return OK;
-
-    case 'E':
-    case 'A': /* ERR */
-      if ((p = strchr (line, '\n')) != NULL)
-        *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) && (line[3] == ' '))
-          if (((p = strchr (line+4, ' ')) != NULL) && ((p-line) > 4)) {
-            *p = '\0';
-            malware_name = string_copy(US line+4);
-            return OK;
-          }
-      }
-      return m_errlog_defer(scanent,
-       string_sprintf("malformed reply received: %s", line));
-  }
-}
-
-static int
-mksd_scan_packed(struct scan * scanent, int sock, uschar *scan_filename)
-{
-  struct iovec iov[3];
-  const char *cmd = "MSQ\n";
-  uschar av_buffer[1024];
-
-  iov[0].iov_base = (void *) cmd;
-  iov[0].iov_len = 3;
-  iov[1].iov_base = CS scan_filename;
-  iov[1].iov_len = Ustrlen(scan_filename);
-  iov[2].iov_base = (void *) (cmd + 3);
-  iov[2].iov_len = 1;
-
-  if (mksd_writev (sock, iov, 3) < 0)
-    return DEFER;
-
-  if (mksd_read_lines (sock, av_buffer, sizeof (av_buffer)) < 0)
-    return DEFER;
-
-  return mksd_parse_line (scanent, CS av_buffer);
-}
-
-#endif /*WITH_CONTENT_SCAN*/
-/*
- * vi: aw ai sw=2
- */
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003 - 2015
+ * License: GPL
+ * Copyright (c) The Exim Maintainers 2016
+ */
+
+/* Code for calling virus (malware) scanners. Called from acl.c. */
+
+#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, M_AVAST} 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 },
+  { M_AVAST,   US"avast",      US"/var/run/avast/scan.sock",         MC_STRM },
+  { -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"
+
+typedef struct clamd_address {
+  uschar * hostspec;
+  unsigned tcp_port;
+  unsigned retry;
+} clamd_address;
+
+#ifndef nelements
+# define nelements(arr) (sizeof(arr) / sizeof(arr[0]))
+#endif
+
+
+#define MALWARE_TIMEOUT 120    /* default timeout, seconds */
+
+
+#define DRWEBD_SCAN_CMD             (1)     /* scan file, buffer or diskfile */
+#define DRWEBD_RETURN_VIRUSES       (1<<0)   /* ask daemon return to us viruses names from report */
+#define DRWEBD_IS_MAIL              (1<<19)  /* say to daemon that format is "archive MAIL" */
+
+#define DERR_READ_ERR               (1<<0)   /* read error */
+#define DERR_NOMEMORY               (1<<2)   /* no memory */
+#define DERR_TIMEOUT                (1<<9)   /* scan timeout has run out */
+#define DERR_BAD_CALL               (1<<15)  /* wrong command */
+
+
+static const uschar * malware_regex_default = US ".+";
+static const pcre * malware_default_re = NULL;
+
+static const uschar * drweb_re_str = US "infected\\swith\\s*(.+?)$";
+static const pcre * drweb_re = NULL;
+
+static const uschar * fsec_re_str = US "\\S{0,5}INFECTED\\t[^\\t]*\\t([^\\t]+)\\t\\S*$";
+static const pcre * fsec_re = NULL;
+
+static const uschar * kav_re_sus_str = US "suspicion:\\s*(.+?)\\s*$";
+static const uschar * kav_re_inf_str = US "infected:\\s*(.+?)\\s*$";
+static const pcre * kav_re_sus = NULL;
+static const pcre * kav_re_inf = NULL;
+
+static const uschar * ava_re_clean_str = US "(?!\\\\)\\t\\[\\+\\]";
+static const uschar * ava_re_virus_str = US "(?!\\\\)\\t\\[L\\]\\d\\.\\d\\t\\d\\s(.*)";
+static const pcre * ava_re_clean = NULL;
+static const pcre * ava_re_virus = NULL;
+
+
+
+/******************************************************************************/
+
+/* 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
+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);
+}
+
+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
+needed header variables if not already set on the command-line? */
+extern int spool_mbox_ok;
+extern uschar spooled_message_id[MESSAGE_ID_LENGTH+1];
+
+
+
+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 * hostport,
+  const uschar * str)
+{
+return malware_errlog_defer(string_sprintf("%s %s : %s",
+  scanent->name, hostport ? hostport : CUS"", str));
+}
+static int
+m_errlog_defer_3(struct scan * scanent, const uschar * hostport,
+  const uschar * str, int fd_to_close)
+{
+(void) close(fd_to_close);
+return m_errlog_defer(scanent, hostport, 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_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(const 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;
+}
+
+/*
+ Simple though inefficient wrapper for reading a line.  Drop CRs and the
+ trailing newline. Can return early on buffer full. Null-terminate.
+ Apply initial timeout if no data ready.
+
+ Return: number of chars - zero for an empty line
+        -1 on EOF
+         -2 on timeout or error
+*/
+static int
+recv_line(int fd, uschar * buffer, int bsize, int tmo)
+{
+uschar * p = buffer;
+ssize_t rcv;
+BOOL ok = FALSE;
+
+if (!fd_ready(fd, tmo-time(NULL)))
+  return -2;
+
+/*XXX tmo handling assumes we always get a whole line */
+/* read until \n */
+errno = 0;
+while ((rcv = read(fd, p, 1)) > 0)
+  {
+  ok = TRUE;
+  if (p-buffer > bsize-2) break;
+  if (*p == '\n') break;
+  if (*p != '\r') p++;
+  }
+if (!ok)
+  {
+  DEBUG(D_acl) debug_printf_indent("Malware scan: read %s (%s)\n",
+               rcv==0 ? "EOF" : "error", strerror(errno));
+  return rcv==0 ? -1 : -2;
+  }
+*p = '\0';
+
+DEBUG(D_acl) debug_printf_indent("Malware scan: read '%s'\n", buffer);
+return p - buffer;
+}
+
+/* return TRUE iff size as requested */
+static BOOL
+recv_len(int sock, void * buf, int size, int tmo)
+{
+return fd_ready(sock, tmo-time(NULL))
+  ? recv(sock, buf, size, 0) == size
+  : FALSE;
+}
+
+
+
+/* ============= private routines for the "mksd" scanner type ============== */
+
+#include <sys/uio.h>
+
+static inline int
+mksd_writev (int sock, struct iovec * iov, int iovcnt)
+{
+int i;
+
+for (;;)
+  {
+  do
+    i = writev (sock, iov, iovcnt);
+  while (i < 0 && errno == EINTR);
+  if (i <= 0)
+    {
+    (void) malware_errlog_defer(
+           US"unable to write to mksd UNIX socket (/var/run/mksd/socket)");
+    return -1;
+    }
+  for (;;)     /* check for short write */
+    if (i >= iov->iov_len)
+      {
+      if (--iovcnt == 0)
+       return 0;
+      i -= iov->iov_len;
+      iov++;
+      }
+    else
+      {
+      iov->iov_len -= i;
+      iov->iov_base = CS iov->iov_base + i;
+      break;
+      }
+  }
+}
+
+static inline int
+mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size, int tmo)
+{
+int offset = 0;
+int i;
+
+do
+  {
+  i = ip_recv(sock, av_buffer+offset, av_buffer_size-offset, tmo-time(NULL));
+  if (i <= 0)
+    {
+    (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)
+    {
+    (void) malware_errlog_defer(US"malformed reply received from mksd");
+    return -1;
+    }
+  } while (av_buffer[offset-1] != '\n');
+
+av_buffer[offset] = '\0';
+return offset;
+}
+
+static inline int
+mksd_parse_line(struct scan * scanent, char * line)
+{
+char *p;
+
+switch (*line)
+  {
+  case 'O': /* OK */
+    return OK;
+
+  case 'E':
+  case 'A': /* ERR */
+    if ((p = strchr (line, '\n')) != NULL)
+      *p = '\0';
+    return m_errlog_defer(scanent, NULL,
+      string_sprintf("scanner failed: %s", line));
+
+  default: /* VIR */
+    if ((p = strchr (line, '\n')) != NULL)
+      {
+      *p = '\0';
+      if (  p-line > 5
+         && line[3] == ' '
+        && (p = strchr(line+4, ' ')) != NULL
+        && p-line > 4
+        )
+       {
+       *p = '\0';
+       malware_name = string_copy(US line+4);
+       return OK;
+       }
+      }
+    return m_errlog_defer(scanent, NULL,
+      string_sprintf("malformed reply received: %s", line));
+  }
+}
+
+static int
+mksd_scan_packed(struct scan * scanent, int sock, const uschar * scan_filename,
+  int tmo)
+{
+struct iovec iov[3];
+const char *cmd = "MSQ\n";
+uschar av_buffer[1024];
+
+iov[0].iov_base = (void *) cmd;
+iov[0].iov_len = 3;
+iov[1].iov_base = (void *) scan_filename;
+iov[1].iov_len = Ustrlen(scan_filename);
+iov[2].iov_base = (void *) (cmd + 3);
+iov[2].iov_len = 1;
+
+if (mksd_writev (sock, iov, 3) < 0)
+  return DEFER;
+
+if (mksd_read_lines (sock, av_buffer, sizeof (av_buffer), tmo) < 0)
+  return DEFER;
+
+return mksd_parse_line (scanent, CS av_buffer);
+}
+
+
+static int
+clamd_option(clamd_address * cd, const uschar * optstr, int * subsep)
+{
+uschar * s;
+
+cd->retry = 0;
+while ((s = string_nextinlist(&optstr, subsep, NULL, 0)))
+  if (Ustrncmp(s, "retry=", 6) == 0)
+    {
+    int sec = readconf_readtime((s += 6), '\0', FALSE);
+    if (sec < 0)
+      return FAIL;
+    cd->retry = sec;
+    }
+  else
+    return FAIL;
+return OK;
+}
+
+/*************************************************
+*          Scan content for malware              *
+*************************************************/
+
+/* This is an internal interface for scanning an email; the normal interface
+is via malware(), or there's malware_in_file() used for testing/debugging.
+
+Arguments:
+  malware_re    match condition for "malware="
+  eml_filename  the file holding the email to be scanned
+  timeout      if nonzero, non-default timeoutl
+  faking        whether or not we're faking this up for the -bmalware test
+
+Returns:        Exim message processing code (OK, FAIL, DEFER, ...)
+                where true means malware was found (condition applies)
+*/
+static int
+malware_internal(const uschar * malware_re, const uschar * eml_filename,
+  int timeout, BOOL faking)
+{
+int sep = 0;
+const uschar *av_scanner_work = av_scanner;
+uschar *scanner_name;
+unsigned long mbox_size;
+FILE *mbox_file;
+const pcre *re;
+uschar * errstr;
+struct scan * scanent;
+const uschar * scanner_options;
+int sock = -1;
+time_t tmo;
+
+/* make sure the eml mbox file is spooled up */
+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);
+
+if (!malware_re)
+  return FAIL;         /* empty means "don't match anything" */
+
+/* parse 1st option */
+  if ( (strcmpic(malware_re, US"false") == 0) ||
+     (Ustrcmp(malware_re,"0") == 0) )
+  return FAIL;         /* explicitly no matching */
+
+/* special cases (match anything except empty) */
+if (  strcmpic(malware_re,US"true") == 0
+   || Ustrcmp(malware_re,"*") == 0
+   || Ustrcmp(malware_re,"1") == 0
+   )
+  {
+  if (  !malware_default_re
+     && !(malware_default_re = m_pcre_compile(malware_regex_default, &errstr)))
+    return malware_errlog_defer(errstr);
+  malware_re = malware_regex_default;
+  re = malware_default_re;
+  }
+
+/* compile the regex, see if it works */
+else if (!(re = m_pcre_compile(malware_re, &errstr)))
+  return malware_errlog_defer(errstr);
+
+/* Reset sep that is set by previous string_nextinlist() call */
+sep = 0;
+
+/* if av_scanner starts with a dollar, expand it first */
+if (*av_scanner == '$')
+  {
+  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(D_acl)
+    debug_printf_indent("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 (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, NULL, 0)))
+    return malware_errlog_defer(US"av_scanner configuration variable is empty");
+  if (!timeout) timeout = MALWARE_TIMEOUT;
+  tmo = time(NULL) + timeout;
+
+  for (scanent = m_scans; ; scanent++)
+    {
+    if (!scanent->name)
+      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)
+    {
+    case MC_TCP:  sock = ip_tcpsocket(scanner_options, &errstr, 5);      break;
+    case MC_UNIX: sock = ip_unixsocket(scanner_options, &errstr);        break;
+    case MC_STRM: sock = ip_streamsocket(scanner_options, &errstr, 5);  break;
+    default: /* compiler quietening */ break;
+    }
+    if (sock < 0)
+      return m_errlog_defer(scanent, CUS callout_address, errstr);
+    break;
+  }
+  DEBUG(D_acl) debug_printf_indent("Malware scan: %s tmo %s\n", scanner_name, readconf_printtime(timeout));
+
+  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;
+      int len;
+
+      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);
+      DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s: %s\n",
+       scanner_name, scanrequest);
+
+      /* send scan request */
+      if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0)
+       return m_errlog_defer(scanent, CUS callout_address, errstr);
+
+      while ((len = recv_line(sock, buf, sizeof(buf), tmo)) >= 0)
+       if (len > 0)
+         {
+         if (Ustrstr(buf, US"<detected type=\"") != NULL)
+           detected = 1;
+         else if (detected && (strhelper = Ustrstr(buf, US"<name>")))
+           {
+           if ((strhelper2 = Ustrstr(buf, US"</name>")) != NULL)
+             {
+             *strhelper2 = '\0';
+             malware_name_internal = string_copy(strhelper+6);
+             }
+           }
+         else if (Ustrstr(buf, US"<summary code=\""))
+           {
+           malware_name = Ustrstr(buf, US"<summary code=\"11\">")
+               ? malware_name_internal : NULL;
+           break;
+           }
+         }
+      if (len < -1)
+       {
+       (void)close(sock);
+       return DEFER;
+       }
+      break;
+      }        /* f-protd */
+
+    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;
+
+      /* 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(CCS eml_filename, O_RDONLY)) == -1)
+         return m_errlog_defer_3(scanent, NULL,
+           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, NULL,
+           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, NULL,
+           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_indent("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, CUS callout_address, string_sprintf(
+           "unable to send commands to socket (%s)", scanner_options),
+           sock);
+         }
+
+       if (!(drweb_fbuf = US malloc(fsize_uint)))
+         {
+         (void)close(drweb_fd);
+         return m_errlog_defer_3(scanent, NULL,
+           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, NULL,
+           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, CUS callout_address, string_sprintf(
+           "unable to send file body to socket (%s)", scanner_options),
+           sock);
+         }
+       }
+      else
+       {
+       drweb_slen = htonl(Ustrlen(eml_filename));
+
+       DEBUG(D_acl) debug_printf_indent("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, CUS callout_address, string_sprintf(
+           "unable to send commands to socket (%s)", scanner_options),
+           sock);
+       }
+
+      /* wait for result */
+      if (!recv_len(sock, &drweb_rc, sizeof(drweb_rc), tmo))
+       return m_errlog_defer_3(scanent, CUS callout_address,
+                   US"unable to read return code", sock);
+      drweb_rc = ntohl(drweb_rc);
+
+      if (!recv_len(sock, &drweb_vnum, sizeof(drweb_vnum), tmo))
+       return m_errlog_defer_3(scanent, CUS callout_address,
+                           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 */
+       if (!drweb_re)
+         drweb_re = m_pcre_compile(drweb_re_str, &errstr);
+
+       /* read and concatenate virus names into one string */
+       for (i = 0; i < drweb_vnum; i++)
+         {
+         int size = 0, off = 0, ovector[10*3];
+         /* read the size of report */
+         if (!recv_len(sock, &drweb_slen, sizeof(drweb_slen), tmo))
+           return m_errlog_defer_3(scanent, CUS callout_address,
+                             US"cannot read report size", sock);
+         drweb_slen = ntohl(drweb_slen);
+         tmpbuf = store_get(drweb_slen);
+
+         /* read report body */
+         if (!recv_len(sock, tmpbuf, drweb_slen, tmo))
+           return m_errlog_defer_3(scanent, CUS callout_address,
+                             US"cannot read report string", sock);
+         tmpbuf[drweb_slen] = '\0';
+
+         /* try matcher on the line, grab substring */
+         result = pcre_exec(drweb_re, NULL, CS tmpbuf, Ustrlen(tmpbuf), 0, 0,
+                                 ovector, nelements(ovector));
+         if (result >= 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 (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, CUS callout_address,
+           string_sprintf("drweb daemon retcode 0x%x (%s)", drweb_rc, drweb_s),
+           sock);
+
+       /* no virus found */
+       malware_name = NULL;
+       }
+      break;
+      }        /* drweb */
+
+    case M_AVES: /* "aveserver" scanner type -------------------------------- */
+      {
+      uschar buf[32768];
+      int result;
+
+      /* read aveserver's greeting and see if it is ready (2xx greeting) */
+      buf[0] = 0;
+      recv_line(sock, buf, sizeof(buf), tmo);
+
+      if (buf[0] != '2')               /* aveserver is having problems */
+       return m_errlog_defer_3(scanent, CUS callout_address,
+         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);
+
+      /* and send it */
+      DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s %s\n",
+       scanner_name, buf);
+      if (m_sock_send(sock, buf, Ustrlen(buf), &errstr) < 0)
+       return m_errlog_defer(scanent, CUS callout_address, errstr);
+
+      malware_name = NULL;
+      result = 0;
+      /* read response lines, find malware name and final response */
+      while (recv_line(sock, buf, sizeof(buf), tmo) > 0)
+       {
+       if (buf[0] == '2')
+         break;
+       if (buf[0] == '5')              /* aveserver is having problems */
+         {
+         result = m_errlog_defer(scanent, CUS callout_address,
+            string_sprintf("unable to scan file %s (Responded: %s).",
+                            eml_filename, buf));
+         break;
+         }
+       if (Ustrncmp(buf,"322",3) == 0)
+         {
+         uschar *p = Ustrchr(&buf[4], ' ');
+         *p = '\0';
+         malware_name = string_copy(&buf[4]);
+         }
+       }
+
+      if (m_sock_send(sock, US"quit\r\n", 6, &errstr) < 0)
+       return m_errlog_defer(scanent, CUS callout_address, errstr);
+
+      /* read aveserver's greeting and see if it is ready (2xx greeting) */
+      buf[0] = 0;
+      recv_line(sock, buf, sizeof(buf), tmo);
+
+      if (buf[0] != '2')               /* aveserver is having problems */
+       return m_errlog_defer_3(scanent, CUS callout_address,
+         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 */
+
+    case M_FSEC: /* "fsecure" scanner type ---------------------------------- */
+      {
+      int i, j, bread = 0;
+      uschar * file_name;
+      uschar av_buffer[1024];
+      static uschar *cmdopt[] = { US"CONFIGURE\tARCHIVE\t1\n",
+                                     US"CONFIGURE\tTIMEOUT\t0\n",
+                                     US"CONFIGURE\tMAXARCH\t5\n",
+                                     US"CONFIGURE\tMIME\t1\n" };
+
+      malware_name = NULL;
+
+      DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s scan [%s]\n",
+         scanner_name, scanner_options);
+      /* 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, CUS callout_address, errstr);
+
+       bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL));
+       if (bread > 0) av_buffer[bread]='\0';
+       if (bread < 0)
+         return m_errlog_defer_3(scanent, CUS callout_address,
+           string_sprintf("unable to read answer %d (%s)", i, strerror(errno)),
+           sock);
+       for (j = 0; j < bread; j++)
+         if (av_buffer[j] == '\r' || av_buffer[j] == '\n')
+           av_buffer[j] ='@';
+       }
+
+      /* pass the mailfile to fsecure */
+      file_name = string_sprintf("SCAN\t%s\n", eml_filename);
+
+      if (m_sock_send(sock, file_name, Ustrlen(file_name), &errstr) < 0)
+       return m_errlog_defer(scanent, CUS callout_address, errstr);
+
+      /* set up match */
+      /* todo also SUSPICION\t */
+      if (!fsec_re)
+       fsec_re = m_pcre_compile(fsec_re_str, &errstr);
+
+      /* read report, linewise. Apply a timeout as the Fsecure daemon
+      sometimes wants an answer to "PING" but they won't tell us what */
+       {
+       uschar * p = av_buffer;
+       uschar * q;
+
+       for (;;)
+         {
+         errno = ETIMEDOUT;
+         i =  av_buffer+sizeof(av_buffer)-p;
+         if ((bread= ip_recv(sock, p, i-1, tmo-time(NULL))) < 0)
+           return m_errlog_defer_3(scanent, CUS callout_address,
+             string_sprintf("unable to read result (%s)", strerror(errno)),
+             sock);
+
+         for (p[bread] = '\0'; (q = Ustrchr(p, '\n')); p = q+1)
+           {
+           *q = '\0';
+
+           /* Really search for virus again? */
+           if (!malware_name)
+             /* try matcher on the line, grab substring */
+             malware_name = m_pcre_exec(fsec_re, p);
+
+           if (Ustrstr(p, "OK\tScan ok."))
+             goto fsec_found;
+           }
+
+         /* copy down the trailing partial line then read another chunk */
+         i =  av_buffer+sizeof(av_buffer)-p;
+         memmove(av_buffer, p, i);
+         p = av_buffer+i;
+         }
+       }
+
+      fsec_found:
+       break;
+      }        /* fsecure */
+
+    case M_KAVD: /* "kavdaemon" scanner type -------------------------------- */
+      {
+      time_t t;
+      uschar tmpbuf[1024];
+      uschar * scanrequest;
+      int kav_rc;
+      unsigned long kav_reportlen;
+      int 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_indent("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, CUS callout_address, errstr);
+
+      /* wait for result */
+      if (!recv_len(sock, tmpbuf, 2, tmo))
+       return m_errlog_defer_3(scanent, CUS callout_address,
+                           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, CUS callout_address,
+               US"please reconfigure kavdaemon to NOT disinfect or remove infected files.",
+               sock);
+      case 1:
+       return m_errlog_defer_3(scanent, CUS callout_address,
+               US"reported 'scanning not completed' (code 1).", sock);
+      case 7:
+       return m_errlog_defer_3(scanent, CUS callout_address,
+               US"reported 'kavdaemon damaged' (code 7).", sock);
+      }
+
+      /* code 8 is not handled, since it is ambiguous. 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 */
+       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 (!recv_len(sock, &kav_reportlen, 4, tmo))
+           return m_errlog_defer_3(scanent, CUS callout_address,
+                 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 */
+           if (kav_rc == 3)
+             {
+             if (!kav_re_sus) kav_re_sus = m_pcre_compile(kav_re_sus_str, &errstr);
+             kav_re = kav_re_sus;
+             }
+           else
+             {
+             if (!kav_re_inf) kav_re_inf = m_pcre_compile(kav_re_inf_str, &errstr);
+             kav_re = kav_re_inf;
+             }
+
+           /* read report, linewise.  Using size from stream to read amount of data
+           from same stream is safe enough. */
+           /* coverity[tainted_data] */
+           while (kav_reportlen > 0)
+             {
+             if ((bread = recv_line(sock, tmpbuf, sizeof(tmpbuf), tmo)) < 0)
+               break;
+             kav_reportlen -= bread+1;
+
+             /* try matcher on the line, grab substring */
+             if ((malware_name = m_pcre_exec(kav_re, tmpbuf)))
+               break;
+             }
+           }
+         }
+       }
+      else /* no virus found */
+       malware_name = NULL;
+
+      break;
+      }
+
+    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;
+      int scanner_fd;
+      FILE *scanner_record = NULL;
+      uschar linebuffer[32767];
+      int rcnt;
+      int trigger = 0;
+      uschar *p;
+
+      if (!cmdline_scanner)
+       return m_errlog_defer(scanent, NULL, 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, NULL, 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, NULL, 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_indent("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, NULL,
+         string_sprintf("call (%s) failed: %s.", commandline, strerror(err)));
+       }
+      scanner_fd = fileno(scanner_out);
+
+      file_name = string_sprintf("%s/scan/%s/%s_scanner_output",
+                               spool_directory, message_id, message_id);
+
+      if (!(scanner_record = modefopen(file_name, "wb", SPOOL_MODE)))
+       {
+       int err = errno;
+       (void) pclose(scanner_out);
+       signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe);
+       return m_errlog_defer(scanent, NULL, string_sprintf(
+           "opening scanner output file (%s) failed: %s.",
+           file_name, strerror(err)));
+       }
+
+      /* look for trigger while recording output */
+      while ((rcnt = recv_line(scanner_fd, linebuffer,
+                     sizeof(linebuffer), tmo)))
+       {
+       if (rcnt < 0)
+         {
+         int err = errno;
+         if (rcnt == -1)
+           break;
+         (void) pclose(scanner_out);
+         signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe);
+         return m_errlog_defer(scanent, NULL, string_sprintf(
+             "unable to read from scanner (%s): %s",
+             commandline, strerror(err)));
+         }
+
+       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, NULL, string_sprintf(
+           "short write on scanner output file (%s).", file_name));
+         }
+       putc('\n', scanner_record);
+       /* try trigger match */
+       if (  !trigger
+          && regex_match_and_setup(cmdline_trigger_re, linebuffer, 0, -1)
+          )
+         trigger = 1;
+       }
+
+      (void)fclose(scanner_record);
+      sep = pclose(scanner_out);
+      signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe);
+      if (sep != 0)
+         return m_errlog_defer(scanent, NULL, 
+             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 */
+
+    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_indent("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, CUS callout_address,
+         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), tmo-time(NULL))) <= 0)
+       return m_errlog_defer_3(scanent, CUS callout_address,
+         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, CUS callout_address,
+               US"scanner reported error", sock);
+      else /* all ok, no virus */
+       malware_name = NULL;
+
+      break;
+      }
+
+    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;
+      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 * cv[MAX_CLAMD_SERVERS];
+      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
+
+      /*XXX if unixdomain socket, only one server supported. Needs fixing;
+      there's no reason we should not mix local and remote servers */
+
+      if (*scanner_options == '/')
+       {
+       clamd_address * cd;
+       const uschar * sublist;
+       int subsep = ' ';
+
+       /* Local file; so we def want to use_scan_command and don't want to try
+        * passing IP/port combinations */
+       use_scan_command = TRUE;
+       cd = (clamd_address *) store_get(sizeof(clamd_address));
+
+       /* extract socket-path part */
+       sublist = scanner_options;
+       cd->hostspec = string_nextinlist(&sublist, &subsep, NULL, 0);
+
+       /* parse options */
+       if (clamd_option(cd, sublist, &subsep) != OK)
+         return m_errlog_defer(scanent, NULL,
+           string_sprintf("bad option '%s'", scanner_options));
+       cv[0] = cd;
+       }
+      else
+       {
+       /* 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 * cd;
+         const uschar * sublist;
+         int subsep = ' ';
+         uschar * s;
+
+         /* The 'local' option means use the SCAN command over the network
+          * socket (ie common file storage in use) */
+         /*XXX we could accept this also as a local option? */
+         if (strcmpic(scanner_options, US"local") == 0)
+           {
+           use_scan_command = TRUE;
+           continue;
+           }
+
+         cd = (clamd_address *) store_get(sizeof(clamd_address));
+
+         /* extract host and port part */
+         sublist = scanner_options;
+         if (!(cd->hostspec = string_nextinlist(&sublist, &subsep, NULL, 0)))
+           {
+           (void) m_errlog_defer(scanent, NULL, 
+                     string_sprintf("missing address: '%s'", scanner_options));
+           continue;
+           }
+         if (!(s = string_nextinlist(&sublist, &subsep, NULL, 0)))
+           {
+           (void) m_errlog_defer(scanent, NULL, 
+                     string_sprintf("missing port: '%s'", scanner_options));
+           continue;
+           }
+         cd->tcp_port = atoi(CS s);
+
+         /* parse options */
+         /*XXX should these options be common over scanner types? */
+         if (clamd_option(cd, sublist, &subsep) != OK)
+           return m_errlog_defer(scanent, NULL,
+             string_sprintf("bad option '%s'", scanner_options));
+
+         cv[num_servers++] = cd;
+         if (num_servers >= MAX_CLAMD_SERVERS)
+           {
+           (void) m_errlog_defer(scanent, NULL,
+                 US"More than " MAX_CLAMD_SERVERS_S " clamd servers "
+                 "specified; only using the first " MAX_CLAMD_SERVERS_S );
+           break;
+           }
+         } while ((scanner_options = string_nextinlist(&av_scanner_work, &sep,
+                                       NULL, 0)));
+
+       /* check if we have at least one server */
+       if (!num_servers)
+         return m_errlog_defer(scanent, NULL,
+           US"no useable server addresses in malware configuration option.");
+       }
+
+      /* 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, NULL,
+         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)
+         {
+         int i = random_number( num_servers );
+         clamd_address * cd = cv[i];
+
+         DEBUG(D_acl) debug_printf_indent("trying server name %s, port %u\n",
+                        cd->hostspec, cd->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) */
+         for (;;)
+           {
+           sock= m_tcpsocket(cd->hostspec, cd->tcp_port, &connhost, &errstr);
+           if (sock >= 0)
+             {
+             /* Connection successfully established with a server */
+             hostname = cd->hostspec;
+             break;
+             }
+           if (cd->retry <= 0) break;
+           while (cd->retry > 0) cd->retry = sleep(cd->retry);
+           }
+         if (sock >= 0)
+           break;
+
+         (void) m_errlog_defer(scanent, CUS callout_address, errstr);
+
+         /* Remove the server from the list. XXX We should free the memory */
+         num_servers--;
+         for (; i < num_servers; i++)
+           cv[i] = cv[i+1];
+         }
+
+       if (num_servers == 0)
+         return m_errlog_defer(scanent, NULL, US"all servers failed");
+       }
+      else
+       for (;;)
+         {
+         if ((sock = ip_unixsocket(cv[0]->hostspec, &errstr)) >= 0)
+           {
+           hostname = cv[0]->hostspec;
+           break;
+           }
+         if (cv[0]->retry <= 0)
+           return m_errlog_defer(scanent, CUS callout_address, errstr);
+         while (cv[0]->retry > 0) cv[0]->retry = sleep(cv[0]->retry);
+         }
+
+      /* 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>\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_indent(
+           "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, CUS callout_address, errstr);
+
+       memset(av_buffer2, 0, sizeof(av_buffer2));
+       bread = ip_recv(sock, av_buffer2, sizeof(av_buffer2), tmo-time(NULL));
+
+       if (bread < 0)
+         return m_errlog_defer_3(scanent, CUS callout_address,
+           string_sprintf("unable to read PORT from socket (%s)",
+               strerror(errno)),
+           sock);
+
+       if (bread == sizeof(av_buffer2))
+         return m_errlog_defer_3(scanent, CUS callout_address,
+                 "buffer too small", sock);
+
+       if (!(*av_buffer2))
+         return m_errlog_defer_3(scanent, CUS callout_address,
+                 "ClamAV returned null", sock);
+
+       av_buffer2[bread] = '\0';
+       if( sscanf(CS av_buffer2, "PORT %u\n", &port) != 1 )
+         return m_errlog_defer_3(scanent, CUS callout_address,
+           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, CUS callout_address, errstr, sock);
+
+# define CLOSE_SOCKDATA (void)close(sockData)
+#else /* WITH_OLD_CLAMAV_STREAM not defined */
+       /* New protocol: "zINSTREAM\n" followed by a sequence of <length><data>
+       chunks, <n> a 4-byte number (network order), terminated by a zero-length
+       chunk. */
+
+       DEBUG(D_acl) debug_printf_indent(
+           "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, CUS hostname,
+           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, NULL,
+           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, NULL,
+           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, NULL,
+           string_sprintf("seeking spool file %s, size overflow",
+             eml_filename),
+           sock);
+         }
+       lseek(clam_fd, 0, SEEK_SET);
+
+       if (!(clamav_fbuf = US malloc(fsize_uint)))
+         {
+         CLOSE_SOCKDATA; (void)close(clam_fd);
+         return m_errlog_defer_3(scanent, NULL,
+           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, NULL,
+           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, NULL,
+           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, NULL,
+           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_indent(
+           "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, CUS callout_address,
+           string_sprintf("unable to write to socket (%s)", strerror(errno)),
+           sock);
+
+       /* 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. */
+
+      /* Read the result */
+      memset(av_buffer, 0, sizeof(av_buffer));
+      bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL));
+      (void)close(sock);
+      sock = -1;
+
+      if (bread <= 0)
+       return m_errlog_defer(scanent, CUS callout_address,
+         string_sprintf("unable to read from socket (%s)",
+         errno == 0 ? "EOF" : strerror(errno)));
+
+      if (bread == sizeof(av_buffer))
+       return m_errlog_defer(scanent, CUS callout_address,
+               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: -> "<filename>: <virusname> FOUND"
+       not-infected: -> "<filename>: OK"
+       error: -> "<filename>: <errcode> ERROR
+      If the ExtendedDetectionInfo option has been turned on, then we get:
+       "<filename>: <virusname>(<virushash>:<virussize>) 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, CUS callout_address,
+               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_indent("Malware response: %s\n", av_buffer);
+
+      while (isspace(*--p) && (p > av_buffer))
+       *p = '\0';
+      if (*p) ++p;
+
+      /* colon in returned output? */
+      if(!(p = Ustrchr(av_buffer,':')))
+       return m_errlog_defer(scanent, CUS callout_address, 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_indent("Malware found, name \"%s\"\n", malware_name);
+
+       }
+      else if (Ustrcmp(result_tag, "ERROR") == 0)
+       return m_errlog_defer(scanent, CUS callout_address,
+         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_indent("Malware not found\n");
+
+       }
+      else
+       return m_errlog_defer(scanent, CUS callout_address,
+         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 */
+       uschar * s = Ustrchr(sockline_scanner, '%');
+       if (s++)
+         if ((*s != 's' && *s != '%') || Ustrchr(s+1, '%'))
+           return m_errlog_defer_3(scanent, NULL,
+                                 US"unsafe sock scanner call spec", sock);
+      }
+      else
+       sockline_scanner = sockline_scanner_default;
+
+      /* 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, NULL, 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, NULL, 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, CUS callout_address, errstr);
+
+      /* Read the result */
+      bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL));
+
+      if (bread <= 0)
+       return m_errlog_defer_3(scanent, CUS callout_address,
+         string_sprintf("unable to read from socket (%s)", strerror(errno)),
+         sock);
+
+      if (bread == sizeof(av_buffer))
+       return m_errlog_defer_3(scanent, CUS callout_address,
+               US"buffer too small", sock);
+      av_buffer[bread] = '\0';
+      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;
+      }
+
+    case M_MKSD: /* "mksd" scanner type ------------------------------------- */
+      {
+      char *mksd_options_end;
+      int mksd_maxproc = 1;  /* default, if no option supplied */
+      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, CUS callout_address,
+           string_sprintf("invalid option '%s'", scanner_options));
+       }
+
+      if((sock = ip_unixsocket(US "/var/run/mksd/socket", &errstr)) < 0)
+       return m_errlog_defer(scanent, CUS callout_address, errstr);
+
+      malware_name = NULL;
+
+      DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s scan\n", scanner_name);
+
+      if ((retval = mksd_scan_packed(scanent, sock, eml_filename, tmo)) != OK)
+       {
+       close (sock);
+       return retval;
+       }
+      break;
+      }
+
+    case M_AVAST: /* "avast" scanner type ----------------------------------- */
+      {
+      int ovector[1*3];
+      uschar buf[1024];
+      uschar * scanrequest;
+      enum {AVA_HELO, AVA_OPT, AVA_RSP, AVA_DONE} avast_stage;
+      int nread;
+
+      /* According to Martin Tuma @avast the protocol uses "escaped
+      whitespace", that is, every embedded whitespace is backslash
+      escaped, as well as backslash is protected by backslash.
+      The returned lines contain the name of the scanned file, a tab
+      and the [ ] marker.
+      [+] - not infected
+      [L] - infected
+      [E] - some error occured
+      Such marker follows the first non-escaped TAB.  */
+      if (  (  !ava_re_clean
+            && !(ava_re_clean = m_pcre_compile(ava_re_clean_str, &errstr)))
+        || (  !ava_re_virus
+           && !(ava_re_virus = m_pcre_compile(ava_re_virus_str, &errstr)))
+        )
+       return malware_errlog_defer(errstr);
+
+      /* wait for result */
+      for (avast_stage = AVA_HELO;
+          (nread = recv_line(sock, buf, sizeof(buf), tmo)) > 0;
+         )
+       {
+       int slen = Ustrlen(buf);
+       if (slen >= 1)
+         {
+         DEBUG(D_acl) debug_printf_indent("got from avast: %s\n", buf);
+         switch (avast_stage)
+           {
+           case AVA_HELO:
+             if (Ustrncmp(buf, "220", 3) != 0)
+               goto endloop;                   /* require a 220 */
+             goto sendreq;
+
+           case AVA_OPT:
+             if (Ustrncmp(buf, "210", 3) == 0)
+               break;                          /* ignore 210 responses */
+             if (Ustrncmp(buf, "200", 3) != 0)
+               goto endloop;                   /* require a 200 */
+
+           sendreq:
+             {
+             int len;
+             /* Check for another option to send. Newline-terminate it. */
+             if ((scanrequest = string_nextinlist(&av_scanner_work, &sep,
+                               NULL, 0)))
+               {
+               scanrequest = string_sprintf("%s\n", scanrequest);
+               avast_stage = AVA_OPT;          /* just sent option */
+               }
+             else
+               {
+               scanrequest = string_sprintf("SCAN %s/scan/%s\n",
+                   spool_directory, message_id);
+               avast_stage = AVA_RSP;          /* just sent command */
+               }
+
+             /* send config-cmd or scan-request to socket */
+             len = Ustrlen(scanrequest);
+             if (send(sock, scanrequest, len, 0) < 0)
+               {
+               scanrequest[len-1] = '\0';
+               return m_errlog_defer_3(scanent, CUS callout_address, string_sprintf(
+                     "unable to send request '%s' to socket (%s): %s",
+                     scanrequest, scanner_options, strerror(errno)), sock);
+               }
+             break;
+             }
+
+           case AVA_RSP:
+             if (Ustrncmp(buf, "210", 3) == 0)
+               break;  /* ignore the "210 SCAN DATA" message */
+
+             if (pcre_exec(ava_re_clean, NULL, CS buf, slen,
+                   0, 0, ovector, nelements(ovector)) > 0)
+               break;
+
+             if ((malware_name = m_pcre_exec(ava_re_virus, buf)))
+               { /* remove backslash in front of [whitespace|backslash] */
+               uschar * p, * p0;
+               for (p = malware_name; *p; ++p)
+                 if (*p == '\\' && (isspace(p[1]) || p[1] == '\\'))
+                   for (p0 = p; *p0; ++p0) *p0 = p0[1];
+
+               avast_stage = AVA_DONE;
+               goto endloop;
+               }
+
+             if (Ustrncmp(buf, "200 SCAN OK", 11) == 0)
+               { /* we're done finally */
+               if (send(sock, "QUIT\n", 5, 0) < 0) /* courtesy */
+                 return m_errlog_defer_3(scanent, CUS callout_address,
+                         string_sprintf(
+                             "unable to send quit request to socket (%s): %s",
+                             scanner_options, strerror(errno)),
+                             sock);
+               malware_name = NULL;
+               avast_stage = AVA_DONE;
+               goto endloop;
+               }
+
+             /* here for any unexpected response from the scanner */
+             goto endloop;
+
+           case AVA_DONE:      log_write(0, LOG_PANIC, "%s:%d:%s: should not happen",
+                           __FILE__, __LINE__, __FUNCTION__);
+           }
+         }
+       }
+      endloop:
+
+      switch(avast_stage)
+       {
+       case AVA_HELO:
+       case AVA_OPT:
+       case AVA_RSP:   return m_errlog_defer_3(scanent, CUS callout_address,
+                           nread >= 0
+                           ? string_sprintf(
+                               "invalid response from scanner: '%s'", buf)
+                           : nread == -1
+                           ? US"EOF from scanner"
+                           : US"timeout from scanner",
+                         sock);
+       default:        break;
+       }
+      }
+      break;
+  }    /* scanner type switch */
+
+  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 && regex_match_and_setup(re, malware_name, 0, -1))
+  {
+  DEBUG(D_acl) debug_printf_indent(
+      "Matched regex to malware [%s] [%s]\n", malware_re, malware_name);
+  return OK;
+  }
+else
+  return FAIL;
+}
+
+
+/*************************************************
+*          Scan an email for malware             *
+*************************************************/
+
+/* This is the normal interface for scanning an email, which doesn't need a
+filename; it's a wrapper around the malware_file function.
+
+Arguments:
+  malware_re  match condition for "malware="
+  timeout     if nonzero, timeout in seconds
+
+Returns:      Exim message processing code (OK, FAIL, DEFER, ...)
+              where true means malware was found (condition applies)
+*/
+int
+malware(const uschar * malware_re, int timeout)
+{
+uschar * scan_filename;
+int ret;
+
+scan_filename = string_sprintf("%s/scan/%s/%s.eml",
+                 spool_directory, message_id, message_id);
+ret = malware_internal(malware_re, scan_filename, timeout, FALSE);
+if (ret == DEFER) av_failed = TRUE;
+
+return ret;
+}
+
+
+/*************************************************
+*          Scan a file for malware               *
+*************************************************/
+
+/* This is a test wrapper for scanning an email, which is not used in
+normal processing.  Scan any file, using the Exim scanning interface.
+This function tampers with various global variables so is unsafe to use
+in any other context.
+
+Arguments:
+  eml_filename  a file holding the message to be scanned
+
+Returns:        Exim message processing code (OK, FAIL, DEFER, ...)
+                where true means malware was found (condition applies)
+*/
+int
+malware_in_file(uschar *eml_filename)
+{
+uschar message_id_buf[64];
+int ret;
+
+/* spool_mbox() assumes various parameters exist, when creating
+the relevant directory and the email within */
+
+(void) string_format(message_id_buf, sizeof(message_id_buf),
+    "dummy-%d", vaguely_random_number(INT_MAX));
+message_id = message_id_buf;
+sender_address = US"malware-sender@example.net";
+return_path = US"";
+recipients_list = NULL;
+receive_add_recipient(US"malware-victim@example.net", -1);
+enable_dollar_recipients = TRUE;
+
+ret = malware_internal(US"*", eml_filename, 0,  TRUE);
+
+Ustrncpy(spooled_message_id, message_id, sizeof(spooled_message_id));
+spool_mbox_ok = 1;
+
+/* don't set no_mbox_unspool; at present, there's no way for it to become
+set, but if that changes, then it should apply to these tests too */
+
+unspool_mbox();
+
+/* silence static analysis tools */
+message_id = NULL;
+
+return ret;
+}
+
+
+void
+malware_init(void)
+{
+if (!malware_default_re)
+  malware_default_re = regex_must_compile(malware_regex_default, FALSE, TRUE);
+if (!drweb_re)
+  drweb_re = regex_must_compile(drweb_re_str, FALSE, TRUE);
+if (!fsec_re)
+  fsec_re = regex_must_compile(fsec_re_str, FALSE, TRUE);
+if (!kav_re_sus)
+  kav_re_sus = regex_must_compile(kav_re_sus_str, FALSE, TRUE);
+if (!kav_re_inf)
+  kav_re_inf = regex_must_compile(kav_re_inf_str, FALSE, TRUE);
+if (!ava_re_clean)
+  ava_re_clean = regex_must_compile(ava_re_clean_str, FALSE, TRUE);
+if (!ava_re_virus)
+  ava_re_virus = regex_must_compile(ava_re_virus_str, FALSE, TRUE);
+}
+
+#endif /*WITH_CONTENT_SCAN*/
+/*
+ * vi: aw ai sw=2
+ */
index 97a0982..fa42187 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for matching strings */
@@ -15,8 +15,8 @@
 strings, domains, and local parts. */
 
 typedef struct check_string_block {
-  uschar *origsubject;               /* caseful; keep these two first, in */
-  uschar *subject;                   /* step with the block below */
+  const uschar *origsubject;           /* caseful; keep these two first, in */
+  const uschar *subject;               /* step with the block below */
   int    expand_setup;
   BOOL   use_partial;
   BOOL   caseless;
@@ -28,7 +28,7 @@ typedef struct check_string_block {
 addresses. */
 
 typedef struct check_address_block {
-  uschar *origaddress;               /* caseful; keep these two first, in */
+  const uschar *origaddress;         /* caseful; keep these two first, in */
   uschar *address;                   /* step with the block above */
   int    expand_setup;
   BOOL   caseless;
@@ -92,12 +92,12 @@ Returns:       OK    if matched
 */
 
 static int
-check_string(void *arg, uschar *pattern, uschar **valueptr, uschar **error)
+check_string(void *arg, const uschar *pattern, const uschar **valueptr, uschar **error)
 {
-check_string_block *cb = (check_string_block *)arg;
+const check_string_block *cb = arg;
 int search_type, partial, affixlen, starflags;
 int expand_setup = cb->expand_setup;
-uschar *affix;
+const uschar *affix;
 uschar *s;
 uschar *filename = NULL;
 uschar *keyquery, *result, *semicolon;
@@ -111,7 +111,7 @@ if (valueptr != NULL) *valueptr = NULL;  /* For non-lookup matches */
 it works if the pattern uses (?-i) to turn off case-independence, overriding
 "caseless". */
 
-s = (pattern[0] == '^')? cb->origsubject : cb->subject;
+s = string_copy(pattern[0] == '^' ? cb->origsubject : cb->subject);
 
 /* If required to set up $0, initialize the data but don't turn on by setting
 expand_nmax until the match is assured. */
@@ -131,7 +131,7 @@ if (pattern[0] == '^')
   {
   const pcre *re = regex_must_compile(pattern, cb->caseless, FALSE);
   return ((expand_setup < 0)?
-           pcre_exec(re, NULL, CS s, Ustrlen(s), 0, PCRE_EOPT, NULL, 0) >= 0
+           pcre_exec(re, NULL, CCS s, Ustrlen(s), 0, PCRE_EOPT, NULL, 0) >= 0
            :
            regex_match_and_setup(re, s, 0, expand_setup)
          )?
@@ -192,8 +192,8 @@ if (cb->at_is_special && pattern[0] == '@')
     BOOL prim = FALSE;
     BOOL secy = FALSE;
     BOOL removed = FALSE;
-    uschar *ss = pattern + 4;
-    uschar *ignore_target_hosts = NULL;
+    const uschar *ss = pattern + 4;
+    const uschar *ignore_target_hosts = NULL;
 
     if (strncmpic(ss, US"any", 3) == 0) ss += 3;
     else if (strncmpic(ss, US"primary", 7) == 0)
@@ -221,8 +221,7 @@ if (cb->at_is_special && pattern[0] == '@')
       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 dnssec request/require XXX ? */
       NULL,                /* no feedback FQDN */
       &removed);           /* feedback if local removed */
 
@@ -337,8 +336,8 @@ Returns:       OK    if matched
 */
 
 int
-match_check_string(uschar *s, uschar *pattern, int expand_setup,
-  BOOL use_partial, BOOL caseless, BOOL at_is_special, uschar **valueptr)
+match_check_string(const uschar *s, const uschar *pattern, int expand_setup,
+  BOOL use_partial, BOOL caseless, BOOL at_is_special, const uschar **valueptr)
 {
 check_string_block cb;
 cb.origsubject = s;
@@ -366,7 +365,7 @@ Arguments:
   type         MCL_STRING, MCL_DOMAIN, MCL_HOST, MCL_ADDRESS, or MCL_LOCALPART
 */
 
-static uschar *
+static const uschar *
 get_check_key(void *arg, int type)
 {
 switch(type)
@@ -436,9 +435,9 @@ Returns:       OK    if matched a non-negated item
 */
 
 int
-match_check_list(uschar **listptr, int sep, tree_node **anchorptr,
-  unsigned int **cache_ptr, int (*func)(void *,uschar *,uschar **,uschar **),
-  void *arg, int type, uschar *name, uschar **valueptr)
+match_check_list(const uschar **listptr, int sep, tree_node **anchorptr,
+  unsigned int **cache_ptr, int (*func)(void *,const uschar *,const uschar **,uschar **),
+  void *arg, int type, const uschar *name, const uschar **valueptr)
 {
 int yield = OK;
 unsigned int *original_cache_bits = *cache_ptr;
@@ -446,7 +445,7 @@ BOOL include_unknown = FALSE;
 BOOL ignore_unknown = FALSE;
 BOOL include_defer = FALSE;
 BOOL ignore_defer = FALSE;
-uschar *list;
+const uschar *list;
 uschar *sss;
 uschar *ot = NULL;
 uschar buffer[1024];
@@ -489,12 +488,12 @@ else
   if (type == MCL_DOMAIN && deliver_domain == NULL)
     {
     check_string_block *cb = (check_string_block *)arg;
-    deliver_domain = cb->subject;
-    list = expand_string(*listptr);
+    deliver_domain = string_copy(cb->subject);
+    list = expand_cstring(*listptr);
     deliver_domain = NULL;
     }
 
-  else list = expand_string(*listptr);
+  else list = expand_cstring(*listptr);
 
   if (list == NULL)
     {
@@ -701,7 +700,7 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
         cached = US" - cached";
         if (valueptr != NULL)
           {
-          uschar *key = get_check_key(arg, type);
+          const uschar *key = get_check_key(arg, type);
           namedlist_cacheblock *p;
           for (p = nb->cache_data; p != NULL; p = p->next)
             {
@@ -740,7 +739,7 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
 
         case DEFER:
         if (error == NULL)
-          error = string_sprintf("DNS lookup of %s deferred", ss);
+          error = string_sprintf("DNS lookup of \"%s\" deferred", ss);
         if (ignore_defer)
           {
           HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_defer\n",
@@ -752,6 +751,7 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
           log_write(0, LOG_MAIN, "%s: accepted by +include_defer", error);
           return OK;
           }
+        if (!search_error_message) search_error_message = error;
         goto DEFER_RETURN;
 
         /* The ERROR return occurs when checking hosts, when either a forward
@@ -771,7 +771,7 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
             include_unknown? "yes":"no", error);
           if (!include_unknown)
             {
-            if ((log_extra_selector & LX_unknown_in_list) != 0)
+            if (LOGGING(unknown_in_list))
               log_write(0, LOG_MAIN, "list matching forced to fail: %s", error);
             return FAIL;
             }
@@ -880,7 +880,7 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
           (void)fclose(f);
           if (!include_unknown)
             {
-            if ((log_extra_selector & LX_unknown_in_list) != 0)
+            if (LOGGING(unknown_in_list))
               log_write(0, LOG_MAIN, "list matching forced to fail: %s", error);
             return FAIL;
             }
@@ -952,8 +952,9 @@ Returns:         OK    if matched a non-negated item
 */
 
 int
-match_isinlist(uschar *s, uschar **listptr, int sep, tree_node **anchorptr,
-  unsigned int *cache_bits, int type, BOOL caseless, uschar **valueptr)
+match_isinlist(const uschar *s, const uschar **listptr, int sep,
+   tree_node **anchorptr,
+  unsigned int *cache_bits, int type, BOOL caseless, const uschar **valueptr)
 {
 unsigned int *local_cache_bits = cache_bits;
 check_string_block cb;
@@ -999,16 +1000,17 @@ Returns:         OK     for a match
 */
 
 static int
-check_address(void *arg, uschar *pattern, uschar **valueptr, uschar **error)
+check_address(void *arg, const uschar *pattern, const uschar **valueptr, uschar **error)
 {
 check_address_block *cb = (check_address_block *)arg;
 check_string_block csb;
 int rc;
 int expand_inc = 0;
 unsigned int *null = NULL;
-uschar *listptr;
+const uschar *listptr;
 uschar *subject = cb->address;
-uschar *s, *pdomain, *sdomain;
+const uschar *s;
+uschar *pdomain, *sdomain;
 
 error = error;  /* Keep clever compilers from complaining */
 
@@ -1070,7 +1072,8 @@ looked up to obtain a list of local parts. If the subject's local part is just
 if (pattern[0] == '@' && pattern[1] == '@')
   {
   int watchdog = 50;
-  uschar *list, *key, *ss;
+  const uschar *key;
+  uschar *list, *ss;
   uschar buffer[1024];
 
   if (sdomain == subject + 1 && *subject == '*') return FAIL;
@@ -1083,7 +1086,7 @@ if (pattern[0] == '@' && pattern[1] == '@')
     int sep = 0;
 
     if ((rc = match_check_string(key, pattern + 2, -1, TRUE, FALSE, FALSE,
-      &list)) != OK) return rc;
+      CUSS &list)) != OK) return rc;
 
     /* Check for chaining from the last item; set up the next key if one
     is found. */
@@ -1102,8 +1105,7 @@ if (pattern[0] == '@' && pattern[1] == '@')
     /* Look up the local parts provided by the list; negation is permitted.
     If a local part has to begin with !, a regex can be used. */
 
-    while ((ss = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))
-           != NULL)
+    while ((ss = string_nextinlist(CUSS &list, &sep, buffer, sizeof(buffer))))
       {
       int local_yield;
 
@@ -1278,9 +1280,9 @@ Returns:          OK    for a positive match, or end list after a negation;
 */
 
 int
-match_address_list(uschar *address, BOOL caseless, BOOL expand,
-  uschar **listptr, unsigned int *cache_bits, int expand_setup, int sep,
-  uschar **valueptr)
+match_address_list(const uschar *address, BOOL caseless, BOOL expand,
+  const uschar **listptr, unsigned int *cache_bits, int expand_setup, int sep,
+  const uschar **valueptr)
 {
 uschar *p;
 check_address_block ab;
index bf95491..6f97b09 100644 (file)
    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.
 
-   2. The origin of this software must not be misrepresented; you must 
-      not claim that you wrote the original software.  If you use this 
-      software in a product, an acknowledgment in the product 
+   2. The origin of this software must not be misrepresented; you must
+      not claim that you wrote the original software.  If you use this
+      software in a product, an acknowledgment in the product
       documentation would be appreciated but is not required.
 
    3. Altered source versions must be plainly marked as such, and must
       not be misrepresented as being the original software.
 
-   4. The name of the author may not be used to endorse or promote 
-      products derived from this software without specific prior written 
+   4. The name of the author may not be used to endorse or promote
+      products derived from this software without specific prior written
       permission.
 
    THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
@@ -53,7 +53,7 @@
    the terms of the GNU General Public License, version 2.  See the
    COPYING file in the source distribution for details.
 
-   ---------------------------------------------------------------- 
+   ----------------------------------------------------------------
 */
 
 
 
 #include "valgrind.h"
 
-/* !! ABIWARNING !! ABIWARNING !! ABIWARNING !! ABIWARNING !! 
+/* !! ABIWARNING !! ABIWARNING !! ABIWARNING !! ABIWARNING !!
    This enum comprises an ABI exported by Valgrind to programs
    which use client requests.  DO NOT CHANGE THE ORDER OF THESE
    ENTRIES, NOR DELETE ANY -- add new ones at the end. */
 typedef
-   enum { 
+   enum {
       VG_USERREQ__MAKE_MEM_NOACCESS = VG_USERREQ_TOOL_BASE('M','C'),
       VG_USERREQ__MAKE_MEM_UNDEFINED,
       VG_USERREQ__MAKE_MEM_DEFINED,
@@ -97,7 +97,7 @@ typedef
       VG_USERREQ__COUNT_LEAK_BLOCKS,
 
       /* This is just for memcheck's internal use - don't use it */
-      _VG_USERREQ__MEMCHECK_RECORD_OVERLAP_ERROR 
+      _VG_USERREQ__MEMCHECK_RECORD_OVERLAP_ERROR
          = VG_USERREQ_TOOL_BASE('M','C') + 256
    } Vg_MemCheckClientRequest;
 
@@ -110,7 +110,7 @@ typedef
     VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */,      \
                             VG_USERREQ__MAKE_MEM_NOACCESS,       \
                             (_qzz_addr), (_qzz_len), 0, 0, 0)
-      
+
 /* Similarly, mark memory at _qzz_addr as addressable but undefined
    for _qzz_len bytes. */
 #define VALGRIND_MAKE_MEM_UNDEFINED(_qzz_addr,_qzz_len)          \
index 95d3da4..821cb54 100644 (file)
@@ -2,8 +2,10 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 */
-/* License: GPL */
+/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004, 2015
+ * License: GPL
+ * Copyright (c) The Exim Maintainers 2016
+ */
 
 #include "exim.h"
 #ifdef WITH_CONTENT_SCAN       /* entire file */
 FILE *mime_stream = NULL;
 uschar *mime_current_boundary = NULL;
 
+static mime_header mime_header_list[] = {
+  { US"content-type:",              13, &mime_content_type },
+  { US"content-disposition:",       20, &mime_content_disposition },
+  { US"content-transfer-encoding:", 26, &mime_content_transfer_encoding },
+  { US"content-id:",                11, &mime_content_id },
+  { US"content-description:",       20, &mime_content_description }
+};
+
+static int mime_header_list_size = nelem(mime_header_list);
+
+static mime_parameter mime_parameter_list[] = {
+  { US"name=",     5, &mime_filename },
+  { US"filename=", 9, &mime_filename },
+  { US"charset=",  8, &mime_charset  },
+  { US"boundary=", 9, &mime_boundary }
+};
+
+
 /*************************************************
 * set MIME anomaly level + text                  *
 *************************************************/
 
 /* Small wrapper to set the two expandables which
    give info on detected "problems" in MIME
-   encodings. Those are defined in mime.h. */
+   encodings. Indexes are defined in mime.h. */
 
 void
-mime_set_anomaly(int level, const char *text)
+mime_set_anomaly(int idx)
 {
-  mime_anomaly_level = level;
-  mime_anomaly_text = CUS text;
+struct anom {
+  int level;
+  const uschar * text;
+} anom[] = { {1, CUS"Broken Quoted-Printable encoding detected"},
+            {2, CUS"Broken BASE64 encoding detected"} };
+
+mime_anomaly_level = anom[idx].level;
+mime_anomaly_text =  anom[idx].text;
 }
 
 
@@ -41,7 +67,7 @@ mime_set_anomaly(int level, const char *text)
            0-255 - char to write
 */
 
-uschar *
+static uschar *
 mime_decode_qp_char(uschar *qp_p, int *c)
 {
 uschar *initial_pos = qp_p;
@@ -99,84 +125,6 @@ mime_decode_asis(FILE* in, FILE* out, uschar* boundary)
 }
 
 
-/* decode base64 MIME part */
-static ssize_t
-mime_decode_base64(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;
-  int bytestate = 0;
-
-  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
-       )
-      break;
-
-    for (ipos = ibuf ; *ipos != '\r' && *ipos != '\n' && *ipos != 0; ++ipos)
-      {
-      if (*ipos == '=')                        /* skip padding */
-        {
-        ++bytestate;
-        continue;
-       }
-      if (mime_b64[*ipos] == 128)      /* skip bad characters */
-        {
-        mime_set_anomaly(MIME_ANOMALY_BROKEN_BASE64);
-        continue;
-       }
-
-      /* simple state-machine */
-      switch((bytestate++) & 3)
-        {
-        case 0:
-          *opos = mime_b64[*ipos] << 2;
-           break;
-        case 1:
-          *opos |= mime_b64[*ipos] >> 4;
-          ++opos;
-          *opos = mime_b64[*ipos] << 4;
-          break;
-        case 2:
-          *opos |= mime_b64[*ipos] >> 2;
-          ++opos;
-          *opos = mime_b64[*ipos] << 6;
-          break;
-        case 3:
-          *opos |= mime_b64[*ipos];
-          ++opos;
-          break;
-       } /* switch */
-      } /* for */
-
-    /* something to write? */
-    len = opos - obuf;
-    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 */
-
-  /* write out last byte if it was incomplete */
-  if (bytestate & 3)
-    {
-    if (fwrite(obuf, 1, 1, out) != 1) return -1;
-    ++size;
-    }
-
-  return size;
-}
-
 
 /* decode quoted-printable MIME part */
 static ssize_t
@@ -240,21 +188,17 @@ return size;
 }
 
 
-FILE *
+/*
+ * Return open filehandle for combo of path and file.
+ * Side-effect: set mime_decoded_filename, to copy in allocated mem
+ */
+static FILE *
 mime_get_decode_file(uschar *pname, uschar *fname)
 {
-FILE *f = NULL;
-uschar *filename;
-
-filename = (uschar *)malloc(2048);
-
 if (pname && fname)
-  {
-  (void)string_format(filename, 2048, "%s/%s", pname, fname);
-  f = modefopen(filename,"wb+",SPOOL_MODE);
-  }
+  mime_decoded_filename = string_sprintf("%s/%s", pname, fname);
 else if (!pname)
-  f = modefopen(fname,"wb+",SPOOL_MODE);
+  mime_decoded_filename = string_copy(fname);
 else if (!fname)
   {
   int file_nr = 0;
@@ -264,29 +208,23 @@ else if (!fname)
   do
     {
     struct stat mystat;
-    (void)string_format(filename, 2048,
-      "%s/%s-%05u", pname, message_id, file_nr++);
+    mime_decoded_filename = string_sprintf("%s/%s-%05u", pname, message_id, file_nr++);
     /* security break */
     if (file_nr >= 1024)
       break;
-    result = stat(CS filename, &mystat);
+    result = stat(CS mime_decoded_filename, &mystat);
     } while(result != -1);
-
-  f = modefopen(filename, "wb+", SPOOL_MODE);
   }
 
-/* set expansion variable */
-mime_decoded_filename = filename;
-
-return f;
+return modefopen(mime_decoded_filename, "wb+", SPOOL_MODE);
 }
 
 
 int
-mime_decode(uschar **listptr)
+mime_decode(const uschar **listptr)
 {
 int sep = 0;
-uschar *list = *listptr;
+const uschar *list = *listptr;
 uschar *option;
 uschar option_buffer[1024];
 uschar decode_path[1024];
@@ -295,11 +233,9 @@ long f_pos = 0;
 ssize_t size_counter = 0;
 ssize_t (*decode_function)(FILE*, FILE*, uschar*);
 
-if (mime_stream == NULL)
+if (!mime_stream || (f_pos = ftell(mime_stream)) < 0)
   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);
 
@@ -309,7 +245,7 @@ if ((option = string_nextinlist(&list, &sep,
                                sizeof(option_buffer))) != NULL)
   {
   /* parse 1st option */
-  if ( (Ustrcmp(option,"false") == 0) || (Ustrcmp(option,"0") == 0) )
+  if ((Ustrcmp(option,"false") == 0) || (Ustrcmp(option,"0") == 0))
     /* explicitly no decoding */
     return FAIL;
 
@@ -358,7 +294,8 @@ decode_function =
 size_counter = decode_function(mime_stream, decode_file, mime_current_boundary);
 
 clearerr(mime_stream);
-fseek(mime_stream, f_pos, SEEK_SET);
+if (fseek(mime_stream, f_pos, SEEK_SET))
+  return DEFER;
 
 if (fclose(decode_file) != 0 || size_counter < 0)
   return DEFER;
@@ -369,7 +306,8 @@ mime_content_size = (size_counter + 1023) / 1024;
 return OK;
 }
 
-int
+
+static int
 mime_get_header(FILE *f, uschar *header)
 {
 int c = EOF;
@@ -474,21 +412,111 @@ else
 }
 
 
+static void
+mime_vars_reset(void)
+{
+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;
+}
+
+
+/* Grab a parameter value, dealing with quoting.
+
+Arguments:
+ str   Input string.  Updated on return to point to terminating ; or NUL
+
+Return:
+ Allocated string with parameter value
+*/
+static uschar *
+mime_param_val(uschar ** sp)
+{
+uschar * s = *sp;
+uschar * val = NULL;
+int size = 0, ptr = 0;
+
+/* debug_printf_indent("   considering paramval '%s'\n", s); */
+
+while (*s && *s != ';')                /* ; terminates */
+  if (*s == '"')
+    {
+    s++;                       /* skip opening " */
+    while (*s && *s != '"')    /* " protects ; */
+      val = string_catn(val, &size, &ptr, s++, 1);
+    if (*s) s++;               /* skip closing " */
+    }
+  else
+    val = string_catn(val, &size, &ptr, s++, 1);
+if (val) val[ptr] = '\0';
+*sp = s;
+return val;
+}
+
+static uschar *
+mime_next_semicolon(uschar * s)
+{
+while (*s && *s != ';')                /* ; terminates */
+  if (*s == '"')
+    {
+    s++;                       /* skip opening " */
+    while (*s && *s != '"')    /* " protects ; */
+      s++;
+    if (*s) s++;               /* skip closing " */
+    }
+  else
+    s++;
+return s;
+}
+
+
+static uschar *
+rfc2231_to_2047(const uschar * fname, const uschar * charset, int * len)
+{
+int size = 0, ptr = 0;
+uschar * val = string_catn(NULL, &size, &ptr, US"=?", 2);
+uschar c;
+
+if (charset)
+  val = string_cat(val, &size, &ptr, charset);
+val = string_catn(val, &size, &ptr, US"?Q?", 3);
+
+while ((c = *fname))
+  if (c == '%' && isxdigit(fname[1]) && isxdigit(fname[2]))
+    {
+    val = string_catn(val, &size, &ptr, US"=", 1);
+    val = string_catn(val, &size, &ptr, ++fname, 2);
+    fname += 2;
+    }
+  else
+    val = string_catn(val, &size, &ptr, fname++, 1);
+
+val = string_catn(val, &size, &ptr, US"?=", 2);
+val[*len = ptr] = '\0';
+return val;
+}
+
+
 int
 mime_acl_check(uschar *acl, FILE *f, struct mime_boundary_context *context,
-                   uschar **user_msgptr, uschar **log_msgptr)
+    uschar **user_msgptr, uschar **log_msgptr)
 {
 int rc = OK;
-uschar *header = NULL;
+uschar * header = NULL;
 struct mime_boundary_context nested_context;
 
 /* 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;
-  }
+header = store_get(MIME_MAX_HEADER_SIZE+1);
 
 /* Not actually used at the moment, but will be vital to fixing
  * some RFC 2046 nonconformance later... */
@@ -498,26 +526,12 @@ nested_context.parent = context;
 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.
-  */
+  mime_vars_reset();
+
+  /* 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
@@ -526,122 +540,188 @@ while(1)
    * (I have moved partway towards adding support, however, by adding
    * a "parent" field to my new boundary-context structure.)
    */
-  if (context != NULL)
+  if (context) for (;;)
     {
-    while(fgets(CS header, MIME_MAX_HEADER_SIZE, f) != NULL)
+    if (!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)
-         {
-         /* 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);
+      /* Hit EOF or read error. Ugh. */
+      DEBUG(D_acl) debug_printf_indent("MIME: Hit EOF ...\n");
+      return rc;
+      }
 
-         /* can't use break here */
-         goto DECODE_HEADERS;
-         }
+    /* boundary line must start with 2 dashes */
+    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)
+       {
+       /* END boundary found */
+       DEBUG(D_acl) debug_printf_indent("MIME: End boundary found %s\n",
+         context->boundary);
+       return rc;
        }
+
+      DEBUG(D_acl) debug_printf_indent("MIME: Next part with boundary %s\n",
+       context->boundary);
+      break;
       }
-    /* 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))
+  while (mime_get_header(f, header))
     {
-    int i;
-    /* loop through header list */
-    for (i = 0; i < mime_header_list_size; i++)
+    struct mime_header * mh;
+
+    /* look for interesting headers */
+    for (mh = mime_header_list;
+        mh < mime_header_list + mime_header_list_size;
+        mh++) if (strncmpic(mh->name, header, mh->namelen) == 0)
       {
-      uschar *header_value = NULL;
-      int header_value_len = 0;
+      uschar * p = header + mh->namelen;
+      uschar * q;
+
+      /* grab the value (normalize to lower case)
+      and copy to its corresponding expansion variable */
+
+      for (q = p; *q != ';' && *q; q++) ;
+      *mh->value = string_copynlc(p, q-p);
+      DEBUG(D_acl) debug_printf_indent("MIME: found %s header, value is '%s'\n",
+       mh->name, *mh->value);
+
+      if (*(p = q)) p++;                       /* jump past the ; */
 
-      /* 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)
+       uschar * mime_fname = NULL;
+       uschar * mime_fname_rfc2231 = NULL;
+       uschar * mime_filename_charset = NULL;
+       BOOL decoding_failed = FALSE;
+
+       /* grab all param=value tags on the remaining line,
+       check if they are interesting */
+
+       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;
-
-           /* found an interesting parameter? */
-           if (strncmpic(mp->name, p, mp->namelen) == 0)
+
+         DEBUG(D_acl) debug_printf_indent("MIME:   considering paramlist '%s'\n", p);
+
+         if (  !mime_filename
+            && strncmpic(CUS"content-disposition:", header, 20) == 0
+            && strncmpic(CUS"filename*", p, 9) == 0
+            )
+           {                                   /* RFC 2231 filename */
+           uschar * q;
+
+           /* find value of the filename */
+           p += 9;
+           while(*p != '=' && *p) p++;
+           if (*p) p++;                        /* p is filename or NUL */
+           q = mime_param_val(&p);             /* p now trailing ; or NUL */
+
+           if (q && *q)
              {
-             uschar *q = p + mp->namelen;
-             int size = 0;
-             int ptr = 0;
+             uschar * temp_string, * err_msg;
+             int slen;
+
+             /* build up an un-decoded filename over successive
+             filename*= parameters (for use when 2047 decode fails) */
+
+             mime_fname_rfc2231 = string_sprintf("%#s%s",
+               mime_fname_rfc2231, q);
 
-             /* yes, grab the value and copy to its corresponding expansion variable */
-             while(*q && *q != ';')            /* ; terminates */
+             if (!decoding_failed)
                {
-               if (*q == '"')
+               int size;
+               if (!mime_filename_charset)
                  {
-                 q++;                          /* skip leading " */
-                 while(*q && *q != '"')        /* which protects ; */
-                   param_value = string_cat(param_value, &size, &ptr, q++, 1);
-                 if (*q) q++;                  /* skip trailing " */
+                 uschar * s = q;
+
+                 /* look for a ' in the "filename" */
+                 while(*s != '\'' && *s) s++;  /* s is 1st ' or NUL */
+
+                 if ((size = s-q) > 0)
+                   mime_filename_charset = string_copyn(q, size);
+
+                 if (*(p = s)) p++;
+                 while(*p == '\'') p++;        /* p is after 2nd ' */
                  }
                else
-                 param_value = string_cat(param_value, &size, &ptr, q++, 1);
+                 p = q;
+
+               DEBUG(D_acl) debug_printf_indent("MIME:    charset %s fname '%s'\n",
+                 mime_filename_charset ? mime_filename_charset : US"<NULL>", p);
+
+               temp_string = rfc2231_to_2047(p, mime_filename_charset, &slen);
+               DEBUG(D_acl) debug_printf_indent("MIME:    2047-name %s\n", temp_string);
+
+               temp_string = rfc2047_decode(temp_string, FALSE, NULL, ' ',
+                 NULL, &err_msg);
+               DEBUG(D_acl) debug_printf_indent("MIME:    plain-name %s\n", temp_string);
+
+               size = Ustrlen(temp_string);
+
+               if (size == slen)
+                 decoding_failed = TRUE;
+               else
+                 /* build up a decoded filename over successive
+                 filename*= parameters */
+
+                 mime_filename = mime_fname = mime_fname
+                   ? string_sprintf("%s%s", mime_fname, temp_string)
+                   : temp_string;
                }
-             param_value[ptr++] = '\0';
-             param_value_len = ptr;
-
-             param_value = rfc2047_decode(param_value, check_rfc2047_length, NULL, 32, &param_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;
+             }
            }
-         }
+
+         else
+           /* look for interesting parameters */
+           for (mp = mime_parameter_list;
+                mp < mime_parameter_list + nelem(mime_parameter_list);
+                mp++
+               ) if (strncmpic(mp->name, p, mp->namelen) == 0)
+             {
+             uschar * q;
+             uschar * dummy_errstr;
+
+             /* grab the value and copy to its expansion variable */
+             p += mp->namelen;
+             q = mime_param_val(&p);           /* p now trailing ; or NUL */
+
+             *mp->value = q && *q
+               ? rfc2047_decode(q, check_rfc2047_length, NULL, 32, NULL,
+                   &dummy_errstr)
+               : NULL;
+             DEBUG(D_acl) debug_printf_indent(
+               "MIME:  found %s parameter in %s header, value '%s'\n",
+               mp->name, mh->name, *mp->value);
+
+             break;                    /* done matching param names */
+             }
+
+
          /* There is something, but not one of our interesting parameters.
-            Advance to the next semicolon */
-         while(*p != ';') p++;
-         p++;
+            Advance past the next semicolon */
+         p = mime_next_semicolon(p);
+         if (*p) p++;
+         }                             /* param scan on line */
+
+       if (strncmpic(CUS"content-disposition:", header, 20) == 0)
+         {
+         if (decoding_failed) mime_filename = mime_fname_rfc2231;
+
+         DEBUG(D_acl) debug_printf_indent(
+           "MIME:  found %s parameter in %s header, value is '%s'\n",
+           "filename", mh->name, mime_filename);
+         }
        }
       }
     }
-  }
 
   /* set additional flag variables (easier access) */
-  if ( (mime_content_type != NULL) &&
-       (Ustrncmp(mime_content_type,"multipart",9) == 0) )
+  if (  mime_content_type
+     && Ustrncmp(mime_content_type,"multipart",9) == 0
+     )
     mime_is_multipart = 1;
 
   /* Make a copy of the boundary pointer.
@@ -672,7 +752,9 @@ NEXT_PARAM_SEARCH:
        (nested_context.boundary != NULL) &&
        (Ustrncmp(mime_content_type,"multipart",9) == 0) )
     {
-    debug_printf("Entering multipart recursion, boundary '%s'\n", nested_context.boundary);
+    DEBUG(D_acl)
+      debug_printf_indent("MIME: Entering multipart recursion, boundary '%s'\n",
+       nested_context.boundary);
 
     nested_context.context =
       context && context->context == MBC_ATTACHMENT
@@ -688,7 +770,7 @@ NEXT_PARAM_SEARCH:
   else if ( (mime_content_type != NULL) &&
          (Ustrncmp(mime_content_type,"message/rfc822",14) == 0) )
     {
-    uschar *rfc822name = NULL;
+    const uschar *rfc822name = NULL;
     uschar filename[2048];
     int file_nr = 0;
     int result = 0;
@@ -717,20 +799,23 @@ NEXT_PARAM_SEARCH:
     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 acl condition warning - could not decode RFC822 MIME part to file.");
+      rc = DEFER;
+      goto out;
       }
     mime_decoded_filename = NULL;
     }
 
 NO_RFC822:
   /* If the boundary of this instance is NULL, we are finished here */
-  if (context == NULL) break;
+  if (!context) break;
 
   if (context->context == MBC_COVERLETTER_ONESHOT)
     context->context = MBC_ATTACHMENT;
   }
 
+out:
+mime_vars_reset();
 return rc;
 }
 
dissimilarity index 82%
index abf68da..5fd4392 100644 (file)
@@ -1,83 +1,44 @@
-/*************************************************
-*     Exim - an Internet mail transport agent    *
-*************************************************/
-
-/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 */
-/* License: GPL */
-
-#ifdef WITH_CONTENT_SCAN
-
-#define MIME_MAX_HEADER_SIZE 8192
-#define MIME_MAX_LINE_LENGTH 32768
-
-#define MBC_ATTACHMENT            0
-#define MBC_COVERLETTER_ONESHOT   1
-#define MBC_COVERLETTER_ALL       2
-
-struct mime_boundary_context
-{
-  struct mime_boundary_context *parent;
-  unsigned char *boundary;
-  int context;
-};
-
-typedef struct mime_header {
-  uschar *name;
-  int    namelen;
-  void   *value;
-} mime_header;
-
-static mime_header mime_header_list[] = {
-  { US"content-type:", 13, &mime_content_type },
-  { US"content-disposition:", 20, &mime_content_disposition },
-  { US"content-transfer-encoding:", 26, &mime_content_transfer_encoding },
-  { US"content-id:", 11, &mime_content_id },
-  { US"content-description:", 20 , &mime_content_description }
-};
-
-static int mime_header_list_size = sizeof(mime_header_list)/sizeof(mime_header);
-
-
-
-typedef struct mime_parameter {
-  uschar *name;
-  int    namelen;
-  void   *value;
-} mime_parameter;
-
-static mime_parameter mime_parameter_list[] = {
-  { US"name=", 5, &mime_filename },
-  { US"filename=", 9, &mime_filename },
-  { US"charset=", 8, &mime_charset },
-  { US"boundary=", 9, &mime_boundary }
-};
-
-static int mime_parameter_list_size = sizeof(mime_parameter_list)/sizeof(mime_parameter);
-
-
-/* MIME Anomaly list */
-#define MIME_ANOMALY_BROKEN_BASE64    2, "Broken BASE64 encoding detected"
-#define MIME_ANOMALY_BROKEN_QP        1, "Broken Quoted-Printable encoding detected"
-
-
-/* BASE64 decoder matrix */
-static unsigned char mime_b64[256]={
-/*   0 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
-/*  16 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
-/*  32 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,   62,  128,  128,  128,   63,
-/*  48 */   52,   53,   54,   55,   56,   57,   58,   59,   60,   61,  128,  128,  128,  255,  128,  128,
-/*  64 */  128,    0,    1,    2,    3,    4,    5,    6,    7,    8,    9,   10,   11,   12,   13,   14,
-/*  80 */   15,   16,   17,   18,   19,   20,   21,   22,   23,   24,   25,  128,  128,  128,  128,  128,
-/*  96 */  128,   26,   27,   28,   29,   30,   31,   32,   33,   34,   35,   36,   37,   38,   39,   40,
-/* 112 */   41,   42,   43,   44,   45,   46,   47,   48,   49,   50,   51,  128,  128,  128,  128,  128,
-/* 128 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
-/* 144 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
-/* 160 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
-/* 176 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
-/* 192 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
-/* 208 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
-/* 224 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,
-/* 240 */  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128,  128
-};
-
-#endif
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004, 2015
+ * License: GPL
+ * Copyright (c) The Exim Maintainers 2016
+ */
+
+#ifdef WITH_CONTENT_SCAN
+
+#define MIME_MAX_HEADER_SIZE 8192
+#define MIME_MAX_LINE_LENGTH 32768
+
+#define MBC_ATTACHMENT            0
+#define MBC_COVERLETTER_ONESHOT   1
+#define MBC_COVERLETTER_ALL       2
+
+struct mime_boundary_context
+{
+  struct mime_boundary_context *parent;
+  unsigned char *boundary;
+  int context;
+};
+
+typedef struct mime_header {
+  uschar *  name;
+  int       namelen;
+  uschar ** value;
+} mime_header;
+
+
+typedef struct mime_parameter {
+  uschar *  name;
+  int       namelen;
+  uschar ** value;
+} mime_parameter;
+
+/* MIME Anomaly list */
+#define MIME_ANOMALY_BROKEN_BASE64    1
+#define MIME_ANOMALY_BROKEN_QP        0
+
+
+#endif
index 4d7b51b..6d922a5 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for sending messages to sender or to mailmaster. */
@@ -86,7 +86,7 @@ else DEBUG(D_any) debug_printf("Child process %d for sending message\n", pid);
 /* Creation of child succeeded */
 
 f = fdopen(fd, "wb");
-if (errors_reply_to != NULL) fprintf(f, "Reply-To: %s\n", errors_reply_to);
+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", recipient);
@@ -94,140 +94,137 @@ fprintf(f, "To: %s\n", recipient);
 switch(ident)
   {
   case ERRMESS_BADARGADDRESS:
-  fprintf(f,
-  "Subject: Mail failure - malformed recipient address\n\n");
-  fprintf(f,
-  "A message that you sent contained a recipient address that was incorrectly\n"
-  "constructed:\n\n");
-  fprintf(f, "  %s  %s\n", eblock->text1, eblock->text2);
-  count = Ustrlen(eblock->text1);
-  if (count > 0 && eblock->text1[count-1] == '.')
     fprintf(f,
-    "\nRecipient addresses must not end with a '.' character.\n");
-  fprintf(f,
-  "\nThe message has not been delivered to any recipients.\n");
-  break;
+      "Subject: Mail failure - malformed recipient address\n\n");
+    fprintf(f,
+      "A message that you sent contained a recipient address that was incorrectly\n"
+      "constructed:\n\n");
+    fprintf(f, "  %s  %s\n", eblock->text1, eblock->text2);
+    count = Ustrlen(eblock->text1);
+    if (count > 0 && eblock->text1[count-1] == '.')
+      fprintf(f,
+       "\nRecipient addresses must not end with a '.' character.\n");
+    fprintf(f,
+      "\nThe message has not been delivered to any recipients.\n");
+    break;
 
   case ERRMESS_BADNOADDRESS:
   case ERRMESS_BADADDRESS:
-  fprintf(f,
-  "Subject: Mail failure - malformed recipient address\n\n");
-  fprintf(f,
-  "A message that you sent contained one or more recipient addresses that were\n"
-  "incorrectly constructed:\n\n");
+    fprintf(f,
+      "Subject: Mail failure - malformed recipient address\n\n");
+    fprintf(f,
+      "A message that you sent contained one or more recipient addresses that were\n"
+      "incorrectly constructed:\n\n");
 
-  while (eblock != NULL)
-    {
-    fprintf(f, "  %s: %s\n", eblock->text1, eblock->text2);
-    count++;
-    eblock = eblock->next;
-    }
+    while (eblock != NULL)
+      {
+      fprintf(f, "  %s: %s\n", eblock->text1, eblock->text2);
+      count++;
+      eblock = eblock->next;
+      }
 
-  fprintf(f, (count == 1)? "\nThis address has been ignored. " :
-    "\nThese addresses have been ignored. ");
+    fprintf(f, (count == 1)? "\nThis address has been ignored. " :
+      "\nThese addresses have been ignored. ");
 
-  fprintf(f, (ident == ERRMESS_BADADDRESS)?
-  "The other addresses in the message were\n"
-  "syntactically valid and have been passed on for an attempt at delivery.\n" :
+    fprintf(f, (ident == ERRMESS_BADADDRESS)?
+      "The other addresses in the message were\n"
+      "syntactically valid and have been passed on for an attempt at delivery.\n" :
 
-  "There were no other addresses in your\n"
-  "message, and so no attempt at delivery was possible.\n");
-  break;
+      "There were no other addresses in your\n"
+      "message, and so no attempt at delivery was possible.\n");
+    break;
 
   case ERRMESS_IGADDRESS:
-  fprintf(f, "Subject: Mail failure - no recipient addresses\n\n");
-  fprintf(f,
-  "A message that you sent using the -t command line option contained no\n"
-  "addresses that were not also on the command line, and were therefore\n"
-  "suppressed. This left no recipient addresses, and so no delivery could\n"
-  "be attempted.\n");
-  break;
+    fprintf(f, "Subject: Mail failure - no recipient addresses\n\n");
+    fprintf(f,
+      "A message that you sent using the -t command line option contained no\n"
+      "addresses that were not also on the command line, and were therefore\n"
+      "suppressed. This left no recipient addresses, and so no delivery could\n"
+      "be attempted.\n");
+    break;
 
   case ERRMESS_NOADDRESS:
-  fprintf(f, "Subject: Mail failure - no recipient addresses\n\n");
-  fprintf(f,
-  "A message that you sent contained no recipient addresses, and therefore no\n"
-  "delivery could be attempted.\n");
-  break;
+    fprintf(f, "Subject: Mail failure - no recipient addresses\n\n");
+    fprintf(f,
+      "A message that you sent contained no recipient addresses, and therefore no\n"
+      "delivery could be attempted.\n");
+    break;
 
   case ERRMESS_IOERR:
-  fprintf(f, "Subject: Mail failure - system failure\n\n");
-  fprintf(f,
-  "A system failure was encountered while processing a message that you sent,\n"
-  "so it has not been possible to deliver it. The error was:\n\n%s\n",
-    eblock->text1);
-  break;
+    fprintf(f, "Subject: Mail failure - system failure\n\n");
+    fprintf(f,
+      "A system failure was encountered while processing a message that you sent,\n"
+      "so it has not been possible to deliver it. The error was:\n\n%s\n",
+      eblock->text1);
+    break;
 
   case ERRMESS_VLONGHEADER:
-  fprintf(f, "Subject: Mail failure - overlong header section\n\n");
-  fprintf(f,
-  "A message that you sent contained a header section that was excessively\n"
-  "long and could not be handled by the mail transmission software. The\n"
-  "message has not been delivered to any recipients.\n");
-  break;
+    fprintf(f, "Subject: Mail failure - overlong header section\n\n");
+    fprintf(f,
+      "A message that you sent contained a header section that was excessively\n"
+      "long and could not be handled by the mail transmission software. The\n"
+      "message has not been delivered to any recipients.\n");
+    break;
 
   case ERRMESS_VLONGHDRLINE:
-  fprintf(f, "Subject: Mail failure - overlong header line\n\n");
-  fprintf(f,
-  "A message that you sent contained a header line that was excessively\n"
-  "long and could not be handled by the mail transmission software. The\n"
-  "message has not been delivered to any recipients.\n");
-  break;
+    fprintf(f, "Subject: Mail failure - overlong header line\n\n");
+    fprintf(f,
+      "A message that you sent contained a header line that was excessively\n"
+      "long and could not be handled by the mail transmission software. The\n"
+      "message has not been delivered to any recipients.\n");
+    break;
 
   case ERRMESS_TOOBIG:
-  fprintf(f, "Subject: Mail failure - message too big\n\n");
-  fprintf(f,
-  "A message that you sent was longer than the maximum size allowed on this\n"
-  "system. It was not delivered to any recipients.\n");
-  break;
+    fprintf(f, "Subject: Mail failure - message too big\n\n");
+    fprintf(f,
+      "A message that you sent was longer than the maximum size allowed on this\n"
+      "system. It was not delivered to any recipients.\n");
+    break;
 
   case ERRMESS_TOOMANYRECIP:
-  fprintf(f, "Subject: Mail failure - too many recipients\n\n");
-  fprintf(f,
-  "A message that you sent contained more recipients than allowed on this\n"
-  "system. It was not delivered to any recipients.\n");
-  break;
+    fprintf(f, "Subject: Mail failure - too many recipients\n\n");
+    fprintf(f,
+      "A message that you sent contained more recipients than allowed on this\n"
+      "system. It was not delivered to any recipients.\n");
+    break;
 
   case ERRMESS_LOCAL_SCAN:
   case ERRMESS_LOCAL_ACL:
-  fprintf(f, "Subject: Mail failure - rejected by local scanning code\n\n");
-  fprintf(f,
-  "A message that you sent was rejected by the local scanning code that\n"
-  "checks incoming messages on this system.");
-  if (eblock->text1 != NULL)
-    {
+    fprintf(f, "Subject: Mail failure - rejected by local scanning code\n\n");
     fprintf(f,
-    " The following error was given:\n\n  %s", eblock->text1);
-    }
+      "A message that you sent was rejected by the local scanning code that\n"
+      "checks incoming messages on this system.");
+      if (eblock->text1)
+       fprintf(f, " The following error was given:\n\n  %s", eblock->text1);
   fprintf(f, "\n");
   break;
 
 #ifdef EXPERIMENTAL_DMARC
   case ERRMESS_DMARC_FORENSIC:
-  bounce_return_message = TRUE;
-  bounce_return_body    = FALSE;
-  fprintf(f,
+    bounce_return_message = TRUE;
+    bounce_return_body    = FALSE;
+    fprintf(f,
           "Subject: DMARC Forensic Report for %s from IP %s\n\n",
          ((eblock == NULL) ? US"Unknown" : eblock->text2),
           sender_host_address);
-  fprintf(f,
-  "A message claiming to be from you has failed the published DMARC\n"
-  "policy for your domain.\n\n");
-  while (eblock != NULL)
-    {
-    fprintf(f, "  %s: %s\n", eblock->text1, eblock->text2);
-    count++;
-    eblock = eblock->next;
-    }
+    fprintf(f,
+      "A message claiming to be from you has failed the published DMARC\n"
+      "policy for your domain.\n\n");
+    while (eblock != NULL)
+      {
+      fprintf(f, "  %s: %s\n", eblock->text1, eblock->text2);
+      count++;
+      eblock = eblock->next;
+      }
   break;
 #endif
 
   default:
-  fprintf(f, "Subject: Mail failure\n\n");
-  fprintf(f,
-  "A message that you sent has caused the error routine to be entered with\n"
-  "an unknown error number (%d).\n", ident);
-  break;
+    fprintf(f, "Subject: Mail failure\n\n");
+    fprintf(f,
+      "A message that you sent has caused the error routine to be entered with\n"
+      "an unknown error number (%d).\n", ident);
+    break;
   }
 
 /* Now, if configured, copy the message; first the headers and then the rest of
@@ -267,7 +264,7 @@ if (bounce_return_message)
   /* If the error occurred before the Received: header was created, its text
   field will still be NULL; just omit such a header line. */
 
-  while (headers != NULL)
+  while (headers)
     {
     if (headers->text != NULL) fprintf(f, "%s", CS headers->text);
     headers = headers->next;
@@ -280,30 +277,45 @@ if (bounce_return_message)
   in which case we might have to terminate on a line containing just "."
   as well as on EOF. We may already have the first line in memory. */
 
-  if (bounce_return_body && message_file != NULL)
+  if (bounce_return_body && message_file)
     {
-    int ch;
-    int state = 1;
     BOOL enddot = dot_ends && message_file == stdin;
-    if (firstline != NULL) fprintf(f, "%s", CS firstline);
-    while ((ch = fgetc(message_file)) != EOF)
+    uschar * buf = store_get(bounce_return_linesize_limit+2);
+
+    if (firstline) fprintf(f, "%s", CS firstline);
+
+    while (fgets(CS buf, bounce_return_linesize_limit+2, message_file))
       {
-      fputc(ch, f);
-      if (size_limit > 0 && ++written > size_limit) break;
-      if (enddot)
-        {
-        if (state == 0) { if (ch == '\n') state = 1; }
-        else if (state == 1)
-          { if (ch == '.') state = 2; else if (ch != '\n') state = 0; }
-        else
-          { if (ch == '\n') break; else state = 0; }
-        }
+      int len;
+
+      if (enddot && *buf == '.' && buf[1] == '\n')
+       {
+       fputc('.', f);
+       break;
+       }
+
+      len = Ustrlen(buf);
+      if (buf[len-1] != '\n')
+       {       /* eat rest of partial line */
+       int ch;
+       while ((ch = fgetc(message_file)) != EOF && ch != '\n') ;
+       }
+
+      if (size_limit > 0 && len > size_limit - written)
+       {
+       buf[size_limit - written] = '\0';
+       fputs(CS buf, f);
+       break;
+       }
+
+      fputs(CS buf, f);
       }
     }
 #ifdef EXPERIMENTAL_DMARC
   /* Overkill, but use exact test in case future code gets inserted */
   else if (bounce_return_body && message_file == NULL)
     {
+    /*XXX limit line length here? */
     /* This doesn't print newlines, disable until can parse and fix
      * output to be legible.  */
     fprintf(f, "%s", expand_string(US"$message_body"));
@@ -365,24 +377,24 @@ moan_to_sender(int ident, error_block *eblock, header_line *headers,
 uschar *firstline = NULL;
 uschar *msg = US"Error while reading message with no usable sender address";
 
-if (message_reference != NULL)
+if (message_reference)
   msg = string_sprintf("%s (R=%s)", msg, message_reference);
 
 /* Find the sender from a From line if permitted and possible */
 
-if (check_sender && message_file != NULL && trusted_caller &&
+if (check_sender && message_file && trusted_caller &&
     Ufgets(big_buffer, BIG_BUFFER_SIZE, message_file) != NULL)
   {
   uschar *new_sender = NULL;
   if (regex_match_and_setup(regex_From, big_buffer, 0, -1))
     new_sender = expand_string(uucp_from_sender);
-  if (new_sender != NULL) sender_address = new_sender;
+  if (new_sender) sender_address = new_sender;
     else firstline = big_buffer;
   }
 
 /* If viable sender address, send a message */
 
-if (sender_address != NULL && sender_address[0] != 0 && !local_error_message)
+if (sender_address && sender_address[0] && !local_error_message)
   return moan_send_message(sender_address, ident, eblock, headers,
     message_file, firstline);
 
@@ -598,7 +610,7 @@ uschar *
 moan_check_errorcopy(uschar *recipient)
 {
 uschar *item, *localpart, *domain;
-uschar *listptr = errors_copy;
+const uschar *listptr = errors_copy;
 uschar *yield = NULL;
 uschar buffer[256];
 int sep = 0;
@@ -619,8 +631,8 @@ llen = domain++ - recipient;
 while ((item = string_nextinlist(&listptr, &sep, buffer, sizeof(buffer)))
        != NULL)
   {
-  uschar *newaddress = item;
-  uschar *pattern = string_dequote(&newaddress);
+  const uschar *newaddress = item;
+  const uschar *pattern = string_dequote(&newaddress);
 
   /* If no new address found, just skip this item. */
 
index 964abf8..b288a32 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -14,15 +14,15 @@ local_scan.h includes it and exim.h includes them both (to get this earlier). */
 #define MYTYPES_H
 
 #ifndef FALSE
-#define FALSE         0
+# define FALSE         0
 #endif
 
 #ifndef TRUE
-#define TRUE          1
+# define TRUE          1
 #endif
 
 #ifndef TRUE_UNSET
-#define TRUE_UNSET    2
+# define TRUE_UNSET    2
 #endif
 
 
@@ -30,17 +30,17 @@ local_scan.h includes it and exim.h includes them both (to get this earlier). */
 the arguments of printf-like functions. This is done by a macro. */
 
 #if defined(__GNUC__) || defined(__clang__)
-#define PRINTF_FUNCTION(A,B)  __attribute__((format(printf,A,B)))
-#define ARG_UNUSED  __attribute__((__unused__))
+# define PRINTF_FUNCTION(A,B)  __attribute__((format(printf,A,B)))
+# define ARG_UNUSED  __attribute__((__unused__))
 #else
-#define PRINTF_FUNCTION(A,B)
-#define ARG_UNUSED  /**/
+# define PRINTF_FUNCTION(A,B)
+# define ARG_UNUSED  /**/
 #endif
 
 #ifdef WANT_DEEPER_PRINTF_CHECKS
-#define ALMOST_PRINTF(A, B) PRINTF_FUNCTION(A, B)
+# define ALMOST_PRINTF(A, B) PRINTF_FUNCTION(A, B)
 #else
-#define ALMOST_PRINTF(A, B)
+# define ALMOST_PRINTF(A, B)
 #endif
 
 
@@ -49,7 +49,7 @@ the standard header files, so we use "uschar". Solaris has u_char in
 sys/types.h. This is just a typing convenience, of course. */
 
 typedef unsigned char uschar;
-typedef int BOOL;
+typedef unsigned BOOL;
 /* We also have SIGNAL_BOOL, which requires signal.h be included, so is defined
 elsewhere */
 
@@ -66,6 +66,7 @@ almost always literal strings. */
 #define US   (unsigned char *)
 #define CUS  (const unsigned char *)
 #define USS  (unsigned char **)
+#define CUSS (const unsigned char **)
 
 /* The C library string functions expect "char *" arguments. Use macros to
 avoid having to write a cast each time. We do this for string and file
index 6e02b8f..ca24e8d 100644 (file)
--- a/src/os.c
+++ b/src/os.c
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #ifdef STAND_ALONE
@@ -833,9 +833,57 @@ os_get_dns_resolver_res(void)
 
 #endif /* OS_GET_DNS_RESOLVER_RES */
 
+/* ----------------------------------------------------------------------- */
+
+/***********************************************************
+*                 unsetenv()                               *
+***********************************************************/
+
+/* Most modern systems define int unsetenv(const char*),
+* some don't. */
+
+#if !defined(OS_UNSETENV)
+int
+os_unsetenv(const unsigned char * name)
+{
+return unsetenv((char *)name);
+}
+#endif
 
 /* ----------------------------------------------------------------------- */
 
+/***********************************************************
+*               getcwd()                                   *
+***********************************************************/
+
+/* Glibc allows getcwd(NULL, 0) to do auto-allocation. Some systems
+do auto-allocation, but need the size of the buffer, and others
+may not even do this. If the OS supports getcwd(NULL, 0) we'll use
+this, for all other systems we provide our own getcwd() */
+
+#if !defined(OS_GETCWD)
+unsigned char *
+os_getcwd(unsigned char * buffer, size_t size)
+{
+return (unsigned char *) getcwd((char *)buffer, size);
+}
+#else
+#ifndef PATH_MAX
+# define PATH_MAX 4096
+#endif
+unsigned char *
+os_getcwd(unsigned char * buffer, size_t size)
+{
+char * b = (char *)buffer;
+
+if (!size) size = PATH_MAX;
+if (!b && !(b = malloc(size))) return NULL;
+if (!(b = getcwd(b, size))) return NULL;
+return buffer ? buffer : realloc(b, strlen(b) + 1);
+}
+#endif
+
+/* ----------------------------------------------------------------------- */
 
 
 
index 5527127..4e6e9a9 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Prototypes for os-specific functions. For utilities, we don't need the one
@@ -32,5 +32,11 @@ extern const char   *os_strexit(int);     /* char to match os_strsignal */
 #ifndef os_strsignal
 extern const char   *os_strsignal(int);   /* char to match strsignal in some OS */
 #endif
+#ifndef os_unsetenv
+extern int           os_unsetenv(const uschar *);
+#endif
+#ifndef os_getcwd
+extern uschar       *os_getcwd(uschar *, size_t);
+#endif
 
 /* End of osfunctions.h */
index 94e42c1..94a1af6 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2017 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for parsing addresses */
@@ -187,7 +187,7 @@ The start of the last potential comment position is remembered to
 make it possible to ignore comments at the end of compound items.
 
 Argument: current character pointer
-Regurns:  new character pointer
+Returns:  new character pointer
 */
 
 static uschar *
@@ -550,9 +550,7 @@ read_addr_spec(uschar *s, uschar *t, int term, uschar **errorptr,
 {
 s = read_local_part(s, t, errorptr, FALSE);
 if (*errorptr == NULL)
-  {
   if (*s != term)
-    {
     if (*s != '@')
       *errorptr = string_sprintf("\"@\" or \".\" expected after \"%s\"", t);
     else
@@ -562,8 +560,6 @@ if (*errorptr == NULL)
       *domainptr = t;
       s = read_domain(s, t, errorptr);
       }
-    }
-  }
 return s;
 }
 
@@ -665,7 +661,7 @@ if (*s != '@' && *s != '<')
   while (*s != '<' && (!parse_allow_group || *s != ':'))
     {
     s = read_local_part(s, t, errorptr, FALSE);
-    if (*errorptr != NULL)
+    if (*errorptr)
       {
       *errorptr = string_sprintf("%s (expected word or \"<\")", *errorptr);
       goto PARSE_FAILED;
@@ -690,8 +686,8 @@ processing it. Note that this is "if" rather than "else if" because it's also
 used after reading a preceding phrase.
 
 There are a lot of broken sendmails out there that put additional pairs of <>
-round <route-addr>s. If strip_excess_angle_brackets is set, allow any number of
-them, as long as they match. */
+round <route-addr>s.  If strip_excess_angle_brackets is set, allow a limited
+number of them, as long as they match. */
 
 if (*s == '<')
   {
@@ -700,8 +696,11 @@ if (*s == '<')
   int bracket_count = 1;
 
   s++;
-  if (strip_excess_angle_brackets)
-    while (*s == '<') { bracket_count++; s++; }
+  if (strip_excess_angle_brackets) while (*s == '<')
+   {
+   if(bracket_count++ > 5) FAILED(US"angle-brackets nested too deep");
+   s++;
+   }
 
   t = yield;
   startptr = s;
@@ -715,7 +714,7 @@ if (*s == '<')
   if (*s == '@')
     {
     s = read_route(s, t, errorptr);
-    if (*errorptr != NULL) goto PARSE_FAILED;
+    if (*errorptr) goto PARSE_FAILED;
     *t = 0;                  /* Ensure route is ignored - probably overkill */
     source_routed = TRUE;
     }
@@ -733,7 +732,7 @@ if (*s == '<')
   else
     {
     s = read_addr_spec(s, t, '>', errorptr, &domainptr);
-    if (*errorptr != NULL) goto PARSE_FAILED;
+    if (*errorptr) goto PARSE_FAILED;
     *domain = domainptr - yield;
     if (source_routed && *domain == 0)
       FAILED(US"domain missing in source-routed address");
@@ -743,9 +742,10 @@ if (*s == '<')
   if (*errorptr != NULL) goto PARSE_FAILED;
   while (bracket_count-- > 0) if (*s++ != '>')
     {
-    *errorptr = (s[-1] == 0)? US"'>' missing at end of address" :
-      string_sprintf("malformed address: %.32s may not follow %.*s",
-        s-1, s - (uschar *)mailbox - 1, mailbox);
+    *errorptr = s[-1] == 0
+      ? US"'>' missing at end of address"
+      : string_sprintf("malformed address: %.32s may not follow %.*s",
+         s-1, s - (uschar *)mailbox - 1, mailbox);
     goto PARSE_FAILED;
     }
 
@@ -817,7 +817,7 @@ if (*end - *start > ADDRESS_MAXLENGTH)
   return NULL;
   }
 
-return (uschar *)yield;
+return yield;
 
 /* Use goto (via the macro FAILED) to get to here from a variety of places.
 We might have an empty address in a group - the caller can choose to ignore
@@ -866,11 +866,11 @@ Returns:       pointer to the original string, if no quoting needed, or
                the introduction
 */
 
-uschar *
-parse_quote_2047(uschar *string, int len, uschar *charset, uschar *buffer,
+const uschar *
+parse_quote_2047(const uschar *string, int len, uschar *charset, uschar *buffer,
   int buffer_size, BOOL fold)
 {
-uschar *s = string;
+const uschar *s = string;
 uschar *p, *t;
 int hlen;
 BOOL coded = FALSE;
@@ -910,7 +910,7 @@ for (; len > 0; len--)
       {
       *t++ = '_';
       first_byte = FALSE;
-      } 
+      }
     else
       {
       sprintf(CS t, "=%02X", ch);
@@ -985,12 +985,13 @@ Arguments:
 Returns:       the fixed RFC822 phrase
 */
 
-uschar *
-parse_fix_phrase(uschar *phrase, int len, uschar *buffer, int buffer_size)
+const uschar *
+parse_fix_phrase(const uschar *phrase, int len, uschar *buffer, int buffer_size)
 {
 int ch, i;
 BOOL quoted = FALSE;
-uschar *s, *t, *end, *yield;
+const uschar *s, *end;
+uschar *t, *yield;
 
 while (len > 0 && isspace(*phrase)) { phrase++; len--; }
 if (len > buffer_size/4) return US"Name too long";
@@ -1119,7 +1120,7 @@ while (s < end)
 
         else if (ch == '(')
           {
-          uschar *ss = s;     /* uschar after '(' */
+          const uschar *ss = s;     /* uschar after '(' */
           int level = 1;
           while(ss < end)
             {
@@ -1245,7 +1246,7 @@ Returns:      FF_DELIVERED      addresses extracted
 
 int
 parse_forward_list(uschar *s, int options, address_item **anchor,
-  uschar **error, uschar *incoming_domain, uschar *directory,
+  uschar **error, const uschar *incoming_domain, uschar *directory,
   error_block **syntax_errors)
 {
 int count = 0;
@@ -1428,7 +1429,7 @@ for (;;)
 
     /* Check file name if required */
 
-    if (directory != NULL)
+    if (directory)
       {
       int len = Ustrlen(directory);
       uschar *p = filename + len;
@@ -1440,16 +1441,53 @@ for (;;)
         return FF_ERROR;
         }
 
+#ifdef EXIM_HAVE_OPENAT
+      /* It is necessary to check that every component inside the directory
+      is NOT a symbolic link, in order to keep the file inside the directory.
+      This is mighty tedious. We open the directory and openat every component,
+      with a flag that fails symlinks. */
+
+      {
+      int fd = open(CS directory, O_RDONLY);
+      if (fd < 0)
+       {
+       *error = string_sprintf("failed to open directory %s", directory);
+       return FF_ERROR;
+       }
+      while (*p)
+       {
+       uschar temp;
+       int fd2;
+       uschar * q = p;
+
+       while (*++p && *p != '/') ;
+       temp = *p;
+       *p = '\0';
+
+       fd2 = openat(fd, CS q, O_RDONLY|O_NOFOLLOW);
+       close(fd);
+       *p = temp;
+       if (fd2 < 0)
+         {
+          *error = string_sprintf("failed to open %s (component of included "
+            "file); could be symbolic link", filename);
+         return FF_ERROR;
+         }
+       fd = fd2;
+       }
+      f = fdopen(fd, "rb");
+      }
+#else
       /* It is necessary to check that every component inside the directory
       is NOT a symbolic link, in order to keep the file inside the directory.
       This is mighty tedious. It is also not totally foolproof in that it
       leaves the possibility of a race attack, but I don't know how to do
       any better. */
 
-      while (*p != 0)
+      while (*p)
         {
         int temp;
-        while (*(++p) != 0 && *p != '/');
+        while (*++p && *p != '/');
         temp = *p;
         *p = 0;
         if (Ulstat(filename, &statbuf) != 0)
@@ -1469,11 +1507,16 @@ for (;;)
           return FF_ERROR;
           }
         }
+#endif
       }
 
-    /* Open and stat the file */
+#ifdef EXIM_HAVE_OPENAT
+    else
+#endif
+      /* Open and stat the file */
+      f = Ufopen(filename, "rb");
 
-    if ((f = Ufopen(filename, "rb")) == NULL)
+    if (!f)
       {
       *error = string_open_failed(errno, "included file %s", filename);
       return FF_INCLUDEFAIL;
@@ -1489,7 +1532,7 @@ for (;;)
 
     /* If directory was checked, double check that we opened a regular file */
 
-    if (directory != NULL && (statbuf.st_mode & S_IFMT) != S_IFREG)
+    if (directory && (statbuf.st_mode & S_IFMT) != S_IFREG)
       {
       *error = string_sprintf("included file %s is not a regular file in "
         "the %s directory", filename, directory);
@@ -1521,10 +1564,9 @@ for (;;)
       error, incoming_domain, directory, syntax_errors);
     if (frc != FF_DELIVERED && frc != FF_NOTDELIVERED) return frc;
 
-    if (addr != NULL)
+    if (addr)
       {
-      last = addr;
-      while (last->next != NULL) { count++; last = last->next; }
+      for (last = addr; last->next; last = last->next) count++;
       last->next = *anchor;
       *anchor = addr;
       count++;
index 610c011..c298568 100644 (file)
@@ -1,6 +1,6 @@
 # Make file for building the pdkim library.
 
-OBJ = base64.o bignum.o pdkim.o rsa.o sha1.o sha2.o
+OBJ = pdkim.o rsa.o
 
 pdkim.a:         $(OBJ)
                 @$(RM_COMMAND) -f pdkim.a
@@ -10,13 +10,9 @@ pdkim.a:         $(OBJ)
 
 .SUFFIXES:       .o .c
 .c.o:;           @echo "$(CC) $*.c"
-                $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) $*.c
+                $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) -I. $*.c
 
-base64.o:           $(HDRS) base64.c
-bignum.o:           $(HDRS) bignum.c
-pdkim.o:            $(HDRS) pdkim.c
-rsa.o:              $(HDRS) rsa.c
-sha1.o:             $(HDRS) sha1.c
-sha2.o:             $(HDRS) sha2.c
+pdkim.o: $(HDRS) crypt_ver.h pdkim.h pdkim.c
+rsa.o:   $(HDRS) crypt_ver.h rsa.h rsa.c
 
 # End
index de04cff..953e86e 100644 (file)
@@ -2,10 +2,8 @@ PDKIM - a RFC4871 (DKIM) implementation
 http://duncanthrax.net/pdkim/
 Copyright (C) 2009      Tom Kistner <tom@duncanthrax.net>
 
-Includes code from the PolarSSL project.
-http://polarssl.org
-Copyright (C) 2009      Paul Bakker <polarssl_maintainer@polarssl.org>
-Copyright (C) 2006-2008 Christophe Devine
+No longer includes code from the PolarSSL project.
+Copyright (C) 2016 Jeremy Harris <jgh@exim.org>
 
 This copy of PDKIM is included with Exim. For a standalone distribution,
 visit http://duncanthrax.net/pdkim/.
diff --git a/src/pdkim/base64.c b/src/pdkim/base64.c
deleted file mode 100644 (file)
index a82fc2d..0000000
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- *  RFC 1521 base64 encoding/decoding
- *
- *  Copyright (C) 2006-2010, Brainspark B.V.
- *
- *  This file is part of PolarSSL (http://www.polarssl.org)
- *  Lead Maintainer: Paul Bakker <polarssl_maintainer at polarssl.org>
- *
- *  All rights reserved.
- *
- *  This program 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 program 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 program; if not, write to the Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "base64.h"
-
-static const unsigned char base64_enc_map[64] =
-{
-    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
-    'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
-    'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
-    'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
-    'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
-    'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
-    '8', '9', '+', '/'
-};
-
-static const unsigned char base64_dec_map[128] =
-{
-    127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
-    127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
-    127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
-    127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
-    127, 127, 127,  62, 127, 127, 127,  63,  52,  53,
-     54,  55,  56,  57,  58,  59,  60,  61, 127, 127,
-    127,  64, 127, 127, 127,   0,   1,   2,   3,   4,
-      5,   6,   7,   8,   9,  10,  11,  12,  13,  14,
-     15,  16,  17,  18,  19,  20,  21,  22,  23,  24,
-     25, 127, 127, 127, 127, 127, 127,  26,  27,  28,
-     29,  30,  31,  32,  33,  34,  35,  36,  37,  38,
-     39,  40,  41,  42,  43,  44,  45,  46,  47,  48,
-     49,  50,  51, 127, 127, 127, 127, 127
-};
-
-/*
- * Encode a buffer into base64 format
- */
-int base64_encode( unsigned char *dst, int *dlen,
-                   const unsigned char *src, int  slen )
-{
-    int i, n;
-    int C1, C2, C3;
-    unsigned char *p;
-
-    if( slen == 0 )
-        return( 0 );
-
-    n = (slen << 3) / 6;
-
-    switch( (slen << 3) - (n * 6) )
-    {
-        case  2: n += 3; break;
-        case  4: n += 2; break;
-        default: break;
-    }
-
-    if( *dlen < n + 1 )
-    {
-        *dlen = n + 1;
-        return( POLARSSL_ERR_BASE64_BUFFER_TOO_SMALL );
-    }
-
-    n = (slen / 3) * 3;
-
-    for( i = 0, p = dst; i < n; i += 3 )
-    {
-        C1 = *src++;
-        C2 = *src++;
-        C3 = *src++;
-
-        *p++ = base64_enc_map[(C1 >> 2) & 0x3F];
-        *p++ = base64_enc_map[(((C1 &  3) << 4) + (C2 >> 4)) & 0x3F];
-        *p++ = base64_enc_map[(((C2 & 15) << 2) + (C3 >> 6)) & 0x3F];
-        *p++ = base64_enc_map[C3 & 0x3F];
-    }
-
-    if( i < slen )
-    {
-        C1 = *src++;
-        C2 = ((i + 1) < slen) ? *src++ : 0;
-
-        *p++ = base64_enc_map[(C1 >> 2) & 0x3F];
-        *p++ = base64_enc_map[(((C1 & 3) << 4) + (C2 >> 4)) & 0x3F];
-
-        if( (i + 1) < slen )
-             *p++ = base64_enc_map[((C2 & 15) << 2) & 0x3F];
-        else *p++ = '=';
-
-        *p++ = '=';
-    }
-
-    *dlen = p - dst;
-    *p = 0;
-
-    return( 0 );
-}
-
-/*
- * Decode a base64-formatted buffer
- */
-int base64_decode( unsigned char *dst, int *dlen,
-                   const unsigned char *src, int  slen )
-{
-    int i, j, n;
-    unsigned long x;
-    unsigned char *p;
-
-    for( i = j = n = 0; i < slen; i++ )
-    {
-        if( ( slen - i ) >= 2 &&
-            src[i] == '\r' && src[i + 1] == '\n' )
-            continue;
-
-        if( src[i] == '\n' )
-            continue;
-
-        if( src[i] == '=' && ++j > 2 )
-            return( POLARSSL_ERR_BASE64_INVALID_CHARACTER );
-
-        if( src[i] > 127 || base64_dec_map[src[i]] == 127 )
-            return( POLARSSL_ERR_BASE64_INVALID_CHARACTER );
-
-        if( base64_dec_map[src[i]] < 64 && j != 0 )
-            return( POLARSSL_ERR_BASE64_INVALID_CHARACTER );
-
-        n++;
-    }
-
-    if( n == 0 )
-        return( 0 );
-
-    n = ((n * 6) + 7) >> 3;
-
-    if( *dlen < n )
-    {
-        *dlen = n;
-        return( POLARSSL_ERR_BASE64_BUFFER_TOO_SMALL );
-    }
-
-   for( j = 3, n = x = 0, p = dst; i > 0; i--, src++ )
-   {
-        if( *src == '\r' || *src == '\n' )
-            continue;
-
-        j -= ( base64_dec_map[*src] == 64 );
-        x  = (x << 6) | ( base64_dec_map[*src] & 0x3F );
-
-        if( ++n == 4 )
-        {
-            n = 0;
-            if( j > 0 ) *p++ = (unsigned char)( x >> 16 );
-            if( j > 1 ) *p++ = (unsigned char)( x >>  8 );
-            if( j > 2 ) *p++ = (unsigned char)( x       );
-        }
-    }
-
-    *dlen = p - dst;
-
-    return( 0 );
-}
diff --git a/src/pdkim/base64.h b/src/pdkim/base64.h
deleted file mode 100644 (file)
index a3fd395..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-/**
- * \file base64.h
- *
- *  Copyright (C) 2006-2010, Brainspark B.V.
- *
- *  This file is part of PolarSSL (http://www.polarssl.org)
- *  Lead Maintainer: Paul Bakker <polarssl_maintainer at polarssl.org>
- *
- *  All rights reserved.
- *
- *  This program 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 program 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 program; if not, write to the Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef POLARSSL_BASE64_H
-#define POLARSSL_BASE64_H
-
-#define POLARSSL_ERR_BASE64_BUFFER_TOO_SMALL               0x0010
-#define POLARSSL_ERR_BASE64_INVALID_CHARACTER              0x0012
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
- * \brief          Encode a buffer into base64 format
- *
- * \param dst      destination buffer
- * \param dlen     size of the buffer
- * \param src      source buffer
- * \param slen     amount of data to be encoded
- *
- * \return         0 if successful, or POLARSSL_ERR_BASE64_BUFFER_TOO_SMALL.
- *                 *dlen is always updated to reflect the amount
- *                 of data that has (or would have) been written.
- *
- * \note           Call this function with *dlen = 0 to obtain the
- *                 required buffer size in *dlen
- */
-int base64_encode( unsigned char *dst, int *dlen,
-                   const unsigned char *src, int  slen );
-
-/**
- * \brief          Decode a base64-formatted buffer
- *
- * \param dst      destination buffer
- * \param dlen     size of the buffer
- * \param src      source buffer
- * \param slen     amount of data to be decoded
- *
- * \return         0 if successful, POLARSSL_ERR_BASE64_BUFFER_TOO_SMALL, or
- *                 POLARSSL_ERR_BASE64_INVALID_DATA if the input data is not
- *                 correct. *dlen is always updated to reflect the amount
- *                 of data that has (or would have) been written.
- *
- * \note           Call this function with *dlen = 0 to obtain the
- *                 required buffer size in *dlen
- */
-int base64_decode( unsigned char *dst, int *dlen,
-                   const unsigned char *src, int  slen );
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* base64.h */
diff --git a/src/pdkim/bignum.c b/src/pdkim/bignum.c
deleted file mode 100644 (file)
index 692fe82..0000000
+++ /dev/null
@@ -1,1865 +0,0 @@
-/*
- *  Multi-precision integer library
- *
- *  Copyright (C) 2006-2010, Brainspark B.V.
- *
- *  This file is part of PolarSSL (http://www.polarssl.org)
- *  Lead Maintainer: Paul Bakker <polarssl_maintainer at polarssl.org>
- *
- *  All rights reserved.
- *
- *  This program 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 program 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 program; if not, write to the Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-/*
- *  This MPI implementation is based on:
- *
- *  http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf
- *  http://www.stillhq.com/extracted/gnupg-api/mpi/
- *  http://math.libtomcrypt.com/files/tommath.pdf
- */
-
-#include "bignum.h"
-#include "bn_mul.h"
-
-#include <string.h>
-#include <stdlib.h>
-#include <stdarg.h>
-
-#define ciL    ((int) sizeof(t_int))    /* chars in limb  */
-#define biL    (ciL << 3)               /* bits  in limb  */
-#define biH    (ciL << 2)               /* half limb size */
-
-/*
- * Convert between bits/chars and number of limbs
- */
-#define BITS_TO_LIMBS(i)  (((i) + biL - 1) / biL)
-#define CHARS_TO_LIMBS(i) (((i) + ciL - 1) / ciL)
-
-/*
- * Initialize one or more mpi
- */
-void mpi_init( mpi *X, ... )
-{
-    va_list args;
-
-    va_start( args, X );
-
-    while( X != NULL )
-    {
-        X->s = 1;
-        X->n = 0;
-        X->p = NULL;
-
-        X = va_arg( args, mpi* );
-    }
-
-    va_end( args );
-}
-
-/*
- * Unallocate one or more mpi
- */
-void mpi_free( mpi *X, ... )
-{
-    va_list args;
-
-    va_start( args, X );
-
-    while( X != NULL )
-    {
-        if( X->p != NULL )
-        {
-            memset( X->p, 0, X->n * ciL );
-            free( X->p );
-        }
-
-        X->s = 1;
-        X->n = 0;
-        X->p = NULL;
-
-        X = va_arg( args, mpi* );
-    }
-
-    va_end( args );
-}
-
-/*
- * Enlarge to the specified number of limbs
- */
-int mpi_grow( mpi *X, int nblimbs )
-{
-    t_int *p;
-
-    if( X->n < nblimbs )
-    {
-        if( ( p = (t_int *) malloc( nblimbs * ciL ) ) == NULL )
-            return( 1 );
-
-        memset( p, 0, nblimbs * ciL );
-
-        if( X->p != NULL )
-        {
-            memcpy( p, X->p, X->n * ciL );
-            memset( X->p, 0, X->n * ciL );
-            free( X->p );
-        }
-
-        X->n = nblimbs;
-        X->p = p;
-    }
-
-    return( 0 );
-}
-
-/*
- * Copy the contents of Y into X
- */
-int mpi_copy( mpi *X, const mpi *Y )
-{
-    int ret, i;
-
-    if( X == Y )
-        return( 0 );
-
-    for( i = Y->n - 1; i > 0; i-- )
-        if( Y->p[i] != 0 )
-            break;
-    i++;
-
-    X->s = Y->s;
-
-    MPI_CHK( mpi_grow( X, i ) );
-
-    memset( X->p, 0, X->n * ciL );
-    memcpy( X->p, Y->p, i * ciL );
-
-cleanup:
-
-    return( ret );
-}
-
-/*
- * Swap the contents of X and Y
- */
-void mpi_swap( mpi *X, mpi *Y )
-{
-    mpi T;
-
-    memcpy( &T,  X, sizeof( mpi ) );
-    memcpy(  X,  Y, sizeof( mpi ) );
-    memcpy(  Y, &T, sizeof( mpi ) );
-}
-
-/*
- * Set value from integer
- */
-int mpi_lset( mpi *X, int z )
-{
-    int ret;
-
-    MPI_CHK( mpi_grow( X, 1 ) );
-    memset( X->p, 0, X->n * ciL );
-
-    X->p[0] = ( z < 0 ) ? -z : z;
-    X->s    = ( z < 0 ) ? -1 : 1;
-
-cleanup:
-
-    return( ret );
-}
-
-/*
- * Return the number of least significant bits
- */
-int mpi_lsb( const mpi *X )
-{
-    int i, j, count = 0;
-
-    for( i = 0; i < X->n; i++ )
-        for( j = 0; j < (int) biL; j++, count++ )
-            if( ( ( X->p[i] >> j ) & 1 ) != 0 )
-                return( count );
-
-    return( 0 );
-}
-
-/*
- * Return the number of most significant bits
- */
-int mpi_msb( const mpi *X )
-{
-    int i, j;
-
-    for( i = X->n - 1; i > 0; i-- )
-        if( X->p[i] != 0 )
-            break;
-
-    for( j = biL - 1; j >= 0; j-- )
-        if( ( ( X->p[i] >> j ) & 1 ) != 0 )
-            break;
-
-    return( ( i * biL ) + j + 1 );
-}
-
-/*
- * Return the total size in bytes
- */
-int mpi_size( const mpi *X )
-{
-    return( ( mpi_msb( X ) + 7 ) >> 3 );
-}
-
-/*
- * Convert an ASCII character to digit value
- */
-static int mpi_get_digit( t_int *d, int radix, char c )
-{
-    *d = 255;
-
-    if( c >= 0x30 && c <= 0x39 ) *d = c - 0x30;
-    if( c >= 0x41 && c <= 0x46 ) *d = c - 0x37;
-    if( c >= 0x61 && c <= 0x66 ) *d = c - 0x57;
-
-    if( *d >= (t_int) radix )
-        return( POLARSSL_ERR_MPI_INVALID_CHARACTER );
-
-    return( 0 );
-}
-
-/*
- * Import from an ASCII string
- */
-int mpi_read_string( mpi *X, int radix, const char *s )
-{
-    int ret, i, j, n, slen;
-    t_int d;
-    mpi T;
-
-    if( radix < 2 || radix > 16 )
-        return( POLARSSL_ERR_MPI_BAD_INPUT_DATA );
-
-    mpi_init( &T, NULL );
-
-    slen = strlen( s );
-
-    if( radix == 16 )
-    {
-        n = BITS_TO_LIMBS( slen << 2 );
-
-        MPI_CHK( mpi_grow( X, n ) );
-        MPI_CHK( mpi_lset( X, 0 ) );
-
-        for( i = slen - 1, j = 0; i >= 0; i--, j++ )
-        {
-            if( i == 0 && s[i] == '-' )
-            {
-                X->s = -1;
-                break;
-            }
-
-            MPI_CHK( mpi_get_digit( &d, radix, s[i] ) );
-            X->p[j / (2 * ciL)] |= d << ( (j % (2 * ciL)) << 2 );
-        }
-    }
-    else
-    {
-        MPI_CHK( mpi_lset( X, 0 ) );
-
-        for( i = 0; i < slen; i++ )
-        {
-            if( i == 0 && s[i] == '-' )
-            {
-                X->s = -1;
-                continue;
-            }
-
-            MPI_CHK( mpi_get_digit( &d, radix, s[i] ) );
-            MPI_CHK( mpi_mul_int( &T, X, radix ) );
-
-            if( X->s == 1 )
-            {
-                MPI_CHK( mpi_add_int( X, &T, d ) );
-            }
-            else
-            {
-                MPI_CHK( mpi_sub_int( X, &T, d ) );
-            }
-        }
-    }
-
-cleanup:
-
-    mpi_free( &T, NULL );
-
-    return( ret );
-}
-
-/*
- * Helper to write the digits high-order first
- */
-static int mpi_write_hlp( mpi *X, int radix, char **p )
-{
-    int ret;
-    t_int r;
-
-    if( radix < 2 || radix > 16 )
-        return( POLARSSL_ERR_MPI_BAD_INPUT_DATA );
-
-    MPI_CHK( mpi_mod_int( &r, X, radix ) );
-    MPI_CHK( mpi_div_int( X, NULL, X, radix ) );
-
-    if( mpi_cmp_int( X, 0 ) != 0 )
-        MPI_CHK( mpi_write_hlp( X, radix, p ) );
-
-    if( r < 10 )
-        *(*p)++ = (char)( r + 0x30 );
-    else
-        *(*p)++ = (char)( r + 0x37 );
-
-cleanup:
-
-    return( ret );
-}
-
-/*
- * Export into an ASCII string
- */
-int mpi_write_string( const mpi *X, int radix, char *s, int *slen )
-{
-    int ret = 0, n;
-    char *p;
-    mpi T;
-
-    if( radix < 2 || radix > 16 )
-        return( POLARSSL_ERR_MPI_BAD_INPUT_DATA );
-
-    n = mpi_msb( X );
-    if( radix >=  4 ) n >>= 1;
-    if( radix >= 16 ) n >>= 1;
-    n += 3;
-
-    if( *slen < n )
-    {
-        *slen = n;
-        return( POLARSSL_ERR_MPI_BUFFER_TOO_SMALL );
-    }
-
-    p = s;
-    mpi_init( &T, NULL );
-
-    if( X->s == -1 )
-        *p++ = '-';
-
-    if( radix == 16 )
-    {
-        int c, i, j, k;
-
-        for( i = X->n - 1, k = 0; i >= 0; i-- )
-        {
-            for( j = ciL - 1; j >= 0; j-- )
-            {
-                c = ( X->p[i] >> (j << 3) ) & 0xFF;
-
-                if( c == 0 && k == 0 && (i + j) != 0 )
-                    continue;
-
-                p += sprintf( p, "%02X", c );
-                k = 1;
-            }
-        }
-    }
-    else
-    {
-        MPI_CHK( mpi_copy( &T, X ) );
-
-        if( T.s == -1 )
-            T.s = 1;
-
-        MPI_CHK( mpi_write_hlp( &T, radix, &p ) );
-    }
-
-    *p++ = '\0';
-    *slen = p - s;
-
-cleanup:
-
-    mpi_free( &T, NULL );
-
-    return( ret );
-}
-
-/*
- * Read X from an opened file
- */
-int mpi_read_file( mpi *X, int radix, FILE *fin )
-{
-    t_int d;
-    int slen;
-    char *p;
-    char s[1024];
-
-    memset( s, 0, sizeof( s ) );
-    if( fgets( s, sizeof( s ) - 1, fin ) == NULL )
-        return( POLARSSL_ERR_MPI_FILE_IO_ERROR );
-
-    slen = strlen( s );
-    if( s[slen - 1] == '\n' ) { slen--; s[slen] = '\0'; }
-    if( s[slen - 1] == '\r' ) { slen--; s[slen] = '\0'; }
-
-    p = s + slen;
-    while( --p >= s )
-        if( mpi_get_digit( &d, radix, *p ) != 0 )
-            break;
-
-    return( mpi_read_string( X, radix, p + 1 ) );
-}
-
-/*
- * Write X into an opened file (or stdout if fout == NULL)
- */
-int mpi_write_file( const char *p, const mpi *X, int radix, FILE *fout )
-{
-    int n, ret;
-    size_t slen;
-    size_t plen;
-    char s[2048];
-
-    n = sizeof( s );
-    memset( s, 0, n );
-    n -= 2;
-
-    MPI_CHK( mpi_write_string( X, radix, s, (int *) &n ) );
-
-    if( p == NULL ) p = "";
-
-    plen = strlen( p );
-    slen = strlen( s );
-    s[slen++] = '\r';
-    s[slen++] = '\n';
-
-    if( fout != NULL )
-    {
-        if( fwrite( p, 1, plen, fout ) != plen ||
-            fwrite( s, 1, slen, fout ) != slen )
-            return( POLARSSL_ERR_MPI_FILE_IO_ERROR );
-    }
-    else
-        printf( "%s%s", p, s );
-
-cleanup:
-
-    return( ret );
-}
-
-/*
- * Import X from unsigned binary data, big endian
- */
-int mpi_read_binary( mpi *X, const unsigned char *buf, int buflen )
-{
-    int ret, i, j, n;
-
-    for( n = 0; n < buflen; n++ )
-        if( buf[n] != 0 )
-            break;
-
-    MPI_CHK( mpi_grow( X, CHARS_TO_LIMBS( buflen - n ) ) );
-    MPI_CHK( mpi_lset( X, 0 ) );
-
-    for( i = buflen - 1, j = 0; i >= n; i--, j++ )
-        X->p[j / ciL] |= ((t_int) buf[i]) << ((j % ciL) << 3);
-
-cleanup:
-
-    return( ret );
-}
-
-/*
- * Export X into unsigned binary data, big endian
- */
-int mpi_write_binary( const mpi *X, unsigned char *buf, int buflen )
-{
-    int i, j, n;
-
-    n = mpi_size( X );
-
-    if( buflen < n )
-        return( POLARSSL_ERR_MPI_BUFFER_TOO_SMALL );
-
-    memset( buf, 0, buflen );
-
-    for( i = buflen - 1, j = 0; n > 0; i--, j++, n-- )
-        buf[i] = (unsigned char)( X->p[j / ciL] >> ((j % ciL) << 3) );
-
-    return( 0 );
-}
-
-/*
- * Left-shift: X <<= count
- */
-int mpi_shift_l( mpi *X, int count )
-{
-    int ret, i, v0, t1;
-    t_int r0 = 0, r1;
-
-    v0 = count / (biL    );
-    t1 = count & (biL - 1);
-
-    i = mpi_msb( X ) + count;
-
-    if( X->n * (int) biL < i )
-        MPI_CHK( mpi_grow( X, BITS_TO_LIMBS( i ) ) );
-
-    ret = 0;
-
-    /*
-     * shift by count / limb_size
-     */
-    if( v0 > 0 )
-    {
-        for( i = X->n - 1; i >= v0; i-- )
-            X->p[i] = X->p[i - v0];
-
-        for( ; i >= 0; i-- )
-            X->p[i] = 0;
-    }
-
-    /*
-     * shift by count % limb_size
-     */
-    if( t1 > 0 )
-    {
-        for( i = v0; i < X->n; i++ )
-        {
-            r1 = X->p[i] >> (biL - t1);
-            X->p[i] <<= t1;
-            X->p[i] |= r0;
-            r0 = r1;
-        }
-    }
-
-cleanup:
-
-    return( ret );
-}
-
-/*
- * Right-shift: X >>= count
- */
-int mpi_shift_r( mpi *X, int count )
-{
-    int i, v0, v1;
-    t_int r0 = 0, r1;
-
-    v0 = count /  biL;
-    v1 = count & (biL - 1);
-
-    /*
-     * shift by count / limb_size
-     */
-    if( v0 > 0 )
-    {
-        for( i = 0; i < X->n - v0; i++ )
-            X->p[i] = X->p[i + v0];
-
-        for( ; i < X->n; i++ )
-            X->p[i] = 0;
-    }
-
-    /*
-     * shift by count % limb_size
-     */
-    if( v1 > 0 )
-    {
-        for( i = X->n - 1; i >= 0; i-- )
-        {
-            r1 = X->p[i] << (biL - v1);
-            X->p[i] >>= v1;
-            X->p[i] |= r0;
-            r0 = r1;
-        }
-    }
-
-    return( 0 );
-}
-
-/*
- * Compare unsigned values
- */
-int mpi_cmp_abs( const mpi *X, const mpi *Y )
-{
-    int i, j;
-
-    for( i = X->n - 1; i >= 0; i-- )
-        if( X->p[i] != 0 )
-            break;
-
-    for( j = Y->n - 1; j >= 0; j-- )
-        if( Y->p[j] != 0 )
-            break;
-
-    if( i < 0 && j < 0 )
-        return( 0 );
-
-    if( i > j ) return(  1 );
-    if( j > i ) return( -1 );
-
-    for( ; i >= 0; i-- )
-    {
-        if( X->p[i] > Y->p[i] ) return(  1 );
-        if( X->p[i] < Y->p[i] ) return( -1 );
-    }
-
-    return( 0 );
-}
-
-/*
- * Compare signed values
- */
-int mpi_cmp_mpi( const mpi *X, const mpi *Y )
-{
-    int i, j;
-
-    for( i = X->n - 1; i >= 0; i-- )
-        if( X->p[i] != 0 )
-            break;
-
-    for( j = Y->n - 1; j >= 0; j-- )
-        if( Y->p[j] != 0 )
-            break;
-
-    if( i < 0 && j < 0 )
-        return( 0 );
-
-    if( i > j ) return(  X->s );
-    if( j > i ) return( -X->s );
-
-    if( X->s > 0 && Y->s < 0 ) return(  1 );
-    if( Y->s > 0 && X->s < 0 ) return( -1 );
-
-    for( ; i >= 0; i-- )
-    {
-        if( X->p[i] > Y->p[i] ) return(  X->s );
-        if( X->p[i] < Y->p[i] ) return( -X->s );
-    }
-
-    return( 0 );
-}
-
-/*
- * Compare signed values
- */
-int mpi_cmp_int( const mpi *X, int z )
-{
-    mpi Y;
-    t_int p[1];
-
-    *p  = ( z < 0 ) ? -z : z;
-    Y.s = ( z < 0 ) ? -1 : 1;
-    Y.n = 1;
-    Y.p = p;
-
-    return( mpi_cmp_mpi( X, &Y ) );
-}
-
-/*
- * Unsigned addition: X = |A| + |B|  (HAC 14.7)
- */
-int mpi_add_abs( mpi *X, const mpi *A, const mpi *B )
-{
-    int ret, i, j;
-    t_int *o, *p, c;
-
-    if( X == B )
-    {
-        const mpi *T = A; A = X; B = T;
-    }
-
-    if( X != A )
-        MPI_CHK( mpi_copy( X, A ) );
-
-    /*
-     * X should always be positive as a result of unsigned additions.
-     */
-    X->s = 1;
-
-    for( j = B->n - 1; j >= 0; j-- )
-        if( B->p[j] != 0 )
-            break;
-
-    MPI_CHK( mpi_grow( X, j + 1 ) );
-
-    o = B->p; p = X->p; c = 0;
-
-    for( i = 0; i <= j; i++, o++, p++ )
-    {
-        *p +=  c; c  = ( *p <  c );
-        *p += *o; c += ( *p < *o );
-    }
-
-    while( c != 0 )
-    {
-        if( i >= X->n )
-        {
-            MPI_CHK( mpi_grow( X, i + 1 ) );
-            p = X->p + i;
-        }
-
-        *p += c; c = ( *p < c ); i++;
-    }
-
-cleanup:
-
-    return( ret );
-}
-
-/*
- * Helper for mpi substraction
- */
-static void mpi_sub_hlp( int n, t_int *s, t_int *d )
-{
-    int i;
-    t_int c, z;
-
-    for( i = c = 0; i < n; i++, s++, d++ )
-    {
-        z = ( *d <  c );     *d -=  c;
-        c = ( *d < *s ) + z; *d -= *s;
-    }
-
-    while( c != 0 )
-    {
-        z = ( *d < c ); *d -= c;
-        c = z; i++; d++;
-    }
-}
-
-/*
- * Unsigned substraction: X = |A| - |B|  (HAC 14.9)
- */
-int mpi_sub_abs( mpi *X, const mpi *A, const mpi *B )
-{
-    mpi TB;
-    int ret, n;
-
-    if( mpi_cmp_abs( A, B ) < 0 )
-        return( POLARSSL_ERR_MPI_NEGATIVE_VALUE );
-
-    mpi_init( &TB, NULL );
-
-    if( X == B )
-    {
-        MPI_CHK( mpi_copy( &TB, B ) );
-        B = &TB;
-    }
-
-    if( X != A )
-        MPI_CHK( mpi_copy( X, A ) );
-
-    /*
-     * X should always be positive as a result of unsigned substractions.
-     */
-    X->s = 1;
-
-    ret = 0;
-
-    for( n = B->n - 1; n >= 0; n-- )
-        if( B->p[n] != 0 )
-            break;
-
-    mpi_sub_hlp( n + 1, B->p, X->p );
-
-cleanup:
-
-    mpi_free( &TB, NULL );
-
-    return( ret );
-}
-
-/*
- * Signed addition: X = A + B
- */
-int mpi_add_mpi( mpi *X, const mpi *A, const mpi *B )
-{
-    int ret, s = A->s;
-
-    if( A->s * B->s < 0 )
-    {
-        if( mpi_cmp_abs( A, B ) >= 0 )
-        {
-            MPI_CHK( mpi_sub_abs( X, A, B ) );
-            X->s =  s;
-        }
-        else
-        {
-            MPI_CHK( mpi_sub_abs( X, B, A ) );
-            X->s = -s;
-        }
-    }
-    else
-    {
-        MPI_CHK( mpi_add_abs( X, A, B ) );
-        X->s = s;
-    }
-
-cleanup:
-
-    return( ret );
-}
-
-/*
- * Signed substraction: X = A - B
- */
-int mpi_sub_mpi( mpi *X, const mpi *A, const mpi *B )
-{
-    int ret, s = A->s;
-
-    if( A->s * B->s > 0 )
-    {
-        if( mpi_cmp_abs( A, B ) >= 0 )
-        {
-            MPI_CHK( mpi_sub_abs( X, A, B ) );
-            X->s =  s;
-        }
-        else
-        {
-            MPI_CHK( mpi_sub_abs( X, B, A ) );
-            X->s = -s;
-        }
-    }
-    else
-    {
-        MPI_CHK( mpi_add_abs( X, A, B ) );
-        X->s = s;
-    }
-
-cleanup:
-
-    return( ret );
-}
-
-/*
- * Signed addition: X = A + b
- */
-int mpi_add_int( mpi *X, const mpi *A, int b )
-{
-    mpi _B;
-    t_int p[1];
-
-    p[0] = ( b < 0 ) ? -b : b;
-    _B.s = ( b < 0 ) ? -1 : 1;
-    _B.n = 1;
-    _B.p = p;
-
-    return( mpi_add_mpi( X, A, &_B ) );
-}
-
-/*
- * Signed substraction: X = A - b
- */
-int mpi_sub_int( mpi *X, const mpi *A, int b )
-{
-    mpi _B;
-    t_int p[1];
-
-    p[0] = ( b < 0 ) ? -b : b;
-    _B.s = ( b < 0 ) ? -1 : 1;
-    _B.n = 1;
-    _B.p = p;
-
-    return( mpi_sub_mpi( X, A, &_B ) );
-}
-
-/*
- * Helper for mpi multiplication
- */
-static void mpi_mul_hlp( int i, t_int *s, t_int *d, t_int b )
-{
-    t_int c = 0, t = 0;
-
-#if defined(MULADDC_HUIT)
-    for( ; i >= 8; i -= 8 )
-    {
-        MULADDC_INIT
-        MULADDC_HUIT
-        MULADDC_STOP
-    }
-
-    for( ; i > 0; i-- )
-    {
-        MULADDC_INIT
-        MULADDC_CORE
-        MULADDC_STOP
-    }
-#else
-    for( ; i >= 16; i -= 16 )
-    {
-        MULADDC_INIT
-        MULADDC_CORE   MULADDC_CORE
-        MULADDC_CORE   MULADDC_CORE
-        MULADDC_CORE   MULADDC_CORE
-        MULADDC_CORE   MULADDC_CORE
-
-        MULADDC_CORE   MULADDC_CORE
-        MULADDC_CORE   MULADDC_CORE
-        MULADDC_CORE   MULADDC_CORE
-        MULADDC_CORE   MULADDC_CORE
-        MULADDC_STOP
-    }
-
-    for( ; i >= 8; i -= 8 )
-    {
-        MULADDC_INIT
-        MULADDC_CORE   MULADDC_CORE
-        MULADDC_CORE   MULADDC_CORE
-
-        MULADDC_CORE   MULADDC_CORE
-        MULADDC_CORE   MULADDC_CORE
-        MULADDC_STOP
-    }
-
-    for( ; i > 0; i-- )
-    {
-        MULADDC_INIT
-        MULADDC_CORE
-        MULADDC_STOP
-    }
-#endif
-
-    t++;
-
-    do {
-        *d += c; c = ( *d < c ); d++;
-    }
-    while( c != 0 );
-}
-
-/*
- * Baseline multiplication: X = A * B  (HAC 14.12)
- */
-int mpi_mul_mpi( mpi *X, const mpi *A, const mpi *B )
-{
-    int ret, i, j;
-    mpi TA, TB;
-
-    mpi_init( &TA, &TB, NULL );
-
-    if( X == A ) { MPI_CHK( mpi_copy( &TA, A ) ); A = &TA; }
-    if( X == B ) { MPI_CHK( mpi_copy( &TB, B ) ); B = &TB; }
-
-    for( i = A->n - 1; i >= 0; i-- )
-        if( A->p[i] != 0 )
-            break;
-
-    for( j = B->n - 1; j >= 0; j-- )
-        if( B->p[j] != 0 )
-            break;
-
-    MPI_CHK( mpi_grow( X, i + j + 2 ) );
-    MPI_CHK( mpi_lset( X, 0 ) );
-
-    for( i++; j >= 0; j-- )
-        mpi_mul_hlp( i, A->p, X->p + j, B->p[j] );
-
-    X->s = A->s * B->s;
-
-cleanup:
-
-    mpi_free( &TB, &TA, NULL );
-
-    return( ret );
-}
-
-/*
- * Baseline multiplication: X = A * b
- */
-int mpi_mul_int( mpi *X, const mpi *A, t_int b )
-{
-    mpi _B;
-    t_int p[1];
-
-    _B.s = 1;
-    _B.n = 1;
-    _B.p = p;
-    p[0] = b;
-
-    return( mpi_mul_mpi( X, A, &_B ) );
-}
-
-/*
- * Division by mpi: A = Q * B + R  (HAC 14.20)
- */
-int mpi_div_mpi( mpi *Q, mpi *R, const mpi *A, const mpi *B )
-{
-    int ret, i, n, t, k;
-    mpi X, Y, Z, T1, T2;
-
-    if( mpi_cmp_int( B, 0 ) == 0 )
-        return( POLARSSL_ERR_MPI_DIVISION_BY_ZERO );
-
-    mpi_init( &X, &Y, &Z, &T1, &T2, NULL );
-
-    if( mpi_cmp_abs( A, B ) < 0 )
-    {
-        if( Q != NULL ) MPI_CHK( mpi_lset( Q, 0 ) );
-        if( R != NULL ) MPI_CHK( mpi_copy( R, A ) );
-        return( 0 );
-    }
-
-    MPI_CHK( mpi_copy( &X, A ) );
-    MPI_CHK( mpi_copy( &Y, B ) );
-    X.s = Y.s = 1;
-
-    MPI_CHK( mpi_grow( &Z, A->n + 2 ) );
-    MPI_CHK( mpi_lset( &Z,  0 ) );
-    MPI_CHK( mpi_grow( &T1, 2 ) );
-    MPI_CHK( mpi_grow( &T2, 3 ) );
-
-    k = mpi_msb( &Y ) % biL;
-    if( k < (int) biL - 1 )
-    {
-        k = biL - 1 - k;
-        MPI_CHK( mpi_shift_l( &X, k ) );
-        MPI_CHK( mpi_shift_l( &Y, k ) );
-    }
-    else k = 0;
-
-    n = X.n - 1;
-    t = Y.n - 1;
-    mpi_shift_l( &Y, biL * (n - t) );
-
-    while( mpi_cmp_mpi( &X, &Y ) >= 0 )
-    {
-        Z.p[n - t]++;
-        mpi_sub_mpi( &X, &X, &Y );
-    }
-    mpi_shift_r( &Y, biL * (n - t) );
-
-    for( i = n; i > t ; i-- )
-    {
-        if( X.p[i] >= Y.p[t] )
-            Z.p[i - t - 1] = ~0;
-        else
-        {
-#if defined(POLARSSL_HAVE_LONGLONG)
-            t_dbl r;
-
-            r  = (t_dbl) X.p[i] << biL;
-            r |= (t_dbl) X.p[i - 1];
-            r /= Y.p[t];
-            if( r > ((t_dbl) 1 << biL) - 1)
-                r = ((t_dbl) 1 << biL) - 1;
-
-            Z.p[i - t - 1] = (t_int) r;
-#else
-            /*
-             * __udiv_qrnnd_c, from gmp/longlong.h
-             */
-            t_int q0, q1, r0, r1;
-            t_int d0, d1, d, m;
-
-            d  = Y.p[t];
-            d0 = ( d << biH ) >> biH;
-            d1 = ( d >> biH );
-
-            q1 = X.p[i] / d1;
-            r1 = X.p[i] - d1 * q1;
-            r1 <<= biH;
-            r1 |= ( X.p[i - 1] >> biH );
-
-            m = q1 * d0;
-            if( r1 < m )
-            {
-                q1--, r1 += d;
-                while( r1 >= d && r1 < m )
-                    q1--, r1 += d;
-            }
-            r1 -= m;
-
-            q0 = r1 / d1;
-            r0 = r1 - d1 * q0;
-            r0 <<= biH;
-            r0 |= ( X.p[i - 1] << biH ) >> biH;
-
-            m = q0 * d0;
-            if( r0 < m )
-            {
-                q0--, r0 += d;
-                while( r0 >= d && r0 < m )
-                    q0--, r0 += d;
-            }
-            r0 -= m;
-
-            Z.p[i - t - 1] = ( q1 << biH ) | q0;
-#endif
-        }
-
-        Z.p[i - t - 1]++;
-        do
-        {
-            Z.p[i - t - 1]--;
-
-            MPI_CHK( mpi_lset( &T1, 0 ) );
-            T1.p[0] = (t < 1) ? 0 : Y.p[t - 1];
-            T1.p[1] = Y.p[t];
-            MPI_CHK( mpi_mul_int( &T1, &T1, Z.p[i - t - 1] ) );
-
-            MPI_CHK( mpi_lset( &T2, 0 ) );
-            T2.p[0] = (i < 2) ? 0 : X.p[i - 2];
-            T2.p[1] = (i < 1) ? 0 : X.p[i - 1];
-            T2.p[2] = X.p[i];
-        }
-        while( mpi_cmp_mpi( &T1, &T2 ) > 0 );
-
-        MPI_CHK( mpi_mul_int( &T1, &Y, Z.p[i - t - 1] ) );
-        MPI_CHK( mpi_shift_l( &T1,  biL * (i - t - 1) ) );
-        MPI_CHK( mpi_sub_mpi( &X, &X, &T1 ) );
-
-        if( mpi_cmp_int( &X, 0 ) < 0 )
-        {
-            MPI_CHK( mpi_copy( &T1, &Y ) );
-            MPI_CHK( mpi_shift_l( &T1, biL * (i - t - 1) ) );
-            MPI_CHK( mpi_add_mpi( &X, &X, &T1 ) );
-            Z.p[i - t - 1]--;
-        }
-    }
-
-    if( Q != NULL )
-    {
-        mpi_copy( Q, &Z );
-        Q->s = A->s * B->s;
-    }
-
-    if( R != NULL )
-    {
-        mpi_shift_r( &X, k );
-        mpi_copy( R, &X );
-
-        R->s = A->s;
-        if( mpi_cmp_int( R, 0 ) == 0 )
-            R->s = 1;
-    }
-
-cleanup:
-
-    mpi_free( &X, &Y, &Z, &T1, &T2, NULL );
-
-    return( ret );
-}
-
-/*
- * Division by int: A = Q * b + R
- *
- * Returns 0 if successful
- *         1 if memory allocation failed
- *         POLARSSL_ERR_MPI_DIVISION_BY_ZERO if b == 0
- */
-int mpi_div_int( mpi *Q, mpi *R, const mpi *A, int b )
-{
-    mpi _B;
-    t_int p[1];
-
-    p[0] = ( b < 0 ) ? -b : b;
-    _B.s = ( b < 0 ) ? -1 : 1;
-    _B.n = 1;
-    _B.p = p;
-
-    return( mpi_div_mpi( Q, R, A, &_B ) );
-}
-
-/*
- * Modulo: R = A mod B
- */
-int mpi_mod_mpi( mpi *R, const mpi *A, const mpi *B )
-{
-    int ret;
-
-    if( mpi_cmp_int( B, 0 ) < 0 )
-        return POLARSSL_ERR_MPI_NEGATIVE_VALUE;
-
-    MPI_CHK( mpi_div_mpi( NULL, R, A, B ) );
-
-    while( mpi_cmp_int( R, 0 ) < 0 )
-      MPI_CHK( mpi_add_mpi( R, R, B ) );
-
-    while( mpi_cmp_mpi( R, B ) >= 0 )
-      MPI_CHK( mpi_sub_mpi( R, R, B ) );
-
-cleanup:
-
-    return( ret );
-}
-
-/*
- * Modulo: r = A mod b
- */
-int mpi_mod_int( t_int *r, const mpi *A, int b )
-{
-    int i;
-    t_int x, y, z;
-
-    if( b == 0 )
-        return( POLARSSL_ERR_MPI_DIVISION_BY_ZERO );
-
-    if( b < 0 )
-        return POLARSSL_ERR_MPI_NEGATIVE_VALUE;
-
-    /*
-     * handle trivial cases
-     */
-    if( b == 1 )
-    {
-        *r = 0;
-        return( 0 );
-    }
-
-    if( b == 2 )
-    {
-        *r = A->p[0] & 1;
-        return( 0 );
-    }
-
-    /*
-     * general case
-     */
-    for( i = A->n - 1, y = 0; i >= 0; i-- )
-    {
-        x  = A->p[i];
-        y  = ( y << biH ) | ( x >> biH );
-        z  = y / b;
-        y -= z * b;
-
-        x <<= biH;
-        y  = ( y << biH ) | ( x >> biH );
-        z  = y / b;
-        y -= z * b;
-    }
-
-    /*
-     * If A is negative, then the current y represents a negative value.
-     * Flipping it to the positive side.
-     */
-    if( A->s < 0 && y != 0 )
-        y = b - y;
-
-    *r = y;
-
-    return( 0 );
-}
-
-/*
- * Fast Montgomery initialization (thanks to Tom St Denis)
- */
-static void mpi_montg_init( t_int *mm, const mpi *N )
-{
-    t_int x, m0 = N->p[0];
-
-    x  = m0;
-    x += ( ( m0 + 2 ) & 4 ) << 1;
-    x *= ( 2 - ( m0 * x ) );
-
-    if( biL >= 16 ) x *= ( 2 - ( m0 * x ) );
-    if( biL >= 32 ) x *= ( 2 - ( m0 * x ) );
-    if( biL >= 64 ) x *= ( 2 - ( m0 * x ) );
-
-    *mm = ~x + 1;
-}
-
-/*
- * Montgomery multiplication: A = A * B * R^-1 mod N  (HAC 14.36)
- */
-static void mpi_montmul( mpi *A, const mpi *B, const mpi *N, t_int mm, const mpi *T )
-{
-    int i, n, m;
-    t_int u0, u1, *d;
-
-    memset( T->p, 0, T->n * ciL );
-
-    d = T->p;
-    n = N->n;
-    m = ( B->n < n ) ? B->n : n;
-
-    for( i = 0; i < n; i++ )
-    {
-        /*
-         * T = (T + u0*B + u1*N) / 2^biL
-         */
-        u0 = A->p[i];
-        u1 = ( d[0] + u0 * B->p[0] ) * mm;
-
-        mpi_mul_hlp( m, B->p, d, u0 );
-        mpi_mul_hlp( n, N->p, d, u1 );
-
-        *d++ = u0; d[n + 1] = 0;
-    }
-
-    memcpy( A->p, d, (n + 1) * ciL );
-
-    if( mpi_cmp_abs( A, N ) >= 0 )
-        mpi_sub_hlp( n, N->p, A->p );
-    else
-        /* prevent timing attacks */
-        mpi_sub_hlp( n, A->p, T->p );
-}
-
-/*
- * Montgomery reduction: A = A * R^-1 mod N
- */
-static void mpi_montred( mpi *A, const mpi *N, t_int mm, const mpi *T )
-{
-    t_int z = 1;
-    mpi U;
-
-    U.n = U.s = z;
-    U.p = &z;
-
-    mpi_montmul( A, &U, N, mm, T );
-}
-
-/*
- * Sliding-window exponentiation: X = A^E mod N  (HAC 14.85)
- */
-int mpi_exp_mod( mpi *X, const mpi *A, const mpi *E, const mpi *N, mpi *_RR )
-{
-    int ret, i, j, wsize, wbits;
-    int bufsize, nblimbs, nbits;
-    t_int ei, mm, state;
-    mpi RR, T, W[64];
-
-    if( mpi_cmp_int( N, 0 ) < 0 || ( N->p[0] & 1 ) == 0 )
-        return( POLARSSL_ERR_MPI_BAD_INPUT_DATA );
-
-    /*
-     * Init temps and window size
-     */
-    mpi_montg_init( &mm, N );
-    mpi_init( &RR, &T, NULL );
-    memset( W, 0, sizeof( W ) );
-
-    i = mpi_msb( E );
-
-    wsize = ( i > 671 ) ? 6 : ( i > 239 ) ? 5 :
-            ( i >  79 ) ? 4 : ( i >  23 ) ? 3 : 1;
-
-    j = N->n + 1;
-    MPI_CHK( mpi_grow( X, j ) );
-    MPI_CHK( mpi_grow( &W[1],  j ) );
-    MPI_CHK( mpi_grow( &T, j * 2 ) );
-
-    /*
-     * If 1st call, pre-compute R^2 mod N
-     */
-    if( _RR == NULL || _RR->p == NULL )
-    {
-        MPI_CHK( mpi_lset( &RR, 1 ) );
-        MPI_CHK( mpi_shift_l( &RR, N->n * 2 * biL ) );
-        MPI_CHK( mpi_mod_mpi( &RR, &RR, N ) );
-
-        if( _RR != NULL )
-            memcpy( _RR, &RR, sizeof( mpi ) );
-    }
-    else
-        memcpy( &RR, _RR, sizeof( mpi ) );
-
-    /*
-     * W[1] = A * R^2 * R^-1 mod N = A * R mod N
-     */
-    if( mpi_cmp_mpi( A, N ) >= 0 )
-        mpi_mod_mpi( &W[1], A, N );
-    else   mpi_copy( &W[1], A );
-
-    mpi_montmul( &W[1], &RR, N, mm, &T );
-
-    /*
-     * X = R^2 * R^-1 mod N = R mod N
-     */
-    MPI_CHK( mpi_copy( X, &RR ) );
-    mpi_montred( X, N, mm, &T );
-
-    if( wsize > 1 )
-    {
-        /*
-         * W[1 << (wsize - 1)] = W[1] ^ (wsize - 1)
-         */
-        j =  1 << (wsize - 1);
-
-        MPI_CHK( mpi_grow( &W[j], N->n + 1 ) );
-        MPI_CHK( mpi_copy( &W[j], &W[1]    ) );
-
-        for( i = 0; i < wsize - 1; i++ )
-            mpi_montmul( &W[j], &W[j], N, mm, &T );
-
-        /*
-         * W[i] = W[i - 1] * W[1]
-         */
-        for( i = j + 1; i < (1 << wsize); i++ )
-        {
-            MPI_CHK( mpi_grow( &W[i], N->n + 1 ) );
-            MPI_CHK( mpi_copy( &W[i], &W[i - 1] ) );
-
-            mpi_montmul( &W[i], &W[1], N, mm, &T );
-        }
-    }
-
-    nblimbs = E->n;
-    bufsize = 0;
-    nbits   = 0;
-    wbits   = 0;
-    state   = 0;
-
-    while( 1 )
-    {
-        if( bufsize == 0 )
-        {
-            if( nblimbs-- == 0 )
-                break;
-
-            bufsize = sizeof( t_int ) << 3;
-        }
-
-        bufsize--;
-
-        ei = (E->p[nblimbs] >> bufsize) & 1;
-
-        /*
-         * skip leading 0s
-         */
-        if( ei == 0 && state == 0 )
-            continue;
-
-        if( ei == 0 && state == 1 )
-        {
-            /*
-             * out of window, square X
-             */
-            mpi_montmul( X, X, N, mm, &T );
-            continue;
-        }
-
-        /*
-         * add ei to current window
-         */
-        state = 2;
-
-        nbits++;
-        wbits |= (ei << (wsize - nbits));
-
-        if( nbits == wsize )
-        {
-            /*
-             * X = X^wsize R^-1 mod N
-             */
-            for( i = 0; i < wsize; i++ )
-                mpi_montmul( X, X, N, mm, &T );
-
-            /*
-             * X = X * W[wbits] R^-1 mod N
-             */
-            mpi_montmul( X, &W[wbits], N, mm, &T );
-
-            state--;
-            nbits = 0;
-            wbits = 0;
-        }
-    }
-
-    /*
-     * process the remaining bits
-     */
-    for( i = 0; i < nbits; i++ )
-    {
-        mpi_montmul( X, X, N, mm, &T );
-
-        wbits <<= 1;
-
-        if( (wbits & (1 << wsize)) != 0 )
-            mpi_montmul( X, &W[1], N, mm, &T );
-    }
-
-    /*
-     * X = A^E * R * R^-1 mod N = A^E mod N
-     */
-    mpi_montred( X, N, mm, &T );
-
-cleanup:
-
-    for( i = (1 << (wsize - 1)); i < (1 << wsize); i++ )
-        mpi_free( &W[i], NULL );
-
-    if( _RR != NULL )
-         mpi_free( &W[1], &T, NULL );
-    else mpi_free( &W[1], &T, &RR, NULL );
-
-    return( ret );
-}
-
-/*
- * Greatest common divisor: G = gcd(A, B)  (HAC 14.54)
- */
-int mpi_gcd( mpi *G, const mpi *A, const mpi *B )
-{
-    int ret, lz, lzt;
-    mpi TG, TA, TB;
-
-    mpi_init( &TG, &TA, &TB, NULL );
-
-    MPI_CHK( mpi_copy( &TA, A ) );
-    MPI_CHK( mpi_copy( &TB, B ) );
-
-    lz = mpi_lsb( &TA );
-    lzt = mpi_lsb( &TB );
-
-    if ( lzt < lz )
-        lz = lzt;
-
-    MPI_CHK( mpi_shift_r( &TA, lz ) );
-    MPI_CHK( mpi_shift_r( &TB, lz ) );
-
-    TA.s = TB.s = 1;
-
-    while( mpi_cmp_int( &TA, 0 ) != 0 )
-    {
-        MPI_CHK( mpi_shift_r( &TA, mpi_lsb( &TA ) ) );
-        MPI_CHK( mpi_shift_r( &TB, mpi_lsb( &TB ) ) );
-
-        if( mpi_cmp_mpi( &TA, &TB ) >= 0 )
-        {
-            MPI_CHK( mpi_sub_abs( &TA, &TA, &TB ) );
-            MPI_CHK( mpi_shift_r( &TA, 1 ) );
-        }
-        else
-        {
-            MPI_CHK( mpi_sub_abs( &TB, &TB, &TA ) );
-            MPI_CHK( mpi_shift_r( &TB, 1 ) );
-        }
-    }
-
-    MPI_CHK( mpi_shift_l( &TB, lz ) );
-    MPI_CHK( mpi_copy( G, &TB ) );
-
-cleanup:
-
-    mpi_free( &TB, &TA, &TG, NULL );
-
-    return( ret );
-}
-
-#if defined(POLARSSL_GENPRIME)
-
-/*
- * Modular inverse: X = A^-1 mod N  (HAC 14.61 / 14.64)
- */
-int mpi_inv_mod( mpi *X, const mpi *A, const mpi *N )
-{
-    int ret;
-    mpi G, TA, TU, U1, U2, TB, TV, V1, V2;
-
-    if( mpi_cmp_int( N, 0 ) <= 0 )
-        return( POLARSSL_ERR_MPI_BAD_INPUT_DATA );
-
-    mpi_init( &TA, &TU, &U1, &U2, &G,
-              &TB, &TV, &V1, &V2, NULL );
-
-    MPI_CHK( mpi_gcd( &G, A, N ) );
-
-    if( mpi_cmp_int( &G, 1 ) != 0 )
-    {
-        ret = POLARSSL_ERR_MPI_NOT_ACCEPTABLE;
-        goto cleanup;
-    }
-
-    MPI_CHK( mpi_mod_mpi( &TA, A, N ) );
-    MPI_CHK( mpi_copy( &TU, &TA ) );
-    MPI_CHK( mpi_copy( &TB, N ) );
-    MPI_CHK( mpi_copy( &TV, N ) );
-
-    MPI_CHK( mpi_lset( &U1, 1 ) );
-    MPI_CHK( mpi_lset( &U2, 0 ) );
-    MPI_CHK( mpi_lset( &V1, 0 ) );
-    MPI_CHK( mpi_lset( &V2, 1 ) );
-
-    do
-    {
-        while( ( TU.p[0] & 1 ) == 0 )
-        {
-            MPI_CHK( mpi_shift_r( &TU, 1 ) );
-
-            if( ( U1.p[0] & 1 ) != 0 || ( U2.p[0] & 1 ) != 0 )
-            {
-                MPI_CHK( mpi_add_mpi( &U1, &U1, &TB ) );
-                MPI_CHK( mpi_sub_mpi( &U2, &U2, &TA ) );
-            }
-
-            MPI_CHK( mpi_shift_r( &U1, 1 ) );
-            MPI_CHK( mpi_shift_r( &U2, 1 ) );
-        }
-
-        while( ( TV.p[0] & 1 ) == 0 )
-        {
-            MPI_CHK( mpi_shift_r( &TV, 1 ) );
-
-            if( ( V1.p[0] & 1 ) != 0 || ( V2.p[0] & 1 ) != 0 )
-            {
-                MPI_CHK( mpi_add_mpi( &V1, &V1, &TB ) );
-                MPI_CHK( mpi_sub_mpi( &V2, &V2, &TA ) );
-            }
-
-            MPI_CHK( mpi_shift_r( &V1, 1 ) );
-            MPI_CHK( mpi_shift_r( &V2, 1 ) );
-        }
-
-        if( mpi_cmp_mpi( &TU, &TV ) >= 0 )
-        {
-            MPI_CHK( mpi_sub_mpi( &TU, &TU, &TV ) );
-            MPI_CHK( mpi_sub_mpi( &U1, &U1, &V1 ) );
-            MPI_CHK( mpi_sub_mpi( &U2, &U2, &V2 ) );
-        }
-        else
-        {
-            MPI_CHK( mpi_sub_mpi( &TV, &TV, &TU ) );
-            MPI_CHK( mpi_sub_mpi( &V1, &V1, &U1 ) );
-            MPI_CHK( mpi_sub_mpi( &V2, &V2, &U2 ) );
-        }
-    }
-    while( mpi_cmp_int( &TU, 0 ) != 0 );
-
-    while( mpi_cmp_int( &V1, 0 ) < 0 )
-        MPI_CHK( mpi_add_mpi( &V1, &V1, N ) );
-
-    while( mpi_cmp_mpi( &V1, N ) >= 0 )
-        MPI_CHK( mpi_sub_mpi( &V1, &V1, N ) );
-
-    MPI_CHK( mpi_copy( X, &V1 ) );
-
-cleanup:
-
-    mpi_free( &V2, &V1, &TV, &TB, &G,
-              &U2, &U1, &TU, &TA, NULL );
-
-    return( ret );
-}
-
-static const int small_prime[] =
-{
-        3,    5,    7,   11,   13,   17,   19,   23,
-       29,   31,   37,   41,   43,   47,   53,   59,
-       61,   67,   71,   73,   79,   83,   89,   97,
-      101,  103,  107,  109,  113,  127,  131,  137,
-      139,  149,  151,  157,  163,  167,  173,  179,
-      181,  191,  193,  197,  199,  211,  223,  227,
-      229,  233,  239,  241,  251,  257,  263,  269,
-      271,  277,  281,  283,  293,  307,  311,  313,
-      317,  331,  337,  347,  349,  353,  359,  367,
-      373,  379,  383,  389,  397,  401,  409,  419,
-      421,  431,  433,  439,  443,  449,  457,  461,
-      463,  467,  479,  487,  491,  499,  503,  509,
-      521,  523,  541,  547,  557,  563,  569,  571,
-      577,  587,  593,  599,  601,  607,  613,  617,
-      619,  631,  641,  643,  647,  653,  659,  661,
-      673,  677,  683,  691,  701,  709,  719,  727,
-      733,  739,  743,  751,  757,  761,  769,  773,
-      787,  797,  809,  811,  821,  823,  827,  829,
-      839,  853,  857,  859,  863,  877,  881,  883,
-      887,  907,  911,  919,  929,  937,  941,  947,
-      953,  967,  971,  977,  983,  991,  997, -103
-};
-
-/*
- * Miller-Rabin primality test  (HAC 4.24)
- */
-int mpi_is_prime( mpi *X, int (*f_rng)(void *), void *p_rng )
-{
-    int ret, i, j, n, s, xs;
-    mpi W, R, T, A, RR;
-    unsigned char *p;
-
-    if( mpi_cmp_int( X, 0 ) == 0 ||
-        mpi_cmp_int( X, 1 ) == 0 )
-        return( POLARSSL_ERR_MPI_NOT_ACCEPTABLE );
-
-    if( mpi_cmp_int( X, 2 ) == 0 )
-        return( 0 );
-
-    mpi_init( &W, &R, &T, &A, &RR, NULL );
-
-    xs = X->s; X->s = 1;
-
-    /*
-     * test trivial factors first
-     */
-    if( ( X->p[0] & 1 ) == 0 )
-        return( POLARSSL_ERR_MPI_NOT_ACCEPTABLE );
-
-    for( i = 0; small_prime[i] > 0; i++ )
-    {
-        t_int r;
-
-        if( mpi_cmp_int( X, small_prime[i] ) <= 0 )
-            return( 0 );
-
-        MPI_CHK( mpi_mod_int( &r, X, small_prime[i] ) );
-
-        if( r == 0 )
-            return( POLARSSL_ERR_MPI_NOT_ACCEPTABLE );
-    }
-
-    /*
-     * W = |X| - 1
-     * R = W >> lsb( W )
-     */
-    MPI_CHK( mpi_sub_int( &W, X, 1 ) );
-    s = mpi_lsb( &W );
-    MPI_CHK( mpi_copy( &R, &W ) );
-    MPI_CHK( mpi_shift_r( &R, s ) );
-
-    i = mpi_msb( X );
-    /*
-     * HAC, table 4.4
-     */
-    n = ( ( i >= 1300 ) ?  2 : ( i >=  850 ) ?  3 :
-          ( i >=  650 ) ?  4 : ( i >=  350 ) ?  8 :
-          ( i >=  250 ) ? 12 : ( i >=  150 ) ? 18 : 27 );
-
-    for( i = 0; i < n; i++ )
-    {
-        /*
-         * pick a random A, 1 < A < |X| - 1
-         */
-        MPI_CHK( mpi_grow( &A, X->n ) );
-
-        p = (unsigned char *) A.p;
-        for( j = 0; j < A.n * ciL; j++ )
-            *p++ = (unsigned char) f_rng( p_rng );
-
-        j = mpi_msb( &A ) - mpi_msb( &W );
-        MPI_CHK( mpi_shift_r( &A, j + 1 ) );
-        A.p[0] |= 3;
-
-        /*
-         * A = A^R mod |X|
-         */
-        MPI_CHK( mpi_exp_mod( &A, &A, &R, X, &RR ) );
-
-        if( mpi_cmp_mpi( &A, &W ) == 0 ||
-            mpi_cmp_int( &A,  1 ) == 0 )
-            continue;
-
-        j = 1;
-        while( j < s && mpi_cmp_mpi( &A, &W ) != 0 )
-        {
-            /*
-             * A = A * A mod |X|
-             */
-            MPI_CHK( mpi_mul_mpi( &T, &A, &A ) );
-            MPI_CHK( mpi_mod_mpi( &A, &T, X  ) );
-
-            if( mpi_cmp_int( &A, 1 ) == 0 )
-                break;
-
-            j++;
-        }
-
-        /*
-         * not prime if A != |X| - 1 or A == 1
-         */
-        if( mpi_cmp_mpi( &A, &W ) != 0 ||
-            mpi_cmp_int( &A,  1 ) == 0 )
-        {
-            ret = POLARSSL_ERR_MPI_NOT_ACCEPTABLE;
-            break;
-        }
-    }
-
-cleanup:
-
-    X->s = xs;
-
-    mpi_free( &RR, &A, &T, &R, &W, NULL );
-
-    return( ret );
-}
-
-/*
- * Prime number generation
- */
-int mpi_gen_prime( mpi *X, int nbits, int dh_flag,
-                   int (*f_rng)(void *), void *p_rng )
-{
-    int ret, k, n;
-    unsigned char *p;
-    mpi Y;
-
-    if( nbits < 3 )
-        return( POLARSSL_ERR_MPI_BAD_INPUT_DATA );
-
-    mpi_init( &Y, NULL );
-
-    n = BITS_TO_LIMBS( nbits );
-
-    MPI_CHK( mpi_grow( X, n ) );
-    MPI_CHK( mpi_lset( X, 0 ) );
-
-    p = (unsigned char *) X->p;
-    for( k = 0; k < X->n * ciL; k++ )
-        *p++ = (unsigned char) f_rng( p_rng );
-
-    k = mpi_msb( X );
-    if( k < nbits ) MPI_CHK( mpi_shift_l( X, nbits - k ) );
-    if( k > nbits ) MPI_CHK( mpi_shift_r( X, k - nbits ) );
-
-    X->p[0] |= 3;
-
-    if( dh_flag == 0 )
-    {
-        while( ( ret = mpi_is_prime( X, f_rng, p_rng ) ) != 0 )
-        {
-            if( ret != POLARSSL_ERR_MPI_NOT_ACCEPTABLE )
-                goto cleanup;
-
-            MPI_CHK( mpi_add_int( X, X, 2 ) );
-        }
-    }
-    else
-    {
-        MPI_CHK( mpi_sub_int( &Y, X, 1 ) );
-        MPI_CHK( mpi_shift_r( &Y, 1 ) );
-
-        while( 1 )
-        {
-            if( ( ret = mpi_is_prime( X, f_rng, p_rng ) ) == 0 )
-            {
-                if( ( ret = mpi_is_prime( &Y, f_rng, p_rng ) ) == 0 )
-                    break;
-
-                if( ret != POLARSSL_ERR_MPI_NOT_ACCEPTABLE )
-                    goto cleanup;
-            }
-
-            if( ret != POLARSSL_ERR_MPI_NOT_ACCEPTABLE )
-                goto cleanup;
-
-            MPI_CHK( mpi_add_int( &Y, X, 1 ) );
-            MPI_CHK( mpi_add_int(  X, X, 2 ) );
-            MPI_CHK( mpi_shift_r( &Y, 1 ) );
-        }
-    }
-
-cleanup:
-
-    mpi_free( &Y, NULL );
-
-    return( ret );
-}
-
-#endif
diff --git a/src/pdkim/bignum.h b/src/pdkim/bignum.h
deleted file mode 100644 (file)
index 581f7f2..0000000
+++ /dev/null
@@ -1,527 +0,0 @@
-/**
- * \file bignum.h
- *
- *  Copyright (C) 2006-2010, Brainspark B.V.
- *
- *  This file is part of PolarSSL (http://www.polarssl.org)
- *  Lead Maintainer: Paul Bakker <polarssl_maintainer at polarssl.org>
- *
- *  All rights reserved.
- *
- *  This program 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 program 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 program; if not, write to the Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef POLARSSL_BIGNUM_H
-#define POLARSSL_BIGNUM_H
-
-#include <stdio.h>
-
-#define POLARSSL_ERR_MPI_FILE_IO_ERROR                     0x0002
-#define POLARSSL_ERR_MPI_BAD_INPUT_DATA                    0x0004
-#define POLARSSL_ERR_MPI_INVALID_CHARACTER                 0x0006
-#define POLARSSL_ERR_MPI_BUFFER_TOO_SMALL                  0x0008
-#define POLARSSL_ERR_MPI_NEGATIVE_VALUE                    0x000A
-#define POLARSSL_ERR_MPI_DIVISION_BY_ZERO                  0x000C
-#define POLARSSL_ERR_MPI_NOT_ACCEPTABLE                    0x000E
-
-#define MPI_CHK(f) if( ( ret = f ) != 0 ) goto cleanup
-
-/*
- * Define the base integer type, architecture-wise
- */
-#if defined(POLARSSL_HAVE_INT8)
-typedef unsigned char  t_int;
-typedef unsigned short t_dbl;
-#else
-#if defined(POLARSSL_HAVE_INT16)
-typedef unsigned short t_int;
-typedef unsigned long  t_dbl;
-#else
-  typedef unsigned long t_int;
-  #if defined(_MSC_VER) && defined(_M_IX86)
-  typedef unsigned __int64 t_dbl;
-  #else
-    #if defined(__amd64__) || defined(__x86_64__)    || \
-        defined(__ppc64__) || defined(__powerpc64__) || \
-        defined(__ia64__)  || defined(__alpha__)
-    typedef unsigned int t_dbl __attribute__((mode(TI)));
-    #else
-      #if defined(POLARSSL_HAVE_LONGLONG)
-      typedef unsigned long long t_dbl;
-      #endif
-    #endif
-  #endif
-#endif
-#endif
-
-/**
- * \brief          MPI structure
- */
-typedef struct
-{
-    int s;              /*!<  integer sign      */
-    int n;              /*!<  total # of limbs  */
-    t_int *p;           /*!<  pointer to limbs  */
-}
-mpi;
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
- * \brief          Initialize one or more mpi
- */
-void mpi_init( mpi *X, ... );
-
-/**
- * \brief          Unallocate one or more mpi
- */
-void mpi_free( mpi *X, ... );
-
-/**
- * \brief          Enlarge to the specified number of limbs
- *
- * \param X        MPI to grow
- * \param nblimbs  The target number of limbs
- *
- * \return         0 if successful,
- *                 1 if memory allocation failed
- */
-int mpi_grow( mpi *X, int nblimbs );
-
-/**
- * \brief          Copy the contents of Y into X
- *
- * \param X        Destination MPI
- * \param Y        Source MPI
- *
- * \return         0 if successful,
- *                 1 if memory allocation failed
- */
-int mpi_copy( mpi *X, const mpi *Y );
-
-/**
- * \brief          Swap the contents of X and Y
- *
- * \param X        First MPI value
- * \param Y        Second MPI value
- */
-void mpi_swap( mpi *X, mpi *Y );
-
-/**
- * \brief          Set value from integer
- *
- * \param X        MPI to set
- * \param z        Value to use
- *
- * \return         0 if successful,
- *                 1 if memory allocation failed
- */
-int mpi_lset( mpi *X, int z );
-
-/**
- * \brief          Return the number of least significant bits
- *
- * \param X        MPI to use
- */
-int mpi_lsb( const mpi *X );
-
-/**
- * \brief          Return the number of most significant bits
- *
- * \param X        MPI to use
- */
-int mpi_msb( const mpi *X );
-
-/**
- * \brief          Return the total size in bytes
- *
- * \param X        MPI to use
- */
-int mpi_size( const mpi *X );
-
-/**
- * \brief          Import from an ASCII string
- *
- * \param X        Destination MPI
- * \param radix    Input numeric base
- * \param s        Null-terminated string buffer
- *
- * \return         0 if successful, or an POLARSSL_ERR_MPI_XXX error code
- */
-int mpi_read_string( mpi *X, int radix, const char *s );
-
-/**
- * \brief          Export into an ASCII string
- *
- * \param X        Source MPI
- * \param radix    Output numeric base
- * \param s        String buffer
- * \param slen     String buffer size
- *
- * \return         0 if successful, or an POLARSSL_ERR_MPI_XXX error code.
- *                 *slen is always updated to reflect the amount
- *                 of data that has (or would have) been written.
- *
- * \note           Call this function with *slen = 0 to obtain the
- *                 minimum required buffer size in *slen.
- */
-int mpi_write_string( const mpi *X, int radix, char *s, int *slen );
-
-/**
- * \brief          Read X from an opened file
- *
- * \param X        Destination MPI
- * \param radix    Input numeric base
- * \param fin      Input file handle
- *
- * \return         0 if successful, or an POLARSSL_ERR_MPI_XXX error code
- */
-int mpi_read_file( mpi *X, int radix, FILE *fin );
-
-/**
- * \brief          Write X into an opened file, or stdout if fout is NULL
- *
- * \param p        Prefix, can be NULL
- * \param X        Source MPI
- * \param radix    Output numeric base
- * \param fout     Output file handle (can be NULL)
- *
- * \return         0 if successful, or an POLARSSL_ERR_MPI_XXX error code
- *
- * \note           Set fout == NULL to print X on the console.
- */
-int mpi_write_file( const char *p, const mpi *X, int radix, FILE *fout );
-
-/**
- * \brief          Import X from unsigned binary data, big endian
- *
- * \param X        Destination MPI
- * \param buf      Input buffer
- * \param buflen   Input buffer size
- *
- * \return         0 if successful,
- *                 1 if memory allocation failed
- */
-int mpi_read_binary( mpi *X, const unsigned char *buf, int buflen );
-
-/**
- * \brief          Export X into unsigned binary data, big endian
- *
- * \param X        Source MPI
- * \param buf      Output buffer
- * \param buflen   Output buffer size
- *
- * \return         0 if successful,
- *                 POLARSSL_ERR_MPI_BUFFER_TOO_SMALL if buf isn't large enough
- */
-int mpi_write_binary( const mpi *X, unsigned char *buf, int buflen );
-
-/**
- * \brief          Left-shift: X <<= count
- *
- * \param X        MPI to shift
- * \param count    Amount to shift
- *
- * \return         0 if successful,
- *                 1 if memory allocation failed
- */
-int mpi_shift_l( mpi *X, int count );
-
-/**
- * \brief          Right-shift: X >>= count
- *
- * \param X        MPI to shift
- * \param count    Amount to shift
- *
- * \return         0 if successful,
- *                 1 if memory allocation failed
- */
-int mpi_shift_r( mpi *X, int count );
-
-/**
- * \brief          Compare unsigned values
- *
- * \param X        Left-hand MPI
- * \param Y        Right-hand MPI
- *
- * \return         1 if |X| is greater than |Y|,
- *                -1 if |X| is lesser  than |Y| or
- *                 0 if |X| is equal to |Y|
- */
-int mpi_cmp_abs( const mpi *X, const mpi *Y );
-
-/**
- * \brief          Compare signed values
- *
- * \param X        Left-hand MPI
- * \param Y        Right-hand MPI
- *
- * \return         1 if X is greater than Y,
- *                -1 if X is lesser  than Y or
- *                 0 if X is equal to Y
- */
-int mpi_cmp_mpi( const mpi *X, const mpi *Y );
-
-/**
- * \brief          Compare signed values
- *
- * \param X        Left-hand MPI
- * \param z        The integer value to compare to
- *
- * \return         1 if X is greater than z,
- *                -1 if X is lesser  than z or
- *                 0 if X is equal to z
- */
-int mpi_cmp_int( const mpi *X, int z );
-
-/**
- * \brief          Unsigned addition: X = |A| + |B|
- *
- * \param X        Destination MPI
- * \param A        Left-hand MPI
- * \param B        Right-hand MPI
- *
- * \return         0 if successful,
- *                 1 if memory allocation failed
- */
-int mpi_add_abs( mpi *X, const mpi *A, const mpi *B );
-
-/**
- * \brief          Unsigned substraction: X = |A| - |B|
- *
- * \param X        Destination MPI
- * \param A        Left-hand MPI
- * \param B        Right-hand MPI
- *
- * \return         0 if successful,
- *                 POLARSSL_ERR_MPI_NEGATIVE_VALUE if B is greater than A
- */
-int mpi_sub_abs( mpi *X, const mpi *A, const mpi *B );
-
-/**
- * \brief          Signed addition: X = A + B
- *
- * \param X        Destination MPI
- * \param A        Left-hand MPI
- * \param B        Right-hand MPI
- *
- * \return         0 if successful,
- *                 1 if memory allocation failed
- */
-int mpi_add_mpi( mpi *X, const mpi *A, const mpi *B );
-
-/**
- * \brief          Signed substraction: X = A - B
- *
- * \param X        Destination MPI
- * \param A        Left-hand MPI
- * \param B        Right-hand MPI
- *
- * \return         0 if successful,
- *                 1 if memory allocation failed
- */
-int mpi_sub_mpi( mpi *X, const mpi *A, const mpi *B );
-
-/**
- * \brief          Signed addition: X = A + b
- *
- * \param X        Destination MPI
- * \param A        Left-hand MPI
- * \param b        The integer value to add
- *
- * \return         0 if successful,
- *                 1 if memory allocation failed
- */
-int mpi_add_int( mpi *X, const mpi *A, int b );
-
-/**
- * \brief          Signed substraction: X = A - b
- *
- * \param X        Destination MPI
- * \param A        Left-hand MPI
- * \param b        The integer value to subtract
- *
- * \return         0 if successful,
- *                 1 if memory allocation failed
- */
-int mpi_sub_int( mpi *X, const mpi *A, int b );
-
-/**
- * \brief          Baseline multiplication: X = A * B
- *
- * \param X        Destination MPI
- * \param A        Left-hand MPI
- * \param B        Right-hand MPI
- *
- * \return         0 if successful,
- *                 1 if memory allocation failed
- */
-int mpi_mul_mpi( mpi *X, const mpi *A, const mpi *B );
-
-/**
- * \brief          Baseline multiplication: X = A * b
- *                 Note: b is an unsigned integer type, thus
- *                 Negative values of b are ignored.
- *
- * \param X        Destination MPI
- * \param A        Left-hand MPI
- * \param b        The integer value to multiply with
- *
- * \return         0 if successful,
- *                 1 if memory allocation failed
- */
-int mpi_mul_int( mpi *X, const mpi *A, t_int b );
-
-/**
- * \brief          Division by mpi: A = Q * B + R
- *
- * \param Q        Destination MPI for the quotient
- * \param R        Destination MPI for the rest value
- * \param A        Left-hand MPI
- * \param B        Right-hand MPI
- *
- * \return         0 if successful,
- *                 1 if memory allocation failed,
- *                 POLARSSL_ERR_MPI_DIVISION_BY_ZERO if B == 0
- *
- * \note           Either Q or R can be NULL.
- */
-int mpi_div_mpi( mpi *Q, mpi *R, const mpi *A, const mpi *B );
-
-/**
- * \brief          Division by int: A = Q * b + R
- *
- * \param Q        Destination MPI for the quotient
- * \param R        Destination MPI for the rest value
- * \param A        Left-hand MPI
- * \param b        Integer to divide by
- *
- * \return         0 if successful,
- *                 1 if memory allocation failed,
- *                 POLARSSL_ERR_MPI_DIVISION_BY_ZERO if b == 0
- *
- * \note           Either Q or R can be NULL.
- */
-int mpi_div_int( mpi *Q, mpi *R, const mpi *A, int b );
-
-/**
- * \brief          Modulo: R = A mod B
- *
- * \param R        Destination MPI for the rest value
- * \param A        Left-hand MPI
- * \param B        Right-hand MPI
- *
- * \return         0 if successful,
- *                 1 if memory allocation failed,
- *                 POLARSSL_ERR_MPI_DIVISION_BY_ZERO if B == 0,
- *                 POLARSSL_ERR_MPI_NEGATIVE_VALUE if B < 0
- */
-int mpi_mod_mpi( mpi *R, const mpi *A, const mpi *B );
-
-/**
- * \brief          Modulo: r = A mod b
- *
- * \param r        Destination t_int
- * \param A        Left-hand MPI
- * \param b        Integer to divide by
- *
- * \return         0 if successful,
- *                 1 if memory allocation failed,
- *                 POLARSSL_ERR_MPI_DIVISION_BY_ZERO if b == 0,
- *                 POLARSSL_ERR_MPI_NEGATIVE_VALUE if b < 0
- */
-int mpi_mod_int( t_int *r, const mpi *A, int b );
-
-/**
- * \brief          Sliding-window exponentiation: X = A^E mod N
- *
- * \param X        Destination MPI
- * \param A        Left-hand MPI
- * \param E        Exponent MPI
- * \param N        Modular MPI
- * \param _RR      Speed-up MPI used for recalculations
- *
- * \return         0 if successful,
- *                 1 if memory allocation failed,
- *                 POLARSSL_ERR_MPI_BAD_INPUT_DATA if N is negative or even
- *
- * \note           _RR is used to avoid re-computing R*R mod N across
- *                 multiple calls, which speeds up things a bit. It can
- *                 be set to NULL if the extra performance is unneeded.
- */
-int mpi_exp_mod( mpi *X, const mpi *A, const mpi *E, const mpi *N, mpi *_RR );
-
-/**
- * \brief          Greatest common divisor: G = gcd(A, B)
- *
- * \param G        Destination MPI
- * \param A        Left-hand MPI
- * \param B        Right-hand MPI
- *
- * \return         0 if successful,
- *                 1 if memory allocation failed
- */
-int mpi_gcd( mpi *G, const mpi *A, const mpi *B );
-
-/**
- * \brief          Modular inverse: X = A^-1 mod N
- *
- * \param X        Destination MPI
- * \param A        Left-hand MPI
- * \param N        Right-hand MPI
- *
- * \return         0 if successful,
- *                 1 if memory allocation failed,
- *                 POLARSSL_ERR_MPI_BAD_INPUT_DATA if N is negative or nil
-                   POLARSSL_ERR_MPI_NOT_ACCEPTABLE if A has no inverse mod N
- */
-int mpi_inv_mod( mpi *X, const mpi *A, const mpi *N );
-
-/**
- * \brief          Miller-Rabin primality test
- *
- * \param X        MPI to check
- * \param f_rng    RNG function
- * \param p_rng    RNG parameter
- *
- * \return         0 if successful (probably prime),
- *                 1 if memory allocation failed,
- *                 POLARSSL_ERR_MPI_NOT_ACCEPTABLE if X is not prime
- */
-int mpi_is_prime( mpi *X, int (*f_rng)(void *), void *p_rng );
-
-/**
- * \brief          Prime number generation
- *
- * \param X        Destination MPI
- * \param nbits    Required size of X in bits
- * \param dh_flag  If 1, then (X-1)/2 will be prime too
- * \param f_rng    RNG function
- * \param p_rng    RNG parameter
- *
- * \return         0 if successful (probably prime),
- *                 1 if memory allocation failed,
- *                 POLARSSL_ERR_MPI_BAD_INPUT_DATA if nbits is < 3
- */
-int mpi_gen_prime( mpi *X, int nbits, int dh_flag,
-                   int (*f_rng)(void *), void *p_rng );
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* bignum.h */
diff --git a/src/pdkim/bn_mul.h b/src/pdkim/bn_mul.h
deleted file mode 100644 (file)
index b3c09f0..0000000
+++ /dev/null
@@ -1,735 +0,0 @@
-/**
- * \file bn_mul.h
- *
- *  Copyright (C) 2006-2010, Brainspark B.V.
- *
- *  This file is part of PolarSSL (http://www.polarssl.org)
- *  Lead Maintainer: Paul Bakker <polarssl_maintainer at polarssl.org>
- *
- *  All rights reserved.
- *
- *  This program 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 program 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 program; if not, write to the Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-/*
- *      Multiply source vector [s] with b, add result
- *       to destination vector [d] and set carry c.
- *
- *      Currently supports:
- *
- *         . IA-32 (386+)         . AMD64 / EM64T
- *         . IA-32 (SSE2)         . Motorola 68000
- *         . PowerPC, 32-bit      . MicroBlaze
- *         . PowerPC, 64-bit      . TriCore
- *         . SPARC v8             . ARM v3+
- *         . Alpha                . MIPS32
- *         . C, longlong          . C, generic
- */
-
-#ifndef POLARSSL_BN_MUL_H
-#define POLARSSL_BN_MUL_H
-
-#if defined(POLARSSL_HAVE_ASM)
-
-#if defined(__GNUC__)
-#if defined(__i386__)
-
-#define MULADDC_INIT                \
-    asm( "                          \
-        movl   %%ebx, %0;           \
-        movl   %5, %%esi;           \
-        movl   %6, %%edi;           \
-        movl   %7, %%ecx;           \
-        movl   %8, %%ebx;           \
-        "
-
-#define MULADDC_CORE                \
-        "                           \
-        lodsl;                      \
-        mull   %%ebx;               \
-        addl   %%ecx,   %%eax;      \
-        adcl   $0,      %%edx;      \
-        addl   (%%edi), %%eax;      \
-        adcl   $0,      %%edx;      \
-        movl   %%edx,   %%ecx;      \
-        stosl;                      \
-        "
-
-#if defined(POLARSSL_HAVE_SSE2)
-
-#define MULADDC_HUIT                    \
-        "                               \
-        movd     %%ecx,     %%mm1;      \
-        movd     %%ebx,     %%mm0;      \
-        movd     (%%edi),   %%mm3;      \
-        paddq    %%mm3,     %%mm1;      \
-        movd     (%%esi),   %%mm2;      \
-        pmuludq  %%mm0,     %%mm2;      \
-        movd     4(%%esi),  %%mm4;      \
-        pmuludq  %%mm0,     %%mm4;      \
-        movd     8(%%esi),  %%mm6;      \
-        pmuludq  %%mm0,     %%mm6;      \
-        movd     12(%%esi), %%mm7;      \
-        pmuludq  %%mm0,     %%mm7;      \
-        paddq    %%mm2,     %%mm1;      \
-        movd     4(%%edi),  %%mm3;      \
-        paddq    %%mm4,     %%mm3;      \
-        movd     8(%%edi),  %%mm5;      \
-        paddq    %%mm6,     %%mm5;      \
-        movd     12(%%edi), %%mm4;      \
-        paddq    %%mm4,     %%mm7;      \
-        movd     %%mm1,     (%%edi);    \
-        movd     16(%%esi), %%mm2;      \
-        pmuludq  %%mm0,     %%mm2;      \
-        psrlq    $32,       %%mm1;      \
-        movd     20(%%esi), %%mm4;      \
-        pmuludq  %%mm0,     %%mm4;      \
-        paddq    %%mm3,     %%mm1;      \
-        movd     24(%%esi), %%mm6;      \
-        pmuludq  %%mm0,     %%mm6;      \
-        movd     %%mm1,     4(%%edi);   \
-        psrlq    $32,       %%mm1;      \
-        movd     28(%%esi), %%mm3;      \
-        pmuludq  %%mm0,     %%mm3;      \
-        paddq    %%mm5,     %%mm1;      \
-        movd     16(%%edi), %%mm5;      \
-        paddq    %%mm5,     %%mm2;      \
-        movd     %%mm1,     8(%%edi);   \
-        psrlq    $32,       %%mm1;      \
-        paddq    %%mm7,     %%mm1;      \
-        movd     20(%%edi), %%mm5;      \
-        paddq    %%mm5,     %%mm4;      \
-        movd     %%mm1,     12(%%edi);  \
-        psrlq    $32,       %%mm1;      \
-        paddq    %%mm2,     %%mm1;      \
-        movd     24(%%edi), %%mm5;      \
-        paddq    %%mm5,     %%mm6;      \
-        movd     %%mm1,     16(%%edi);  \
-        psrlq    $32,       %%mm1;      \
-        paddq    %%mm4,     %%mm1;      \
-        movd     28(%%edi), %%mm5;      \
-        paddq    %%mm5,     %%mm3;      \
-        movd     %%mm1,     20(%%edi);  \
-        psrlq    $32,       %%mm1;      \
-        paddq    %%mm6,     %%mm1;      \
-        movd     %%mm1,     24(%%edi);  \
-        psrlq    $32,       %%mm1;      \
-        paddq    %%mm3,     %%mm1;      \
-        movd     %%mm1,     28(%%edi);  \
-        addl     $32,       %%edi;      \
-        addl     $32,       %%esi;      \
-        psrlq    $32,       %%mm1;      \
-        movd     %%mm1,     %%ecx;      \
-        "
-
-#define MULADDC_STOP            \
-        "                       \
-        emms;                   \
-        movl   %4, %%ebx;       \
-        movl   %%ecx, %1;       \
-        movl   %%edi, %2;       \
-        movl   %%esi, %3;       \
-        "                       \
-        : "=m" (t), "=m" (c), "=m" (d), "=m" (s)        \
-        : "m" (t), "m" (s), "m" (d), "m" (c), "m" (b)   \
-        : "eax", "ecx", "edx", "esi", "edi"             \
-    );
-
-#else
-
-#define MULADDC_STOP            \
-        "                       \
-        movl   %4, %%ebx;       \
-        movl   %%ecx, %1;       \
-        movl   %%edi, %2;       \
-        movl   %%esi, %3;       \
-        "                       \
-        : "=m" (t), "=m" (c), "=m" (d), "=m" (s)        \
-        : "m" (t), "m" (s), "m" (d), "m" (c), "m" (b)   \
-        : "eax", "ecx", "edx", "esi", "edi"             \
-    );
-#endif /* SSE2 */
-#endif /* i386 */
-
-#if defined(__amd64__) || defined (__x86_64__)
-
-#define MULADDC_INIT                            \
-    asm( "movq   %0, %%rsi      " :: "m" (s));  \
-    asm( "movq   %0, %%rdi      " :: "m" (d));  \
-    asm( "movq   %0, %%rcx      " :: "m" (c));  \
-    asm( "movq   %0, %%rbx      " :: "m" (b));  \
-    asm( "xorq   %r8, %r8       " );
-
-#define MULADDC_CORE                            \
-    asm( "movq  (%rsi),%rax     " );            \
-    asm( "mulq   %rbx           " );            \
-    asm( "addq   $8,   %rsi     " );            \
-    asm( "addq   %rcx, %rax     " );            \
-    asm( "movq   %r8,  %rcx     " );            \
-    asm( "adcq   $0,   %rdx     " );            \
-    asm( "nop                   " );            \
-    asm( "addq   %rax, (%rdi)   " );            \
-    asm( "adcq   %rdx, %rcx     " );            \
-    asm( "addq   $8,   %rdi     " );
-
-#define MULADDC_STOP                            \
-    asm( "movq   %%rcx, %0      " : "=m" (c));  \
-    asm( "movq   %%rdi, %0      " : "=m" (d));  \
-    asm( "movq   %%rsi, %0      " : "=m" (s) :: \
-    "rax", "rcx", "rdx", "rbx", "rsi", "rdi", "r8" );
-
-#endif /* AMD64 */
-
-#if defined(__mc68020__) || defined(__mcpu32__)
-
-#define MULADDC_INIT                            \
-    asm( "movl   %0, %%a2       " :: "m" (s));  \
-    asm( "movl   %0, %%a3       " :: "m" (d));  \
-    asm( "movl   %0, %%d3       " :: "m" (c));  \
-    asm( "movl   %0, %%d2       " :: "m" (b));  \
-    asm( "moveq  #0, %d0        " );
-
-#define MULADDC_CORE                            \
-    asm( "movel  %a2@+, %d1     " );            \
-    asm( "mulul  %d2, %d4:%d1   " );            \
-    asm( "addl   %d3, %d1       " );            \
-    asm( "addxl  %d0, %d4       " );            \
-    asm( "moveq  #0,  %d3       " );            \
-    asm( "addl   %d1, %a3@+     " );            \
-    asm( "addxl  %d4, %d3       " );
-
-#define MULADDC_STOP                            \
-    asm( "movl   %%d3, %0       " : "=m" (c));  \
-    asm( "movl   %%a3, %0       " : "=m" (d));  \
-    asm( "movl   %%a2, %0       " : "=m" (s) :: \
-    "d0", "d1", "d2", "d3", "d4", "a2", "a3" );
-
-#define MULADDC_HUIT                            \
-    asm( "movel  %a2@+, %d1     " );            \
-    asm( "mulul  %d2, %d4:%d1   " );            \
-    asm( "addxl  %d3, %d1       " );            \
-    asm( "addxl  %d0, %d4       " );            \
-    asm( "addl   %d1, %a3@+     " );            \
-    asm( "movel  %a2@+, %d1     " );            \
-    asm( "mulul  %d2, %d3:%d1   " );            \
-    asm( "addxl  %d4, %d1       " );            \
-    asm( "addxl  %d0, %d3       " );            \
-    asm( "addl   %d1, %a3@+     " );            \
-    asm( "movel  %a2@+, %d1     " );            \
-    asm( "mulul  %d2, %d4:%d1   " );            \
-    asm( "addxl  %d3, %d1       " );            \
-    asm( "addxl  %d0, %d4       " );            \
-    asm( "addl   %d1, %a3@+     " );            \
-    asm( "movel  %a2@+, %d1     " );            \
-    asm( "mulul  %d2, %d3:%d1   " );            \
-    asm( "addxl  %d4, %d1       " );            \
-    asm( "addxl  %d0, %d3       " );            \
-    asm( "addl   %d1, %a3@+     " );            \
-    asm( "movel  %a2@+, %d1     " );            \
-    asm( "mulul  %d2, %d4:%d1   " );            \
-    asm( "addxl  %d3, %d1       " );            \
-    asm( "addxl  %d0, %d4       " );            \
-    asm( "addl   %d1, %a3@+     " );            \
-    asm( "movel  %a2@+, %d1     " );            \
-    asm( "mulul  %d2, %d3:%d1   " );            \
-    asm( "addxl  %d4, %d1       " );            \
-    asm( "addxl  %d0, %d3       " );            \
-    asm( "addl   %d1, %a3@+     " );            \
-    asm( "movel  %a2@+, %d1     " );            \
-    asm( "mulul  %d2, %d4:%d1   " );            \
-    asm( "addxl  %d3, %d1       " );            \
-    asm( "addxl  %d0, %d4       " );            \
-    asm( "addl   %d1, %a3@+     " );            \
-    asm( "movel  %a2@+, %d1     " );            \
-    asm( "mulul  %d2, %d3:%d1   " );            \
-    asm( "addxl  %d4, %d1       " );            \
-    asm( "addxl  %d0, %d3       " );            \
-    asm( "addl   %d1, %a3@+     " );            \
-    asm( "addxl  %d0, %d3       " );
-
-#endif /* MC68000 */
-
-#if defined(__powerpc__)   || defined(__ppc__)
-#if defined(__powerpc64__) || defined(__ppc64__)
-
-#if defined(__MACH__) && defined(__APPLE__)
-
-#define MULADDC_INIT                            \
-    asm( "ld     r3, %0         " :: "m" (s));  \
-    asm( "ld     r4, %0         " :: "m" (d));  \
-    asm( "ld     r5, %0         " :: "m" (c));  \
-    asm( "ld     r6, %0         " :: "m" (b));  \
-    asm( "addi   r3, r3, -8     " );            \
-    asm( "addi   r4, r4, -8     " );            \
-    asm( "addic  r5, r5,  0     " );
-
-#define MULADDC_CORE                            \
-    asm( "ldu    r7, 8(r3)      " );            \
-    asm( "mulld  r8, r7, r6     " );            \
-    asm( "mulhdu r9, r7, r6     " );            \
-    asm( "adde   r8, r8, r5     " );            \
-    asm( "ld     r7, 8(r4)      " );            \
-    asm( "addze  r5, r9         " );            \
-    asm( "addc   r8, r8, r7     " );            \
-    asm( "stdu   r8, 8(r4)      " );
-
-#define MULADDC_STOP                            \
-    asm( "addze  r5, r5         " );            \
-    asm( "addi   r4, r4, 8      " );            \
-    asm( "addi   r3, r3, 8      " );            \
-    asm( "std    r5, %0         " : "=m" (c));  \
-    asm( "std    r4, %0         " : "=m" (d));  \
-    asm( "std    r3, %0         " : "=m" (s) :: \
-    "r3", "r4", "r5", "r6", "r7", "r8", "r9" );
-
-#else
-
-#define MULADDC_INIT                            \
-    asm( "ld     %%r3, %0       " :: "m" (s));  \
-    asm( "ld     %%r4, %0       " :: "m" (d));  \
-    asm( "ld     %%r5, %0       " :: "m" (c));  \
-    asm( "ld     %%r6, %0       " :: "m" (b));  \
-    asm( "addi   %r3, %r3, -8   " );            \
-    asm( "addi   %r4, %r4, -8   " );            \
-    asm( "addic  %r5, %r5,  0   " );
-
-#define MULADDC_CORE                            \
-    asm( "ldu    %r7, 8(%r3)    " );            \
-    asm( "mulld  %r8, %r7, %r6  " );            \
-    asm( "mulhdu %r9, %r7, %r6  " );            \
-    asm( "adde   %r8, %r8, %r5  " );            \
-    asm( "ld     %r7, 8(%r4)    " );            \
-    asm( "addze  %r5, %r9       " );            \
-    asm( "addc   %r8, %r8, %r7  " );            \
-    asm( "stdu   %r8, 8(%r4)    " );
-
-#define MULADDC_STOP                            \
-    asm( "addze  %r5, %r5       " );            \
-    asm( "addi   %r4, %r4, 8    " );            \
-    asm( "addi   %r3, %r3, 8    " );            \
-    asm( "std    %%r5, %0       " : "=m" (c));  \
-    asm( "std    %%r4, %0       " : "=m" (d));  \
-    asm( "std    %%r3, %0       " : "=m" (s) :: \
-    "r3", "r4", "r5", "r6", "r7", "r8", "r9" );
-
-#endif
-
-#else /* PPC32 */
-
-#if defined(__MACH__) && defined(__APPLE__)
-
-#define MULADDC_INIT                            \
-    asm( "lwz    r3, %0         " :: "m" (s));  \
-    asm( "lwz    r4, %0         " :: "m" (d));  \
-    asm( "lwz    r5, %0         " :: "m" (c));  \
-    asm( "lwz    r6, %0         " :: "m" (b));  \
-    asm( "addi   r3, r3, -4     " );            \
-    asm( "addi   r4, r4, -4     " );            \
-    asm( "addic  r5, r5,  0     " );
-
-#define MULADDC_CORE                            \
-    asm( "lwzu   r7, 4(r3)      " );            \
-    asm( "mullw  r8, r7, r6     " );            \
-    asm( "mulhwu r9, r7, r6     " );            \
-    asm( "adde   r8, r8, r5     " );            \
-    asm( "lwz    r7, 4(r4)      " );            \
-    asm( "addze  r5, r9         " );            \
-    asm( "addc   r8, r8, r7     " );            \
-    asm( "stwu   r8, 4(r4)      " );
-
-#define MULADDC_STOP                            \
-    asm( "addze  r5, r5         " );            \
-    asm( "addi   r4, r4, 4      " );            \
-    asm( "addi   r3, r3, 4      " );            \
-    asm( "stw    r5, %0         " : "=m" (c));  \
-    asm( "stw    r4, %0         " : "=m" (d));  \
-    asm( "stw    r3, %0         " : "=m" (s) :: \
-    "r3", "r4", "r5", "r6", "r7", "r8", "r9" );
-
-#else
-
-#define MULADDC_INIT                            \
-    asm( "lwz    %%r3, %0       " :: "m" (s));  \
-    asm( "lwz    %%r4, %0       " :: "m" (d));  \
-    asm( "lwz    %%r5, %0       " :: "m" (c));  \
-    asm( "lwz    %%r6, %0       " :: "m" (b));  \
-    asm( "addi   %r3, %r3, -4   " );            \
-    asm( "addi   %r4, %r4, -4   " );            \
-    asm( "addic  %r5, %r5,  0   " );
-
-#define MULADDC_CORE                            \
-    asm( "lwzu   %r7, 4(%r3)    " );            \
-    asm( "mullw  %r8, %r7, %r6  " );            \
-    asm( "mulhwu %r9, %r7, %r6  " );            \
-    asm( "adde   %r8, %r8, %r5  " );            \
-    asm( "lwz    %r7, 4(%r4)    " );            \
-    asm( "addze  %r5, %r9       " );            \
-    asm( "addc   %r8, %r8, %r7  " );            \
-    asm( "stwu   %r8, 4(%r4)    " );
-
-#define MULADDC_STOP                            \
-    asm( "addze  %r5, %r5       " );            \
-    asm( "addi   %r4, %r4, 4    " );            \
-    asm( "addi   %r3, %r3, 4    " );            \
-    asm( "stw    %%r5, %0       " : "=m" (c));  \
-    asm( "stw    %%r4, %0       " : "=m" (d));  \
-    asm( "stw    %%r3, %0       " : "=m" (s) :: \
-    "r3", "r4", "r5", "r6", "r7", "r8", "r9" );
-
-#endif
-
-#endif /* PPC32 */
-#endif /* PPC64 */
-
-#if defined(__sparc__)
-
-#define MULADDC_INIT                            \
-    asm( "ld     %0, %%o0       " :: "m" (s));  \
-    asm( "ld     %0, %%o1       " :: "m" (d));  \
-    asm( "ld     %0, %%o2       " :: "m" (c));  \
-    asm( "ld     %0, %%o3       " :: "m" (b));
-
-#define MULADDC_CORE                            \
-    asm( "ld    [%o0], %o4      " );            \
-    asm( "inc      4,  %o0      " );            \
-    asm( "ld    [%o1], %o5      " );            \
-    asm( "umul   %o3,  %o4, %o4 " );            \
-    asm( "addcc  %o4,  %o2, %o4 " );            \
-    asm( "rd      %y,  %g1      " );            \
-    asm( "addx   %g1,    0, %g1 " );            \
-    asm( "addcc  %o4,  %o5, %o4 " );            \
-    asm( "st     %o4, [%o1]     " );            \
-    asm( "addx   %g1,    0, %o2 " );            \
-    asm( "inc      4,  %o1      " );
-
-#define MULADDC_STOP                            \
-    asm( "st     %%o2, %0       " : "=m" (c));  \
-    asm( "st     %%o1, %0       " : "=m" (d));  \
-    asm( "st     %%o0, %0       " : "=m" (s) :: \
-    "g1", "o0", "o1", "o2", "o3", "o4", "o5" );
-
-#endif /* SPARCv8 */
-
-#if defined(__microblaze__) || defined(microblaze)
-
-#define MULADDC_INIT                            \
-    asm( "lwi   r3,   %0        " :: "m" (s));  \
-    asm( "lwi   r4,   %0        " :: "m" (d));  \
-    asm( "lwi   r5,   %0        " :: "m" (c));  \
-    asm( "lwi   r6,   %0        " :: "m" (b));  \
-    asm( "andi  r7,   r6, 0xffff" );            \
-    asm( "bsrli r6,   r6, 16    " );
-
-#define MULADDC_CORE                            \
-    asm( "lhui  r8,   r3,   0   " );            \
-    asm( "addi  r3,   r3,   2   " );            \
-    asm( "lhui  r9,   r3,   0   " );            \
-    asm( "addi  r3,   r3,   2   " );            \
-    asm( "mul   r10,  r9,  r6   " );            \
-    asm( "mul   r11,  r8,  r7   " );            \
-    asm( "mul   r12,  r9,  r7   " );            \
-    asm( "mul   r13,  r8,  r6   " );            \
-    asm( "bsrli  r8, r10,  16   " );            \
-    asm( "bsrli  r9, r11,  16   " );            \
-    asm( "add   r13, r13,  r8   " );            \
-    asm( "add   r13, r13,  r9   " );            \
-    asm( "bslli r10, r10,  16   " );            \
-    asm( "bslli r11, r11,  16   " );            \
-    asm( "add   r12, r12, r10   " );            \
-    asm( "addc  r13, r13,  r0   " );            \
-    asm( "add   r12, r12, r11   " );            \
-    asm( "addc  r13, r13,  r0   " );            \
-    asm( "lwi   r10,  r4,   0   " );            \
-    asm( "add   r12, r12, r10   " );            \
-    asm( "addc  r13, r13,  r0   " );            \
-    asm( "add   r12, r12,  r5   " );            \
-    asm( "addc   r5, r13,  r0   " );            \
-    asm( "swi   r12,  r4,   0   " );            \
-    asm( "addi   r4,  r4,   4   " );
-
-#define MULADDC_STOP                            \
-    asm( "swi   r5,   %0        " : "=m" (c));  \
-    asm( "swi   r4,   %0        " : "=m" (d));  \
-    asm( "swi   r3,   %0        " : "=m" (s) :: \
-     "r3", "r4" , "r5" , "r6" , "r7" , "r8" ,   \
-     "r9", "r10", "r11", "r12", "r13" );
-
-#endif /* MicroBlaze */
-
-#if defined(__tricore__)
-
-#define MULADDC_INIT                            \
-    asm( "ld.a   %%a2, %0       " :: "m" (s));  \
-    asm( "ld.a   %%a3, %0       " :: "m" (d));  \
-    asm( "ld.w   %%d4, %0       " :: "m" (c));  \
-    asm( "ld.w   %%d1, %0       " :: "m" (b));  \
-    asm( "xor    %d5, %d5       " );
-
-#define MULADDC_CORE                            \
-    asm( "ld.w   %d0,   [%a2+]      " );        \
-    asm( "madd.u %e2, %e4, %d0, %d1 " );        \
-    asm( "ld.w   %d0,   [%a3]       " );        \
-    asm( "addx   %d2,    %d2,  %d0  " );        \
-    asm( "addc   %d3,    %d3,    0  " );        \
-    asm( "mov    %d4,    %d3        " );        \
-    asm( "st.w  [%a3+],  %d2        " );
-
-#define MULADDC_STOP                            \
-    asm( "st.w   %0, %%d4       " : "=m" (c));  \
-    asm( "st.a   %0, %%a3       " : "=m" (d));  \
-    asm( "st.a   %0, %%a2       " : "=m" (s) :: \
-    "d0", "d1", "e2", "d4", "a2", "a3" );
-
-#endif /* TriCore */
-
-#if defined(__arm__)
-
-#define MULADDC_INIT                            \
-    asm( "ldr    r0, %0         " :: "m" (s));  \
-    asm( "ldr    r1, %0         " :: "m" (d));  \
-    asm( "ldr    r2, %0         " :: "m" (c));  \
-    asm( "ldr    r3, %0         " :: "m" (b));
-
-#define MULADDC_CORE                            \
-    asm( "ldr    r4, [r0], #4   " );            \
-    asm( "mov    r5, #0         " );            \
-    asm( "ldr    r6, [r1]       " );            \
-    asm( "umlal  r2, r5, r3, r4 " );            \
-    asm( "adds   r7, r6, r2     " );            \
-    asm( "adc    r2, r5, #0     " );            \
-    asm( "str    r7, [r1], #4   " );
-
-#define MULADDC_STOP                            \
-    asm( "str    r2, %0         " : "=m" (c));  \
-    asm( "str    r1, %0         " : "=m" (d));  \
-    asm( "str    r0, %0         " : "=m" (s) :: \
-    "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7" );
-
-#endif /* ARMv3 */
-
-#if defined(__alpha__)
-
-#define MULADDC_INIT                            \
-    asm( "ldq    $1, %0         " :: "m" (s));  \
-    asm( "ldq    $2, %0         " :: "m" (d));  \
-    asm( "ldq    $3, %0         " :: "m" (c));  \
-    asm( "ldq    $4, %0         " :: "m" (b));
-
-#define MULADDC_CORE                            \
-    asm( "ldq    $6,  0($1)     " );            \
-    asm( "addq   $1,  8, $1     " );            \
-    asm( "mulq   $6, $4, $7     " );            \
-    asm( "umulh  $6, $4, $6     " );            \
-    asm( "addq   $7, $3, $7     " );            \
-    asm( "cmpult $7, $3, $3     " );            \
-    asm( "ldq    $5,  0($2)     " );            \
-    asm( "addq   $7, $5, $7     " );            \
-    asm( "cmpult $7, $5, $5     " );            \
-    asm( "stq    $7,  0($2)     " );            \
-    asm( "addq   $2,  8, $2     " );            \
-    asm( "addq   $6, $3, $3     " );            \
-    asm( "addq   $5, $3, $3     " );
-
-#define MULADDC_STOP                            \
-    asm( "stq    $3, %0         " : "=m" (c));  \
-    asm( "stq    $2, %0         " : "=m" (d));  \
-    asm( "stq    $1, %0         " : "=m" (s) :: \
-    "$1", "$2", "$3", "$4", "$5", "$6", "$7" );
-
-#endif /* Alpha */
-
-#if defined(__mips__)
-
-#define MULADDC_INIT                            \
-    asm( "lw     $10, %0        " :: "m" (s));  \
-    asm( "lw     $11, %0        " :: "m" (d));  \
-    asm( "lw     $12, %0        " :: "m" (c));  \
-    asm( "lw     $13, %0        " :: "m" (b));
-
-#define MULADDC_CORE                            \
-    asm( "lw     $14, 0($10)    " );            \
-    asm( "multu  $13, $14       " );            \
-    asm( "addi   $10, $10, 4    " );            \
-    asm( "mflo   $14            " );            \
-    asm( "mfhi   $9             " );            \
-    asm( "addu   $14, $12, $14  " );            \
-    asm( "lw     $15, 0($11)    " );            \
-    asm( "sltu   $12, $14, $12  " );            \
-    asm( "addu   $15, $14, $15  " );            \
-    asm( "sltu   $14, $15, $14  " );            \
-    asm( "addu   $12, $12, $9   " );            \
-    asm( "sw     $15, 0($11)    " );            \
-    asm( "addu   $12, $12, $14  " );            \
-    asm( "addi   $11, $11, 4    " );
-
-#define MULADDC_STOP                            \
-    asm( "sw     $12, %0        " : "=m" (c));  \
-    asm( "sw     $11, %0        " : "=m" (d));  \
-    asm( "sw     $10, %0        " : "=m" (s) :: \
-    "$9", "$10", "$11", "$12", "$13", "$14", "$15" );
-
-#endif /* MIPS */
-#endif /* GNUC */
-
-#if (defined(_MSC_VER) && defined(_M_IX86)) || defined(__WATCOMC__)
-
-#define MULADDC_INIT                            \
-    __asm   mov     esi, s                      \
-    __asm   mov     edi, d                      \
-    __asm   mov     ecx, c                      \
-    __asm   mov     ebx, b
-
-#define MULADDC_CORE                            \
-    __asm   lodsd                               \
-    __asm   mul     ebx                         \
-    __asm   add     eax, ecx                    \
-    __asm   adc     edx, 0                      \
-    __asm   add     eax, [edi]                  \
-    __asm   adc     edx, 0                      \
-    __asm   mov     ecx, edx                    \
-    __asm   stosd
-
-#if defined(POLARSSL_HAVE_SSE2)
-
-#define EMIT __asm _emit
-
-#define MULADDC_HUIT                            \
-    EMIT 0x0F  EMIT 0x6E  EMIT 0xC9             \
-    EMIT 0x0F  EMIT 0x6E  EMIT 0xC3             \
-    EMIT 0x0F  EMIT 0x6E  EMIT 0x1F             \
-    EMIT 0x0F  EMIT 0xD4  EMIT 0xCB             \
-    EMIT 0x0F  EMIT 0x6E  EMIT 0x16             \
-    EMIT 0x0F  EMIT 0xF4  EMIT 0xD0             \
-    EMIT 0x0F  EMIT 0x6E  EMIT 0x66  EMIT 0x04  \
-    EMIT 0x0F  EMIT 0xF4  EMIT 0xE0             \
-    EMIT 0x0F  EMIT 0x6E  EMIT 0x76  EMIT 0x08  \
-    EMIT 0x0F  EMIT 0xF4  EMIT 0xF0             \
-    EMIT 0x0F  EMIT 0x6E  EMIT 0x7E  EMIT 0x0C  \
-    EMIT 0x0F  EMIT 0xF4  EMIT 0xF8             \
-    EMIT 0x0F  EMIT 0xD4  EMIT 0xCA             \
-    EMIT 0x0F  EMIT 0x6E  EMIT 0x5F  EMIT 0x04  \
-    EMIT 0x0F  EMIT 0xD4  EMIT 0xDC             \
-    EMIT 0x0F  EMIT 0x6E  EMIT 0x6F  EMIT 0x08  \
-    EMIT 0x0F  EMIT 0xD4  EMIT 0xEE             \
-    EMIT 0x0F  EMIT 0x6E  EMIT 0x67  EMIT 0x0C  \
-    EMIT 0x0F  EMIT 0xD4  EMIT 0xFC             \
-    EMIT 0x0F  EMIT 0x7E  EMIT 0x0F             \
-    EMIT 0x0F  EMIT 0x6E  EMIT 0x56  EMIT 0x10  \
-    EMIT 0x0F  EMIT 0xF4  EMIT 0xD0             \
-    EMIT 0x0F  EMIT 0x73  EMIT 0xD1  EMIT 0x20  \
-    EMIT 0x0F  EMIT 0x6E  EMIT 0x66  EMIT 0x14  \
-    EMIT 0x0F  EMIT 0xF4  EMIT 0xE0             \
-    EMIT 0x0F  EMIT 0xD4  EMIT 0xCB             \
-    EMIT 0x0F  EMIT 0x6E  EMIT 0x76  EMIT 0x18  \
-    EMIT 0x0F  EMIT 0xF4  EMIT 0xF0             \
-    EMIT 0x0F  EMIT 0x7E  EMIT 0x4F  EMIT 0x04  \
-    EMIT 0x0F  EMIT 0x73  EMIT 0xD1  EMIT 0x20  \
-    EMIT 0x0F  EMIT 0x6E  EMIT 0x5E  EMIT 0x1C  \
-    EMIT 0x0F  EMIT 0xF4  EMIT 0xD8             \
-    EMIT 0x0F  EMIT 0xD4  EMIT 0xCD             \
-    EMIT 0x0F  EMIT 0x6E  EMIT 0x6F  EMIT 0x10  \
-    EMIT 0x0F  EMIT 0xD4  EMIT 0xD5             \
-    EMIT 0x0F  EMIT 0x7E  EMIT 0x4F  EMIT 0x08  \
-    EMIT 0x0F  EMIT 0x73  EMIT 0xD1  EMIT 0x20  \
-    EMIT 0x0F  EMIT 0xD4  EMIT 0xCF             \
-    EMIT 0x0F  EMIT 0x6E  EMIT 0x6F  EMIT 0x14  \
-    EMIT 0x0F  EMIT 0xD4  EMIT 0xE5             \
-    EMIT 0x0F  EMIT 0x7E  EMIT 0x4F  EMIT 0x0C  \
-    EMIT 0x0F  EMIT 0x73  EMIT 0xD1  EMIT 0x20  \
-    EMIT 0x0F  EMIT 0xD4  EMIT 0xCA             \
-    EMIT 0x0F  EMIT 0x6E  EMIT 0x6F  EMIT 0x18  \
-    EMIT 0x0F  EMIT 0xD4  EMIT 0xF5             \
-    EMIT 0x0F  EMIT 0x7E  EMIT 0x4F  EMIT 0x10  \
-    EMIT 0x0F  EMIT 0x73  EMIT 0xD1  EMIT 0x20  \
-    EMIT 0x0F  EMIT 0xD4  EMIT 0xCC             \
-    EMIT 0x0F  EMIT 0x6E  EMIT 0x6F  EMIT 0x1C  \
-    EMIT 0x0F  EMIT 0xD4  EMIT 0xDD             \
-    EMIT 0x0F  EMIT 0x7E  EMIT 0x4F  EMIT 0x14  \
-    EMIT 0x0F  EMIT 0x73  EMIT 0xD1  EMIT 0x20  \
-    EMIT 0x0F  EMIT 0xD4  EMIT 0xCE             \
-    EMIT 0x0F  EMIT 0x7E  EMIT 0x4F  EMIT 0x18  \
-    EMIT 0x0F  EMIT 0x73  EMIT 0xD1  EMIT 0x20  \
-    EMIT 0x0F  EMIT 0xD4  EMIT 0xCB             \
-    EMIT 0x0F  EMIT 0x7E  EMIT 0x4F  EMIT 0x1C  \
-    EMIT 0x83  EMIT 0xC7  EMIT 0x20             \
-    EMIT 0x83  EMIT 0xC6  EMIT 0x20             \
-    EMIT 0x0F  EMIT 0x73  EMIT 0xD1  EMIT 0x20  \
-    EMIT 0x0F  EMIT 0x7E  EMIT 0xC9
-
-#define MULADDC_STOP                            \
-    EMIT 0x0F  EMIT 0x77                        \
-    __asm   mov     c, ecx                      \
-    __asm   mov     d, edi                      \
-    __asm   mov     s, esi                      \
-
-#else
-
-#define MULADDC_STOP                            \
-    __asm   mov     c, ecx                      \
-    __asm   mov     d, edi                      \
-    __asm   mov     s, esi                      \
-
-#endif /* SSE2 */
-#endif /* MSVC */
-
-#endif /* POLARSSL_HAVE_ASM */
-
-#if !defined(MULADDC_CORE)
-#if defined(POLARSSL_HAVE_LONGLONG)
-
-#define MULADDC_INIT                    \
-{                                       \
-    t_dbl r;                            \
-    t_int r0, r1;
-
-#define MULADDC_CORE                    \
-    r   = *(s++) * (t_dbl) b;           \
-    r0  = r;                            \
-    r1  = r >> biL;                     \
-    r0 += c;  r1 += (r0 <  c);          \
-    r0 += *d; r1 += (r0 < *d);          \
-    c = r1; *(d++) = r0;
-
-#define MULADDC_STOP                    \
-}
-
-#else
-#define MULADDC_INIT                    \
-{                                       \
-    t_int s0, s1, b0, b1;               \
-    t_int r0, r1, rx, ry;               \
-    b0 = ( b << biH ) >> biH;           \
-    b1 = ( b >> biH );
-
-#define MULADDC_CORE                    \
-    s0 = ( *s << biH ) >> biH;          \
-    s1 = ( *s >> biH ); s++;            \
-    rx = s0 * b1; r0 = s0 * b0;         \
-    ry = s1 * b0; r1 = s1 * b1;         \
-    r1 += ( rx >> biH );                \
-    r1 += ( ry >> biH );                \
-    rx <<= biH; ry <<= biH;             \
-    r0 += rx; r1 += (r0 < rx);          \
-    r0 += ry; r1 += (r0 < ry);          \
-    r0 +=  c; r1 += (r0 <  c);          \
-    r0 += *d; r1 += (r0 < *d);          \
-    c = r1; *(d++) = r0;
-
-#define MULADDC_STOP                    \
-}
-
-#endif /* C (generic)  */
-#endif /* C (longlong) */
-
-#endif /* bn_mul.h */
diff --git a/src/pdkim/config.h b/src/pdkim/config.h
new file mode 100644 (file)
index 0000000..fdd4cfe
--- /dev/null
@@ -0,0 +1,4 @@
+#define POLARSSL_BASE64_C
+
+
+
diff --git a/src/pdkim/crypt_ver.h b/src/pdkim/crypt_ver.h
new file mode 100644 (file)
index 0000000..cd2171c
--- /dev/null
@@ -0,0 +1,26 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Jeremy Harris 2016 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* RSA and SHA routine selection for PDKIM */
+
+#include "../exim.h"
+#include "../sha_ver.h"
+
+
+#ifdef USE_GNUTLS
+# include <gnutls/gnutls.h>
+
+# if GNUTLS_VERSION_NUMBER >= 0x30000
+#  define RSA_GNUTLS
+# else
+#  define RSA_GCRYPT
+# endif
+
+#else
+# define RSA_OPENSSL
+#endif
+
dissimilarity index 88%
index 4f0da3f..4c93de7 100644 (file)
-/*
- *  PDKIM - a RFC4871 (DKIM) implementation
- *
- *  Copyright (C) 2009 - 2012  Tom Kistner <tom@duncanthrax.net>
- *
- *  http://duncanthrax.net/pdkim/
- *
- *  This program 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 program 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 program; if not, write to the Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <ctype.h>
-
-#include "pdkim.h"
-
-#include "sha1.h"
-#include "sha2.h"
-#include "rsa.h"
-#include "base64.h"
-
-#define PDKIM_SIGNATURE_VERSION     "1"
-#define PDKIM_PUB_RECORD_VERSION    "DKIM1"
-
-#define PDKIM_MAX_HEADER_LEN        65536
-#define PDKIM_MAX_HEADERS           512
-#define PDKIM_MAX_BODY_LINE_LEN     16384
-#define PDKIM_DNS_TXT_MAX_NAMELEN   1024
-#define PDKIM_DEFAULT_SIGN_HEADERS "From:Sender:Reply-To:Subject:Date:"\
-                             "Message-ID:To:Cc:MIME-Version:Content-Type:"\
-                             "Content-Transfer-Encoding:Content-ID:"\
-                             "Content-Description:Resent-Date:Resent-From:"\
-                             "Resent-Sender:Resent-To:Resent-Cc:"\
-                             "Resent-Message-ID:In-Reply-To:References:"\
-                             "List-Id:List-Help:List-Unsubscribe:"\
-                             "List-Subscribe:List-Post:List-Owner:List-Archive"
-
-/* -------------------------------------------------------------------------- */
-struct pdkim_stringlist {
-  char *value;
-  int  tag;
-  void *next;
-};
-
-#define PDKIM_STR_ALLOC_FRAG 256
-struct pdkim_str {
-  char         *str;
-  unsigned int  len;
-  unsigned int  allocated;
-};
-
-/* -------------------------------------------------------------------------- */
-/* A bunch of list constants */
-const char *pdkim_querymethods[] = {
-  "dns/txt",
-  NULL
-};
-const char *pdkim_algos[] = {
-  "rsa-sha256",
-  "rsa-sha1",
-  NULL
-};
-const char *pdkim_canons[] = {
-  "simple",
-  "relaxed",
-  NULL
-};
-const char *pdkim_hashes[] = {
-  "sha256",
-  "sha1",
-  NULL
-};
-const char *pdkim_keytypes[] = {
-  "rsa",
-  NULL
-};
-
-typedef struct pdkim_combined_canon_entry {
-  const char *str;
-  int canon_headers;
-  int canon_body;
-} pdkim_combined_canon_entry;
-pdkim_combined_canon_entry pdkim_combined_canons[] = {
-  { "simple/simple",    PDKIM_CANON_SIMPLE,   PDKIM_CANON_SIMPLE },
-  { "simple/relaxed",   PDKIM_CANON_SIMPLE,   PDKIM_CANON_RELAXED },
-  { "relaxed/simple",   PDKIM_CANON_RELAXED,  PDKIM_CANON_SIMPLE },
-  { "relaxed/relaxed",  PDKIM_CANON_RELAXED,  PDKIM_CANON_RELAXED },
-  { "simple",           PDKIM_CANON_SIMPLE,   PDKIM_CANON_SIMPLE },
-  { "relaxed",          PDKIM_CANON_RELAXED,  PDKIM_CANON_SIMPLE },
-  { NULL,               0,                    0 }
-};
-
-
-const char *pdkim_verify_status_str(int status) {
-  switch(status) {
-    case PDKIM_VERIFY_NONE:    return "PDKIM_VERIFY_NONE";
-    case PDKIM_VERIFY_INVALID: return "PDKIM_VERIFY_INVALID";
-    case PDKIM_VERIFY_FAIL:    return "PDKIM_VERIFY_FAIL";
-    case PDKIM_VERIFY_PASS:    return "PDKIM_VERIFY_PASS";
-    default:                   return "PDKIM_VERIFY_UNKNOWN";
-  }
-}
-const char *pdkim_verify_ext_status_str(int ext_status) {
-  switch(ext_status) {
-    case PDKIM_VERIFY_FAIL_BODY: return "PDKIM_VERIFY_FAIL_BODY";
-    case PDKIM_VERIFY_FAIL_MESSAGE: return "PDKIM_VERIFY_FAIL_MESSAGE";
-    case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE: return "PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE";
-    case PDKIM_VERIFY_INVALID_BUFFER_SIZE: return "PDKIM_VERIFY_INVALID_BUFFER_SIZE";
-    case PDKIM_VERIFY_INVALID_PUBKEY_PARSING: return "PDKIM_VERIFY_INVALID_PUBKEY_PARSING";
-    default: return "PDKIM_VERIFY_UNKNOWN";
-  }
-}
-
-
-/* -------------------------------------------------------------------------- */
-/* Print debugging functions */
-#ifdef PDKIM_DEBUG
-void pdkim_quoteprint(FILE *stream, const char *data, int len, int lf) {
-  int i;
-  const unsigned char *p = (const unsigned char *)data;
-
-  for (i=0;i<len;i++) {
-    const int c = p[i];
-    switch (c) {
-      case ' ' : fprintf(stream,"{SP}"); break;
-      case '\t': fprintf(stream,"{TB}"); break;
-      case '\r': fprintf(stream,"{CR}"); break;
-      case '\n': fprintf(stream,"{LF}"); break;
-      case '{' : fprintf(stream,"{BO}"); break;
-      case '}' : fprintf(stream,"{BC}"); break;
-      default:
-        if ( (c < 32) || (c > 127) )
-          fprintf(stream,"{%02x}",c);
-        else
-          fputc(c,stream);
-      break;
-    }
-  }
-  if (lf)
-    fputc('\n',stream);
-}
-void pdkim_hexprint(FILE *stream, const char *data, int len, int lf) {
-  int i;
-  const unsigned char *p = (const unsigned char *)data;
-
-  for (i=0;i<len;i++) {
-    const int c = p[i];
-    fprintf(stream,"%02x",c);
-  }
-  if (lf)
-    fputc('\n',stream);
-}
-#endif
-
-
-/* -------------------------------------------------------------------------- */
-/* Simple string list implementation for convinience */
-pdkim_stringlist *pdkim_append_stringlist(pdkim_stringlist *base, char *str) {
-  pdkim_stringlist *new_entry = malloc(sizeof(pdkim_stringlist));
-  if (new_entry == NULL) return NULL;
-  memset(new_entry,0,sizeof(pdkim_stringlist));
-  new_entry->value = strdup(str);
-  if (new_entry->value == NULL) return NULL;
-  if (base != NULL) {
-    pdkim_stringlist *last = base;
-    while (last->next != NULL) { last = last->next; }
-    last->next = new_entry;
-    return base;
-  }
-  else return new_entry;
-}
-pdkim_stringlist *pdkim_prepend_stringlist(pdkim_stringlist *base, char *str) {
-  pdkim_stringlist *new_entry = malloc(sizeof(pdkim_stringlist));
-  if (new_entry == NULL) return NULL;
-  memset(new_entry,0,sizeof(pdkim_stringlist));
-  new_entry->value = strdup(str);
-  if (new_entry->value == NULL) return NULL;
-  if (base != NULL) {
-    new_entry->next = base;
-  }
-  return new_entry;
-}
-
-
-/* -------------------------------------------------------------------------- */
-/* A small "growing string" implementation to escape malloc/realloc hell */
-pdkim_str *pdkim_strnew (const char *cstr) {
-  unsigned int len = cstr?strlen(cstr):0;
-  pdkim_str *p = malloc(sizeof(pdkim_str));
-  if (p == NULL) return NULL;
-  memset(p,0,sizeof(pdkim_str));
-  p->str = malloc(len+1);
-  if (p->str == NULL) {
-    free(p);
-    return NULL;
-  }
-  p->allocated=(len+1);
-  p->len=len;
-  if (cstr) strcpy(p->str,cstr);
-  else p->str[p->len] = '\0';
-  return p;
-}
-char *pdkim_strncat(pdkim_str *str, const char *data, int len) {
-  if ((str->allocated - str->len) < (len+1)) {
-    /* Extend the buffer */
-    int num_frags = ((len+1)/PDKIM_STR_ALLOC_FRAG)+1;
-    char *n = realloc(str->str,
-                      (str->allocated+(num_frags*PDKIM_STR_ALLOC_FRAG)));
-    if (n == NULL) return NULL;
-    str->str = n;
-    str->allocated += (num_frags*PDKIM_STR_ALLOC_FRAG);
-  }
-  strncpy(&(str->str[str->len]),data,len);
-  str->len+=len;
-  str->str[str->len] = '\0';
-  return str->str;
-}
-char *pdkim_strcat(pdkim_str *str, const char *cstr) {
-  return pdkim_strncat(str, cstr, strlen(cstr));
-}
-char *pdkim_numcat(pdkim_str *str, unsigned long num) {
-  char minibuf[20];
-  snprintf(minibuf,20,"%lu",num);
-  return pdkim_strcat(str,minibuf);
-}
-char *pdkim_strtrim(pdkim_str *str) {
-  char *p = str->str;
-  char *q = str->str;
-  while ( (*p != '\0') && ((*p == '\t') || (*p == ' ')) ) p++;
-  while (*p != '\0') {*q = *p; q++; p++;}
-  *q = '\0';
-  while ( (q != str->str) && ( (*q == '\0') || (*q == '\t') || (*q == ' ') ) ) {
-    *q = '\0';
-    q--;
-  }
-  str->len = strlen(str->str);
-  return str->str;
-}
-char *pdkim_strclear(pdkim_str *str) {
-  str->str[0] = '\0';
-  str->len = 0;
-  return str->str;
-}
-void pdkim_strfree(pdkim_str *str) {
-  if (str == NULL) return;
-  if (str->str != NULL) free(str->str);
-  free(str);
-}
-
-
-
-/* -------------------------------------------------------------------------- */
-void pdkim_free_pubkey(pdkim_pubkey *pub) {
-  if (pub) {
-    if (pub->version        != NULL) free(pub->version);
-    if (pub->granularity    != NULL) free(pub->granularity);
-    if (pub->hashes         != NULL) free(pub->hashes);
-    if (pub->keytype        != NULL) free(pub->keytype);
-    if (pub->srvtype        != NULL) free(pub->srvtype);
-    if (pub->notes          != NULL) free(pub->notes);
-    if (pub->key            != NULL) free(pub->key);
-    free(pub);
-  }
-}
-
-
-/* -------------------------------------------------------------------------- */
-void pdkim_free_sig(pdkim_signature *sig) {
-  if (sig) {
-    pdkim_signature *next = (pdkim_signature *)sig->next;
-
-    pdkim_stringlist *e = sig->headers;
-    while(e != NULL) {
-      pdkim_stringlist *c = e;
-      if (e->value != NULL) free(e->value);
-      e = e->next;
-      free(c);
-    }
-
-    if (sig->sigdata          != NULL) free(sig->sigdata);
-    if (sig->bodyhash         != NULL) free(sig->bodyhash);
-    if (sig->selector         != NULL) free(sig->selector);
-    if (sig->domain           != NULL) free(sig->domain);
-    if (sig->identity         != NULL) free(sig->identity);
-    if (sig->headernames      != NULL) free(sig->headernames);
-    if (sig->copiedheaders    != NULL) free(sig->copiedheaders);
-    if (sig->rsa_privkey      != NULL) free(sig->rsa_privkey);
-    if (sig->sign_headers     != NULL) free(sig->sign_headers);
-    if (sig->signature_header != NULL) free(sig->signature_header);
-    if (sig->sha1_body        != NULL) free(sig->sha1_body);
-    if (sig->sha2_body        != NULL) free(sig->sha2_body);
-
-    if (sig->pubkey != NULL) pdkim_free_pubkey(sig->pubkey);
-
-    free(sig);
-    if (next != NULL) pdkim_free_sig(next);
-  }
-}
-
-
-/* -------------------------------------------------------------------------- */
-DLLEXPORT void pdkim_free_ctx(pdkim_ctx *ctx) {
-  if (ctx) {
-    pdkim_stringlist *e = ctx->headers;
-    while(e != NULL) {
-      pdkim_stringlist *c = e;
-      if (e->value != NULL) free(e->value);
-      e = e->next;
-      free(c);
-    }
-    pdkim_free_sig(ctx->sig);
-    pdkim_strfree(ctx->cur_header);
-    free(ctx);
-  }
-}
-
-
-/* -------------------------------------------------------------------------- */
-/* Matches the name of the passed raw "header" against
-   the passed colon-separated "list", starting at entry
-   "start". Returns the position of the header name in
-   the list. */
-int header_name_match(const char *header,
-                      char       *tick,
-                      int         do_tick) {
-  char *hname;
-  char *lcopy;
-  char *p;
-  char *q;
-  int rc = PDKIM_FAIL;
-
-  /* Get header name */
-  char *hcolon = strchr(header,':');
-  if (hcolon == NULL) return rc; /* This isn't a header */
-  hname = malloc((hcolon-header)+1);
-  if (hname == NULL) return PDKIM_ERR_OOM;
-  memset(hname,0,(hcolon-header)+1);
-  strncpy(hname,header,(hcolon-header));
-
-  /* Copy tick-off list locally, so we can punch zeroes into it */
-  lcopy = strdup(tick);
-  if (lcopy == NULL) {
-    free(hname);
-    return PDKIM_ERR_OOM;
-  }
-  p = lcopy;
-  q = strchr(p,':');
-  while (q != NULL) {
-    *q = '\0';
-
-    if (strcasecmp(p,hname) == 0) {
-      rc = PDKIM_OK;
-      /* Invalidate header name instance in tick-off list */
-      if (do_tick) tick[p-lcopy] = '_';
-      goto BAIL;
-    }
-
-    p = q+1;
-    q = strchr(p,':');
-  }
-
-  if (strcasecmp(p,hname) == 0) {
-    rc = PDKIM_OK;
-    /* Invalidate header name instance in tick-off list */
-    if (do_tick) tick[p-lcopy] = '_';
-  }
-
-  BAIL:
-  free(hname);
-  free(lcopy);
-  return rc;
-}
-
-
-/* -------------------------------------------------------------------------- */
-/* Performs "relaxed" canonicalization of a header. The returned pointer needs
-   to be free()d. */
-char *pdkim_relax_header (char *header, int crlf) {
-  int past_field_name = 0;
-  int seen_wsp = 0;
-  char *p = header;
-  char *q;
-  char *relaxed = malloc(strlen(header)+3);
-  if (relaxed == NULL) return NULL;
-  q = relaxed;
-  while (*p != '\0') {
-    int c = *p;
-    /* Ignore CR & LF */
-    if ( (c == '\r') || (c == '\n') ) {
-      p++;
-      continue;
-    }
-    if ( (c == '\t') || (c == ' ') ) {
-      c = ' '; /* Turns WSP into SP */
-      if (seen_wsp) {
-        p++;
-        continue;
-      }
-      else seen_wsp = 1;
-    }
-    else {
-      if ( (!past_field_name) && (c == ':') ) {
-        if (seen_wsp) q--;   /* This removes WSP before the colon */
-        seen_wsp = 1;        /* This removes WSP after the colon */
-        past_field_name = 1;
-      }
-      else seen_wsp = 0;
-    }
-    /* Lowercase header name */
-    if (!past_field_name) c = tolower(c);
-    *q = c;
-    p++;
-    q++;
-  }
-  if ((q>relaxed) && (*(q-1) == ' ')) q--; /* Squash eventual trailing SP */
-  *q = '\0';
-  if (crlf) strcat(relaxed,"\r\n");
-  return relaxed;
-}
-
-
-/* -------------------------------------------------------------------------- */
-#define PDKIM_QP_ERROR_DECODE -1
-char *pdkim_decode_qp_char(char *qp_p, int *c) {
-  char *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 */
-    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;
-  }
-
-  /* Illegal char here */
-  *c = PDKIM_QP_ERROR_DECODE;
-  return initial_pos;
-}
-
-
-/* -------------------------------------------------------------------------- */
-char *pdkim_decode_qp(char *str) {
-  int nchar = 0;
-  char *q;
-  char *p = str;
-  char *n = malloc(strlen(p)+1);
-  if (n == NULL) return NULL;
-  *n = '\0';
-  q = n;
-  while (*p != '\0') {
-    if (*p == '=') {
-      p = pdkim_decode_qp_char(p,&nchar);
-      if (nchar >= 0) {
-        *q = nchar;
-        q++;
-        continue;
-      }
-    }
-    else {
-      *q = *p;
-      q++;
-    }
-    p++;
-  }
-  *q = '\0';
-  return n;
-}
-
-
-/* -------------------------------------------------------------------------- */
-char *pdkim_decode_base64(char *str, int *num_decoded) {
-  int dlen = 0;
-  char *res;
-
-  base64_decode(NULL, &dlen, (unsigned char *)str, strlen(str));
-  res = malloc(dlen+1);
-  if (res == NULL) return NULL;
-  if (base64_decode((unsigned char *)res,&dlen,(unsigned char *)str,strlen(str)) != 0) {
-    free(res);
-    return NULL;
-  }
-  if (num_decoded != NULL) *num_decoded = dlen;
-  return res;
-}
-
-/* -------------------------------------------------------------------------- */
-char *pdkim_encode_base64(char *str, int num) {
-  int dlen = 0;
-  char *res;
-
-  base64_encode(NULL, &dlen, (unsigned char *)str, num);
-  res = malloc(dlen+1);
-  if (res == NULL) return NULL;
-  if (base64_encode((unsigned char *)res,&dlen,(unsigned char *)str,num) != 0) {
-    free(res);
-    return NULL;
-  }
-  return res;
-}
-
-
-/* -------------------------------------------------------------------------- */
-#define PDKIM_HDR_LIMBO 0
-#define PDKIM_HDR_TAG   1
-#define PDKIM_HDR_VALUE 2
-pdkim_signature *pdkim_parse_sig_header(pdkim_ctx *ctx, char *raw_hdr) {
-  pdkim_signature *sig ;
-  char *p,*q;
-  pdkim_str *cur_tag = NULL;
-  pdkim_str *cur_val = NULL;
-  int past_hname = 0;
-  int in_b_val = 0;
-  int where = PDKIM_HDR_LIMBO;
-  int i;
-
-  sig = malloc(sizeof(pdkim_signature));
-  if (sig == NULL) return NULL;
-  memset(sig,0,sizeof(pdkim_signature));
-  sig->bodylength = -1;
-
-  sig->rawsig_no_b_val = malloc(strlen(raw_hdr)+1);
-  if (sig->rawsig_no_b_val == NULL) {
-    free(sig);
-    return NULL;
-  }
-
-  p = raw_hdr;
-  q = sig->rawsig_no_b_val;
-
-  while (1) {
-
-    /* Ignore FWS */
-    if ( (*p == '\r') || (*p == '\n') )
-      goto NEXT_CHAR;
-
-    /* Fast-forward through header name */
-    if (!past_hname) {
-      if (*p == ':') past_hname = 1;
-      goto NEXT_CHAR;
-    }
-
-    if (where == PDKIM_HDR_LIMBO) {
-      /* In limbo, just wait for a tag-char to appear */
-      if (!((*p >= 'a') && (*p <= 'z')))
-        goto NEXT_CHAR;
-
-      where = PDKIM_HDR_TAG;
-    }
-
-    if (where == PDKIM_HDR_TAG) {
-      if (cur_tag == NULL)
-        cur_tag = pdkim_strnew(NULL);
-
-      if ((*p >= 'a') && (*p <= 'z'))
-        pdkim_strncat(cur_tag,p,1);
-
-      if (*p == '=') {
-        if (strcmp(cur_tag->str,"b") == 0) {
-          *q = '='; q++;
-          in_b_val = 1;
-        }
-        where = PDKIM_HDR_VALUE;
-        goto NEXT_CHAR;
-      }
-    }
-
-    if (where == PDKIM_HDR_VALUE) {
-      if (cur_val == NULL)
-        cur_val = pdkim_strnew(NULL);
-
-      if ( (*p == '\r') || (*p == '\n') || (*p == ' ') || (*p == '\t') )
-        goto NEXT_CHAR;
-
-      if ( (*p == ';') || (*p == '\0') ) {
-        if (cur_tag->len > 0) {
-          pdkim_strtrim(cur_val);
-          #ifdef PDKIM_DEBUG
-          if (ctx->debug_stream)
-            fprintf(ctx->debug_stream, "%s=%s\n", cur_tag->str, cur_val->str);
-          #endif
-          switch (cur_tag->str[0]) {
-            case 'b':
-              switch (cur_tag->str[1]) {
-                case 'h':
-                  sig->bodyhash = pdkim_decode_base64(cur_val->str,&(sig->bodyhash_len));
-                break;
-                default:
-                  sig->sigdata = pdkim_decode_base64(cur_val->str,&(sig->sigdata_len));
-                break;
-              }
-            break;
-            case 'v':
-              if (strcmp(cur_val->str,PDKIM_SIGNATURE_VERSION) == 0) {
-                /* We only support version 1, and that is currently the
-                   only version there is. */
-                sig->version = 1;
-              }
-            break;
-            case 'a':
-              i = 0;
-              while (pdkim_algos[i] != NULL) {
-                if (strcmp(cur_val->str,pdkim_algos[i]) == 0 ) {
-                  sig->algo = i;
-                  break;
-                }
-                i++;
-              }
-            break;
-            case 'c':
-              i = 0;
-              while (pdkim_combined_canons[i].str != NULL) {
-                if (strcmp(cur_val->str,pdkim_combined_canons[i].str) == 0 ) {
-                  sig->canon_headers = pdkim_combined_canons[i].canon_headers;
-                  sig->canon_body    = pdkim_combined_canons[i].canon_body;
-                  break;
-                }
-                i++;
-              }
-            break;
-            case 'q':
-              i = 0;
-              while (pdkim_querymethods[i] != NULL) {
-                if (strcmp(cur_val->str,pdkim_querymethods[i]) == 0 ) {
-                  sig->querymethod = i;
-                  break;
-                }
-                i++;
-              }
-            break;
-            case 's':
-              sig->selector = strdup(cur_val->str);
-            break;
-            case 'd':
-              sig->domain = strdup(cur_val->str);
-            break;
-            case 'i':
-              sig->identity = pdkim_decode_qp(cur_val->str);
-            break;
-            case 't':
-              sig->created = strtoul(cur_val->str,NULL,10);
-            break;
-            case 'x':
-              sig->expires = strtoul(cur_val->str,NULL,10);
-            break;
-            case 'l':
-              sig->bodylength = strtol(cur_val->str,NULL,10);
-            break;
-            case 'h':
-              sig->headernames = strdup(cur_val->str);
-            break;
-            case 'z':
-              sig->copiedheaders = pdkim_decode_qp(cur_val->str);
-            break;
-            default:
-              #ifdef PDKIM_DEBUG
-              if (ctx->debug_stream)
-                fprintf(ctx->debug_stream, "Unknown tag encountered\n");
-              #endif
-            break;
-          }
-        }
-        pdkim_strclear(cur_tag);
-        pdkim_strclear(cur_val);
-        in_b_val = 0;
-        where = PDKIM_HDR_LIMBO;
-        goto NEXT_CHAR;
-      }
-      else pdkim_strncat(cur_val,p,1);
-    }
-
-    NEXT_CHAR:
-    if (*p == '\0') break;
-
-    if (!in_b_val) {
-      *q = *p;
-      q++;
-    }
-    p++;
-  }
-
-  /* Make sure the most important bits are there. */
-  if (!(sig->domain      && (*(sig->domain)      != '\0') &&
-        sig->selector    && (*(sig->selector)    != '\0') &&
-        sig->headernames && (*(sig->headernames) != '\0') &&
-        sig->bodyhash    &&
-        sig->sigdata     &&
-        sig->version)) {
-    pdkim_free_sig(sig);
-    return NULL;
-  }
-
-  *q = '\0';
-  /* Chomp raw header. The final newline must not be added to the signature. */
-  q--;
-  while( (q > sig->rawsig_no_b_val) && ((*q == '\r') || (*q == '\n')) ) {
-    *q = '\0'; q--;
-  }
-
-  #ifdef PDKIM_DEBUG
-  if (ctx->debug_stream) {
-    fprintf(ctx->debug_stream,
-            "PDKIM >> Raw signature w/o b= tag value >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
-    pdkim_quoteprint(ctx->debug_stream,
-                     sig->rawsig_no_b_val,
-                     strlen(sig->rawsig_no_b_val), 1);
-    fprintf(ctx->debug_stream,
-            "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
-  }
-  #endif
-
-  sig->sha1_body = malloc(sizeof(sha1_context));
-  if (sig->sha1_body == NULL) {
-    pdkim_free_sig(sig);
-    return NULL;
-  }
-  sig->sha2_body = malloc(sizeof(sha2_context));
-  if (sig->sha2_body == NULL) {
-    pdkim_free_sig(sig);
-    return NULL;
-  }
-
-  sha1_starts(sig->sha1_body);
-  sha2_starts(sig->sha2_body,0);
-
-  return sig;
-}
-
-
-/* -------------------------------------------------------------------------- */
-pdkim_pubkey *pdkim_parse_pubkey_record(pdkim_ctx *ctx, char *raw_record) {
-  pdkim_pubkey *pub ;
-  char *p;
-  pdkim_str *cur_tag = NULL;
-  pdkim_str *cur_val = NULL;
-  int where = PDKIM_HDR_LIMBO;
-
-  pub = malloc(sizeof(pdkim_pubkey));
-  if (pub == NULL) return NULL;
-  memset(pub,0,sizeof(pdkim_pubkey));
-
-  p = raw_record;
-
-  while (1) {
-
-    /* Ignore FWS */
-    if ( (*p == '\r') || (*p == '\n') )
-      goto NEXT_CHAR;
-
-    if (where == PDKIM_HDR_LIMBO) {
-      /* In limbo, just wait for a tag-char to appear */
-      if (!((*p >= 'a') && (*p <= 'z')))
-        goto NEXT_CHAR;
-
-      where = PDKIM_HDR_TAG;
-    }
-
-    if (where == PDKIM_HDR_TAG) {
-      if (cur_tag == NULL)
-        cur_tag = pdkim_strnew(NULL);
-
-      if ((*p >= 'a') && (*p <= 'z'))
-        pdkim_strncat(cur_tag,p,1);
-
-      if (*p == '=') {
-        where = PDKIM_HDR_VALUE;
-        goto NEXT_CHAR;
-      }
-    }
-
-    if (where == PDKIM_HDR_VALUE) {
-      if (cur_val == NULL)
-        cur_val = pdkim_strnew(NULL);
-
-      if ( (*p == '\r') || (*p == '\n') )
-        goto NEXT_CHAR;
-
-      if ( (*p == ';') || (*p == '\0') ) {
-        if (cur_tag->len > 0) {
-          pdkim_strtrim(cur_val);
-          #ifdef PDKIM_DEBUG
-          if (ctx->debug_stream)
-            fprintf(ctx->debug_stream, "%s=%s\n", cur_tag->str, cur_val->str);
-          #endif
-          switch (cur_tag->str[0]) {
-            case 'v':
-              /* This tag isn't evaluated because:
-                 - We only support version DKIM1.
-                 - Which is the default for this value (set below)
-                 - Other versions are currently not specified.      */
-            break;
-            case 'h':
-              pub->hashes = strdup(cur_val->str);
-            break;
-            case 'g':
-              pub->granularity = strdup(cur_val->str);
-            break;
-            case 'n':
-              pub->notes = pdkim_decode_qp(cur_val->str);
-            break;
-            case 'p':
-              pub->key = pdkim_decode_base64(cur_val->str,&(pub->key_len));
-            break;
-            case 'k':
-              pub->hashes = strdup(cur_val->str);
-            break;
-            case 's':
-              pub->srvtype = strdup(cur_val->str);
-            break;
-            case 't':
-              if (strchr(cur_val->str,'y') != NULL) pub->testing = 1;
-              if (strchr(cur_val->str,'s') != NULL) pub->no_subdomaining = 1;
-            break;
-            default:
-              #ifdef PDKIM_DEBUG
-              if (ctx->debug_stream)
-                fprintf(ctx->debug_stream, "Unknown tag encountered\n");
-              #endif
-            break;
-          }
-        }
-        pdkim_strclear(cur_tag);
-        pdkim_strclear(cur_val);
-        where = PDKIM_HDR_LIMBO;
-        goto NEXT_CHAR;
-      }
-      else pdkim_strncat(cur_val,p,1);
-    }
-
-    NEXT_CHAR:
-    if (*p == '\0') break;
-    p++;
-  }
-
-  /* Set fallback defaults */
-  if (pub->version     == NULL) pub->version     = strdup(PDKIM_PUB_RECORD_VERSION);
-  if (pub->granularity == NULL) pub->granularity = strdup("*");
-  if (pub->keytype     == NULL) pub->keytype     = strdup("rsa");
-  if (pub->srvtype     == NULL) pub->srvtype     = strdup("*");
-
-  /* p= is required */
-  if (pub->key == NULL) {
-    pdkim_free_pubkey(pub);
-    return NULL;
-  }
-
-  return pub;
-}
-
-
-/* -------------------------------------------------------------------------- */
-int pdkim_update_bodyhash(pdkim_ctx *ctx, const char *data, int len) {
-  pdkim_signature *sig = ctx->sig;
-  /* Cache relaxed version of data */
-  char *relaxed_data = NULL;
-  int   relaxed_len  = 0;
-
-  /* Traverse all signatures, updating their hashes. */
-  while (sig != NULL) {
-    /* Defaults to simple canon (no further treatment necessary) */
-    const char *canon_data = data;
-    int         canon_len = len;
-
-    if (sig->canon_body == PDKIM_CANON_RELAXED) {
-      /* Relax the line if not done already */
-      if (relaxed_data == NULL) {
-        int seen_wsp = 0;
-        const char *p = data;
-        int q = 0;
-        relaxed_data = malloc(len+1);
-        if (relaxed_data == NULL) return PDKIM_ERR_OOM;
-        while (*p != '\0') {
-          char c = *p;
-          if (c == '\r') {
-            if ( (q > 0) && (relaxed_data[q-1] == ' ') ) q--;
-          }
-          else if ( (c == '\t') || (c == ' ') ) {
-            c = ' '; /* Turns WSP into SP */
-            if (seen_wsp) {
-              p++;
-              continue;
-            }
-            else seen_wsp = 1;
-          }
-          else seen_wsp = 0;
-          relaxed_data[q++] = c;
-          p++;
-        }
-        relaxed_data[q] = '\0';
-        relaxed_len = q;
-      }
-      canon_data = relaxed_data;
-      canon_len  = relaxed_len;
-    }
-
-    /* Make sure we don't exceed the to-be-signed body length */
-    if ((sig->bodylength >= 0) &&
-        ((sig->signed_body_bytes+(unsigned long)canon_len) > sig->bodylength))
-      canon_len = (sig->bodylength - sig->signed_body_bytes);
-
-    if (canon_len > 0) {
-      if (sig->algo == PDKIM_ALGO_RSA_SHA1)
-        sha1_update(sig->sha1_body,(unsigned char *)canon_data,canon_len);
-      else
-        sha2_update(sig->sha2_body,(unsigned char *)canon_data,canon_len);
-      sig->signed_body_bytes += canon_len;
-#ifdef PDKIM_DEBUG
-      if (ctx->debug_stream!=NULL)
-        pdkim_quoteprint(ctx->debug_stream,canon_data,canon_len,0);
-#endif
-    }
-
-    sig = sig->next;
-  }
-
-  if (relaxed_data != NULL) free(relaxed_data);
-  return PDKIM_OK;
-}
-
-
-/* -------------------------------------------------------------------------- */
-int pdkim_finish_bodyhash(pdkim_ctx *ctx) {
-  pdkim_signature *sig = ctx->sig;
-
-  /* Traverse all signatures */
-  while (sig != NULL) {
-
-    /* Finish hashes */
-    unsigned char bh[32]; /* SHA-256 = 32 Bytes,  SHA-1 = 20 Bytes */
-    if (sig->algo == PDKIM_ALGO_RSA_SHA1)
-      sha1_finish(sig->sha1_body,bh);
-    else
-      sha2_finish(sig->sha2_body,bh);
-
-    #ifdef PDKIM_DEBUG
-    if (ctx->debug_stream) {
-      fprintf(ctx->debug_stream, "PDKIM [%s] Body bytes hashed: %lu\n",
-        sig->domain, sig->signed_body_bytes);
-      fprintf(ctx->debug_stream, "PDKIM [%s] bh  computed: ", sig->domain);
-      pdkim_hexprint(ctx->debug_stream, (char *)bh,
-                     (sig->algo == PDKIM_ALGO_RSA_SHA1)?20:32,1);
-    }
-    #endif
-
-    /* SIGNING -------------------------------------------------------------- */
-    if (ctx->mode == PDKIM_MODE_SIGN) {
-      sig->bodyhash_len = (sig->algo == PDKIM_ALGO_RSA_SHA1)?20:32;
-      sig->bodyhash = malloc(sig->bodyhash_len);
-      if (sig->bodyhash == NULL) return PDKIM_ERR_OOM;
-      memcpy(sig->bodyhash,bh,sig->bodyhash_len);
-
-      /* If bodylength limit is set, and we have received less bytes
-         than the requested amount, effectively remove the limit tag. */
-      if (sig->signed_body_bytes < sig->bodylength) sig->bodylength = -1;
-    }
-    /* VERIFICATION --------------------------------------------------------- */
-    else {
-      /* Compare bodyhash */
-      if (memcmp(bh,sig->bodyhash,
-                 (sig->algo == PDKIM_ALGO_RSA_SHA1)?20:32) == 0) {
-        #ifdef PDKIM_DEBUG
-        if (ctx->debug_stream)
-          fprintf(ctx->debug_stream, "PDKIM [%s] Body hash verified OK\n",
-                  sig->domain);
-        #endif
-      }
-      else {
-        #ifdef PDKIM_DEBUG
-        if (ctx->debug_stream) {
-          fprintf(ctx->debug_stream, "PDKIM [%s] Body hash did NOT verify\n",
-                  sig->domain);
-          fprintf(ctx->debug_stream, "PDKIM [%s] bh signature: ", sig->domain);
-          pdkim_hexprint(ctx->debug_stream, sig->bodyhash,
-                           (sig->algo == PDKIM_ALGO_RSA_SHA1)?20:32,1);
-        }
-        #endif
-        sig->verify_status     = PDKIM_VERIFY_FAIL;
-        sig->verify_ext_status = PDKIM_VERIFY_FAIL_BODY;
-      }
-    }
-
-    sig = sig->next;
-  }
-
-  return PDKIM_OK;
-}
-
-
-
-/* -------------------------------------------------------------------------- */
-/* Callback from pdkim_feed below for processing complete body lines */
-int pdkim_bodyline_complete(pdkim_ctx *ctx) {
-  char *p = ctx->linebuf;
-  int   n = ctx->linebuf_offset;
-
-  /* Ignore extra data if we've seen the end-of-data marker */
-  if (ctx->seen_eod) goto BAIL;
-
-  /* We've always got one extra byte to stuff a zero ... */
-  ctx->linebuf[(ctx->linebuf_offset)] = '\0';
-
-  if (ctx->input_mode == PDKIM_INPUT_SMTP) {
-    /* Terminate on EOD marker */
-    if (memcmp(p,".\r\n",3) == 0) {
-      ctx->seen_eod = 1;
-      goto BAIL;
-    }
-    /* Unstuff dots */
-    if (memcmp(p,"..",2) == 0) {
-      p++;
-      n--;
-    }
-  }
-
-  /* Empty lines need to be buffered until we find a non-empty line */
-  if (memcmp(p,"\r\n",2) == 0) {
-    ctx->num_buffered_crlf++;
-    goto BAIL;
-  }
-
-  /* At this point, we have a non-empty line, so release the buffered ones. */
-  while (ctx->num_buffered_crlf) {
-    pdkim_update_bodyhash(ctx,"\r\n",2);
-    ctx->num_buffered_crlf--;
-  }
-
-  pdkim_update_bodyhash(ctx,p,n);
-
-  BAIL:
-  ctx->linebuf_offset = 0;
-  return PDKIM_OK;
-}
-
-
-/* -------------------------------------------------------------------------- */
-/* Callback from pdkim_feed below for processing complete headers */
-#define DKIM_SIGNATURE_HEADERNAME "DKIM-Signature:"
-int pdkim_header_complete(pdkim_ctx *ctx) {
-  pdkim_signature *sig = ctx->sig;
-
-  /* Special case: The last header can have an extra \r appended */
-  if ( (ctx->cur_header->len > 1) &&
-       (ctx->cur_header->str[(ctx->cur_header->len)-1] == '\r') ) {
-    ctx->cur_header->str[(ctx->cur_header->len)-1] = '\0';
-    ctx->cur_header->len--;
-  }
-
-  ctx->num_headers++;
-  if (ctx->num_headers > PDKIM_MAX_HEADERS) goto BAIL;
-
-  /* SIGNING -------------------------------------------------------------- */
-  if (ctx->mode == PDKIM_MODE_SIGN) {
-    /* Traverse all signatures */
-    while (sig != NULL) {
-      pdkim_stringlist *list;
-
-      if (header_name_match(ctx->cur_header->str,
-                            sig->sign_headers?
-                              sig->sign_headers:
-                              PDKIM_DEFAULT_SIGN_HEADERS, 0) != PDKIM_OK) goto NEXT_SIG;
-
-      /* Add header to the signed headers list (in reverse order) */
-      list = pdkim_prepend_stringlist(sig->headers,
-                                      ctx->cur_header->str);
-      if (list == NULL) return PDKIM_ERR_OOM;
-      sig->headers = list;
-  
-      NEXT_SIG:
-      sig = sig->next;
-    }
-  }
-
-  /* DKIM-Signature: headers are added to the verification list */
-  if (ctx->mode == PDKIM_MODE_VERIFY) {
-    if (strncasecmp(ctx->cur_header->str,
-                    DKIM_SIGNATURE_HEADERNAME,
-                    strlen(DKIM_SIGNATURE_HEADERNAME)) == 0) {
-      pdkim_signature *new_sig;
-      /* Create and chain new signature block */
-      #ifdef PDKIM_DEBUG
-      if (ctx->debug_stream)
-        fprintf(ctx->debug_stream,
-          "PDKIM >> Found sig, trying to parse >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
-      #endif
-      new_sig = pdkim_parse_sig_header(ctx, ctx->cur_header->str);
-      if (new_sig != NULL) {
-        pdkim_signature *last_sig = ctx->sig;
-        if (last_sig == NULL) {
-          ctx->sig = new_sig;
-        }
-        else {
-          while (last_sig->next != NULL) { last_sig = last_sig->next; }
-          last_sig->next = new_sig;
-        }
-      }
-      else {
-        #ifdef PDKIM_DEBUG
-        if (ctx->debug_stream) {
-          fprintf(ctx->debug_stream,"Error while parsing signature header\n");
-          fprintf(ctx->debug_stream,
-            "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
-        }
-        #endif
-      }
-    }
-    /* every other header is stored for signature verification */
-    else {
-      pdkim_stringlist *list;
-
-      list = pdkim_prepend_stringlist(ctx->headers,
-                                      ctx->cur_header->str);
-      if (list == NULL) return PDKIM_ERR_OOM;
-      ctx->headers = list;
-    }
-  }
-
-  BAIL:
-  pdkim_strclear(ctx->cur_header); /* Re-use existing pdkim_str */
-  return PDKIM_OK;
-}
-
-
-
-/* -------------------------------------------------------------------------- */
-#define HEADER_BUFFER_FRAG_SIZE 256
-DLLEXPORT int pdkim_feed (pdkim_ctx *ctx,
-                char *data,
-                int   len) {
-  int p;
-  for (p=0;p<len;p++) {
-    char c = data[p];
-    if (ctx->past_headers) {
-      /* Processing body byte */
-      ctx->linebuf[(ctx->linebuf_offset)++] = c;
-      if (c == '\n') {
-        int rc = pdkim_bodyline_complete(ctx); /* End of line */
-        if (rc != PDKIM_OK) return rc;
-      }
-      if (ctx->linebuf_offset == (PDKIM_MAX_BODY_LINE_LEN-1))
-        return PDKIM_ERR_LONG_LINE;
-    }
-    else {
-      /* Processing header byte */
-      if (c != '\r') {
-        if (c == '\n') {
-          if (ctx->seen_lf) {
-            int rc = pdkim_header_complete(ctx); /* Seen last header line */
-            if (rc != PDKIM_OK) return rc;
-            ctx->past_headers = 1;
-            ctx->seen_lf = 0;
-#ifdef PDKIM_DEBUG
-            if (ctx->debug_stream)
-              fprintf(ctx->debug_stream,
-                "PDKIM >> Hashed body data, canonicalized >>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
-#endif
-            continue;
-          }
-          else ctx->seen_lf = 1;
-        }
-        else if (ctx->seen_lf) {
-          if (! ((c == '\t') || (c == ' '))) {
-            int rc = pdkim_header_complete(ctx); /* End of header */
-            if (rc != PDKIM_OK) return rc;
-          }
-          ctx->seen_lf = 0;
-        }
-      }
-      if (ctx->cur_header == NULL) {
-        ctx->cur_header = pdkim_strnew(NULL);
-        if (ctx->cur_header == NULL) return PDKIM_ERR_OOM;
-      }
-      if (ctx->cur_header->len < PDKIM_MAX_HEADER_LEN)
-        if (pdkim_strncat(ctx->cur_header,&data[p],1) == NULL)
-          return PDKIM_ERR_OOM;
-    }
-  }
-  return PDKIM_OK;
-}
-
-
-/* -------------------------------------------------------------------------- */
-char *pdkim_create_header(pdkim_signature *sig, int final) {
-  char *rc = NULL;
-  char *base64_bh = NULL;
-  char *base64_b  = NULL;
-  pdkim_str *hdr = pdkim_strnew("DKIM-Signature: v="PDKIM_SIGNATURE_VERSION);
-  if (hdr == NULL) return NULL;
-
-  base64_bh = pdkim_encode_base64(sig->bodyhash, sig->bodyhash_len);
-  if (base64_bh == NULL) goto BAIL;
-
-  /* Required and static bits */
-  if (
-        pdkim_strcat(hdr,"; a=")                                &&
-        pdkim_strcat(hdr,pdkim_algos[sig->algo])                &&
-        pdkim_strcat(hdr,"; q=")                                &&
-        pdkim_strcat(hdr,pdkim_querymethods[sig->querymethod])  &&
-        pdkim_strcat(hdr,"; c=")                                &&
-        pdkim_strcat(hdr,pdkim_canons[sig->canon_headers])      &&
-        pdkim_strcat(hdr,"/")                                   &&
-        pdkim_strcat(hdr,pdkim_canons[sig->canon_body])         &&
-        pdkim_strcat(hdr,"; d=")                                &&
-        pdkim_strcat(hdr,sig->domain)                           &&
-        pdkim_strcat(hdr,"; s=")                                &&
-        pdkim_strcat(hdr,sig->selector)                         &&
-        pdkim_strcat(hdr,";\r\n\th=")                           &&
-        pdkim_strcat(hdr,sig->headernames)                      &&
-        pdkim_strcat(hdr,"; bh=")                               &&
-        pdkim_strcat(hdr,base64_bh)                             &&
-        pdkim_strcat(hdr,";\r\n\t")
-     ) {
-    /* Optional bits */
-    if (sig->identity != NULL) {
-      if (!( pdkim_strcat(hdr,"i=")                             &&
-             pdkim_strcat(hdr,sig->identity)                    &&
-             pdkim_strcat(hdr,";") ) ) {
-        goto BAIL;
-      }
-    }
-    if (sig->created > 0) {
-      if (!( pdkim_strcat(hdr,"t=")                             &&
-             pdkim_numcat(hdr,sig->created)                     &&
-             pdkim_strcat(hdr,";") ) ) {
-        goto BAIL;
-      }
-    }
-    if (sig->expires > 0) {
-      if (!( pdkim_strcat(hdr,"x=")                             &&
-             pdkim_numcat(hdr,sig->expires)                     &&
-             pdkim_strcat(hdr,";") ) ) {
-        goto BAIL;
-      }
-    }
-    if (sig->bodylength >= 0) {
-      if (!( pdkim_strcat(hdr,"l=")                             &&
-             pdkim_numcat(hdr,sig->bodylength)                  &&
-             pdkim_strcat(hdr,";") ) ) {
-        goto BAIL;
-      }
-    }
-    /* Extra linebreak */
-    if (hdr->str[(hdr->len)-1] == ';') {
-      if (!pdkim_strcat(hdr," \r\n\t")) goto BAIL;
-    }
-    /* Preliminary or final version? */
-    if (final) {
-      base64_b = pdkim_encode_base64(sig->sigdata, sig->sigdata_len);
-      if (base64_b == NULL) goto BAIL;
-      if (
-            pdkim_strcat(hdr,"b=")                              &&
-            pdkim_strcat(hdr,base64_b)                          &&
-            pdkim_strcat(hdr,";")
-         ) goto DONE;
-    }
-    else {
-      if (pdkim_strcat(hdr,"b=;")) goto DONE;
-    }
-
-    goto BAIL;
-  }
-
-  DONE:
-  rc = strdup(hdr->str);
-
-  BAIL:
-  pdkim_strfree(hdr);
-  if (base64_bh != NULL) free(base64_bh);
-  if (base64_b  != NULL) free(base64_b);
-  return rc;
-}
-
-
-/* -------------------------------------------------------------------------- */
-DLLEXPORT int pdkim_feed_finish(pdkim_ctx *ctx, pdkim_signature **return_signatures) {
-  pdkim_signature *sig = ctx->sig;
-  pdkim_str *headernames = NULL;             /* Collected signed header names */
-
-  /* Check if we must still flush a (partial) header. If that is the
-     case, the message has no body, and we must compute a body hash
-     out of '<CR><LF>' */
-  if (ctx->cur_header && ctx->cur_header->len) {
-    int rc = pdkim_header_complete(ctx);
-    if (rc != PDKIM_OK) return rc;
-    pdkim_update_bodyhash(ctx,"\r\n",2);
-  }
-  else {
-    /* For non-smtp input, check if there's an unfinished line in the
-       body line buffer. If that is the case, we must add a CRLF to the
-       hash to properly terminate the message. */
-    if ((ctx->input_mode == PDKIM_INPUT_NORMAL) && ctx->linebuf_offset) {
-      pdkim_update_bodyhash(ctx, ctx->linebuf, ctx->linebuf_offset);
-      pdkim_update_bodyhash(ctx,"\r\n",2);
-    }
-    #ifdef PDKIM_DEBUG
-    if (ctx->debug_stream)
-      fprintf(ctx->debug_stream,
-        "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
-    #endif
-  }
-
-  /* Build (and/or evaluate) body hash */
-  if (pdkim_finish_bodyhash(ctx) != PDKIM_OK) return PDKIM_ERR_OOM;
-
-  /* SIGNING -------------------------------------------------------------- */
-  if (ctx->mode == PDKIM_MODE_SIGN) {
-    headernames = pdkim_strnew(NULL);
-    if (headernames == NULL) return PDKIM_ERR_OOM;
-  }
-  /* ---------------------------------------------------------------------- */
-
-  while (sig != NULL) {
-    sha1_context sha1_headers;
-    sha2_context sha2_headers;
-    char *sig_hdr;
-    char headerhash[32];
-
-    if (sig->algo == PDKIM_ALGO_RSA_SHA1)
-      sha1_starts(&sha1_headers);
-    else
-      sha2_starts(&sha2_headers,0);
-
-    #ifdef PDKIM_DEBUG
-    if (ctx->debug_stream)
-      fprintf(ctx->debug_stream,
-              "PDKIM >> Hashed header data, canonicalized, in sequence >>>>>>>>>>>>>>\n");
-    #endif
-
-    /* SIGNING ---------------------------------------------------------------- */
-    /* When signing, walk through our header list and add them to the hash. As we
-       go, construct a list of the header's names to use for the h= parameter. */
-    if (ctx->mode == PDKIM_MODE_SIGN) {
-      pdkim_stringlist *p = sig->headers;
-      while (p != NULL) {
-        char *rh = NULL;
-        /* Collect header names (Note: colon presence is guaranteed here) */
-        char *q = strchr(p->value,':');
-        if (pdkim_strncat(headernames, p->value,
-                          (q-(p->value))+((p->next==NULL)?0:1)) == NULL)
-          return PDKIM_ERR_OOM;
-
-        if (sig->canon_headers == PDKIM_CANON_RELAXED)
-          rh = pdkim_relax_header(p->value,1); /* cook header for relaxed canon */
-        else
-          rh = strdup(p->value);               /* just copy it for simple canon */
-
-        if (rh == NULL) return PDKIM_ERR_OOM;
-
-        /* Feed header to the hash algorithm */
-        if (sig->algo == PDKIM_ALGO_RSA_SHA1)
-          sha1_update(&(sha1_headers),(unsigned char *)rh,strlen(rh));
-        else
-          sha2_update(&(sha2_headers),(unsigned char *)rh,strlen(rh));
-        #ifdef PDKIM_DEBUG
-        if (ctx->debug_stream)
-          pdkim_quoteprint(ctx->debug_stream, rh, strlen(rh), 1);
-        #endif
-        free(rh);
-        p = p->next;
-      }
-    }
-    /* VERIFICATION ----------------------------------------------------------- */
-    /* When verifying, walk through the header name list in the h= parameter and
-       add the headers to the hash in that order. */
-    else {
-      char *b = strdup(sig->headernames);
-      char *p = b;
-      char *q = NULL;
-      pdkim_stringlist *hdrs = ctx->headers;
-
-      if (b == NULL) return PDKIM_ERR_OOM;
-
-      /* clear tags */
-      while (hdrs != NULL) {
-        hdrs->tag = 0;
-        hdrs = hdrs->next;
-      }
-
-      while(1) {
-        hdrs = ctx->headers;
-        q = strchr(p,':');
-        if (q != NULL) *q = '\0';
-        while (hdrs != NULL) {
-          if ( (hdrs->tag == 0) &&
-               (strncasecmp(hdrs->value,p,strlen(p)) == 0) &&
-               ((hdrs->value)[strlen(p)] == ':') ) {
-            char *rh = NULL;
-            if (sig->canon_headers == PDKIM_CANON_RELAXED)
-              rh = pdkim_relax_header(hdrs->value,1); /* cook header for relaxed canon */
-            else
-              rh = strdup(hdrs->value);               /* just copy it for simple canon */
-            if (rh == NULL) return PDKIM_ERR_OOM;
-            /* Feed header to the hash algorithm */
-            if (sig->algo == PDKIM_ALGO_RSA_SHA1)
-              sha1_update(&(sha1_headers),(unsigned char *)rh,strlen(rh));
-            else
-              sha2_update(&(sha2_headers),(unsigned char *)rh,strlen(rh));
-            #ifdef PDKIM_DEBUG
-            if (ctx->debug_stream)
-              pdkim_quoteprint(ctx->debug_stream, rh, strlen(rh), 1);
-            #endif
-            free(rh);
-            hdrs->tag = 1;
-            break;
-          }
-          hdrs = hdrs->next;
-        }
-        if (q == NULL) break;
-        p = q+1;
-      }
-      free(b);
-    }
-
-    #ifdef PDKIM_DEBUG
-    if (ctx->debug_stream)
-      fprintf(ctx->debug_stream,
-              "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
-    #endif
-
-    /* SIGNING ---------------------------------------------------------------- */
-    if (ctx->mode == PDKIM_MODE_SIGN) {
-      /* Copy headernames to signature struct */
-      sig->headernames = strdup(headernames->str);
-      pdkim_strfree(headernames);
-
-      /* Create signature header with b= omitted */
-      sig_hdr = pdkim_create_header(ctx->sig,0);
-    }
-    /* VERIFICATION ----------------------------------------------------------- */
-    else {
-      sig_hdr = strdup(sig->rawsig_no_b_val);
-    }
-    /* ------------------------------------------------------------------------ */
-
-    if (sig_hdr == NULL) return PDKIM_ERR_OOM;
-
-    /* Relax header if necessary */
-    if (sig->canon_headers == PDKIM_CANON_RELAXED) {
-      char *relaxed_hdr = pdkim_relax_header(sig_hdr,0);
-      free(sig_hdr);
-      if (relaxed_hdr == NULL) return PDKIM_ERR_OOM;
-      sig_hdr = relaxed_hdr;
-    }
-
-    #ifdef PDKIM_DEBUG
-    if (ctx->debug_stream) {
-      fprintf(ctx->debug_stream,
-              "PDKIM >> Signed DKIM-Signature header, canonicalized >>>>>>>>>>>>>>>>>\n");
-      pdkim_quoteprint(ctx->debug_stream, sig_hdr, strlen(sig_hdr), 1);
-      fprintf(ctx->debug_stream,
-              "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
-    }
-    #endif
-
-    /* Finalize header hash */
-    if (sig->algo == PDKIM_ALGO_RSA_SHA1) {
-      sha1_update(&(sha1_headers),(unsigned char *)sig_hdr,strlen(sig_hdr));
-      sha1_finish(&(sha1_headers),(unsigned char *)headerhash);
-      #ifdef PDKIM_DEBUG
-      if (ctx->debug_stream) {
-        fprintf(ctx->debug_stream, "PDKIM [%s] hh computed: ", sig->domain);
-        pdkim_hexprint(ctx->debug_stream, headerhash, 20, 1);
-      }
-      #endif
-    }
-    else {
-      sha2_update(&(sha2_headers),(unsigned char *)sig_hdr,strlen(sig_hdr));
-      sha2_finish(&(sha2_headers),(unsigned char *)headerhash);
-      #ifdef PDKIM_DEBUG
-      if (ctx->debug_stream) {
-        fprintf(ctx->debug_stream, "PDKIM [%s] hh computed: ", sig->domain);
-        pdkim_hexprint(ctx->debug_stream, headerhash, 32, 1);
-      }
-      #endif
-    }
-
-    free(sig_hdr);
-
-    /* SIGNING ---------------------------------------------------------------- */
-    if (ctx->mode == PDKIM_MODE_SIGN) {
-      rsa_context rsa;
-
-      rsa_init(&rsa,RSA_PKCS_V15,0);
-
-      /* Perform private key operation */
-      if (rsa_parse_key(&rsa, (unsigned char *)sig->rsa_privkey,
-                        strlen(sig->rsa_privkey), NULL, 0) != 0) {
-        return PDKIM_ERR_RSA_PRIVKEY;
-      }
-
-      sig->sigdata_len = mpi_size(&(rsa.N));
-      sig->sigdata = malloc(sig->sigdata_len);
-      if (sig->sigdata == NULL) return PDKIM_ERR_OOM;
-
-      if (rsa_pkcs1_sign( &rsa, RSA_PRIVATE,
-                          ((sig->algo == PDKIM_ALGO_RSA_SHA1)?
-                             SIG_RSA_SHA1:SIG_RSA_SHA256),
-                          0,
-                          (unsigned char *)headerhash,
-                          (unsigned char *)sig->sigdata ) != 0) {
-        return PDKIM_ERR_RSA_SIGNING;
-      }
-
-      rsa_free(&rsa);
-
-      #ifdef PDKIM_DEBUG
-      if (ctx->debug_stream) {
-        fprintf(ctx->debug_stream, "PDKIM [%s] b computed: ",
-                sig->domain);
-        pdkim_hexprint(ctx->debug_stream, sig->sigdata, sig->sigdata_len, 1);
-      }
-      #endif
-
-      sig->signature_header = pdkim_create_header(ctx->sig,1);
-      if (sig->signature_header == NULL) return PDKIM_ERR_OOM;
-    }
-    /* VERIFICATION ----------------------------------------------------------- */
-    else {
-      rsa_context rsa;
-      char *dns_txt_name, *dns_txt_reply;
-
-      rsa_init(&rsa,RSA_PKCS_V15,0);
-
-      dns_txt_name  = malloc(PDKIM_DNS_TXT_MAX_NAMELEN);
-      if (dns_txt_name == NULL) return PDKIM_ERR_OOM;
-      dns_txt_reply = malloc(PDKIM_DNS_TXT_MAX_RECLEN);
-      if (dns_txt_reply == NULL) {
-        free(dns_txt_name);
-        return PDKIM_ERR_OOM;
-      }
-      memset(dns_txt_reply,0,PDKIM_DNS_TXT_MAX_RECLEN);
-      memset(dns_txt_name ,0,PDKIM_DNS_TXT_MAX_NAMELEN);
-
-      if (snprintf(dns_txt_name,PDKIM_DNS_TXT_MAX_NAMELEN,
-                   "%s._domainkey.%s.",
-                   sig->selector,sig->domain) >= PDKIM_DNS_TXT_MAX_NAMELEN) {
-        sig->verify_status =      PDKIM_VERIFY_INVALID;
-        sig->verify_ext_status =  PDKIM_VERIFY_INVALID_BUFFER_SIZE;
-        goto NEXT_VERIFY;
-      }
-
-      if ((ctx->dns_txt_callback(dns_txt_name, dns_txt_reply) != PDKIM_OK) ||
-          (dns_txt_reply[0] == '\0')) {
-        sig->verify_status =      PDKIM_VERIFY_INVALID;
-        sig->verify_ext_status =  PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE;
-        goto NEXT_VERIFY;
-      }
-
-      #ifdef PDKIM_DEBUG
-      if (ctx->debug_stream) {
-        fprintf(ctx->debug_stream,
-                "PDKIM >> Parsing public key record >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
-        fprintf(ctx->debug_stream,"Raw record: ");
-        pdkim_quoteprint(ctx->debug_stream, dns_txt_reply, strlen(dns_txt_reply), 1);
-      }
-      #endif
-
-      sig->pubkey = pdkim_parse_pubkey_record(ctx,dns_txt_reply);
-      if (sig->pubkey == NULL) {
-        sig->verify_status =      PDKIM_VERIFY_INVALID;
-        sig->verify_ext_status =  PDKIM_VERIFY_INVALID_PUBKEY_PARSING;
-        #ifdef PDKIM_DEBUG
-        if (ctx->debug_stream) {
-          fprintf(ctx->debug_stream,"Error while parsing public key record\n");
-          fprintf(ctx->debug_stream,
-            "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
-        }
-        #endif
-        goto NEXT_VERIFY;
-      }
-
-      #ifdef PDKIM_DEBUG
-      if (ctx->debug_stream) {
-        fprintf(ctx->debug_stream,
-          "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
-      }
-      #endif
-
-      if (rsa_parse_public_key(&rsa,
-                              (unsigned char *)sig->pubkey->key,
-                               sig->pubkey->key_len) != 0) {
-        sig->verify_status =      PDKIM_VERIFY_INVALID;
-        sig->verify_ext_status =  PDKIM_VERIFY_INVALID_PUBKEY_PARSING;
-        goto NEXT_VERIFY;
-      }
-
-      /* Check the signature */
-      if (rsa_pkcs1_verify(&rsa,
-                        RSA_PUBLIC,
-                        ((sig->algo == PDKIM_ALGO_RSA_SHA1)?
-                             SIG_RSA_SHA1:SIG_RSA_SHA256),
-                        0,
-                        (unsigned char *)headerhash,
-                        (unsigned char *)sig->sigdata) != 0) {
-        sig->verify_status =      PDKIM_VERIFY_FAIL;
-        sig->verify_ext_status =  PDKIM_VERIFY_FAIL_MESSAGE;
-        goto NEXT_VERIFY;
-      }
-
-      /* We have a winner! (if bodydhash was correct earlier) */
-      if (sig->verify_status == PDKIM_VERIFY_NONE) {
-        sig->verify_status = PDKIM_VERIFY_PASS;
-      }
-
-      NEXT_VERIFY:
-
-      #ifdef PDKIM_DEBUG
-      if (ctx->debug_stream) {
-        fprintf(ctx->debug_stream, "PDKIM [%s] signature status: %s",
-                sig->domain, pdkim_verify_status_str(sig->verify_status));
-        if (sig->verify_ext_status > 0) {
-          fprintf(ctx->debug_stream, " (%s)\n",
-                  pdkim_verify_ext_status_str(sig->verify_ext_status));
-        }
-        else {
-          fprintf(ctx->debug_stream, "\n");
-        }
-      }
-      #endif
-
-      rsa_free(&rsa);
-      free(dns_txt_name);
-      free(dns_txt_reply);
-    }
-
-    sig = sig->next;
-  }
-
-  /* If requested, set return pointer to signature(s) */
-  if (return_signatures != NULL) {
-    *return_signatures = ctx->sig;
-  }
-
-  return PDKIM_OK;
-}
-
-
-/* -------------------------------------------------------------------------- */
-DLLEXPORT pdkim_ctx *pdkim_init_verify(int input_mode,
-                             int(*dns_txt_callback)(char *, char *)
-                             ) {
-  pdkim_ctx *ctx = malloc(sizeof(pdkim_ctx));
-  if (ctx == NULL) return NULL;
-  memset(ctx,0,sizeof(pdkim_ctx));
-
-  ctx->linebuf = malloc(PDKIM_MAX_BODY_LINE_LEN);
-  if (ctx->linebuf == NULL) {
-    free(ctx);
-    return NULL;
-  }
-
-  ctx->mode = PDKIM_MODE_VERIFY;
-  ctx->input_mode = input_mode;
-  ctx->dns_txt_callback = dns_txt_callback;
-
-  return ctx;
-}
-
-
-/* -------------------------------------------------------------------------- */
-DLLEXPORT pdkim_ctx *pdkim_init_sign(int input_mode,
-                           char *domain,
-                           char *selector,
-                           char *rsa_privkey) {
-  pdkim_ctx *ctx;
-  pdkim_signature *sig;
-
-  if (!domain || !selector || !rsa_privkey) return NULL;
-
-  ctx = malloc(sizeof(pdkim_ctx));
-  if (ctx == NULL) return NULL;
-  memset(ctx,0,sizeof(pdkim_ctx));
-
-  ctx->linebuf = malloc(PDKIM_MAX_BODY_LINE_LEN);
-  if (ctx->linebuf == NULL) {
-    free(ctx);
-    return NULL;
-  }
-
-  sig = malloc(sizeof(pdkim_signature));
-  if (sig == NULL) {
-    free(ctx->linebuf);
-    free(ctx);
-    return NULL;
-  }
-  memset(sig,0,sizeof(pdkim_signature));
-  sig->bodylength = -1;
-
-  ctx->mode = PDKIM_MODE_SIGN;
-  ctx->input_mode = input_mode;
-  ctx->sig = sig;
-
-  ctx->sig->domain = strdup(domain);
-  ctx->sig->selector = strdup(selector);
-  ctx->sig->rsa_privkey = strdup(rsa_privkey);
-
-  if (!ctx->sig->domain || !ctx->sig->selector || !ctx->sig->rsa_privkey) {
-    pdkim_free_ctx(ctx);
-    return NULL;
-  }
-
-  ctx->sig->sha1_body = malloc(sizeof(sha1_context));
-  if (ctx->sig->sha1_body == NULL) {
-    pdkim_free_ctx(ctx);
-    return NULL;
-  }
-  sha1_starts(ctx->sig->sha1_body);
-
-  ctx->sig->sha2_body = malloc(sizeof(sha2_context));
-  if (ctx->sig->sha2_body == NULL) {
-    pdkim_free_ctx(ctx);
-    return NULL;
-  }
-  sha2_starts(ctx->sig->sha2_body,0);
-
-  return ctx;
-}
-
-#ifdef PDKIM_DEBUG
-/* -------------------------------------------------------------------------- */
-DLLEXPORT void pdkim_set_debug_stream(pdkim_ctx *ctx,
-                            FILE *debug_stream) {
-  ctx->debug_stream = debug_stream;
-}
-#endif
-
-/* -------------------------------------------------------------------------- */
-DLLEXPORT int pdkim_set_optional(pdkim_ctx *ctx,
-                       char *sign_headers,
-                       char *identity,
-                       int canon_headers,
-                       int canon_body,
-                       long bodylength,
-                       int algo,
-                       unsigned long created,
-                       unsigned long expires) {
-
-  if (identity != NULL) {
-    ctx->sig->identity = strdup(identity);
-    if (ctx->sig->identity == NULL) return PDKIM_ERR_OOM;
-  }
-
-  if (sign_headers != NULL) {
-    ctx->sig->sign_headers = strdup(sign_headers);
-    if (ctx->sig->sign_headers == NULL) return PDKIM_ERR_OOM;
-  }
-
-  ctx->sig->canon_headers = canon_headers;
-  ctx->sig->canon_body = canon_body;
-  ctx->sig->bodylength = bodylength;
-  ctx->sig->algo = algo;
-  ctx->sig->created = created;
-  ctx->sig->expires = expires;
-
-  return PDKIM_OK;
-}
+/*
+ *  PDKIM - a RFC4871 (DKIM) implementation
+ *
+ *  Copyright (C) 2009 - 2016  Tom Kistner <tom@duncanthrax.net>
+ *  Copyright (C) 2016 - 2017  Jeremy Harris <jgh@exim.org>
+ *
+ *  http://duncanthrax.net/pdkim/
+ *
+ *  This program 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 program 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 program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "../exim.h"
+
+
+#ifndef DISABLE_DKIM   /* entire file */
+
+#ifndef SUPPORT_TLS
+# error Need SUPPORT_TLS for DKIM
+#endif
+
+#include "crypt_ver.h"
+
+#ifdef RSA_OPENSSL
+# include <openssl/rsa.h>
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+#elif defined(RSA_GNUTLS)
+# include <gnutls/gnutls.h>
+# include <gnutls/x509.h>
+#endif
+
+#include "pdkim.h"
+#include "rsa.h"
+
+#define PDKIM_SIGNATURE_VERSION     "1"
+#define PDKIM_PUB_RECORD_VERSION    US "DKIM1"
+
+#define PDKIM_MAX_HEADER_LEN        65536
+#define PDKIM_MAX_HEADERS           512
+#define PDKIM_MAX_BODY_LINE_LEN     16384
+#define PDKIM_DNS_TXT_MAX_NAMELEN   1024
+#define PDKIM_DEFAULT_SIGN_HEADERS "From:Sender:Reply-To:Subject:Date:"\
+                             "Message-ID:To:Cc:MIME-Version:Content-Type:"\
+                             "Content-Transfer-Encoding:Content-ID:"\
+                             "Content-Description:Resent-Date:Resent-From:"\
+                             "Resent-Sender:Resent-To:Resent-Cc:"\
+                             "Resent-Message-ID:In-Reply-To:References:"\
+                             "List-Id:List-Help:List-Unsubscribe:"\
+                             "List-Subscribe:List-Post:List-Owner:List-Archive"
+
+/* -------------------------------------------------------------------------- */
+struct pdkim_stringlist {
+  uschar * value;
+  int      tag;
+  void *   next;
+};
+
+/* -------------------------------------------------------------------------- */
+/* A bunch of list constants */
+const uschar * pdkim_querymethods[] = {
+  US"dns/txt",
+  NULL
+};
+const uschar * pdkim_algos[] = {
+  US"rsa-sha256",
+  US"rsa-sha1",
+  NULL
+};
+const uschar * pdkim_canons[] = {
+  US"simple",
+  US"relaxed",
+  NULL
+};
+const uschar * pdkim_hashes[] = {
+  US"sha256",
+  US"sha1",
+  NULL
+};
+const uschar * pdkim_keytypes[] = {
+  US"rsa",
+  NULL
+};
+
+typedef struct pdkim_combined_canon_entry {
+  const uschar * str;
+  int canon_headers;
+  int canon_body;
+} pdkim_combined_canon_entry;
+
+pdkim_combined_canon_entry pdkim_combined_canons[] = {
+  { US"simple/simple",    PDKIM_CANON_SIMPLE,   PDKIM_CANON_SIMPLE },
+  { US"simple/relaxed",   PDKIM_CANON_SIMPLE,   PDKIM_CANON_RELAXED },
+  { US"relaxed/simple",   PDKIM_CANON_RELAXED,  PDKIM_CANON_SIMPLE },
+  { US"relaxed/relaxed",  PDKIM_CANON_RELAXED,  PDKIM_CANON_RELAXED },
+  { US"simple",           PDKIM_CANON_SIMPLE,   PDKIM_CANON_SIMPLE },
+  { US"relaxed",          PDKIM_CANON_RELAXED,  PDKIM_CANON_SIMPLE },
+  { NULL,                 0,                    0 }
+};
+
+
+/* -------------------------------------------------------------------------- */
+
+const char *
+pdkim_verify_status_str(int status)
+{
+switch(status)
+  {
+  case PDKIM_VERIFY_NONE:    return "PDKIM_VERIFY_NONE";
+  case PDKIM_VERIFY_INVALID: return "PDKIM_VERIFY_INVALID";
+  case PDKIM_VERIFY_FAIL:    return "PDKIM_VERIFY_FAIL";
+  case PDKIM_VERIFY_PASS:    return "PDKIM_VERIFY_PASS";
+  default:                   return "PDKIM_VERIFY_UNKNOWN";
+  }
+}
+
+const char *
+pdkim_verify_ext_status_str(int ext_status)
+{
+switch(ext_status)
+  {
+  case PDKIM_VERIFY_FAIL_BODY: return "PDKIM_VERIFY_FAIL_BODY";
+  case PDKIM_VERIFY_FAIL_MESSAGE: return "PDKIM_VERIFY_FAIL_MESSAGE";
+  case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE: return "PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE";
+  case PDKIM_VERIFY_INVALID_BUFFER_SIZE: return "PDKIM_VERIFY_INVALID_BUFFER_SIZE";
+  case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD: return "PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD";
+  case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT: return "PDKIM_VERIFY_INVALID_PUBKEY_IMPORT";
+  case PDKIM_VERIFY_INVALID_SIGNATURE_ERROR: return "PDKIM_VERIFY_INVALID_SIGNATURE_ERROR";
+  case PDKIM_VERIFY_INVALID_DKIM_VERSION: return "PDKIM_VERIFY_INVALID_DKIM_VERSION";
+  default: return "PDKIM_VERIFY_UNKNOWN";
+  }
+}
+
+const char *
+pdkim_errstr(int status)
+{
+switch(status)
+  {
+  case PDKIM_OK:               return "OK";
+  case PDKIM_FAIL:             return "FAIL";
+  case PDKIM_ERR_RSA_PRIVKEY:  return "RSA_PRIVKEY";
+  case PDKIM_ERR_RSA_SIGNING:  return "RSA SIGNING";
+  case PDKIM_ERR_LONG_LINE:    return "RSA_LONG_LINE";
+  case PDKIM_ERR_BUFFER_TOO_SMALL:     return "BUFFER_TOO_SMALL";
+  case PDKIM_SIGN_PRIVKEY_WRAP:        return "PRIVKEY_WRAP";
+  case PDKIM_SIGN_PRIVKEY_B64D:        return "PRIVKEY_B64D";
+  default: return "(unknown)";
+  }
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* Print debugging functions */
+static void
+pdkim_quoteprint(const uschar *data, int len)
+{
+int i;
+for (i = 0; i < len; i++)
+  {
+  const int c = data[i];
+  switch (c)
+    {
+    case ' ' : debug_printf("{SP}"); break;
+    case '\t': debug_printf("{TB}"); break;
+    case '\r': debug_printf("{CR}"); break;
+    case '\n': debug_printf("{LF}"); break;
+    case '{' : debug_printf("{BO}"); break;
+    case '}' : debug_printf("{BC}"); break;
+    default:
+      if ( (c < 32) || (c > 127) )
+       debug_printf("{%02x}", c);
+      else
+       debug_printf("%c", c);
+      break;
+    }
+  }
+debug_printf("\n");
+}
+
+static void
+pdkim_hexprint(const uschar *data, int len)
+{
+int i;
+if (data) for (i = 0 ; i < len; i++) debug_printf("%02x", data[i]);
+else debug_printf("<NULL>");
+debug_printf("\n");
+}
+
+
+
+static pdkim_stringlist *
+pdkim_prepend_stringlist(pdkim_stringlist * base, const uschar * str)
+{
+pdkim_stringlist * new_entry = store_get(sizeof(pdkim_stringlist));
+
+memset(new_entry, 0, sizeof(pdkim_stringlist));
+new_entry->value = string_copy(str);
+if (base) new_entry->next = base;
+return new_entry;
+}
+
+
+
+/* Trim whitespace fore & aft */
+
+static void
+pdkim_strtrim(uschar * str)
+{
+uschar * p = str;
+uschar * q = str;
+while (*p == '\t' || *p == ' ') p++;           /* skip whitespace */
+while (*p) {*q = *p; q++; p++;}                        /* dump the leading whitespace */
+*q = '\0';
+while (q != str && ( (*q == '\0') || (*q == '\t') || (*q == ' ') ) )
+  {                                            /* dump trailing whitespace */
+  *q = '\0';
+  q--;
+  }
+}
+
+
+
+/* -------------------------------------------------------------------------- */
+
+DLLEXPORT void
+pdkim_free_ctx(pdkim_ctx *ctx)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* Matches the name of the passed raw "header" against
+   the passed colon-separated "tick", and invalidates
+   the entry in tick. Returns OK or fail-code */
+/*XXX might be safer done using a pdkim_stringlist for "tick" */
+
+static int
+header_name_match(const uschar * header, uschar * tick)
+{
+uschar * hname;
+uschar * lcopy;
+uschar * p;
+uschar * q;
+uschar * hcolon = Ustrchr(header, ':');                /* Get header name */
+
+if (!hcolon)
+  return PDKIM_FAIL; /* This isn't a header */
+
+/* if we had strncmpic() we wouldn't need this copy */
+hname = string_copyn(header, hcolon-header);
+
+/* Copy tick-off list locally, so we can punch zeroes into it */
+p = lcopy = string_copy(tick);
+
+for (q = Ustrchr(p, ':'); q; q = Ustrchr(p, ':'))
+  {
+  *q = '\0';
+  if (strcmpic(p, hname) == 0)
+    goto found;
+
+  p = q+1;
+  }
+
+if (strcmpic(p, hname) == 0)
+  goto found;
+
+return PDKIM_FAIL;
+
+found:
+  /* Invalidate header name instance in tick-off list */
+  tick[p-lcopy] = '_';
+  return PDKIM_OK;
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* Performs "relaxed" canonicalization of a header. */
+
+static uschar *
+pdkim_relax_header(const uschar * header, int crlf)
+{
+BOOL past_field_name = FALSE;
+BOOL seen_wsp = FALSE;
+const uschar * p;
+uschar * relaxed = store_get(Ustrlen(header)+3);
+uschar * q = relaxed;
+
+for (p = header; *p; p++)
+  {
+  uschar c = *p;
+  /* Ignore CR & LF */
+  if (c == '\r' || c == '\n')
+    continue;
+  if (c == '\t' || c == ' ')
+    {
+    if (seen_wsp)
+      continue;
+    c = ' ';                   /* Turns WSP into SP */
+    seen_wsp = TRUE;
+    }
+  else
+    if (!past_field_name && c == ':')
+      {
+      if (seen_wsp) q--;       /* This removes WSP before the colon */
+      seen_wsp = TRUE;         /* This removes WSP after the colon */
+      past_field_name = TRUE;
+      }
+    else
+      seen_wsp = FALSE;
+
+  /* Lowercase header name */
+  if (!past_field_name) c = tolower(c);
+  *q++ = c;
+  }
+
+if (q > relaxed && q[-1] == ' ') q--; /* Squash eventual trailing SP */
+
+if (crlf) { *q++ = '\r'; *q++ = '\n'; }
+*q = '\0';
+return relaxed;
+}
+
+
+/* -------------------------------------------------------------------------- */
+#define PDKIM_QP_ERROR_DECODE -1
+
+static uschar *
+pdkim_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;
+  *c |= isdigit(qp_p[1]) ? qp_p[1] - '0' : toupper(qp_p[1]) - 'A' + 10;
+  return qp_p + 2;
+  }
+
+/* Illegal char here */
+*c = PDKIM_QP_ERROR_DECODE;
+return initial_pos;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+static uschar *
+pdkim_decode_qp(uschar * str)
+{
+int nchar = 0;
+uschar * q;
+uschar * p = str;
+uschar * n = store_get(Ustrlen(str)+1);
+
+*n = '\0';
+q = n;
+while (*p)
+  {
+  if (*p == '=')
+    {
+    p = pdkim_decode_qp_char(p, &nchar);
+    if (nchar >= 0)
+      {
+      *q++ = nchar;
+      continue;
+      }
+    }
+  else
+    *q++ = *p;
+  p++;
+  }
+*q = '\0';
+return n;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+static void
+pdkim_decode_base64(uschar *str, blob * b)
+{
+int dlen;
+dlen = b64decode(str, &b->data);
+if (dlen < 0) b->data = NULL;
+b->len = dlen;
+}
+
+static uschar *
+pdkim_encode_base64(blob * b)
+{
+return b64encode(b->data, b->len);
+}
+
+
+/* -------------------------------------------------------------------------- */
+#define PDKIM_HDR_LIMBO 0
+#define PDKIM_HDR_TAG   1
+#define PDKIM_HDR_VALUE 2
+
+static pdkim_signature *
+pdkim_parse_sig_header(pdkim_ctx *ctx, uschar * raw_hdr)
+{
+pdkim_signature *sig ;
+uschar *p, *q;
+uschar * cur_tag = NULL; int ts = 0, tl = 0;
+uschar * cur_val = NULL; int vs = 0, vl = 0;
+BOOL past_hname = FALSE;
+BOOL in_b_val = FALSE;
+int where = PDKIM_HDR_LIMBO;
+int i;
+
+sig = store_get(sizeof(pdkim_signature));
+memset(sig, 0, sizeof(pdkim_signature));
+sig->bodylength = -1;
+
+/* Set so invalid/missing data error display is accurate */
+sig->algo = -1;
+sig->version = 0;
+
+q = sig->rawsig_no_b_val = store_get(Ustrlen(raw_hdr)+1);
+
+for (p = raw_hdr; ; p++)
+  {
+  char c = *p;
+
+  /* Ignore FWS */
+  if (c == '\r' || c == '\n')
+    goto NEXT_CHAR;
+
+  /* Fast-forward through header name */
+  if (!past_hname)
+    {
+    if (c == ':') past_hname = TRUE;
+    goto NEXT_CHAR;
+    }
+
+  if (where == PDKIM_HDR_LIMBO)
+    {
+    /* In limbo, just wait for a tag-char to appear */
+    if (!(c >= 'a' && c <= 'z'))
+      goto NEXT_CHAR;
+
+    where = PDKIM_HDR_TAG;
+    }
+
+  if (where == PDKIM_HDR_TAG)
+    {
+    if (c >= 'a' && c <= 'z')
+      cur_tag = string_catn(cur_tag, &ts, &tl, p, 1);
+
+    if (c == '=')
+      {
+      cur_tag[tl] = '\0';
+      if (Ustrcmp(cur_tag, "b") == 0)
+        {
+       *q++ = '=';
+       in_b_val = TRUE;
+       }
+      where = PDKIM_HDR_VALUE;
+      goto NEXT_CHAR;
+      }
+    }
+
+  if (where == PDKIM_HDR_VALUE)
+    {
+    if (c == '\r' || c == '\n' || c == ' ' || c == '\t')
+      goto NEXT_CHAR;
+
+    if (c == ';' || c == '\0')
+      {
+      if (tl && vl)
+        {
+       cur_val[vl] = '\0';
+       pdkim_strtrim(cur_val);
+
+       DEBUG(D_acl) debug_printf(" %s=%s\n", cur_tag, cur_val);
+
+       switch (*cur_tag)
+         {
+         case 'b':
+           pdkim_decode_base64(cur_val,
+                           cur_tag[1] == 'h' ? &sig->bodyhash : &sig->sighash);
+           break;
+         case 'v':
+             /* We only support version 1, and that is currently the
+                only version there is. */
+           sig->version =
+             Ustrcmp(cur_val, PDKIM_SIGNATURE_VERSION) == 0 ? 1 : -1;
+           break;
+         case 'a':
+           for (i = 0; pdkim_algos[i]; i++)
+             if (Ustrcmp(cur_val, pdkim_algos[i]) == 0)
+               {
+               sig->algo = i;
+               break;
+               }
+           break;
+         case 'c':
+           for (i = 0; pdkim_combined_canons[i].str; i++)
+             if (Ustrcmp(cur_val, pdkim_combined_canons[i].str) == 0)
+               {
+               sig->canon_headers = pdkim_combined_canons[i].canon_headers;
+               sig->canon_body    = pdkim_combined_canons[i].canon_body;
+               break;
+               }
+           break;
+         case 'q':
+           for (i = 0; pdkim_querymethods[i]; i++)
+             if (Ustrcmp(cur_val, pdkim_querymethods[i]) == 0)
+               {
+               sig->querymethod = i;
+               break;
+               }
+           break;
+         case 's':
+           sig->selector = string_copy(cur_val); break;
+         case 'd':
+           sig->domain = string_copy(cur_val); break;
+         case 'i':
+           sig->identity = pdkim_decode_qp(cur_val); break;
+         case 't':
+           sig->created = strtoul(CS cur_val, NULL, 10); break;
+         case 'x':
+           sig->expires = strtoul(CS cur_val, NULL, 10); break;
+         case 'l':
+           sig->bodylength = strtol(CS cur_val, NULL, 10); break;
+         case 'h':
+           sig->headernames = string_copy(cur_val); break;
+         case 'z':
+           sig->copiedheaders = pdkim_decode_qp(cur_val); break;
+         default:
+           DEBUG(D_acl) debug_printf(" Unknown tag encountered\n");
+           break;
+         }
+       }
+      tl = 0;
+      vl = 0;
+      in_b_val = FALSE;
+      where = PDKIM_HDR_LIMBO;
+      }
+    else
+      cur_val = string_catn(cur_val, &vs, &vl, p, 1);
+    }
+
+NEXT_CHAR:
+  if (c == '\0')
+    break;
+
+  if (!in_b_val)
+    *q++ = c;
+  }
+
+*q = '\0';
+/* Chomp raw header. The final newline must not be added to the signature. */
+while (--q > sig->rawsig_no_b_val  && (*q == '\r' || *q == '\n'))
+  *q = '\0';
+
+DEBUG(D_acl)
+  {
+  debug_printf(
+         "PDKIM >> Raw signature w/o b= tag value >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
+  pdkim_quoteprint(US sig->rawsig_no_b_val, Ustrlen(sig->rawsig_no_b_val));
+  debug_printf(
+         "PDKIM >> Sig size: %4u bits\n", (unsigned) sig->sighash.len*8);
+  debug_printf(
+         "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+  }
+
+if (!exim_sha_init(&sig->body_hash_ctx,
+              sig->algo == PDKIM_ALGO_RSA_SHA1 ? HASH_SHA1 : HASH_SHA256))
+  {
+  DEBUG(D_acl) debug_printf("PDKIM: hash init internal error\n");
+  return NULL;
+  }
+return sig;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+static pdkim_pubkey *
+pdkim_parse_pubkey_record(pdkim_ctx *ctx, const uschar *raw_record)
+{
+pdkim_pubkey *pub;
+const uschar *p;
+uschar * cur_tag = NULL; int ts = 0, tl = 0;
+uschar * cur_val = NULL; int vs = 0, vl = 0;
+int where = PDKIM_HDR_LIMBO;
+
+pub = store_get(sizeof(pdkim_pubkey));
+memset(pub, 0, sizeof(pdkim_pubkey));
+
+for (p = raw_record; ; p++)
+    {
+    uschar c = *p;
+
+    /* Ignore FWS */
+    if (c != '\r' && c != '\n') switch (where)
+      {
+      case PDKIM_HDR_LIMBO:            /* In limbo, just wait for a tag-char to appear */
+       if (!(c >= 'a' && c <= 'z'))
+         break;
+       where = PDKIM_HDR_TAG;
+       /*FALLTHROUGH*/
+
+      case PDKIM_HDR_TAG:
+       if (c >= 'a' && c <= 'z')
+         cur_tag = string_catn(cur_tag, &ts, &tl, p, 1);
+
+       if (c == '=')
+         {
+         cur_tag[tl] = '\0';
+         where = PDKIM_HDR_VALUE;
+         }
+       break;
+
+      case PDKIM_HDR_VALUE:
+       if (c == ';' || c == '\0')
+         {
+         if (tl && vl)
+           {
+           cur_val[vl] = '\0';
+           pdkim_strtrim(cur_val);
+           DEBUG(D_acl) debug_printf(" %s=%s\n", cur_tag, cur_val);
+
+           switch (cur_tag[0])
+             {
+             case 'v':
+               pub->version = string_copy(cur_val); break;
+             case 'h':
+             case 'k':
+/* This field appears to never be used. Also, unclear why
+a 'k' (key-type_ would go in this field name.  There is a field
+"keytype", also never used.
+               pub->hashes = string_copy(cur_val);
+*/
+               break;
+             case 'g':
+               pub->granularity = string_copy(cur_val); break;
+             case 'n':
+               pub->notes = pdkim_decode_qp(cur_val); break;
+             case 'p':
+               pdkim_decode_base64(US cur_val, &pub->key); break;
+             case 's':
+               pub->srvtype = string_copy(cur_val); break;
+             case 't':
+               if (Ustrchr(cur_val, 'y') != NULL) pub->testing = 1;
+               if (Ustrchr(cur_val, 's') != NULL) pub->no_subdomaining = 1;
+               break;
+             default:
+               DEBUG(D_acl) debug_printf(" Unknown tag encountered\n");
+               break;
+             }
+           }
+         tl = 0;
+         vl = 0;
+         where = PDKIM_HDR_LIMBO;
+         }
+       else
+         cur_val = string_catn(cur_val, &vs, &vl, p, 1);
+       break;
+      }
+
+    if (c == '\0') break;
+    }
+
+/* Set fallback defaults */
+if (!pub->version    ) pub->version     = string_copy(PDKIM_PUB_RECORD_VERSION);
+else if (Ustrcmp(pub->version, PDKIM_PUB_RECORD_VERSION) != 0) return NULL;
+
+if (!pub->granularity) pub->granularity = string_copy(US"*");
+/*
+if (!pub->keytype    ) pub->keytype     = string_copy(US"rsa");
+*/
+if (!pub->srvtype    ) pub->srvtype     = string_copy(US"*");
+
+/* p= is required */
+if (pub->key.data)
+  return pub;
+
+return NULL;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+static int
+pdkim_update_bodyhash(pdkim_ctx * ctx, const char * data, int len)
+{
+pdkim_signature * sig;
+uschar * relaxed_data = NULL;  /* Cache relaxed version of data */
+int relaxed_len = 0;
+
+/* Traverse all signatures, updating their hashes. */
+for (sig = ctx->sig; sig; sig = sig->next)
+  {
+  /* Defaults to simple canon (no further treatment necessary) */
+  const uschar *canon_data = CUS data;
+  int           canon_len = len;
+
+  if (sig->canon_body == PDKIM_CANON_RELAXED)
+    {
+    /* Relax the line if not done already */
+    if (!relaxed_data)
+      {
+      BOOL seen_wsp = FALSE;
+      const char *p;
+      int q = 0;
+
+      /* We want to be able to free this else we allocate
+      for the entire message which could be many MB. Since
+      we don't know what allocations the SHA routines might
+      do, not safe to use store_get()/store_reset(). */
+
+      relaxed_data = store_malloc(len+1);
+
+      for (p = data; *p; p++)
+        {
+       char c = *p;
+       if (c == '\r')
+         {
+         if (q > 0 && relaxed_data[q-1] == ' ')
+           q--;
+         }
+       else if (c == '\t' || c == ' ')
+         {
+         c = ' '; /* Turns WSP into SP */
+         if (seen_wsp)
+           continue;
+         seen_wsp = TRUE;
+         }
+       else
+         seen_wsp = FALSE;
+       relaxed_data[q++] = c;
+       }
+      relaxed_data[q] = '\0';
+      relaxed_len = q;
+      }
+    canon_data = relaxed_data;
+    canon_len  = relaxed_len;
+    }
+
+  /* Make sure we don't exceed the to-be-signed body length */
+  if (  sig->bodylength >= 0
+     && sig->signed_body_bytes + (unsigned long)canon_len > sig->bodylength
+     )
+    canon_len = sig->bodylength - sig->signed_body_bytes;
+
+  if (canon_len > 0)
+    {
+    exim_sha_update(&sig->body_hash_ctx, CUS canon_data, canon_len);
+    sig->signed_body_bytes += canon_len;
+    DEBUG(D_acl) pdkim_quoteprint(canon_data, canon_len);
+    }
+  }
+
+if (relaxed_data) store_free(relaxed_data);
+return PDKIM_OK;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+static void
+pdkim_finish_bodyhash(pdkim_ctx *ctx)
+{
+pdkim_signature *sig;
+
+/* Traverse all signatures */
+for (sig = ctx->sig; sig; sig = sig->next)
+  {                                    /* Finish hashes */
+  blob bh;
+
+  exim_sha_finish(&sig->body_hash_ctx, &bh);
+
+  DEBUG(D_acl)
+    {
+    debug_printf("PDKIM [%s] Body bytes hashed: %lu\n"
+                "PDKIM [%s] Body hash computed: ",
+               sig->domain, sig->signed_body_bytes, sig->domain);
+    pdkim_hexprint(CUS bh.data, bh.len);
+    }
+
+  /* SIGNING -------------------------------------------------------------- */
+  if (ctx->flags & PDKIM_MODE_SIGN)
+    {
+    sig->bodyhash = bh;
+
+    /* If bodylength limit is set, and we have received less bytes
+       than the requested amount, effectively remove the limit tag. */
+    if (sig->signed_body_bytes < sig->bodylength)
+      sig->bodylength = -1;
+    }
+
+  else
+  /* VERIFICATION --------------------------------------------------------- */
+  /* Be careful that the header sig included a bodyash */
+
+    if (sig->bodyhash.data && memcmp(bh.data, sig->bodyhash.data, bh.len) == 0)
+      {
+      DEBUG(D_acl) debug_printf("PDKIM [%s] Body hash verified OK\n", sig->domain);
+      }
+    else
+      {
+      DEBUG(D_acl)
+        {
+       debug_printf("PDKIM [%s] Body hash signature from headers: ", sig->domain);
+       pdkim_hexprint(sig->bodyhash.data, sig->bodyhash.len);
+       debug_printf("PDKIM [%s] Body hash did NOT verify\n", sig->domain);
+       }
+      sig->verify_status     = PDKIM_VERIFY_FAIL;
+      sig->verify_ext_status = PDKIM_VERIFY_FAIL_BODY;
+      }
+  }
+}
+
+
+
+static int
+pdkim_body_complete(pdkim_ctx * ctx)
+{
+pdkim_signature * sig = ctx->sig;      /*XXX assumes only one sig */
+
+/* In simple body mode, if any empty lines were buffered,
+replace with one. rfc 4871 3.4.3 */
+/*XXX checking the signed-body-bytes is a gross hack; I think
+it indicates that all linebreaks should be buffered, including
+the one terminating a text line */
+
+if (  sig && sig->canon_body == PDKIM_CANON_SIMPLE
+   && sig->signed_body_bytes == 0
+   && ctx->num_buffered_crlf > 0
+   )
+  pdkim_update_bodyhash(ctx, "\r\n", 2);
+
+ctx->flags |= PDKIM_SEEN_EOD;
+ctx->linebuf_offset = 0;
+return PDKIM_OK;
+}
+
+
+
+/* -------------------------------------------------------------------------- */
+/* Call from pdkim_feed below for processing complete body lines */
+
+static int
+pdkim_bodyline_complete(pdkim_ctx *ctx)
+{
+char *p = ctx->linebuf;
+int   n = ctx->linebuf_offset;
+pdkim_signature *sig = ctx->sig;       /*XXX assumes only one sig */
+
+/* Ignore extra data if we've seen the end-of-data marker */
+if (ctx->flags & PDKIM_SEEN_EOD) goto BAIL;
+
+/* We've always got one extra byte to stuff a zero ... */
+ctx->linebuf[ctx->linebuf_offset] = '\0';
+
+/* Terminate on EOD marker */
+if (ctx->flags & PDKIM_DOT_TERM)
+  {
+  if (memcmp(p, ".\r\n", 3) == 0)
+    return pdkim_body_complete(ctx);
+
+  /* Unstuff dots */
+  if (memcmp(p, "..", 2) == 0)
+    {
+    p++;
+    n--;
+    }
+  }
+
+/* Empty lines need to be buffered until we find a non-empty line */
+if (memcmp(p, "\r\n", 2) == 0)
+  {
+  ctx->num_buffered_crlf++;
+  goto BAIL;
+  }
+
+if (sig && sig->canon_body == PDKIM_CANON_RELAXED)
+  {
+  /* Lines with just spaces need to be buffered too */
+  char *check = p;
+  while (memcmp(check, "\r\n", 2) != 0)
+    {
+    char c = *check;
+
+    if (c != '\t' && c != ' ')
+      goto PROCESS;
+    check++;
+    }
+
+  ctx->num_buffered_crlf++;
+  goto BAIL;
+}
+
+PROCESS:
+/* At this point, we have a non-empty line, so release the buffered ones. */
+while (ctx->num_buffered_crlf)
+  {
+  pdkim_update_bodyhash(ctx, "\r\n", 2);
+  ctx->num_buffered_crlf--;
+  }
+
+pdkim_update_bodyhash(ctx, p, n);
+
+BAIL:
+ctx->linebuf_offset = 0;
+return PDKIM_OK;
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* Callback from pdkim_feed below for processing complete headers */
+#define DKIM_SIGNATURE_HEADERNAME "DKIM-Signature:"
+
+static int
+pdkim_header_complete(pdkim_ctx *ctx)
+{
+/* Special case: The last header can have an extra \r appended */
+if ( (ctx->cur_header_len > 1) &&
+     (ctx->cur_header[(ctx->cur_header_len)-1] == '\r') )
+  --ctx->cur_header_len;
+ctx->cur_header[ctx->cur_header_len] = '\0';
+
+ctx->num_headers++;
+if (ctx->num_headers > PDKIM_MAX_HEADERS) goto BAIL;
+
+/* SIGNING -------------------------------------------------------------- */
+if (ctx->flags & PDKIM_MODE_SIGN)
+  {
+  pdkim_signature *sig;
+
+  for (sig = ctx->sig; sig; sig = sig->next)                   /* Traverse all signatures */
+
+    /* Add header to the signed headers list (in reverse order) */
+    sig->headers = pdkim_prepend_stringlist(sig->headers,
+                                 ctx->cur_header);
+  }
+
+/* VERIFICATION ----------------------------------------------------------- */
+/* DKIM-Signature: headers are added to the verification list */
+else
+  {
+#ifdef notdef
+  DEBUG(D_acl)
+    {
+    debug_printf("PDKIM >> raw hdr: ");
+    pdkim_quoteprint(CUS ctx->cur_header, Ustrlen(ctx->cur_header));
+    }
+#endif
+  if (strncasecmp(CCS ctx->cur_header,
+                 DKIM_SIGNATURE_HEADERNAME,
+                 Ustrlen(DKIM_SIGNATURE_HEADERNAME)) == 0)
+    {
+    pdkim_signature * new_sig, * last_sig;
+
+    /* Create and chain new signature block.  We could error-check for all
+    required tags here, but prefer to create the internal sig and expicitly
+    fail verification of it later. */
+
+    DEBUG(D_acl) debug_printf(
+       "PDKIM >> Found sig, trying to parse >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
+
+    new_sig = pdkim_parse_sig_header(ctx, ctx->cur_header);
+
+    if (!(last_sig = ctx->sig))
+      ctx->sig = new_sig;
+    else
+      {
+      while (last_sig->next) last_sig = last_sig->next;
+      last_sig->next = new_sig;
+      }
+    }
+
+  /* all headers are stored for signature verification */
+  ctx->headers = pdkim_prepend_stringlist(ctx->headers, ctx->cur_header);
+  }
+
+BAIL:
+*ctx->cur_header = '\0';
+ctx->cur_header_len = 0;       /* leave buffer for reuse */
+return PDKIM_OK;
+}
+
+
+
+/* -------------------------------------------------------------------------- */
+#define HEADER_BUFFER_FRAG_SIZE 256
+
+DLLEXPORT int
+pdkim_feed(pdkim_ctx *ctx, char *data, int len)
+{
+int p, rc;
+
+/* Alternate EOD signal, used in non-dotstuffing mode */
+if (!data)
+  pdkim_body_complete(ctx);
+
+else for (p = 0; p<len; p++)
+  {
+  uschar c = data[p];
+
+  if (ctx->flags & PDKIM_PAST_HDRS)
+    {
+    if (c == '\n' && !(ctx->flags & PDKIM_SEEN_CR))    /* emulate the CR */
+      {
+      ctx->linebuf[ctx->linebuf_offset++] = '\r';
+      if (ctx->linebuf_offset == PDKIM_MAX_BODY_LINE_LEN-1)
+       return PDKIM_ERR_LONG_LINE;
+      }
+
+    /* Processing body byte */
+    ctx->linebuf[ctx->linebuf_offset++] = c;
+    if (c == '\r')
+      ctx->flags |= PDKIM_SEEN_CR;
+    else if (c == '\n')
+      {
+      ctx->flags &= ~PDKIM_SEEN_CR;
+      if ((rc = pdkim_bodyline_complete(ctx)) != PDKIM_OK)
+       return rc;
+      }
+
+    if (ctx->linebuf_offset == PDKIM_MAX_BODY_LINE_LEN-1)
+      return PDKIM_ERR_LONG_LINE;
+    }
+  else
+    {
+    /* Processing header byte */
+    if (c == '\r')
+      ctx->flags |= PDKIM_SEEN_CR;
+    else if (c == '\n')
+      {
+      if (!(ctx->flags & PDKIM_SEEN_CR))               /* emulate the CR */
+       ctx->cur_header = string_catn(ctx->cur_header, &ctx->cur_header_size,
+                               &ctx->cur_header_len, CUS "\r", 1);
+
+      if (ctx->flags & PDKIM_SEEN_LF)          /* Seen last header line */
+       {
+       if ((rc = pdkim_header_complete(ctx)) != PDKIM_OK)
+         return rc;
+
+       ctx->flags = (ctx->flags & ~(PDKIM_SEEN_LF|PDKIM_SEEN_CR)) | PDKIM_PAST_HDRS;
+       DEBUG(D_acl) debug_printf(
+           "PDKIM >> Body data for hash, canonicalized >>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
+       continue;
+       }
+      else
+       ctx->flags = (ctx->flags & ~PDKIM_SEEN_CR) | PDKIM_SEEN_LF;
+      }
+    else if (ctx->flags & PDKIM_SEEN_LF)
+      {
+      if (!(c == '\t' || c == ' '))                    /* End of header */
+       if ((rc = pdkim_header_complete(ctx)) != PDKIM_OK)
+         return rc;
+      ctx->flags &= ~PDKIM_SEEN_LF;
+      }
+
+    if (ctx->cur_header_len < PDKIM_MAX_HEADER_LEN)
+      ctx->cur_header = string_catn(ctx->cur_header, &ctx->cur_header_size,
+                                 &ctx->cur_header_len, CUS &data[p], 1);
+    }
+  }
+return PDKIM_OK;
+}
+
+
+
+/* Extend a grwong header with a continuation-linebreak */
+static uschar *
+pdkim_hdr_cont(uschar * str, int * size, int * ptr, int * col)
+{
+*col = 1;
+return string_catn(str, size, ptr, US"\r\n\t", 3);
+}
+
+
+
+/*
+ * RFC 5322 specifies that header line length SHOULD be no more than 78
+ * lets make it so!
+ *  pdkim_headcat
+ *
+ * returns uschar * (not nul-terminated)
+ *
+ * col: this int holds and receives column number (octets since last '\n')
+ * str: partial string to append to
+ * size: current buffer size for str
+ * ptr: current tail-pointer for str
+ * pad: padding, split line or space after before or after eg: ";"
+ * intro: - must join to payload eg "h=", usually the tag name
+ * payload: eg base64 data - long data can be split arbitrarily.
+ *
+ * this code doesn't fold the header in some of the places that RFC4871
+ * allows: As per RFC5322(2.2.3) it only folds before or after tag-value
+ * pairs and inside long values. it also always spaces or breaks after the
+ * "pad"
+ *
+ * no guarantees are made for output given out-of range input. like tag
+ * names longer than 78, or bogus col. Input is assumed to be free of line breaks.
+ */
+
+static uschar *
+pdkim_headcat(int * col, uschar * str, int * size, int * ptr,
+  const uschar * pad, const uschar * intro, const uschar * payload)
+{
+size_t l;
+
+if (pad)
+  {
+  l = Ustrlen(pad);
+  if (*col + l > 78)
+    str = pdkim_hdr_cont(str, size, ptr, col);
+  str = string_catn(str, size, ptr, pad, l);
+  *col += l;
+  }
+
+l = (pad?1:0) + (intro?Ustrlen(intro):0);
+
+if (*col + l > 78)
+  { /*can't fit intro - start a new line to make room.*/
+  str = pdkim_hdr_cont(str, size, ptr, col);
+  l = intro?Ustrlen(intro):0;
+  }
+
+l += payload ? Ustrlen(payload):0 ;
+
+while (l>77)
+  { /* this fragment will not fit on a single line */
+  if (pad)
+    {
+    str = string_catn(str, size, ptr, US" ", 1);
+    *col += 1;
+    pad = NULL; /* only want this once */
+    l--;
+    }
+
+  if (intro)
+    {
+    size_t sl = Ustrlen(intro);
+
+    str = string_catn(str, size, ptr, intro, sl);
+    *col += sl;
+    l -= sl;
+    intro = NULL; /* only want this once */
+    }
+
+  if (payload)
+    {
+    size_t sl = Ustrlen(payload);
+    size_t chomp = *col+sl < 77 ? sl : 78-*col;
+
+    str = string_catn(str, size, ptr, payload, chomp);
+    *col += chomp;
+    payload += chomp;
+    l -= chomp-1;
+    }
+
+  /* the while precondition tells us it didn't fit. */
+  str = pdkim_hdr_cont(str, size, ptr, col);
+  }
+
+if (*col + l > 78)
+  {
+  str = pdkim_hdr_cont(str, size, ptr, col);
+  pad = NULL;
+  }
+
+if (pad)
+  {
+  str = string_catn(str, size, ptr, US" ", 1);
+  *col += 1;
+  pad = NULL;
+  }
+
+if (intro)
+  {
+  size_t sl = Ustrlen(intro);
+
+  str = string_catn(str, size, ptr, intro, sl);
+  *col += sl;
+  l -= sl;
+  intro = NULL;
+  }
+
+if (payload)
+  {
+  size_t sl = Ustrlen(payload);
+
+  str = string_catn(str, size, ptr, payload, sl);
+  *col += sl;
+  }
+
+return str;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+static uschar *
+pdkim_create_header(pdkim_signature *sig, BOOL final)
+{
+uschar * base64_bh;
+uschar * base64_b;
+int col = 0;
+uschar * hdr;       int hdr_size = 0, hdr_len = 0;
+uschar * canon_all; int can_size = 0, can_len = 0;
+
+canon_all = string_cat (NULL, &can_size, &can_len,
+                     pdkim_canons[sig->canon_headers]);
+canon_all = string_catn(canon_all, &can_size, &can_len, US"/", 1);
+canon_all = string_cat (canon_all, &can_size, &can_len,
+                     pdkim_canons[sig->canon_body]);
+canon_all[can_len] = '\0';
+
+hdr = string_cat(NULL, &hdr_size, &hdr_len,
+                     US"DKIM-Signature: v="PDKIM_SIGNATURE_VERSION);
+col = hdr_len;
+
+/* Required and static bits */
+hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"a=",
+                   pdkim_algos[sig->algo]);
+hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"q=",
+                   pdkim_querymethods[sig->querymethod]);
+hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"c=",
+                   canon_all);
+hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"d=",
+                   sig->domain);
+hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"s=",
+                   sig->selector);
+
+/* list of header names can be split between items. */
+  {
+  uschar * n = string_copy(sig->headernames);
+  uschar * i = US"h=";
+  uschar * s = US";";
+
+  while (*n)
+    {
+    uschar * c = Ustrchr(n, ':');
+
+    if (c) *c ='\0';
+
+    if (!i)
+      hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, NULL, NULL, US":");
+
+    hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, s, i, n);
+
+    if (!c)
+      break;
+
+    n = c+1;
+    s = NULL;
+    i = NULL;
+    }
+  }
+
+base64_bh = pdkim_encode_base64(&sig->bodyhash);
+hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"bh=", base64_bh);
+
+/* Optional bits */
+if (sig->identity)
+  hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"i=", sig->identity);
+
+if (sig->created > 0)
+  {
+  uschar minibuf[20];
+
+  snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->created);
+  hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"t=", minibuf);
+}
+
+if (sig->expires > 0)
+  {
+  uschar minibuf[20];
+
+  snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->expires);
+  hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"x=", minibuf);
+  }
+
+if (sig->bodylength >= 0)
+  {
+  uschar minibuf[20];
+
+  snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->bodylength);
+  hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"l=", minibuf);
+  }
+
+/* Preliminary or final version? */
+base64_b = final ? pdkim_encode_base64(&sig->sighash) : US"";
+hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"b=", base64_b);
+
+/* add trailing semicolon: I'm not sure if this is actually needed */
+hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, NULL, US";", US"");
+
+hdr[hdr_len] = '\0';
+return hdr;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+static pdkim_pubkey *
+pdkim_key_from_dns(pdkim_ctx * ctx, pdkim_signature * sig, ev_ctx * vctx)
+{
+uschar * dns_txt_name, * dns_txt_reply;
+pdkim_pubkey * p;
+const uschar * errstr;
+
+/* Fetch public key for signing domain, from DNS */
+
+dns_txt_name = string_sprintf("%s._domainkey.%s.", sig->selector, sig->domain);
+
+dns_txt_reply = store_get(PDKIM_DNS_TXT_MAX_RECLEN);
+memset(dns_txt_reply, 0, PDKIM_DNS_TXT_MAX_RECLEN);
+
+if (  ctx->dns_txt_callback(CS dns_txt_name, CS dns_txt_reply) != PDKIM_OK 
+   || dns_txt_reply[0] == '\0'
+   )
+  {
+  sig->verify_status =      PDKIM_VERIFY_INVALID;
+  sig->verify_ext_status =  PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE;
+  return NULL;
+  }
+
+DEBUG(D_acl)
+  {
+  debug_printf(
+    "PDKIM >> Parsing public key record >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"
+    " Raw record: ");
+  pdkim_quoteprint(CUS dns_txt_reply, Ustrlen(dns_txt_reply));
+  }
+
+if (  !(p = pdkim_parse_pubkey_record(ctx, CUS dns_txt_reply))
+   || (Ustrcmp(p->srvtype, "*") != 0 && Ustrcmp(p->srvtype, "email") != 0)
+   )
+  {
+  sig->verify_status =      PDKIM_VERIFY_INVALID;
+  sig->verify_ext_status =  PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD;
+
+  DEBUG(D_acl)
+    {
+    if (p)
+      debug_printf(" Invalid public key service type '%s'\n", p->srvtype);
+    else
+      debug_printf(" Error while parsing public key record\n");
+    debug_printf(
+      "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+    }
+  return NULL;
+  }
+
+DEBUG(D_acl) debug_printf(
+      "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+
+/* Import public key */
+if ((errstr = exim_rsa_verify_init(&p->key, vctx)))
+  {
+  DEBUG(D_acl) debug_printf("verify_init: %s\n", errstr);
+  sig->verify_status =      PDKIM_VERIFY_INVALID;
+  sig->verify_ext_status =  PDKIM_VERIFY_INVALID_PUBKEY_IMPORT;
+  return NULL;
+  }
+
+return p;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+DLLEXPORT int
+pdkim_feed_finish(pdkim_ctx *ctx, pdkim_signature **return_signatures)
+{
+pdkim_signature *sig = ctx->sig;
+
+/* Check if we must still flush a (partial) header. If that is the
+   case, the message has no body, and we must compute a body hash
+   out of '<CR><LF>' */
+if (ctx->cur_header && ctx->cur_header_len)
+  {
+  int rc = pdkim_header_complete(ctx);
+  if (rc != PDKIM_OK) return rc;
+  pdkim_update_bodyhash(ctx, "\r\n", 2);
+  }
+else
+  DEBUG(D_acl) debug_printf(
+      "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+
+/* Build (and/or evaluate) body hash */
+pdkim_finish_bodyhash(ctx);
+
+while (sig)
+  {
+  BOOL is_sha1 = sig->algo == PDKIM_ALGO_RSA_SHA1;
+  hctx hhash_ctx;
+  uschar * sig_hdr = US"";
+  blob hhash;
+  blob hdata;
+  int hdata_alloc = 0;
+
+  hdata.data = NULL;
+  hdata.len = 0;
+
+  if (!exim_sha_init(&hhash_ctx, is_sha1 ? HASH_SHA1 : HASH_SHA256))
+    {
+    DEBUG(D_acl) debug_printf("PDKIM: hask setup internal error\n");
+    break;
+    }
+
+  DEBUG(D_acl) debug_printf(
+      "PDKIM >> Header data for hash, canonicalized, in sequence >>>>>>>>>>>>>>\n");
+
+  /* SIGNING ---------------------------------------------------------------- */
+  /* When signing, walk through our header list and add them to the hash. As we
+     go, construct a list of the header's names to use for the h= parameter.
+     Then append to that list any remaining header names for which there was no
+     header to sign. */
+
+  if (ctx->flags & PDKIM_MODE_SIGN)
+    {
+    uschar * headernames = NULL;       /* Collected signed header names */
+    int hs = 0, hl = 0;
+    pdkim_stringlist *p;
+    const uschar * l;
+    uschar * s;
+    int sep = 0;
+
+    for (p = sig->headers; p; p = p->next)
+      if (header_name_match(p->value, sig->sign_headers) == PDKIM_OK)
+       {
+       uschar * rh;
+       /* Collect header names (Note: colon presence is guaranteed here) */
+       uschar * q = Ustrchr(p->value, ':');
+
+       headernames = string_catn(headernames, &hs, &hl,
+                       p->value, (q - US p->value) + (p->next ? 1 : 0));
+
+       rh = sig->canon_headers == PDKIM_CANON_RELAXED
+         ? pdkim_relax_header(p->value, 1) /* cook header for relaxed canon */
+         : string_copy(CUS p->value);      /* just copy it for simple canon */
+
+       /* Feed header to the hash algorithm */
+       exim_sha_update(&hhash_ctx, CUS rh, Ustrlen(rh));
+
+       /* Remember headers block for signing (when the library cannot do incremental)  */
+       (void) exim_rsa_data_append(&hdata, &hdata_alloc, rh);
+
+       DEBUG(D_acl) pdkim_quoteprint(rh, Ustrlen(rh));
+       }
+
+    l = sig->sign_headers;
+    while((s = string_nextinlist(&l, &sep, NULL, 0)))
+      if (*s != '_')
+       {                       /*SSS string_append_listele() */
+       if (hl > 0 && headernames[hl-1] != ':')
+         headernames = string_catn(headernames, &hs, &hl, US":", 1);
+
+       headernames = string_cat(headernames, &hs, &hl, s);
+       }
+    headernames[hl] = '\0';
+
+    /* Copy headernames to signature struct */
+    sig->headernames = headernames;
+
+    /* Create signature header with b= omitted */
+    sig_hdr = pdkim_create_header(sig, FALSE);
+    }
+
+  /* VERIFICATION ----------------------------------------------------------- */
+  /* When verifying, walk through the header name list in the h= parameter and
+     add the headers to the hash in that order. */
+  else
+    {
+    uschar * p = sig->headernames;
+    uschar * q;
+    pdkim_stringlist * hdrs;
+
+    if (p)
+      {
+      /* clear tags */
+      for (hdrs = ctx->headers; hdrs; hdrs = hdrs->next)
+       hdrs->tag = 0;
+
+      p = string_copy(p);
+      while(1)
+       {
+       if ((q = Ustrchr(p, ':')))
+         *q = '\0';
+
+  /*XXX walk the list of headers in same order as received. */
+       for (hdrs = ctx->headers; hdrs; hdrs = hdrs->next)
+         if (  hdrs->tag == 0
+            && strncasecmp(CCS hdrs->value, CCS p, Ustrlen(p)) == 0
+            && (hdrs->value)[Ustrlen(p)] == ':'
+            )
+           {
+           /* cook header for relaxed canon, or just copy it for simple  */
+
+           uschar * rh = sig->canon_headers == PDKIM_CANON_RELAXED
+             ? pdkim_relax_header(hdrs->value, 1)
+             : string_copy(CUS hdrs->value);
+
+           /* Feed header to the hash algorithm */
+           exim_sha_update(&hhash_ctx, CUS rh, Ustrlen(rh));
+
+           DEBUG(D_acl) pdkim_quoteprint(rh, Ustrlen(rh));
+           hdrs->tag = 1;
+           break;
+           }
+
+       if (!q) break;
+       p = q+1;
+       }
+
+      sig_hdr = string_copy(sig->rawsig_no_b_val);
+      }
+    }
+
+  DEBUG(D_acl) debug_printf(
+           "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+
+  /* Relax header if necessary */
+  if (sig->canon_headers == PDKIM_CANON_RELAXED)
+    sig_hdr = pdkim_relax_header(sig_hdr, 0);
+
+  DEBUG(D_acl)
+    {
+    debug_printf(
+           "PDKIM >> Signed DKIM-Signature header, canonicalized >>>>>>>>>>>>>>>>>\n");
+    pdkim_quoteprint(CUS sig_hdr, Ustrlen(sig_hdr));
+    debug_printf(
+           "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+    }
+
+  /* Finalize header hash */
+  exim_sha_update(&hhash_ctx, CUS sig_hdr, Ustrlen(sig_hdr));
+  exim_sha_finish(&hhash_ctx, &hhash);
+
+  DEBUG(D_acl)
+    {
+    debug_printf("PDKIM [%s] Header hash computed: ", sig->domain);
+    pdkim_hexprint(hhash.data, hhash.len);
+    }
+
+  /* Remember headers block for signing (when the library cannot do incremental)  */
+  if (ctx->flags & PDKIM_MODE_SIGN)
+    (void) exim_rsa_data_append(&hdata, &hdata_alloc, US sig_hdr);
+
+  /* SIGNING ---------------------------------------------------------------- */
+  if (ctx->flags & PDKIM_MODE_SIGN)
+    {
+    es_ctx sctx;
+    const uschar * errstr;
+
+    /* Import private key */
+    if ((errstr = exim_rsa_signing_init(US sig->rsa_privkey, &sctx)))
+      {
+      DEBUG(D_acl) debug_printf("signing_init: %s\n", errstr);
+      return PDKIM_ERR_RSA_PRIVKEY;
+      }
+
+    /* Do signing.  With OpenSSL we are signing the hash of headers just
+    calculated, with GnuTLS we have to sign an entire block of headers
+    (due to available interfaces) and it recalculates the hash internally. */
+
+#if defined(RSA_OPENSSL) || defined(RSA_GCRYPT)
+    hdata = hhash;
+#endif
+
+    if ((errstr = exim_rsa_sign(&sctx, is_sha1, &hdata, &sig->sighash)))
+      {
+      DEBUG(D_acl) debug_printf("signing: %s\n", errstr);
+      return PDKIM_ERR_RSA_SIGNING;
+      }
+
+    DEBUG(D_acl)
+      {
+      debug_printf( "PDKIM [%s] b computed: ", sig->domain);
+      pdkim_hexprint(sig->sighash.data, sig->sighash.len);
+      }
+
+    sig->signature_header = pdkim_create_header(sig, TRUE);
+    }
+
+  /* VERIFICATION ----------------------------------------------------------- */
+  else
+    {
+    ev_ctx vctx;
+    const uschar * errstr;
+    pdkim_pubkey * p;
+
+    /* Make sure we have all required signature tags */
+    if (!(  sig->domain        && *sig->domain
+        && sig->selector      && *sig->selector
+        && sig->headernames   && *sig->headernames
+        && sig->bodyhash.data
+        && sig->sighash.data
+        && sig->algo > -1
+        && sig->version
+       ) )
+      {
+      sig->verify_status     = PDKIM_VERIFY_INVALID;
+      sig->verify_ext_status = PDKIM_VERIFY_INVALID_SIGNATURE_ERROR;
+
+      DEBUG(D_acl) debug_printf(
+         " Error in DKIM-Signature header: tags missing or invalid\n"
+         "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+      goto NEXT_VERIFY;
+      }
+
+    /* Make sure sig uses supported DKIM version (only v1) */
+    if (sig->version != 1)
+      {
+      sig->verify_status     = PDKIM_VERIFY_INVALID;
+      sig->verify_ext_status = PDKIM_VERIFY_INVALID_DKIM_VERSION;
+
+      DEBUG(D_acl) debug_printf(
+          " Error in DKIM-Signature header: unsupported DKIM version\n"
+          "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+      goto NEXT_VERIFY;
+      }
+
+    if (!(sig->pubkey = pdkim_key_from_dns(ctx, sig, &vctx)))
+      goto NEXT_VERIFY;
+
+    /* Check the signature */
+    if ((errstr = exim_rsa_verify(&vctx, is_sha1, &hhash, &sig->sighash)))
+      {
+      DEBUG(D_acl) debug_printf("headers verify: %s\n", errstr);
+      sig->verify_status =      PDKIM_VERIFY_FAIL;
+      sig->verify_ext_status =  PDKIM_VERIFY_FAIL_MESSAGE;
+      goto NEXT_VERIFY;
+      }
+
+
+    /* We have a winner! (if bodyhash was correct earlier) */
+    if (sig->verify_status == PDKIM_VERIFY_NONE)
+      sig->verify_status = PDKIM_VERIFY_PASS;
+
+NEXT_VERIFY:
+
+    DEBUG(D_acl)
+      {
+      debug_printf("PDKIM [%s] signature status: %s",
+             sig->domain, pdkim_verify_status_str(sig->verify_status));
+      if (sig->verify_ext_status > 0)
+       debug_printf(" (%s)\n",
+               pdkim_verify_ext_status_str(sig->verify_ext_status));
+      else
+       debug_printf("\n");
+      }
+    }
+
+  sig = sig->next;
+  }
+
+/* If requested, set return pointer to signature(s) */
+if (return_signatures)
+  *return_signatures = ctx->sig;
+
+return PDKIM_OK;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+DLLEXPORT pdkim_ctx *
+pdkim_init_verify(int(*dns_txt_callback)(char *, char *), BOOL dot_stuffing)
+{
+pdkim_ctx * ctx;
+
+ctx = store_get(sizeof(pdkim_ctx));
+memset(ctx, 0, sizeof(pdkim_ctx));
+
+if (dot_stuffing) ctx->flags = PDKIM_DOT_TERM;
+ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN);
+ctx->dns_txt_callback = dns_txt_callback;
+
+return ctx;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+DLLEXPORT pdkim_ctx *
+pdkim_init_sign(char * domain, char * selector, char * rsa_privkey, int algo,
+  BOOL dot_stuffed, int(*dns_txt_callback)(char *, char *))
+{
+pdkim_ctx * ctx;
+pdkim_signature * sig;
+
+if (!domain || !selector || !rsa_privkey)
+  return NULL;
+
+ctx = store_get(sizeof(pdkim_ctx) + PDKIM_MAX_BODY_LINE_LEN + sizeof(pdkim_signature));
+memset(ctx, 0, sizeof(pdkim_ctx));
+
+ctx->flags = dot_stuffed ? PDKIM_MODE_SIGN | PDKIM_DOT_TERM : PDKIM_MODE_SIGN;
+ctx->linebuf = CS (ctx+1);
+
+DEBUG(D_acl) ctx->dns_txt_callback = dns_txt_callback;
+
+sig = (pdkim_signature *)(ctx->linebuf + PDKIM_MAX_BODY_LINE_LEN);
+memset(sig, 0, sizeof(pdkim_signature));
+
+sig->bodylength = -1;
+ctx->sig = sig;
+
+sig->domain = string_copy(US domain);
+sig->selector = string_copy(US selector);
+sig->rsa_privkey = string_copy(US rsa_privkey);
+sig->algo = algo;
+
+if (!exim_sha_init(&sig->body_hash_ctx,
+              algo == PDKIM_ALGO_RSA_SHA1 ? HASH_SHA1 : HASH_SHA256))
+  {
+  DEBUG(D_acl) debug_printf("PDKIM: hash setup internal error\n");
+  return NULL;
+  }
+
+DEBUG(D_acl)
+  {
+  pdkim_signature s = *sig;
+  ev_ctx vctx;
+
+  debug_printf("PDKIM (checking verify key)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+  if (!pdkim_key_from_dns(ctx, &s, &vctx))
+    debug_printf("WARNING: bad dkim key in dns\n");
+  debug_printf("PDKIM (finished checking verify key)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+  }
+return ctx;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+DLLEXPORT int
+pdkim_set_optional(pdkim_ctx *ctx,
+                       char *sign_headers,
+                       char *identity,
+                       int canon_headers,
+                       int canon_body,
+                       long bodylength,
+                       unsigned long created,
+                       unsigned long expires)
+{
+pdkim_signature * sig = ctx->sig;
+
+if (identity)
+  sig->identity = string_copy(US identity);
+
+sig->sign_headers = string_copy(sign_headers
+       ? US sign_headers : US PDKIM_DEFAULT_SIGN_HEADERS);
+
+sig->canon_headers = canon_headers;
+sig->canon_body = canon_body;
+sig->bodylength = bodylength;
+sig->created = created;
+sig->expires = expires;
+
+return PDKIM_OK;
+}
+
+
+void
+pdkim_init(void)
+{
+exim_rsa_init();
+}
+
+
+
+#endif /*DISABLE_DKIM*/
index 1d364a3..8b8b950 100644 (file)
@@ -2,6 +2,7 @@
  *  PDKIM - a RFC4871 (DKIM) implementation
  *
  *  Copyright (C) 2009 - 2012  Tom Kistner <tom@duncanthrax.net>
+ *  Copyright (c) 2016 - 2017  Jeremy Harris
  *
  *  http://duncanthrax.net/pdkim/
  *
  *  with this program; if not, write to the Free Software Foundation, Inc.,
  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
+#ifndef PDKIM_H
+#define PDKIM_H
 
-/* -------------------------------------------------------------------------- */
-/* Debugging. This can also be enabled/disabled at run-time. I recommend to
-   leave it defined. */
-#define PDKIM_DEBUG
+#include "../blob.h"
+#include "../hash.h"
 
 /* -------------------------------------------------------------------------- */
 /* Length of the preallocated buffer for the "answer" from the dns/txt
 /* Function success / error codes */
 #define PDKIM_OK                      0
 #define PDKIM_FAIL                   -1
-#define PDKIM_ERR_OOM              -100
 #define PDKIM_ERR_RSA_PRIVKEY      -101
 #define PDKIM_ERR_RSA_SIGNING      -102
 #define PDKIM_ERR_LONG_LINE        -103
 #define PDKIM_ERR_BUFFER_TOO_SMALL -104
+#define PDKIM_SIGN_PRIVKEY_WRAP    -105
+#define PDKIM_SIGN_PRIVKEY_B64D    -106
 
 /* -------------------------------------------------------------------------- */
 /* Main/Extended verification status */
 #define PDKIM_VERIFY_FAIL      2
 #define PDKIM_VERIFY_PASS      3
 
-#define PDKIM_VERIFY_FAIL_BODY                  1
-#define PDKIM_VERIFY_FAIL_MESSAGE               2
-#define PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE 3
-#define PDKIM_VERIFY_INVALID_BUFFER_SIZE        4
-#define PDKIM_VERIFY_INVALID_PUBKEY_PARSING     5
+#define PDKIM_VERIFY_FAIL_BODY                    1
+#define PDKIM_VERIFY_FAIL_MESSAGE                 2
+#define PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE   3
+#define PDKIM_VERIFY_INVALID_BUFFER_SIZE          4
+#define PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD     5
+#define PDKIM_VERIFY_INVALID_PUBKEY_IMPORT        6
+#define PDKIM_VERIFY_INVALID_SIGNATURE_ERROR      7
+#define PDKIM_VERIFY_INVALID_DKIM_VERSION         8
 
 /* -------------------------------------------------------------------------- */
 /* Some parameter values */
@@ -92,17 +97,17 @@ typedef struct sha2_context sha2_context;
 /* -------------------------------------------------------------------------- */
 /* Public key as (usually) fetched from DNS */
 typedef struct pdkim_pubkey {
-  char *version;                  /* v=  */
-  char *granularity;              /* g=  */
-
-  char *hashes;                   /* h=  */
-  char *keytype;                  /* k=  */
-  char *srvtype;                  /* s=  */
-  char *notes;                    /* n=  */
+  uschar *version;                /* v=  */
+  uschar *granularity;            /* g=  */
 
-  char *key;                      /* p=  */
-  int   key_len;
+#ifdef notdef
+  uschar *hashes;                 /* h=  */
+  uschar *keytype;                /* k=  */
+#endif
+  uschar *srvtype;                /* s=  */
+  uschar *notes;                  /* n=  */
 
+  blob  key;                      /* p=  */
   int   testing;                  /* t=y */
   int   no_subdomaining;          /* t=s */
 } pdkim_pubkey;
@@ -133,13 +138,13 @@ typedef struct pdkim_signature {
   int querymethod;
 
   /* (s=) The selector string as given in the signature */
-  char *selector;
+  uschar *selector;
 
   /* (d=) The domain as given in the signature */
-  char *domain;
+  uschar *domain;
 
   /* (i=) The identity as given in the signature */
-  char *identity;
+  uschar *identity;
 
   /* (t=) Timestamp of signature creation */
   unsigned long created;
@@ -153,24 +158,22 @@ typedef struct pdkim_signature {
 
   /* (h=) Colon-separated list of header names that are included in the
      signature */
-  char *headernames;
+  uschar *headernames;
 
   /* (z=) */
-  char *copiedheaders;
+  uschar *copiedheaders;
 
   /* (b=) Raw signature data, along with its length in bytes */
-  char *sigdata;
-  int   sigdata_len;
+  blob sighash;
 
   /* (bh=) Raw body hash data, along with its length in bytes */
-  char *bodyhash;
-  int   bodyhash_len;
+  blob bodyhash;
 
   /* Folded DKIM-Signature: header. Singing only, NULL for verifying.
      Ready for insertion into the message. Note: Folded using CRLFTB,
      but final line terminator is NOT included. Note2: This buffer is
      free()d when you call pdkim_free_ctx(). */
-  char *signature_header;
+  uschar *signature_header;
 
   /* The main verification status. Verification only. One of:
 
@@ -230,30 +233,28 @@ typedef struct pdkim_signature {
   /* Properties below this point are used internally only ------------- */
 
   /* Per-signature helper variables ----------------------------------- */
-  sha1_context *sha1_body; /* SHA1 block                                */
-  sha2_context *sha2_body; /* SHA256 block                              */
+  hctx         body_hash_ctx;
+
   unsigned long signed_body_bytes; /* How many body bytes we hashed     */
   pdkim_stringlist *headers; /* Raw headers included in the sig         */
   /* Signing specific ------------------------------------------------- */
-  char *rsa_privkey;     /* Private RSA key                             */
-  char *sign_headers;    /* To-be-signed header names                   */
-  char *rawsig_no_b_val; /* Original signature header w/o b= tag value. */
+  uschar * rsa_privkey;     /* Private RSA key                             */
+  uschar * sign_headers;    /* To-be-signed header names                   */
+  uschar * rawsig_no_b_val; /* Original signature header w/o b= tag value. */
 } pdkim_signature;
 
 
 /* -------------------------------------------------------------------------- */
 /* Context to keep state between all operations. */
-#define PDKIM_MODE_SIGN     0
-#define PDKIM_MODE_VERIFY   1
-#define PDKIM_INPUT_NORMAL  0
-#define PDKIM_INPUT_SMTP    1
 typedef struct pdkim_ctx {
 
-  /* PDKIM_MODE_VERIFY or PDKIM_MODE_SIGN */
-  int mode;
-
-  /* PDKIM_INPUT_SMTP or PDKIM_INPUT_NORMAL */
-  int input_mode;
+#define PDKIM_MODE_SIGN   BIT(0)       /* if unset, mode==verify */
+#define PDKIM_DOT_TERM   BIT(1)        /* dot termination and unstuffing */
+#define PDKIM_SEEN_CR    BIT(2)
+#define PDKIM_SEEN_LF    BIT(3)
+#define PDKIM_PAST_HDRS          BIT(4)
+#define PDKIM_SEEN_EOD   BIT(5)
+  unsigned   flags;
 
   /* One (signing) or several chained (verification) signatures */
   pdkim_signature *sig;
@@ -262,22 +263,14 @@ typedef struct pdkim_ctx {
   int(*dns_txt_callback)(char *, char *);
 
   /* Coder's little helpers */
-  pdkim_str *cur_header;
+  uschar    *cur_header;
+  int        cur_header_size;
+  int        cur_header_len;
   char      *linebuf;
   int        linebuf_offset;
-  int        seen_lf;
-  int        seen_eod;
-  int        past_headers;
   int        num_buffered_crlf;
   int        num_headers;
   pdkim_stringlist *headers; /* Raw headers for verification         */
-
-#ifdef PDKIM_DEBUG
-  /* A FILE pointer. When not NULL, debug output will be generated
-    and sent to this stream */
-  FILE *debug_stream;
-#endif
-
 } pdkim_ctx;
 
 
@@ -290,15 +283,18 @@ typedef struct pdkim_ctx {
 extern "C" {
 #endif
 
+void      pdkim_init         (void);
+
 DLLEXPORT
-pdkim_ctx *pdkim_init_sign    (int, char *, char *, char *);
+pdkim_ctx *pdkim_init_sign    (char *, char *, char *, int,
+                             BOOL, int(*)(char *, char *));
 
 DLLEXPORT
-pdkim_ctx *pdkim_init_verify  (int, int(*)(char *, char *));
+pdkim_ctx *pdkim_init_verify  (int(*)(char *, char *), BOOL);
 
 DLLEXPORT
 int        pdkim_set_optional (pdkim_ctx *, char *, char *,int, int,
-                               long, int,
+                               long,
                                unsigned long,
                                unsigned long);
 
@@ -310,11 +306,11 @@ int        pdkim_feed_finish  (pdkim_ctx *, pdkim_signature **);
 DLLEXPORT
 void       pdkim_free_ctx     (pdkim_ctx *);
 
-#ifdef PDKIM_DEBUG
-DLLEXPORT
-void       pdkim_set_debug_stream(pdkim_ctx *, FILE *);
-#endif
+
+const char *   pdkim_errstr(int);
 
 #ifdef __cplusplus
 }
 #endif
+
+#endif
diff --git a/src/pdkim/pdkim_hash.h b/src/pdkim/pdkim_hash.h
new file mode 100644 (file)
index 0000000..143cd19
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ *  PDKIM - a RFC4871 (DKIM) implementation
+ *
+ *  Copyright (C) 2016  Exim maintainers
+ *
+ *  Hash interface functions
+ */
+
+#include "../exim.h"
+
+#if !defined(HASH_H)   /* entire file */
+#define HASH_H
+
+#ifndef SUPPORT_TLS
+# error Need SUPPORT_TLS for DKIM
+#endif
+
+#include "crypt_ver.h"
+#include "../blob.h"
+#include "../hash.h"
+
+#ifdef RSA_OPENSSL
+# include <openssl/rsa.h>
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+#elif defined(RSA_GNUTLS)
+# include <gnutls/gnutls.h>
+# include <gnutls/x509.h>
+#endif
+
+#if defined(SHA_OPENSSL)
+# include "pdkim.h"
+#elif defined(SHA_GCRYPT)
+# include "pdkim.h"
+#endif
+
+#endif
+/* End of File */
dissimilarity index 98%
index 68b201d..950c617 100644 (file)
-/*
- *  The RSA public-key cryptosystem
- *
- *  Copyright (C) 2006-2010, Brainspark B.V.
- *
- *  This file is part of PolarSSL (http://www.polarssl.org)
- *  Lead Maintainer: Paul Bakker <polarssl_maintainer at polarssl.org>
- *
- *  All rights reserved.
- *
- *  This program 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 program 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 program; if not, write to the Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-/*
- *  RSA was designed by Ron Rivest, Adi Shamir and Len Adleman.
- *
- *  http://theory.lcs.mit.edu/~rivest/rsapaper.pdf
- *  http://www.cacr.math.uwaterloo.ca/hac/about/chap8.pdf
- */
-
-#include "rsa.h"
-#include "base64.h"
-
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-
-/* *************** begin copy from x509parse.c ********************/
-/*
- * ASN.1 DER decoding routines
- */
-static int asn1_get_len( unsigned char **p,
-                         const unsigned char *end,
-                         int *len )
-{
-    if( ( end - *p ) < 1 )
-        return( POLARSSL_ERR_ASN1_OUT_OF_DATA );
-
-    if( ( **p & 0x80 ) == 0 )
-        *len = *(*p)++;
-    else
-    {
-        switch( **p & 0x7F )
-        {
-        case 1:
-            if( ( end - *p ) < 2 )
-                return( POLARSSL_ERR_ASN1_OUT_OF_DATA );
-
-            *len = (*p)[1];
-            (*p) += 2;
-            break;
-
-        case 2:
-            if( ( end - *p ) < 3 )
-                return( POLARSSL_ERR_ASN1_OUT_OF_DATA );
-
-            *len = ( (*p)[1] << 8 ) | (*p)[2];
-            (*p) += 3;
-            break;
-
-        default:
-            return( POLARSSL_ERR_ASN1_INVALID_LENGTH );
-            break;
-        }
-    }
-
-    if( *len > (int) ( end - *p ) )
-        return( POLARSSL_ERR_ASN1_OUT_OF_DATA );
-
-    return( 0 );
-}
-
-static int asn1_get_tag( unsigned char **p,
-                         const unsigned char *end,
-                         int *len, int tag )
-{
-    if( ( end - *p ) < 1 )
-        return( POLARSSL_ERR_ASN1_OUT_OF_DATA );
-
-    if( **p != tag )
-        return( POLARSSL_ERR_ASN1_UNEXPECTED_TAG );
-
-    (*p)++;
-
-    return( asn1_get_len( p, end, len ) );
-}
-
-static int asn1_get_int( unsigned char **p,
-                         const unsigned char *end,
-                         int *val )
-{
-    int ret, len;
-
-    if( ( ret = asn1_get_tag( p, end, &len, ASN1_INTEGER ) ) != 0 )
-        return( ret );
-
-    if( len > (int) sizeof( int ) || ( **p & 0x80 ) != 0 )
-        return( POLARSSL_ERR_ASN1_INVALID_LENGTH );
-
-    *val = 0;
-
-    while( len-- > 0 )
-    {
-        *val = ( *val << 8 ) | **p;
-        (*p)++;
-    }
-
-    return( 0 );
-}
-
-static int asn1_get_mpi( unsigned char **p,
-                         const unsigned char *end,
-                         mpi *X )
-{
-    int ret, len;
-
-    if( ( ret = asn1_get_tag( p, end, &len, ASN1_INTEGER ) ) != 0 )
-        return( ret );
-
-    ret = mpi_read_binary( X, *p, len );
-
-    *p += len;
-
-    return( ret );
-}
-/* ***************   end copy from x509parse.c ********************/
-
-
-
-
-/*
- * Initialize an RSA context
- */
-void rsa_init( rsa_context *ctx,
-               int padding,
-               int hash_id )
-{
-    memset( ctx, 0, sizeof( rsa_context ) );
-
-    ctx->padding = padding;
-    ctx->hash_id = hash_id;
-}
-
-#if defined(POLARSSL_GENPRIME)
-
-/*
- * Generate an RSA keypair
- */
-int rsa_gen_key( rsa_context *ctx,
-        int (*f_rng)(void *),
-        void *p_rng,
-        int nbits, int exponent )
-{
-    int ret;
-    mpi P1, Q1, H, G;
-
-    if( f_rng == NULL || nbits < 128 || exponent < 3 )
-        return( POLARSSL_ERR_RSA_BAD_INPUT_DATA );
-
-    mpi_init( &P1, &Q1, &H, &G, NULL );
-
-    /*
-     * find primes P and Q with Q < P so that:
-     * GCD( E, (P-1)*(Q-1) ) == 1
-     */
-    MPI_CHK( mpi_lset( &ctx->E, exponent ) );
-
-    do
-    {
-        MPI_CHK( mpi_gen_prime( &ctx->P, ( nbits + 1 ) >> 1, 0, 
-                                f_rng, p_rng ) );
-
-        MPI_CHK( mpi_gen_prime( &ctx->Q, ( nbits + 1 ) >> 1, 0,
-                                f_rng, p_rng ) );
-
-        if( mpi_cmp_mpi( &ctx->P, &ctx->Q ) < 0 )
-            mpi_swap( &ctx->P, &ctx->Q );
-
-        if( mpi_cmp_mpi( &ctx->P, &ctx->Q ) == 0 )
-            continue;
-
-        MPI_CHK( mpi_mul_mpi( &ctx->N, &ctx->P, &ctx->Q ) );
-        if( mpi_msb( &ctx->N ) != nbits )
-            continue;
-
-        MPI_CHK( mpi_sub_int( &P1, &ctx->P, 1 ) );
-        MPI_CHK( mpi_sub_int( &Q1, &ctx->Q, 1 ) );
-        MPI_CHK( mpi_mul_mpi( &H, &P1, &Q1 ) );
-        MPI_CHK( mpi_gcd( &G, &ctx->E, &H  ) );
-    }
-    while( mpi_cmp_int( &G, 1 ) != 0 );
-
-    /*
-     * D  = E^-1 mod ((P-1)*(Q-1))
-     * DP = D mod (P - 1)
-     * DQ = D mod (Q - 1)
-     * QP = Q^-1 mod P
-     */
-    MPI_CHK( mpi_inv_mod( &ctx->D , &ctx->E, &H  ) );
-    MPI_CHK( mpi_mod_mpi( &ctx->DP, &ctx->D, &P1 ) );
-    MPI_CHK( mpi_mod_mpi( &ctx->DQ, &ctx->D, &Q1 ) );
-    MPI_CHK( mpi_inv_mod( &ctx->QP, &ctx->Q, &ctx->P ) );
-
-    ctx->len = ( mpi_msb( &ctx->N ) + 7 ) >> 3;
-
-cleanup:
-
-    mpi_free( &G, &H, &Q1, &P1, NULL );
-
-    if( ret != 0 )
-    {
-        rsa_free( ctx );
-        return( POLARSSL_ERR_RSA_KEY_GEN_FAILED | ret );
-    }
-
-    return( 0 );   
-}
-
-#endif
-
-/*
- * Check a public RSA key
- */
-int rsa_check_pubkey( const rsa_context *ctx )
-{
-    if( !ctx->N.p || !ctx->E.p )
-        return( POLARSSL_ERR_RSA_KEY_CHECK_FAILED );
-
-    if( ( ctx->N.p[0] & 1 ) == 0 ||
-        ( ctx->E.p[0] & 1 ) == 0 )
-        return( POLARSSL_ERR_RSA_KEY_CHECK_FAILED );
-
-    if( mpi_msb( &ctx->N ) < 128 ||
-        mpi_msb( &ctx->N ) > 4096 )
-        return( POLARSSL_ERR_RSA_KEY_CHECK_FAILED );
-
-    if( mpi_msb( &ctx->E ) < 2 ||
-        mpi_msb( &ctx->E ) > 64 )
-        return( POLARSSL_ERR_RSA_KEY_CHECK_FAILED );
-
-    return( 0 );
-}
-
-/*
- * Check a private RSA key
- */
-int rsa_check_privkey( const rsa_context *ctx )
-{
-    int ret;
-    mpi PQ, DE, P1, Q1, H, I, G, G2, L1, L2;
-
-    if( ( ret = rsa_check_pubkey( ctx ) ) != 0 )
-        return( ret );
-
-    if( !ctx->P.p || !ctx->Q.p || !ctx->D.p )
-        return( POLARSSL_ERR_RSA_KEY_CHECK_FAILED );
-
-    mpi_init( &PQ, &DE, &P1, &Q1, &H, &I, &G, &G2, &L1, &L2, NULL );
-
-    MPI_CHK( mpi_mul_mpi( &PQ, &ctx->P, &ctx->Q ) );
-    MPI_CHK( mpi_mul_mpi( &DE, &ctx->D, &ctx->E ) );
-    MPI_CHK( mpi_sub_int( &P1, &ctx->P, 1 ) );
-    MPI_CHK( mpi_sub_int( &Q1, &ctx->Q, 1 ) );
-    MPI_CHK( mpi_mul_mpi( &H, &P1, &Q1 ) );
-    MPI_CHK( mpi_gcd( &G, &ctx->E, &H  ) );
-
-    MPI_CHK( mpi_gcd( &G2, &P1, &Q1 ) );
-    MPI_CHK( mpi_div_mpi( &L1, &L2, &H, &G2 ) );  
-    MPI_CHK( mpi_mod_mpi( &I, &DE, &L1  ) );
-
-    /*
-     * Check for a valid PKCS1v2 private key
-     */
-    if( mpi_cmp_mpi( &PQ, &ctx->N ) == 0 &&
-        mpi_cmp_int( &L2, 0 ) == 0 &&
-        mpi_cmp_int( &I, 1 ) == 0 &&
-        mpi_cmp_int( &G, 1 ) == 0 )
-    {
-        mpi_free( &G, &I, &H, &Q1, &P1, &DE, &PQ, &G2, &L1, &L2, NULL );
-        return( 0 );
-    }
-
-    
-cleanup:
-
-    mpi_free( &G, &I, &H, &Q1, &P1, &DE, &PQ, &G2, &L1, &L2, NULL );
-    return( POLARSSL_ERR_RSA_KEY_CHECK_FAILED | ret );
-}
-
-/*
- * Do an RSA public key operation
- */
-int rsa_public( rsa_context *ctx,
-                const unsigned char *input,
-                unsigned char *output )
-{
-    int ret, olen;
-    mpi T;
-
-    mpi_init( &T, NULL );
-
-    MPI_CHK( mpi_read_binary( &T, input, ctx->len ) );
-
-    if( mpi_cmp_mpi( &T, &ctx->N ) >= 0 )
-    {
-        mpi_free( &T, NULL );
-        return( POLARSSL_ERR_RSA_BAD_INPUT_DATA );
-    }
-
-    olen = ctx->len;
-    MPI_CHK( mpi_exp_mod( &T, &T, &ctx->E, &ctx->N, &ctx->RN ) );
-    MPI_CHK( mpi_write_binary( &T, output, olen ) );
-
-cleanup:
-
-    mpi_free( &T, NULL );
-
-    if( ret != 0 )
-        return( POLARSSL_ERR_RSA_PUBLIC_FAILED | ret );
-
-    return( 0 );
-}
-
-/*
- * Do an RSA private key operation
- */
-int rsa_private( rsa_context *ctx,
-                 const unsigned char *input,
-                 unsigned char *output )
-{
-    int ret, olen;
-    mpi T, T1, T2;
-
-    mpi_init( &T, &T1, &T2, NULL );
-
-    MPI_CHK( mpi_read_binary( &T, input, ctx->len ) );
-
-    if( mpi_cmp_mpi( &T, &ctx->N ) >= 0 )
-    {
-        mpi_free( &T, NULL );
-        return( POLARSSL_ERR_RSA_BAD_INPUT_DATA );
-    }
-
-#if 0
-    MPI_CHK( mpi_exp_mod( &T, &T, &ctx->D, &ctx->N, &ctx->RN ) );
-#else
-    /*
-     * faster decryption using the CRT
-     *
-     * T1 = input ^ dP mod P
-     * T2 = input ^ dQ mod Q
-     */
-    MPI_CHK( mpi_exp_mod( &T1, &T, &ctx->DP, &ctx->P, &ctx->RP ) );
-    MPI_CHK( mpi_exp_mod( &T2, &T, &ctx->DQ, &ctx->Q, &ctx->RQ ) );
-
-    /*
-     * T = (T1 - T2) * (Q^-1 mod P) mod P
-     */
-    MPI_CHK( mpi_sub_mpi( &T, &T1, &T2 ) );
-    MPI_CHK( mpi_mul_mpi( &T1, &T, &ctx->QP ) );
-    MPI_CHK( mpi_mod_mpi( &T, &T1, &ctx->P ) );
-
-    /*
-     * output = T2 + T * Q
-     */
-    MPI_CHK( mpi_mul_mpi( &T1, &T, &ctx->Q ) );
-    MPI_CHK( mpi_add_mpi( &T, &T2, &T1 ) );
-#endif
-
-    olen = ctx->len;
-    MPI_CHK( mpi_write_binary( &T, output, olen ) );
-
-cleanup:
-
-    mpi_free( &T, &T1, &T2, NULL );
-
-    if( ret != 0 )
-        return( POLARSSL_ERR_RSA_PRIVATE_FAILED | ret );
-
-    return( 0 );
-}
-
-/*
- * Add the message padding, then do an RSA operation
- */
-int rsa_pkcs1_encrypt( rsa_context *ctx,
-                       int (*f_rng)(void *),
-                       void *p_rng,
-                       int mode, int  ilen,
-                       const unsigned char *input,
-                       unsigned char *output )
-{
-    int nb_pad, olen;
-    unsigned char *p = output;
-
-    olen = ctx->len;
-
-    switch( ctx->padding )
-    {
-        case RSA_PKCS_V15:
-
-            if( ilen < 0 || olen < ilen + 11 || f_rng == NULL )
-                return( POLARSSL_ERR_RSA_BAD_INPUT_DATA );
-
-            nb_pad = olen - 3 - ilen;
-
-            *p++ = 0;
-            *p++ = RSA_CRYPT;
-
-            while( nb_pad-- > 0 )
-            {
-                int rng_dl = 100;
-
-                do {
-                    *p = (unsigned char) f_rng( p_rng );
-                } while( *p == 0 && --rng_dl );
-
-                // Check if RNG failed to generate data
-                //
-                if( rng_dl == 0 )
-                    return POLARSSL_ERR_RSA_RNG_FAILED;
-
-                p++;
-            }
-            *p++ = 0;
-            memcpy( p, input, ilen );
-            break;
-
-        default:
-
-            return( POLARSSL_ERR_RSA_INVALID_PADDING );
-    }
-
-    return( ( mode == RSA_PUBLIC )
-            ? rsa_public(  ctx, output, output )
-            : rsa_private( ctx, output, output ) );
-}
-
-/*
- * Do an RSA operation, then remove the message padding
- */
-int rsa_pkcs1_decrypt( rsa_context *ctx,
-                       int mode, int *olen,
-                       const unsigned char *input,
-                       unsigned char *output,
-                       int output_max_len)
-{
-    int ret, ilen;
-    unsigned char *p;
-    unsigned char buf[1024];
-
-    ilen = ctx->len;
-
-    if( ilen < 16 || ilen > (int) sizeof( buf ) )
-        return( POLARSSL_ERR_RSA_BAD_INPUT_DATA );
-
-    ret = ( mode == RSA_PUBLIC )
-          ? rsa_public(  ctx, input, buf )
-          : rsa_private( ctx, input, buf );
-
-    if( ret != 0 )
-        return( ret );
-
-    p = buf;
-
-    switch( ctx->padding )
-    {
-        case RSA_PKCS_V15:
-
-            if( *p++ != 0 || *p++ != RSA_CRYPT )
-                return( POLARSSL_ERR_RSA_INVALID_PADDING );
-
-            while( *p != 0 )
-            {
-                if( p >= buf + ilen - 1 )
-                    return( POLARSSL_ERR_RSA_INVALID_PADDING );
-                p++;
-            }
-            p++;
-            break;
-
-        default:
-
-            return( POLARSSL_ERR_RSA_INVALID_PADDING );
-    }
-
-    if (ilen - (int)(p - buf) > output_max_len)
-      return( POLARSSL_ERR_RSA_OUTPUT_TOO_LARGE );
-
-    *olen = ilen - (int)(p - buf);
-    memcpy( output, p, *olen );
-
-    return( 0 );
-}
-
-/*
- * Do an RSA operation to sign the message digest
- */
-int rsa_pkcs1_sign( rsa_context *ctx,
-                    int mode,
-                    int hash_id,
-                    int hashlen,
-                    const unsigned char *hash,
-                    unsigned char *sig )
-{
-    int nb_pad, olen;
-    unsigned char *p = sig;
-
-    olen = ctx->len;
-
-    switch( ctx->padding )
-    {
-        case RSA_PKCS_V15:
-
-            switch( hash_id )
-            {
-                case SIG_RSA_RAW:
-                    nb_pad = olen - 3 - hashlen;
-                    break;
-
-                case SIG_RSA_MD2:
-                case SIG_RSA_MD4:
-                case SIG_RSA_MD5:
-                    nb_pad = olen - 3 - 34;
-                    break;
-
-                case SIG_RSA_SHA1:
-                    nb_pad = olen - 3 - 35;
-                    break;
-
-                case SIG_RSA_SHA224:
-                    nb_pad = olen - 3 - 47;
-                    break;
-
-                case SIG_RSA_SHA256:
-                    nb_pad = olen - 3 - 51;
-                    break;
-
-                case SIG_RSA_SHA384:
-                    nb_pad = olen - 3 - 67;
-                    break;
-
-                case SIG_RSA_SHA512:
-                    nb_pad = olen - 3 - 83;
-                    break;
-
-
-                default:
-                    return( POLARSSL_ERR_RSA_BAD_INPUT_DATA );
-            }
-
-            if( nb_pad < 8 )
-                return( POLARSSL_ERR_RSA_BAD_INPUT_DATA );
-
-            *p++ = 0;
-            *p++ = RSA_SIGN;
-            memset( p, 0xFF, nb_pad );
-            p += nb_pad;
-            *p++ = 0;
-            break;
-
-        default:
-
-            return( POLARSSL_ERR_RSA_INVALID_PADDING );
-    }
-
-    switch( hash_id )
-    {
-        case SIG_RSA_RAW:
-            memcpy( p, hash, hashlen );
-            break;
-
-        case SIG_RSA_MD2:
-            memcpy( p, ASN1_HASH_MDX, 18 );
-            memcpy( p + 18, hash, 16 );
-            p[13] = 2; break;
-
-        case SIG_RSA_MD4:
-            memcpy( p, ASN1_HASH_MDX, 18 );
-            memcpy( p + 18, hash, 16 );
-            p[13] = 4; break;
-
-        case SIG_RSA_MD5:
-            memcpy( p, ASN1_HASH_MDX, 18 );
-            memcpy( p + 18, hash, 16 );
-            p[13] = 5; break;
-
-        case SIG_RSA_SHA1:
-            memcpy( p, ASN1_HASH_SHA1, 15 );
-            memcpy( p + 15, hash, 20 );
-            break;
-
-        case SIG_RSA_SHA224:
-            memcpy( p, ASN1_HASH_SHA2X, 19 );
-            memcpy( p + 19, hash, 28 );
-            p[1] += 28; p[14] = 4; p[18] += 28; break;
-
-        case SIG_RSA_SHA256:
-            memcpy( p, ASN1_HASH_SHA2X, 19 );
-            memcpy( p + 19, hash, 32 );
-            p[1] += 32; p[14] = 1; p[18] += 32; break;
-
-        case SIG_RSA_SHA384:
-            memcpy( p, ASN1_HASH_SHA2X, 19 );
-            memcpy( p + 19, hash, 48 );
-            p[1] += 48; p[14] = 2; p[18] += 48; break;
-
-        case SIG_RSA_SHA512:
-            memcpy( p, ASN1_HASH_SHA2X, 19 );
-            memcpy( p + 19, hash, 64 );
-            p[1] += 64; p[14] = 3; p[18] += 64; break;
-
-        default:
-            return( POLARSSL_ERR_RSA_BAD_INPUT_DATA );
-    }
-
-    return( ( mode == RSA_PUBLIC )
-            ? rsa_public(  ctx, sig, sig )
-            : rsa_private( ctx, sig, sig ) );
-}
-
-/*
- * Do an RSA operation and check the message digest
- */
-int rsa_pkcs1_verify( rsa_context *ctx,
-                      int mode,
-                      int hash_id,
-                      int hashlen,
-                      const unsigned char *hash,
-                      unsigned char *sig )
-{
-    int ret, len, siglen;
-    unsigned char *p, c;
-    unsigned char buf[1024];
-
-    siglen = ctx->len;
-
-    if( siglen < 16 || siglen > (int) sizeof( buf ) )
-        return( POLARSSL_ERR_RSA_BAD_INPUT_DATA );
-
-    ret = ( mode == RSA_PUBLIC )
-          ? rsa_public(  ctx, sig, buf )
-          : rsa_private( ctx, sig, buf );
-
-    if( ret != 0 )
-        return( ret );
-
-    p = buf;
-
-    switch( ctx->padding )
-    {
-        case RSA_PKCS_V15:
-
-            if( *p++ != 0 || *p++ != RSA_SIGN )
-                return( POLARSSL_ERR_RSA_INVALID_PADDING );
-
-            while( *p != 0 )
-            {
-                if( p >= buf + siglen - 1 || *p != 0xFF )
-                    return( POLARSSL_ERR_RSA_INVALID_PADDING );
-                p++;
-            }
-            p++;
-            break;
-
-        default:
-
-            return( POLARSSL_ERR_RSA_INVALID_PADDING );
-    }
-
-    len = siglen - (int)( p - buf );
-
-    if( len == 34 )
-    {
-        c = p[13];
-        p[13] = 0;
-
-        if( memcmp( p, ASN1_HASH_MDX, 18 ) != 0 )
-            return( POLARSSL_ERR_RSA_VERIFY_FAILED );
-
-        if( ( c == 2 && hash_id == SIG_RSA_MD2 ) ||
-            ( c == 4 && hash_id == SIG_RSA_MD4 ) ||
-            ( c == 5 && hash_id == SIG_RSA_MD5 ) )
-        {
-            if( memcmp( p + 18, hash, 16 ) == 0 )
-                return( 0 );
-            else
-                return( POLARSSL_ERR_RSA_VERIFY_FAILED );
-        }
-    }
-
-    if( len == 35 && hash_id == SIG_RSA_SHA1 )
-    {
-        if( memcmp( p, ASN1_HASH_SHA1, 15 ) == 0 &&
-            memcmp( p + 15, hash, 20 ) == 0 )
-            return( 0 );
-        else
-            return( POLARSSL_ERR_RSA_VERIFY_FAILED );
-    }
-    if( ( len == 19 + 28 && p[14] == 4 && hash_id == SIG_RSA_SHA224 ) ||
-        ( len == 19 + 32 && p[14] == 1 && hash_id == SIG_RSA_SHA256 ) ||
-        ( len == 19 + 48 && p[14] == 2 && hash_id == SIG_RSA_SHA384 ) ||
-        ( len == 19 + 64 && p[14] == 3 && hash_id == SIG_RSA_SHA512 ) )
-    {
-        c = p[1] - 17;
-        p[1] = 17;
-        p[14] = 0;
-
-        if( p[18] == c &&
-                memcmp( p, ASN1_HASH_SHA2X, 18 ) == 0 &&
-                memcmp( p + 19, hash, c ) == 0 )
-            return( 0 );
-        else
-            return( POLARSSL_ERR_RSA_VERIFY_FAILED );
-    }
-
-    if( len == hashlen && hash_id == SIG_RSA_RAW )
-    {
-        if( memcmp( p, hash, hashlen ) == 0 )
-            return( 0 );
-        else
-            return( POLARSSL_ERR_RSA_VERIFY_FAILED );
-    }
-
-    return( POLARSSL_ERR_RSA_INVALID_PADDING );
-}
-
-/*
- * Free the components of an RSA key
- */
-void rsa_free( rsa_context *ctx )
-{
-    mpi_free( &ctx->RQ, &ctx->RP, &ctx->RN,
-              &ctx->QP, &ctx->DQ, &ctx->DP,
-              &ctx->Q,  &ctx->P,  &ctx->D,
-              &ctx->E,  &ctx->N,  NULL );
-}
-
-
-/* PDKIM code (not copied from polarssl) */
-/*
- * Parse a public RSA key
-
-OpenSSL RSA public key ASN1 container
-  0:d=0  hl=3 l= 159 cons: SEQUENCE
-  3:d=1  hl=2 l=  13 cons: SEQUENCE
-  5:d=2  hl=2 l=   9 prim: OBJECT:rsaEncryption
- 16:d=2  hl=2 l=   0 prim: NULL
- 18:d=1  hl=3 l= 141 prim: BIT STRING:RSAPublicKey (below)
-
-RSAPublicKey ASN1 container
-  0:d=0  hl=3 l= 137 cons: SEQUENCE
-  3:d=1  hl=3 l= 129 prim: INTEGER:Public modulus
-135:d=1  hl=2 l=   3 prim: INTEGER:Public exponent
-*/
-
-int rsa_parse_public_key( rsa_context *rsa, unsigned char *buf, int buflen )
-{
-    unsigned char *p, *end;
-    int ret, len;
-
-    p = buf;
-    end = buf+buflen;
-
-    if( ( ret = asn1_get_tag( &p, end, &len,
-            ASN1_CONSTRUCTED | ASN1_SEQUENCE ) ) != 0 ) {
-        return( POLARSSL_ERR_X509_KEY_INVALID_FORMAT | ret );
-    }
-
-    if( ( ret = asn1_get_tag( &p, end, &len,
-            ASN1_CONSTRUCTED | ASN1_SEQUENCE ) ) == 0 ) {
-        /* Skip over embedded rsaEncryption Object */
-        p+=len;
-
-        /* The RSAPublicKey ASN1 container is wrapped in a BIT STRING */
-        if( ( ret = asn1_get_tag( &p, end, &len,
-                ASN1_BIT_STRING ) ) != 0 ) {
-            return( POLARSSL_ERR_X509_KEY_INVALID_FORMAT | ret );
-        }
-
-        /* Limit range to that BIT STRING */
-        end = p + len;
-        p++;
-
-        if( ( ret = asn1_get_tag( &p, end, &len,
-                ASN1_CONSTRUCTED | ASN1_SEQUENCE ) ) != 0 ) {
-            return( POLARSSL_ERR_X509_KEY_INVALID_FORMAT | ret );
-        }
-    }
-
-    if ( ( ( ret = asn1_get_mpi( &p, end, &(rsa->N)  ) ) == 0 ) &&
-         ( ( ret = asn1_get_mpi( &p, end, &(rsa->E)  ) ) == 0 ) ) {
-        rsa->len = mpi_size( &rsa->N );
-        return 0;
-    }
-
-    return( POLARSSL_ERR_X509_KEY_INVALID_FORMAT | ret );
-}
-
-/*
- * Parse a private RSA key
- */
-int rsa_parse_key( rsa_context *rsa, unsigned char *buf, int buflen,
-                                     unsigned char *pwd, int pwdlen )
-{
-    int ret, len, enc;
-    unsigned char *s1, *s2;
-    unsigned char *p, *end;
-
-    s1 = (unsigned char *) strstr( (char *) buf,
-        "-----BEGIN RSA PRIVATE KEY-----" );
-
-    if( s1 != NULL )
-    {
-        s2 = (unsigned char *) strstr( (char *) buf,
-            "-----END RSA PRIVATE KEY-----" );
-
-        if( s2 == NULL || s2 <= s1 )
-            return( POLARSSL_ERR_X509_KEY_INVALID_PEM );
-
-        s1 += 31;
-        if( *s1 == '\r' ) s1++;
-        if( *s1 == '\n' ) s1++;
-            else return( POLARSSL_ERR_X509_KEY_INVALID_PEM );
-
-        enc = 0;
-
-        if( memcmp( s1, "Proc-Type: 4,ENCRYPTED", 22 ) == 0 )
-        {
-            return( POLARSSL_ERR_X509_FEATURE_UNAVAILABLE );
-        }
-
-        len = 0;
-        ret = base64_decode( NULL, &len, s1, s2 - s1 );
-
-        if( ret == POLARSSL_ERR_BASE64_INVALID_CHARACTER )
-            return( ret | POLARSSL_ERR_X509_KEY_INVALID_PEM );
-
-        if( ( buf = (unsigned char *) malloc( len ) ) == NULL )
-            return( 1 );
-
-        if( ( ret = base64_decode( buf, &len, s1, s2 - s1 ) ) != 0 )
-        {
-            free( buf );
-            return( ret | POLARSSL_ERR_X509_KEY_INVALID_PEM );
-        }
-
-        buflen = len;
-
-        if( enc != 0 )
-        {
-            return( POLARSSL_ERR_X509_FEATURE_UNAVAILABLE );
-        }
-    }
-
-    memset( rsa, 0, sizeof( rsa_context ) );
-
-    p = buf;
-    end = buf + buflen;
-
-    /*
-     *  RSAPrivateKey ::= SEQUENCE {
-     *      version           Version,
-     *      modulus           INTEGER,  -- n
-     *      publicExponent    INTEGER,  -- e
-     *      privateExponent   INTEGER,  -- d
-     *      prime1            INTEGER,  -- p
-     *      prime2            INTEGER,  -- q
-     *      exponent1         INTEGER,  -- d mod (p-1)
-     *      exponent2         INTEGER,  -- d mod (q-1)
-     *      coefficient       INTEGER,  -- (inverse of q) mod p
-     *      otherPrimeInfos   OtherPrimeInfos OPTIONAL
-     *  }
-     */
-    if( ( ret = asn1_get_tag( &p, end, &len,
-            ASN1_CONSTRUCTED | ASN1_SEQUENCE ) ) != 0 )
-    {
-        if( s1 != NULL )
-            free( buf );
-
-        rsa_free( rsa );
-        return( POLARSSL_ERR_X509_KEY_INVALID_FORMAT | ret );
-    }
-
-    end = p + len;
-
-    if( ( ret = asn1_get_int( &p, end, &rsa->ver ) ) != 0 )
-    {
-        if( s1 != NULL )
-            free( buf );
-
-        rsa_free( rsa );
-        return( POLARSSL_ERR_X509_KEY_INVALID_FORMAT | ret );
-    }
-
-    if( rsa->ver != 0 )
-    {
-        if( s1 != NULL )
-            free( buf );
-
-        rsa_free( rsa );
-        return( ret | POLARSSL_ERR_X509_KEY_INVALID_VERSION );
-    }
-
-    if( ( ret = asn1_get_mpi( &p, end, &rsa->N  ) ) != 0 ||
-        ( ret = asn1_get_mpi( &p, end, &rsa->E  ) ) != 0 ||
-        ( ret = asn1_get_mpi( &p, end, &rsa->D  ) ) != 0 ||
-        ( ret = asn1_get_mpi( &p, end, &rsa->P  ) ) != 0 ||
-        ( ret = asn1_get_mpi( &p, end, &rsa->Q  ) ) != 0 ||
-        ( ret = asn1_get_mpi( &p, end, &rsa->DP ) ) != 0 ||
-        ( ret = asn1_get_mpi( &p, end, &rsa->DQ ) ) != 0 ||
-        ( ret = asn1_get_mpi( &p, end, &rsa->QP ) ) != 0 )
-    {
-        if( s1 != NULL )
-            free( buf );
-
-        rsa_free( rsa );
-        return( ret | POLARSSL_ERR_X509_KEY_INVALID_FORMAT );
-    }
-
-    rsa->len = mpi_size( &rsa->N );
-
-    if( p != end )
-    {
-        if( s1 != NULL )
-            free( buf );
-
-        rsa_free( rsa );
-        return( POLARSSL_ERR_X509_KEY_INVALID_FORMAT |
-                POLARSSL_ERR_ASN1_LENGTH_MISMATCH );
-    }
-
-    if( ( ret = rsa_check_privkey( rsa ) ) != 0 )
-    {
-        if( s1 != NULL )
-            free( buf );
-
-        rsa_free( rsa );
-        return( ret );
-    }
-
-    if( s1 != NULL )
-        free( buf );
-
-    return( 0 );
-}
+/*
+ *  PDKIM - a RFC4871 (DKIM) implementation
+ *
+ *  Copyright (C) 2016  Exim maintainers
+ *
+ *  RSA signing/verification interface
+ */
+
+#include "../exim.h"
+
+#ifndef DISABLE_DKIM   /* entire file */
+
+#ifndef SUPPORT_TLS
+# error Need SUPPORT_TLS for DKIM
+#endif
+
+#include "crypt_ver.h"
+#include "rsa.h"
+
+
+/******************************************************************************/
+#ifdef RSA_GNUTLS
+
+void
+exim_rsa_init(void)
+{
+}
+
+
+/* accumulate data (gnutls-only).  String to be appended must be nul-terminated. */
+blob *
+exim_rsa_data_append(blob * b, int * alloc, uschar * s)
+{
+int len = b->len;
+b->data = string_append(b->data, alloc, &len, 1, s);
+b->len = len;
+return b;
+}
+
+
+
+/* import private key from PEM string in memory.
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_rsa_signing_init(uschar * privkey_pem, es_ctx * sign_ctx)
+{
+gnutls_datum_t k;
+int rc;
+
+k.data = privkey_pem;
+k.size = strlen(privkey_pem);
+
+if (  (rc = gnutls_x509_privkey_init(&sign_ctx->rsa)) != GNUTLS_E_SUCCESS
+   /*|| (rc = gnutls_x509_privkey_import(sign_ctx->rsa, &k,
+         GNUTLS_X509_FMT_PEM)) != GNUTLS_E_SUCCESS */
+   )
+  return gnutls_strerror(rc);
+
+if (  /* (rc = gnutls_x509_privkey_init(&sign_ctx->rsa)) != GNUTLS_E_SUCCESS
+   ||*/ (rc = gnutls_x509_privkey_import(sign_ctx->rsa, &k,
+         GNUTLS_X509_FMT_PEM)) != GNUTLS_E_SUCCESS
+   )
+  return gnutls_strerror(rc);
+
+return NULL;
+}
+
+
+
+/* allocate mem for signature (when signing) */
+/* sign data (gnutls_only)
+OR
+sign hash.
+
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_rsa_sign(es_ctx * sign_ctx, BOOL is_sha1, blob * data, blob * sig)
+{
+gnutls_datum_t k;
+size_t sigsize = 0;
+int rc;
+const uschar * ret = NULL;
+
+/* Allocate mem for signature */
+k.data = data->data;
+k.size = data->len;
+(void) gnutls_x509_privkey_sign_data(sign_ctx->rsa,
+  is_sha1 ? GNUTLS_DIG_SHA1 : GNUTLS_DIG_SHA256,
+  0, &k, NULL, &sigsize);
+
+sig->data = store_get(sigsize);
+sig->len = sigsize;
+
+/* Do signing */
+if ((rc = gnutls_x509_privkey_sign_data(sign_ctx->rsa,
+           is_sha1 ? GNUTLS_DIG_SHA1 : GNUTLS_DIG_SHA256,
+           0, &k, sig->data, &sigsize)) != GNUTLS_E_SUCCESS
+   )
+  ret = gnutls_strerror(rc);
+
+gnutls_x509_privkey_deinit(sign_ctx->rsa);
+return ret;
+}
+
+
+
+/* import public key (from DER in memory)
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_rsa_verify_init(blob * pubkey_der, ev_ctx * verify_ctx)
+{
+gnutls_datum_t k;
+int rc;
+const uschar * ret = NULL;
+
+gnutls_pubkey_init(&verify_ctx->rsa);
+
+k.data = pubkey_der->data;
+k.size = pubkey_der->len;
+
+if ((rc = gnutls_pubkey_import(verify_ctx->rsa, &k, GNUTLS_X509_FMT_DER))
+       != GNUTLS_E_SUCCESS)
+  ret = gnutls_strerror(rc);
+return ret;
+}
+
+
+/* verify signature (of hash)  (given pubkey & alleged sig)
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_rsa_verify(ev_ctx * verify_ctx, BOOL is_sha1, blob * data_hash, blob * sig)
+{
+gnutls_datum_t k, s;
+int rc;
+const uschar * ret = NULL;
+
+k.data = data_hash->data;
+k.size = data_hash->len;
+s.data = sig->data;
+s.size = sig->len;
+if ((rc = gnutls_pubkey_verify_hash2(verify_ctx->rsa,
+           is_sha1 ? GNUTLS_SIGN_RSA_SHA1 : GNUTLS_SIGN_RSA_SHA256,
+           0, &k, &s)) < 0)
+  ret = gnutls_strerror(rc);
+
+gnutls_pubkey_deinit(verify_ctx->rsa);
+return ret;
+}
+
+
+
+
+#elif defined(RSA_GCRYPT)
+/******************************************************************************/
+
+
+/* Internal service routine:
+Read and move past an asn.1 header, checking class & tag,
+optionally returning the data-length */
+
+static int
+as_tag(blob * der, uschar req_cls, long req_tag, long * alen)
+{
+int rc;
+uschar tag_class;
+int taglen;
+long tag, len;
+
+/* debug_printf_indent("as_tag: %02x %02x %02x %02x\n",
+       der->data[0], der->data[1], der->data[2], der->data[3]); */
+
+if ((rc = asn1_get_tag_der(der->data++, der->len--, &tag_class, &taglen, &tag))
+    != ASN1_SUCCESS)
+  return rc;
+
+if (tag_class != req_cls || tag != req_tag) return ASN1_ELEMENT_NOT_FOUND;
+
+if ((len = asn1_get_length_der(der->data, der->len, &taglen)) < 0)
+  return ASN1_DER_ERROR;
+if (alen) *alen = len;
+
+/* debug_printf_indent("as_tag:  tlen %d dlen %d\n", taglen, (int)len); */
+
+der->data += taglen;
+der->len -= taglen;
+return rc;
+}
+
+/* Internal service routine:
+Read and move over an asn.1 integer, setting an MPI to the value
+*/
+
+static uschar *
+as_mpi(blob * der, gcry_mpi_t * mpi)
+{
+long alen;
+int rc;
+gcry_error_t gerr;
+
+/* integer; move past the header */
+if ((rc = as_tag(der, 0, ASN1_TAG_INTEGER, &alen)) != ASN1_SUCCESS)
+  return US asn1_strerror(rc);
+
+/* read to an MPI */
+if ((gerr = gcry_mpi_scan(mpi, GCRYMPI_FMT_STD, der->data, alen, NULL)))
+  return US gcry_strerror(gerr);
+
+/* move over the data */
+der->data += alen; der->len -= alen;
+return NULL;
+}
+
+
+
+void
+exim_rsa_init(void)
+{
+/* Version check should be the very first call because it
+makes sure that important subsystems are initialized. */
+if (!gcry_check_version (GCRYPT_VERSION))
+  {
+  fputs ("libgcrypt version mismatch\n", stderr);
+  exit (2);
+  }
+
+/* We don't want to see any warnings, e.g. because we have not yet
+parsed program options which might be used to suppress such
+warnings. */
+gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
+
+/* ... If required, other initialization goes here.  Note that the
+process might still be running with increased privileges and that
+the secure memory has not been initialized.  */
+
+/* Allocate a pool of 16k secure memory.  This make the secure memory
+available and also drops privileges where needed.  */
+gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
+
+/* It is now okay to let Libgcrypt complain when there was/is
+a problem with the secure memory. */
+gcry_control (GCRYCTL_RESUME_SECMEM_WARN);
+
+/* ... If required, other initialization goes here.  */
+
+/* Tell Libgcrypt that initialization has completed. */
+gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
+
+return;
+}
+
+
+
+
+/* Accumulate data (gnutls-only).
+String to be appended must be nul-terminated. */
+
+blob *
+exim_rsa_data_append(blob * b, int * alloc, uschar * s)
+{
+return b;      /*dummy*/
+}
+
+
+
+/* import private key from PEM string in memory.
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_rsa_signing_init(uschar * privkey_pem, es_ctx * sign_ctx)
+{
+uschar * s1, * s2;
+blob der;
+long alen;
+int rc;
+
+/*
+ *  RSAPrivateKey ::= SEQUENCE
+ *      version           Version,
+ *      modulus           INTEGER,  -- n
+ *      publicExponent    INTEGER,  -- e
+ *      privateExponent   INTEGER,  -- d
+ *      prime1            INTEGER,  -- p
+ *      prime2            INTEGER,  -- q
+ *      exponent1         INTEGER,  -- d mod (p-1)
+ *      exponent2         INTEGER,  -- d mod (q-1)
+ *      coefficient       INTEGER,  -- (inverse of q) mod p
+ *      otherPrimeInfos   OtherPrimeInfos OPTIONAL
+ */
+if (  !(s1 = Ustrstr(CS privkey_pem, "-----BEGIN RSA PRIVATE KEY-----"))
+   || !(s2 = Ustrstr(CS (s1+=31),    "-----END RSA PRIVATE KEY-----" ))
+   )
+  return US"Bad PEM wrapper";
+
+*s2 = '\0';
+
+if ((der.len = b64decode(s1, &der.data)) < 0)
+  return US"Bad PEM-DER b64 decode";
+
+/* untangle asn.1 */
+
+/* sequence; just move past the header */
+if ((rc = as_tag(&der, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, NULL))
+   != ASN1_SUCCESS) goto asn_err;
+
+/* integer version; move past the header, check is zero */
+if ((rc = as_tag(&der, 0, ASN1_TAG_INTEGER, &alen)) != ASN1_SUCCESS)
+  goto asn_err;
+if (alen != 1 || *der.data != 0)
+  return US"Bad version number";
+der.data++; der.len--;
+
+if (  (s1 = as_mpi(&der, &sign_ctx->n))
+   || (s1 = as_mpi(&der, &sign_ctx->e))
+   || (s1 = as_mpi(&der, &sign_ctx->d))
+   || (s1 = as_mpi(&der, &sign_ctx->p))
+   || (s1 = as_mpi(&der, &sign_ctx->q))
+   || (s1 = as_mpi(&der, &sign_ctx->dp))
+   || (s1 = as_mpi(&der, &sign_ctx->dq))
+   || (s1 = as_mpi(&der, &sign_ctx->qp))
+   )
+  return s1;
+
+DEBUG(D_acl) debug_printf_indent("rsa_signing_init:\n");
+  {
+  uschar * s;
+  gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->n);
+  debug_printf_indent(" N : %s\n", s);
+  gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->e);
+  debug_printf_indent(" E : %s\n", s);
+  gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->d);
+  debug_printf_indent(" D : %s\n", s);
+  gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->p);
+  debug_printf_indent(" P : %s\n", s);
+  gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->q);
+  debug_printf_indent(" Q : %s\n", s);
+  gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->dp);
+  debug_printf_indent(" DP: %s\n", s);
+  gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->dq);
+  debug_printf_indent(" DQ: %s\n", s);
+  gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->qp);
+  debug_printf_indent(" QP: %s\n", s);
+  }
+return NULL;
+
+asn_err: return US asn1_strerror(rc);
+}
+
+
+
+/* allocate mem for signature (when signing) */
+/* sign data (gnutls_only)
+OR
+sign hash.
+
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_rsa_sign(es_ctx * sign_ctx, BOOL is_sha1, blob * data, blob * sig)
+{
+gcry_sexp_t s_hash = NULL, s_key = NULL, s_sig = NULL;
+gcry_mpi_t m_sig;
+uschar * errstr;
+gcry_error_t gerr;
+
+#define SIGSPACE 128
+sig->data = store_get(SIGSPACE);
+
+if (gcry_mpi_cmp (sign_ctx->p, sign_ctx->q) > 0)
+  {
+  gcry_mpi_swap (sign_ctx->p, sign_ctx->q);
+  gcry_mpi_invm (sign_ctx->qp, sign_ctx->p, sign_ctx->q);
+  }
+
+if (  (gerr = gcry_sexp_build (&s_key, NULL,
+               "(private-key (rsa (n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))",
+               sign_ctx->n, sign_ctx->e,
+               sign_ctx->d, sign_ctx->p,
+               sign_ctx->q, sign_ctx->qp))
+   || (gerr = gcry_sexp_build (&s_hash, NULL,
+               is_sha1
+               ? "(data(flags pkcs1)(hash sha1 %b))"
+               : "(data(flags pkcs1)(hash sha256 %b))",
+               (int) data->len, CS data->data))
+   ||  (gerr = gcry_pk_sign (&s_sig, s_hash, s_key))
+   )
+  return US gcry_strerror(gerr);
+
+/* gcry_sexp_dump(s_sig); */
+
+if (  !(s_sig = gcry_sexp_find_token(s_sig, "s", 0))
+   )
+  return US"no sig result";
+
+m_sig = gcry_sexp_nth_mpi(s_sig, 1, GCRYMPI_FMT_USG);
+
+DEBUG(D_acl)
+  {
+  uschar * s;
+  gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, m_sig);
+  debug_printf_indent(" SG: %s\n", s);
+  }
+
+gerr = gcry_mpi_print(GCRYMPI_FMT_USG, sig->data, SIGSPACE, &sig->len, m_sig);
+if (gerr)
+  {
+  debug_printf_indent("signature conversion from MPI to buffer failed\n");
+  return US gcry_strerror(gerr);
+  }
+#undef SIGSPACE
+
+return NULL;
+}
+
+
+/* import public key (from DER in memory)
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_rsa_verify_init(blob * pubkey_der, ev_ctx * verify_ctx)
+{
+/*
+in code sequence per b81207d2bfa92 rsa_parse_public_key() and asn1_get_mpi()
+*/
+uschar tag_class;
+int taglen;
+long alen;
+int rc;
+uschar * errstr;
+gcry_error_t gerr;
+uschar * stage = US"S1";
+
+/*
+sequence
+ sequence
+  OBJECT:rsaEncryption
+  NULL
+ BIT STRING:RSAPublicKey
+  sequence
+   INTEGER:Public modulus
+   INTEGER:Public exponent
+
+openssl rsa -in aux-fixed/dkim/dkim.private -pubout -outform DER | od -t x1 | head;
+openssl rsa -in aux-fixed/dkim/dkim.private -pubout | openssl asn1parse -dump;
+openssl rsa -in aux-fixed/dkim/dkim.private -pubout | openssl asn1parse -dump -offset 22;
+*/
+
+/* sequence; just move past the header */
+if ((rc = as_tag(pubkey_der, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, NULL))
+   != ASN1_SUCCESS) goto asn_err;
+
+/* sequence; skip the entire thing */
+DEBUG(D_acl) stage = US"S2";
+if ((rc = as_tag(pubkey_der, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, &alen))
+   != ASN1_SUCCESS) goto asn_err;
+pubkey_der->data += alen; pubkey_der->len -= alen;
+
+
+/* bitstring: limit range to size of bitstring;
+move over header + content wrapper */
+DEBUG(D_acl) stage = US"BS";
+if ((rc = as_tag(pubkey_der, 0, ASN1_TAG_BIT_STRING, &alen)) != ASN1_SUCCESS)
+  goto asn_err;
+pubkey_der->len = alen;
+pubkey_der->data++; pubkey_der->len--;
+
+/* sequence; just move past the header */
+DEBUG(D_acl) stage = US"S3";
+if ((rc = as_tag(pubkey_der, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, NULL))
+   != ASN1_SUCCESS) goto asn_err;
+
+/* read two integers */
+DEBUG(D_acl) stage = US"MPI";
+if (  (errstr = as_mpi(pubkey_der, &verify_ctx->n))
+   || (errstr = as_mpi(pubkey_der, &verify_ctx->e))
+   )
+  return errstr;
+
+DEBUG(D_acl) debug_printf_indent("rsa_verify_init:\n");
+       {
+       uschar * s;
+       gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, verify_ctx->n);
+       debug_printf_indent(" N : %s\n", s);
+       gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, verify_ctx->e);
+       debug_printf_indent(" E : %s\n", s);
+       }
+
+return NULL;
+
+asn_err:
+DEBUG(D_acl) return string_sprintf("%s: %s", stage, asn1_strerror(rc));
+            return US asn1_strerror(rc);
+}
+
+
+/* verify signature (of hash)  (given pubkey & alleged sig)
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_rsa_verify(ev_ctx * verify_ctx, BOOL is_sha1, blob * data_hash, blob * sig)
+{
+/*
+cf. libgnutls 2.8.5 _wrap_gcry_pk_verify()
+*/
+gcry_mpi_t m_sig;
+gcry_sexp_t s_sig = NULL, s_hash = NULL, s_pkey = NULL;
+gcry_error_t gerr;
+uschar * stage;
+
+if (  (stage = US"pkey sexp build",
+       gerr = gcry_sexp_build (&s_pkey, NULL, "(public-key(rsa(n%m)(e%m)))",
+                       verify_ctx->n, verify_ctx->e))
+   || (stage = US"data sexp build",
+       gerr = gcry_sexp_build (&s_hash, NULL,
+               is_sha1
+               ? "(data(flags pkcs1)(hash sha1 %b))"
+               : "(data(flags pkcs1)(hash sha256 %b))",
+               (int) data_hash->len, CS data_hash->data))
+   || (stage = US"sig mpi scan",
+       gerr = gcry_mpi_scan(&m_sig, GCRYMPI_FMT_USG, sig->data, sig->len, NULL))
+   || (stage = US"sig sexp build",
+       gerr = gcry_sexp_build (&s_sig, NULL, "(sig-val(rsa(s%m)))", m_sig))
+   || (stage = US"verify",
+       gerr = gcry_pk_verify (s_sig, s_hash, s_pkey))
+   )
+  {
+  DEBUG(D_acl) debug_printf_indent("verify: error in stage '%s'\n", stage);
+  return US gcry_strerror(gerr);
+  }
+
+if (s_sig) gcry_sexp_release (s_sig);
+if (s_hash) gcry_sexp_release (s_hash);
+if (s_pkey) gcry_sexp_release (s_pkey);
+gcry_mpi_release (m_sig);
+gcry_mpi_release (verify_ctx->n);
+gcry_mpi_release (verify_ctx->e);
+
+return NULL;
+}
+
+
+
+
+#elif defined(RSA_OPENSSL)
+/******************************************************************************/
+
+void
+exim_rsa_init(void)
+{
+}
+
+
+/* accumulate data (gnutls-only) */
+blob *
+exim_rsa_data_append(blob * b, int * alloc, uschar * s)
+{
+return b;      /*dummy*/
+}
+
+
+/* import private key from PEM string in memory.
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_rsa_signing_init(uschar * privkey_pem, es_ctx * sign_ctx)
+{
+uschar * p, * q;
+int len;
+
+/* Convert PEM to DER */
+if (  !(p = Ustrstr(privkey_pem, "-----BEGIN RSA PRIVATE KEY-----"))
+   || !(q = Ustrstr(p+=31,       "-----END RSA PRIVATE KEY-----"))
+   )
+  return US"Bad PEM wrapping";
+
+*q = '\0';
+if ((len = b64decode(p, &p)) < 0)
+  return US"b64decode failed";
+
+if (!(sign_ctx->rsa = d2i_RSAPrivateKey(NULL, CUSS &p, len)))
+  {
+  char ssl_errstring[256];
+  ERR_load_crypto_strings();   /*XXX move to a startup routine */
+  ERR_error_string(ERR_get_error(), ssl_errstring);
+  return string_copy(US ssl_errstring);
+  }
+
+return NULL;
+}
+
+
+
+/* allocate mem for signature (when signing) */
+/* sign data (gnutls_only)
+OR
+sign hash.
+
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_rsa_sign(es_ctx * sign_ctx, BOOL is_sha1, blob * data, blob * sig)
+{
+uint len;
+const uschar * ret = NULL;
+
+/* Allocate mem for signature */
+len = RSA_size(sign_ctx->rsa);
+sig->data = store_get(len);
+sig->len = len;
+
+/* Do signing */
+if (RSA_sign(is_sha1 ? NID_sha1 : NID_sha256,
+      CUS data->data, data->len,
+      US sig->data, &len, sign_ctx->rsa) != 1)
+  {
+  char ssl_errstring[256];
+  ERR_load_crypto_strings();   /*XXX move to a startup routine */
+  ERR_error_string(ERR_get_error(), ssl_errstring);
+  ret = string_copy(US ssl_errstring);
+  }
+
+RSA_free(sign_ctx->rsa);
+return ret;;
+}
+
+
+
+/* import public key (from DER in memory)
+Return: nULL for success, or an error string */
+
+const uschar *
+exim_rsa_verify_init(blob * pubkey_der, ev_ctx * verify_ctx)
+{
+const uschar * p = CUS pubkey_der->data;
+const uschar * ret = NULL;
+
+if (!(verify_ctx->rsa = d2i_RSA_PUBKEY(NULL, &p, (long) pubkey_der->len)))
+  {
+  char ssl_errstring[256];
+  ERR_load_crypto_strings();   /*XXX move to a startup routine */
+  ERR_error_string(ERR_get_error(), ssl_errstring);
+  ret = string_copy(CUS ssl_errstring);
+  }
+return ret;
+}
+
+
+
+
+/* verify signature (of hash)  (given pubkey & alleged sig)
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_rsa_verify(ev_ctx * verify_ctx, BOOL is_sha1, blob * data_hash, blob * sig)
+{
+const uschar * ret = NULL;
+
+if (RSA_verify(is_sha1 ? NID_sha1 : NID_sha256,
+      CUS data_hash->data, data_hash->len,
+      US sig->data, (uint) sig->len, verify_ctx->rsa) != 1)
+  {
+  char ssl_errstring[256];
+  ERR_load_crypto_strings();   /*XXX move to a startup routine */
+  ERR_error_string(ERR_get_error(), ssl_errstring);
+  ret = string_copy(US ssl_errstring);
+  }
+return ret;
+}
+
+
+#endif
+/******************************************************************************/
+
+#endif /*DISABLE_DKIM*/
+/* End of File */
dissimilarity index 99%
index af6823b..6018eba 100644 (file)
-/**
- * \file rsa.h
- *
- *  Copyright (C) 2006-2010, Brainspark B.V.
- *
- *  This file is part of PolarSSL (http://www.polarssl.org)
- *  Lead Maintainer: Paul Bakker <polarssl_maintainer at polarssl.org>
- *
- *  All rights reserved.
- *
- *  This program 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 program 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 program; if not, write to the Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef POLARSSL_RSA_H
-#define POLARSSL_RSA_H
-
-#include "bignum.h"
-
-/*
- * RSA Error codes
- */
-#define POLARSSL_ERR_RSA_BAD_INPUT_DATA                    -0x0400
-#define POLARSSL_ERR_RSA_INVALID_PADDING                   -0x0410
-#define POLARSSL_ERR_RSA_KEY_GEN_FAILED                    -0x0420
-#define POLARSSL_ERR_RSA_KEY_CHECK_FAILED                  -0x0430
-#define POLARSSL_ERR_RSA_PUBLIC_FAILED                     -0x0440
-#define POLARSSL_ERR_RSA_PRIVATE_FAILED                    -0x0450
-#define POLARSSL_ERR_RSA_VERIFY_FAILED                     -0x0460
-#define POLARSSL_ERR_RSA_OUTPUT_TOO_LARGE                  -0x0470
-#define POLARSSL_ERR_RSA_RNG_FAILED                        -0x0480
-
-/* *************** begin copy from x509.h  ************************/
-/*
- * ASN1 Error codes
- *
- * These error codes will be OR'ed to X509 error codes for
- * higher error granularity.
- */
-#define POLARSSL_ERR_ASN1_OUT_OF_DATA                      0x0014
-#define POLARSSL_ERR_ASN1_UNEXPECTED_TAG                   0x0016
-#define POLARSSL_ERR_ASN1_INVALID_LENGTH                   0x0018
-#define POLARSSL_ERR_ASN1_LENGTH_MISMATCH                  0x001A
-#define POLARSSL_ERR_ASN1_INVALID_DATA                     0x001C
-
-/*
- * X509 Error codes
- */
-#define POLARSSL_ERR_X509_FEATURE_UNAVAILABLE              -0x0020
-#define POLARSSL_ERR_X509_CERT_INVALID_PEM                 -0x0040
-#define POLARSSL_ERR_X509_CERT_INVALID_FORMAT              -0x0060
-#define POLARSSL_ERR_X509_CERT_INVALID_VERSION             -0x0080
-#define POLARSSL_ERR_X509_CERT_INVALID_SERIAL              -0x00A0
-#define POLARSSL_ERR_X509_CERT_INVALID_ALG                 -0x00C0
-#define POLARSSL_ERR_X509_CERT_INVALID_NAME                -0x00E0
-#define POLARSSL_ERR_X509_CERT_INVALID_DATE                -0x0100
-#define POLARSSL_ERR_X509_CERT_INVALID_PUBKEY              -0x0120
-#define POLARSSL_ERR_X509_CERT_INVALID_SIGNATURE           -0x0140
-#define POLARSSL_ERR_X509_CERT_INVALID_EXTENSIONS          -0x0160
-#define POLARSSL_ERR_X509_CERT_UNKNOWN_VERSION             -0x0180
-#define POLARSSL_ERR_X509_CERT_UNKNOWN_SIG_ALG             -0x01A0
-#define POLARSSL_ERR_X509_CERT_UNKNOWN_PK_ALG              -0x01C0
-#define POLARSSL_ERR_X509_CERT_SIG_MISMATCH                -0x01E0
-#define POLARSSL_ERR_X509_CERT_VERIFY_FAILED               -0x0200
-#define POLARSSL_ERR_X509_KEY_INVALID_PEM                  -0x0220
-#define POLARSSL_ERR_X509_KEY_INVALID_VERSION              -0x0240
-#define POLARSSL_ERR_X509_KEY_INVALID_FORMAT               -0x0260
-#define POLARSSL_ERR_X509_KEY_INVALID_ENC_IV               -0x0280
-#define POLARSSL_ERR_X509_KEY_UNKNOWN_ENC_ALG              -0x02A0
-#define POLARSSL_ERR_X509_KEY_PASSWORD_REQUIRED            -0x02C0
-#define POLARSSL_ERR_X509_KEY_PASSWORD_MISMATCH            -0x02E0
-#define POLARSSL_ERR_X509_POINT_ERROR                      -0x0300
-#define POLARSSL_ERR_X509_VALUE_TO_LENGTH                  -0x0320
-
-/*
- * DER constants
- */
-#define ASN1_BOOLEAN                 0x01
-#define ASN1_INTEGER                 0x02
-#define ASN1_BIT_STRING              0x03
-#define ASN1_OCTET_STRING            0x04
-#define ASN1_NULL                    0x05
-#define ASN1_OID                     0x06
-#define ASN1_UTF8_STRING             0x0C
-#define ASN1_SEQUENCE                0x10
-#define ASN1_SET                     0x11
-#define ASN1_PRINTABLE_STRING        0x13
-#define ASN1_T61_STRING              0x14
-#define ASN1_IA5_STRING              0x16
-#define ASN1_UTC_TIME                0x17
-#define ASN1_GENERALIZED_TIME        0x18
-#define ASN1_UNIVERSAL_STRING        0x1C
-#define ASN1_BMP_STRING              0x1E
-#define ASN1_PRIMITIVE               0x00
-#define ASN1_CONSTRUCTED             0x20
-#define ASN1_CONTEXT_SPECIFIC        0x80
-/* ***************   end copy from x509.h  ************************/
-
-/*
- * PKCS#1 constants
- */
-#define SIG_RSA_RAW     0
-#define SIG_RSA_MD2     2
-#define SIG_RSA_MD4     3
-#define SIG_RSA_MD5     4
-#define SIG_RSA_SHA1    5
-#define SIG_RSA_SHA224  14
-#define SIG_RSA_SHA256  11
-#define SIG_RSA_SHA384  12
-#define SIG_RSA_SHA512  13
-
-#define RSA_PUBLIC      0
-#define RSA_PRIVATE     1
-
-#define RSA_PKCS_V15    0
-#define RSA_PKCS_V21    1
-
-#define RSA_SIGN        1
-#define RSA_CRYPT       2
-
-#define ASN1_STR_CONSTRUCTED_SEQUENCE "\x30"
-#define ASN1_STR_NULL                 "\x05"
-#define ASN1_STR_OID                  "\x06"
-#define ASN1_STR_OCTET_STRING         "\x04"
-
-#define OID_DIGEST_ALG_MDX            "\x2A\x86\x48\x86\xF7\x0D\x02\x00"
-#define OID_HASH_ALG_SHA1             "\x2b\x0e\x03\x02\x1a"
-#define OID_HASH_ALG_SHA2X            "\x60\x86\x48\x01\x65\x03\x04\x02\x00"
-
-#define OID_ISO_MEMBER_BODIES         "\x2a"
-#define OID_ISO_IDENTIFIED_ORG        "\x2b"
-
-/*
- * ISO Member bodies OID parts
- */
-#define OID_COUNTRY_US                "\x86\x48"
-#define OID_RSA_DATA_SECURITY         "\x86\xf7\x0d"
-
-/*
- * ISO Identified organization OID parts
- */
-#define OID_OIW_SECSIG_SHA1           "\x0e\x03\x02\x1a"
-
-/*
- * DigestInfo ::= SEQUENCE {
- *   digestAlgorithm DigestAlgorithmIdentifier,
- *   digest Digest }
- *
- * DigestAlgorithmIdentifier ::= AlgorithmIdentifier
- *
- * Digest ::= OCTET STRING
- */
-#define ASN1_HASH_MDX \
-( \
-    ASN1_STR_CONSTRUCTED_SEQUENCE "\x20" \
-    ASN1_STR_CONSTRUCTED_SEQUENCE "\x0C" \
-    ASN1_STR_OID "\x08" \
-    OID_DIGEST_ALG_MDX \
-    ASN1_STR_NULL "\x00" \
-    ASN1_STR_OCTET_STRING "\x10" \
-)
-
-#define ASN1_HASH_SHA1 \
-    ASN1_STR_CONSTRUCTED_SEQUENCE "\x21" \
-    ASN1_STR_CONSTRUCTED_SEQUENCE "\x09" \
-    ASN1_STR_OID "\x05" \
-    OID_HASH_ALG_SHA1 \
-    ASN1_STR_NULL "\x00" \
-    ASN1_STR_OCTET_STRING "\x14"
-
-#define ASN1_HASH_SHA2X \
-    ASN1_STR_CONSTRUCTED_SEQUENCE "\x11" \
-    ASN1_STR_CONSTRUCTED_SEQUENCE "\x0d" \
-    ASN1_STR_OID "\x09" \
-    OID_HASH_ALG_SHA2X \
-    ASN1_STR_NULL "\x00" \
-    ASN1_STR_OCTET_STRING "\x00"
-
-/**
- * \brief          RSA context structure
- */
-typedef struct
-{
-    int ver;                    /*!<  always 0          */
-    int len;                    /*!<  size(N) in chars  */
-
-    mpi N;                      /*!<  public modulus    */
-    mpi E;                      /*!<  public exponent   */
-
-    mpi D;                      /*!<  private exponent  */
-    mpi P;                      /*!<  1st prime factor  */
-    mpi Q;                      /*!<  2nd prime factor  */
-    mpi DP;                     /*!<  D % (P - 1)       */
-    mpi DQ;                     /*!<  D % (Q - 1)       */
-    mpi QP;                     /*!<  1 / (Q % P)       */
-
-    mpi RN;                     /*!<  cached R^2 mod N  */
-    mpi RP;                     /*!<  cached R^2 mod P  */
-    mpi RQ;                     /*!<  cached R^2 mod Q  */
-
-    int padding;                /*!<  1.5 or OAEP/PSS   */
-    int hash_id;                /*!<  hash identifier   */
-}
-rsa_context;
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
- * \brief          Initialize an RSA context
- *
- * \param ctx      RSA context to be initialized
- * \param padding  RSA_PKCS_V15 or RSA_PKCS_V21
- * \param hash_id  RSA_PKCS_V21 hash identifier
- *
- * \note           The hash_id parameter is actually ignored
- *                 when using RSA_PKCS_V15 padding.
- *
- * \note           Currently, RSA_PKCS_V21 padding
- *                 is not supported.
- */
-void rsa_init( rsa_context *ctx,
-               int padding,
-               int hash_id);
-
-/**
- * \brief          Generate an RSA keypair
- *
- * \param ctx      RSA context that will hold the key
- * \param f_rng    RNG function
- * \param p_rng    RNG parameter
- * \param nbits    size of the public key in bits
- * \param exponent public exponent (e.g., 65537)
- *
- * \note           rsa_init() must be called beforehand to setup
- *                 the RSA context.
- *
- * \return         0 if successful, or an POLARSSL_ERR_RSA_XXX error code
- */
-int rsa_gen_key( rsa_context *ctx,
-                 int (*f_rng)(void *),
-                 void *p_rng,
-                 int nbits, int exponent );
-
-/**
- * \brief          Check a public RSA key
- *
- * \param ctx      RSA context to be checked
- *
- * \return         0 if successful, or an POLARSSL_ERR_RSA_XXX error code
- */
-int rsa_check_pubkey( const rsa_context *ctx );
-
-/**
- * \brief          Check a private RSA key
- *
- * \param ctx      RSA context to be checked
- *
- * \return         0 if successful, or an POLARSSL_ERR_RSA_XXX error code
- */
-int rsa_check_privkey( const rsa_context *ctx );
-
-/**
- * \brief          Do an RSA public key operation
- *
- * \param ctx      RSA context
- * \param input    input buffer
- * \param output   output buffer
- *
- * \return         0 if successful, or an POLARSSL_ERR_RSA_XXX error code
- *
- * \note           This function does NOT take care of message
- *                 padding. Also, be sure to set input[0] = 0 or assure that
- *                 input is smaller than N.
- *
- * \note           The input and output buffers must be large
- *                 enough (eg. 128 bytes if RSA-1024 is used).
- */
-int rsa_public( rsa_context *ctx,
-                const unsigned char *input,
-                unsigned char *output );
-
-/**
- * \brief          Do an RSA private key operation
- *
- * \param ctx      RSA context
- * \param input    input buffer
- * \param output   output buffer
- *
- * \return         0 if successful, or an POLARSSL_ERR_RSA_XXX error code
- *
- * \note           The input and output buffers must be large
- *                 enough (eg. 128 bytes if RSA-1024 is used).
- */
-int rsa_private( rsa_context *ctx,
-                 const unsigned char *input,
-                 unsigned char *output );
-
-/**
- * \brief          Add the message padding, then do an RSA operation
- *
- * \param ctx      RSA context
- * \param f_rng    RNG function
- * \param p_rng    RNG parameter
- * \param mode     RSA_PUBLIC or RSA_PRIVATE
- * \param ilen     contains the plaintext length
- * \param input    buffer holding the data to be encrypted
- * \param output   buffer that will hold the ciphertext
- *
- * \return         0 if successful, or an POLARSSL_ERR_RSA_XXX error code
- *
- * \note           The output buffer must be as large as the size
- *                 of ctx->N (eg. 128 bytes if RSA-1024 is used).
- */
-int rsa_pkcs1_encrypt( rsa_context *ctx,
-                       int (*f_rng)(void *),
-                       void *p_rng,
-                       int mode, int  ilen,
-                       const unsigned char *input,
-                       unsigned char *output );
-
-/**
- * \brief          Do an RSA operation, then remove the message padding
- *
- * \param ctx      RSA context
- * \param mode     RSA_PUBLIC or RSA_PRIVATE
- * \param input    buffer holding the encrypted data
- * \param output   buffer that will hold the plaintext
- * \param olen     will contain the plaintext length
- * \param output_max_len  maximum length of the output buffer
- *
- * \return         0 if successful, or an POLARSSL_ERR_RSA_XXX error code
- *
- * \note           The output buffer must be as large as the size
- *                 of ctx->N (eg. 128 bytes if RSA-1024 is used) otherwise
- *                 an error is thrown.
- */
-int rsa_pkcs1_decrypt( rsa_context *ctx,
-                       int mode, int *olen,
-                       const unsigned char *input,
-                       unsigned char *output,
-                       int output_max_len );
-
-/**
- * \brief          Do a private RSA to sign a message digest
- *
- * \param ctx      RSA context
- * \param mode     RSA_PUBLIC or RSA_PRIVATE
- * \param hash_id  SIG_RSA_RAW, SIG_RSA_MD{2,4,5} or SIG_RSA_SHA{1,224,256,384,512}
- * \param hashlen  message digest length (for SIG_RSA_RAW only)
- * \param hash     buffer holding the message digest
- * \param sig      buffer that will hold the ciphertext
- *
- * \return         0 if the signing operation was successful,
- *                 or an POLARSSL_ERR_RSA_XXX error code
- *
- * \note           The "sig" buffer must be as large as the size
- *                 of ctx->N (eg. 128 bytes if RSA-1024 is used).
- */
-int rsa_pkcs1_sign( rsa_context *ctx,
-                    int mode,
-                    int hash_id,
-                    int hashlen,
-                    const unsigned char *hash,
-                    unsigned char *sig );
-
-/**
- * \brief          Do a public RSA and check the message digest
- *
- * \param ctx      points to an RSA public key
- * \param mode     RSA_PUBLIC or RSA_PRIVATE
- * \param hash_id  SIG_RSA_RAW, SIG_RSA_MD{2,4,5} or SIG_RSA_SHA{1,224,256,384,512}
- * \param hashlen  message digest length (for SIG_RSA_RAW only)
- * \param hash     buffer holding the message digest
- * \param sig      buffer holding the ciphertext
- *
- * \return         0 if the verify operation was successful,
- *                 or an POLARSSL_ERR_RSA_XXX error code
- *
- * \note           The "sig" buffer must be as large as the size
- *                 of ctx->N (eg. 128 bytes if RSA-1024 is used).
- */
-int rsa_pkcs1_verify( rsa_context *ctx,
-                      int mode,
-                      int hash_id,
-                      int hashlen,
-                      const unsigned char *hash,
-                      unsigned char *sig );
-
-/**
- * \brief          Free the components of an RSA key
- *
- * \param ctx      RSA Context to free
- */
-void rsa_free( rsa_context *ctx );
-
-/* PDKIM declarations (not part of polarssl) */
-int rsa_parse_public_key( rsa_context *rsa, unsigned char *buf, int buflen );
-int rsa_parse_key( rsa_context *rsa, unsigned char *buf, int buflen,
-                                     unsigned char *pwd, int pwdlen );
-
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* rsa.h */
+/*
+ *  PDKIM - a RFC4871 (DKIM) implementation
+ *
+ *  Copyright (C) 2016  Exim maintainers
+ *
+ *  RSA signing/verification interface
+ */
+
+#include "../exim.h"
+
+#ifndef DISABLE_DKIM   /* entire file */
+
+#include "crypt_ver.h"
+
+#ifdef RSA_OPENSSL
+# include <openssl/rsa.h>
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+#elif defined(RSA_GNUTLS)
+# include <gnutls/gnutls.h>
+# include <gnutls/x509.h>
+#  include <gnutls/abstract.h>
+#elif defined(RSA_GCRYPT)
+#  include <gcrypt.h>
+#  include <libtasn1.h>
+#endif
+
+#include "../blob.h"
+
+
+#ifdef RSA_OPENSSL
+
+typedef struct {
+  RSA * rsa;
+} es_ctx;
+
+typedef struct {
+  RSA * rsa;
+} ev_ctx;
+
+#elif defined(RSA_GNUTLS)
+
+typedef struct {
+  gnutls_x509_privkey_t rsa;
+} es_ctx;
+
+typedef struct {
+  gnutls_pubkey_t rsa;
+} ev_ctx;
+
+#elif defined(RSA_GCRYPT)
+
+typedef struct {
+  gcry_mpi_t n;
+  gcry_mpi_t e;
+  gcry_mpi_t d;
+  gcry_mpi_t p;
+  gcry_mpi_t q;
+  gcry_mpi_t dp;
+  gcry_mpi_t dq;
+  gcry_mpi_t qp;
+} es_ctx;
+
+typedef struct {
+  gcry_mpi_t n;
+  gcry_mpi_t e;
+} ev_ctx;
+
+#endif
+
+
+extern void exim_rsa_init(void);
+extern blob * exim_rsa_data_append(blob *, int *, uschar *);
+
+extern const uschar * exim_rsa_signing_init(uschar *, es_ctx *);
+extern const uschar * exim_rsa_sign(es_ctx *, BOOL, blob *, blob *);
+extern const uschar * exim_rsa_verify_init(blob *, ev_ctx *);
+extern const uschar * exim_rsa_verify(ev_ctx *, BOOL, blob *, blob *);
+
+#endif /*DISABLE_DKIM*/
+/* End of File */
diff --git a/src/pdkim/sha1.c b/src/pdkim/sha1.c
deleted file mode 100644 (file)
index 81b862f..0000000
+++ /dev/null
@@ -1,434 +0,0 @@
-/*
- *  FIPS-180-1 compliant SHA-1 implementation
- *
- *  Copyright (C) 2006-2010, Brainspark B.V.
- *
- *  This file is part of PolarSSL (http://www.polarssl.org)
- *  Lead Maintainer: Paul Bakker <polarssl_maintainer at polarssl.org>
- *
- *  All rights reserved.
- *
- *  This program 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 program 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 program; if not, write to the Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-/*
- *  The SHA-1 standard was published by NIST in 1993.
- *
- *  http://www.itl.nist.gov/fipspubs/fip180-1.htm
- */
-
-#include "sha1.h"
-
-#include <string.h>
-#include <stdio.h>
-
-/*
- * 32-bit integer manipulation macros (big endian)
- */
-#ifndef GET_ULONG_BE
-#define GET_ULONG_BE(n,b,i)                             \
-{                                                       \
-    (n) = ( (unsigned long) (b)[(i)    ] << 24 )        \
-        | ( (unsigned long) (b)[(i) + 1] << 16 )        \
-        | ( (unsigned long) (b)[(i) + 2] <<  8 )        \
-        | ( (unsigned long) (b)[(i) + 3]       );       \
-}
-#endif
-
-#ifndef PUT_ULONG_BE
-#define PUT_ULONG_BE(n,b,i)                             \
-{                                                       \
-    (b)[(i)    ] = (unsigned char) ( (n) >> 24 );       \
-    (b)[(i) + 1] = (unsigned char) ( (n) >> 16 );       \
-    (b)[(i) + 2] = (unsigned char) ( (n) >>  8 );       \
-    (b)[(i) + 3] = (unsigned char) ( (n)       );       \
-}
-#endif
-
-/*
- * SHA-1 context setup
- */
-void sha1_starts( sha1_context *ctx )
-{
-    ctx->total[0] = 0;
-    ctx->total[1] = 0;
-
-    ctx->state[0] = 0x67452301;
-    ctx->state[1] = 0xEFCDAB89;
-    ctx->state[2] = 0x98BADCFE;
-    ctx->state[3] = 0x10325476;
-    ctx->state[4] = 0xC3D2E1F0;
-}
-
-static void sha1_process( sha1_context *ctx, const unsigned char data[64] )
-{
-    unsigned long temp, W[16], A, B, C, D, E;
-
-    GET_ULONG_BE( W[ 0], data,  0 );
-    GET_ULONG_BE( W[ 1], data,  4 );
-    GET_ULONG_BE( W[ 2], data,  8 );
-    GET_ULONG_BE( W[ 3], data, 12 );
-    GET_ULONG_BE( W[ 4], data, 16 );
-    GET_ULONG_BE( W[ 5], data, 20 );
-    GET_ULONG_BE( W[ 6], data, 24 );
-    GET_ULONG_BE( W[ 7], data, 28 );
-    GET_ULONG_BE( W[ 8], data, 32 );
-    GET_ULONG_BE( W[ 9], data, 36 );
-    GET_ULONG_BE( W[10], data, 40 );
-    GET_ULONG_BE( W[11], data, 44 );
-    GET_ULONG_BE( W[12], data, 48 );
-    GET_ULONG_BE( W[13], data, 52 );
-    GET_ULONG_BE( W[14], data, 56 );
-    GET_ULONG_BE( W[15], data, 60 );
-
-#define S(x,n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n)))
-
-#define R(t)                                            \
-(                                                       \
-    temp = W[(t -  3) & 0x0F] ^ W[(t - 8) & 0x0F] ^     \
-           W[(t - 14) & 0x0F] ^ W[ t      & 0x0F],      \
-    ( W[t & 0x0F] = S(temp,1) )                         \
-)
-
-#define P(a,b,c,d,e,x)                                  \
-{                                                       \
-    e += S(a,5) + F(b,c,d) + K + x; b = S(b,30);        \
-}
-
-    A = ctx->state[0];
-    B = ctx->state[1];
-    C = ctx->state[2];
-    D = ctx->state[3];
-    E = ctx->state[4];
-
-#define F(x,y,z) (z ^ (x & (y ^ z)))
-#define K 0x5A827999
-
-    P( A, B, C, D, E, W[0]  );
-    P( E, A, B, C, D, W[1]  );
-    P( D, E, A, B, C, W[2]  );
-    P( C, D, E, A, B, W[3]  );
-    P( B, C, D, E, A, W[4]  );
-    P( A, B, C, D, E, W[5]  );
-    P( E, A, B, C, D, W[6]  );
-    P( D, E, A, B, C, W[7]  );
-    P( C, D, E, A, B, W[8]  );
-    P( B, C, D, E, A, W[9]  );
-    P( A, B, C, D, E, W[10] );
-    P( E, A, B, C, D, W[11] );
-    P( D, E, A, B, C, W[12] );
-    P( C, D, E, A, B, W[13] );
-    P( B, C, D, E, A, W[14] );
-    P( A, B, C, D, E, W[15] );
-    P( E, A, B, C, D, R(16) );
-    P( D, E, A, B, C, R(17) );
-    P( C, D, E, A, B, R(18) );
-    P( B, C, D, E, A, R(19) );
-
-#undef K
-#undef F
-
-#define F(x,y,z) (x ^ y ^ z)
-#define K 0x6ED9EBA1
-
-    P( A, B, C, D, E, R(20) );
-    P( E, A, B, C, D, R(21) );
-    P( D, E, A, B, C, R(22) );
-    P( C, D, E, A, B, R(23) );
-    P( B, C, D, E, A, R(24) );
-    P( A, B, C, D, E, R(25) );
-    P( E, A, B, C, D, R(26) );
-    P( D, E, A, B, C, R(27) );
-    P( C, D, E, A, B, R(28) );
-    P( B, C, D, E, A, R(29) );
-    P( A, B, C, D, E, R(30) );
-    P( E, A, B, C, D, R(31) );
-    P( D, E, A, B, C, R(32) );
-    P( C, D, E, A, B, R(33) );
-    P( B, C, D, E, A, R(34) );
-    P( A, B, C, D, E, R(35) );
-    P( E, A, B, C, D, R(36) );
-    P( D, E, A, B, C, R(37) );
-    P( C, D, E, A, B, R(38) );
-    P( B, C, D, E, A, R(39) );
-
-#undef K
-#undef F
-
-#define F(x,y,z) ((x & y) | (z & (x | y)))
-#define K 0x8F1BBCDC
-
-    P( A, B, C, D, E, R(40) );
-    P( E, A, B, C, D, R(41) );
-    P( D, E, A, B, C, R(42) );
-    P( C, D, E, A, B, R(43) );
-    P( B, C, D, E, A, R(44) );
-    P( A, B, C, D, E, R(45) );
-    P( E, A, B, C, D, R(46) );
-    P( D, E, A, B, C, R(47) );
-    P( C, D, E, A, B, R(48) );
-    P( B, C, D, E, A, R(49) );
-    P( A, B, C, D, E, R(50) );
-    P( E, A, B, C, D, R(51) );
-    P( D, E, A, B, C, R(52) );
-    P( C, D, E, A, B, R(53) );
-    P( B, C, D, E, A, R(54) );
-    P( A, B, C, D, E, R(55) );
-    P( E, A, B, C, D, R(56) );
-    P( D, E, A, B, C, R(57) );
-    P( C, D, E, A, B, R(58) );
-    P( B, C, D, E, A, R(59) );
-
-#undef K
-#undef F
-
-#define F(x,y,z) (x ^ y ^ z)
-#define K 0xCA62C1D6
-
-    P( A, B, C, D, E, R(60) );
-    P( E, A, B, C, D, R(61) );
-    P( D, E, A, B, C, R(62) );
-    P( C, D, E, A, B, R(63) );
-    P( B, C, D, E, A, R(64) );
-    P( A, B, C, D, E, R(65) );
-    P( E, A, B, C, D, R(66) );
-    P( D, E, A, B, C, R(67) );
-    P( C, D, E, A, B, R(68) );
-    P( B, C, D, E, A, R(69) );
-    P( A, B, C, D, E, R(70) );
-    P( E, A, B, C, D, R(71) );
-    P( D, E, A, B, C, R(72) );
-    P( C, D, E, A, B, R(73) );
-    P( B, C, D, E, A, R(74) );
-    P( A, B, C, D, E, R(75) );
-    P( E, A, B, C, D, R(76) );
-    P( D, E, A, B, C, R(77) );
-    P( C, D, E, A, B, R(78) );
-    P( B, C, D, E, A, R(79) );
-
-#undef K
-#undef F
-
-    ctx->state[0] += A;
-    ctx->state[1] += B;
-    ctx->state[2] += C;
-    ctx->state[3] += D;
-    ctx->state[4] += E;
-}
-
-/*
- * SHA-1 process buffer
- */
-void sha1_update( sha1_context *ctx, const unsigned char *input, int ilen )
-{
-    int fill;
-    unsigned long left;
-
-    if( ilen <= 0 )
-        return;
-
-    left = ctx->total[0] & 0x3F;
-    fill = 64 - left;
-
-    ctx->total[0] += ilen;
-    ctx->total[0] &= 0xFFFFFFFF;
-
-    if( ctx->total[0] < (unsigned long) ilen )
-        ctx->total[1]++;
-
-    if( left && ilen >= fill )
-    {
-        memcpy( (void *) (ctx->buffer + left),
-                (void *) input, fill );
-        sha1_process( ctx, ctx->buffer );
-        input += fill;
-        ilen  -= fill;
-        left = 0;
-    }
-
-    while( ilen >= 64 )
-    {
-        sha1_process( ctx, input );
-        input += 64;
-        ilen  -= 64;
-    }
-
-    if( ilen > 0 )
-    {
-        memcpy( (void *) (ctx->buffer + left),
-                (void *) input, ilen );
-    }
-}
-
-static const unsigned char sha1_padding[64] =
-{
- 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
-};
-
-/*
- * SHA-1 final digest
- */
-void sha1_finish( sha1_context *ctx, unsigned char output[20] )
-{
-    unsigned long last, padn;
-    unsigned long high, low;
-    unsigned char msglen[8];
-
-    high = ( ctx->total[0] >> 29 )
-         | ( ctx->total[1] <<  3 );
-    low  = ( ctx->total[0] <<  3 );
-
-    PUT_ULONG_BE( high, msglen, 0 );
-    PUT_ULONG_BE( low,  msglen, 4 );
-
-    last = ctx->total[0] & 0x3F;
-    padn = ( last < 56 ) ? ( 56 - last ) : ( 120 - last );
-
-    sha1_update( ctx, (unsigned char *) sha1_padding, padn );
-    sha1_update( ctx, msglen, 8 );
-
-    PUT_ULONG_BE( ctx->state[0], output,  0 );
-    PUT_ULONG_BE( ctx->state[1], output,  4 );
-    PUT_ULONG_BE( ctx->state[2], output,  8 );
-    PUT_ULONG_BE( ctx->state[3], output, 12 );
-    PUT_ULONG_BE( ctx->state[4], output, 16 );
-}
-
-/*
- * output = SHA-1( input buffer )
- */
-void sha1( const unsigned char *input, int ilen, unsigned char output[20] )
-{
-    sha1_context ctx;
-
-    sha1_starts( &ctx );
-    sha1_update( &ctx, input, ilen );
-    sha1_finish( &ctx, output );
-
-    memset( &ctx, 0, sizeof( sha1_context ) );
-}
-
-/*
- * output = SHA-1( file contents )
- */
-int sha1_file( const char *path, unsigned char output[20] )
-{
-    FILE *f;
-    size_t n;
-    sha1_context ctx;
-    unsigned char buf[1024];
-
-    if( ( f = fopen( path, "rb" ) ) == NULL )
-        return( 1 );
-
-    sha1_starts( &ctx );
-
-    while( ( n = fread( buf, 1, sizeof( buf ), f ) ) > 0 )
-        sha1_update( &ctx, buf, (int) n );
-
-    sha1_finish( &ctx, output );
-
-    memset( &ctx, 0, sizeof( sha1_context ) );
-
-    if( ferror( f ) != 0 )
-    {
-        fclose( f );
-        return( 2 );
-    }
-
-    fclose( f );
-    return( 0 );
-}
-
-/*
- * SHA-1 HMAC context setup
- */
-void sha1_hmac_starts( sha1_context *ctx, const unsigned char *key, int keylen )
-{
-    int i;
-    unsigned char sum[20];
-
-    if( keylen > 64 )
-    {
-        sha1( key, keylen, sum );
-        keylen = 20;
-        key = sum;
-    }
-
-    memset( ctx->ipad, 0x36, 64 );
-    memset( ctx->opad, 0x5C, 64 );
-
-    for( i = 0; i < keylen; i++ )
-    {
-        ctx->ipad[i] = (unsigned char)( ctx->ipad[i] ^ key[i] );
-        ctx->opad[i] = (unsigned char)( ctx->opad[i] ^ key[i] );
-    }
-
-    sha1_starts( ctx );
-    sha1_update( ctx, ctx->ipad, 64 );
-
-    memset( sum, 0, sizeof( sum ) );
-}
-
-/*
- * SHA-1 HMAC process buffer
- */
-void sha1_hmac_update( sha1_context *ctx, const unsigned char *input, int ilen )
-{
-    sha1_update( ctx, input, ilen );
-}
-
-/*
- * SHA-1 HMAC final digest
- */
-void sha1_hmac_finish( sha1_context *ctx, unsigned char output[20] )
-{
-    unsigned char tmpbuf[20];
-
-    sha1_finish( ctx, tmpbuf );
-    sha1_starts( ctx );
-    sha1_update( ctx, ctx->opad, 64 );
-    sha1_update( ctx, tmpbuf, 20 );
-    sha1_finish( ctx, output );
-
-    memset( tmpbuf, 0, sizeof( tmpbuf ) );
-}
-
-/*
- * SHA1 HMAC context reset
- */
-void sha1_hmac_reset( sha1_context *ctx )
-{
-    sha1_starts( ctx );
-    sha1_update( ctx, ctx->ipad, 64 );
-}
-
-/*
- * output = HMAC-SHA-1( hmac key, input buffer )
- */
-void sha1_hmac( const unsigned char *key, int keylen,
-                const unsigned char *input, int ilen,
-                unsigned char output[20] )
-{
-    sha1_context ctx;
-
-    sha1_hmac_starts( &ctx, key, keylen );
-    sha1_hmac_update( &ctx, input, ilen );
-    sha1_hmac_finish( &ctx, output );
-
-    memset( &ctx, 0, sizeof( sha1_context ) );
-}
diff --git a/src/pdkim/sha1.h b/src/pdkim/sha1.h
deleted file mode 100644 (file)
index 29a9070..0000000
+++ /dev/null
@@ -1,145 +0,0 @@
-/**
- * \file sha1.h
- *
- *  Copyright (C) 2006-2010, Brainspark B.V.
- *
- *  This file is part of PolarSSL (http://www.polarssl.org)
- *  Lead Maintainer: Paul Bakker <polarssl_maintainer at polarssl.org>
- *
- *  All rights reserved.
- *
- *  This program 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 program 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 program; if not, write to the Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef POLARSSL_SHA1_H
-#define POLARSSL_SHA1_H
-
-/**
- * \brief          SHA-1 context structure
- */
-#ifndef HAVE_SHA1_CONTEXT
-#define HAVE_SHA1_CONTEXT
-typedef struct sha1_context sha1_context;
-#endif
-
-struct sha1_context
-{
-    unsigned long total[2];     /*!< number of bytes processed  */
-    unsigned long state[5];     /*!< intermediate digest state  */
-    unsigned char buffer[64];   /*!< data block being processed */
-
-    unsigned char ipad[64];     /*!< HMAC: inner padding        */
-    unsigned char opad[64];     /*!< HMAC: outer padding        */
-};
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
- * \brief          SHA-1 context setup
- *
- * \param ctx      context to be initialized
- */
-void sha1_starts( sha1_context *ctx );
-
-/**
- * \brief          SHA-1 process buffer
- *
- * \param ctx      SHA-1 context
- * \param input    buffer holding the  data
- * \param ilen     length of the input data
- */
-void sha1_update( sha1_context *ctx, const unsigned char *input, int ilen );
-
-/**
- * \brief          SHA-1 final digest
- *
- * \param ctx      SHA-1 context
- * \param output   SHA-1 checksum result
- */
-void sha1_finish( sha1_context *ctx, unsigned char output[20] );
-
-/**
- * \brief          Output = SHA-1( input buffer )
- *
- * \param input    buffer holding the  data
- * \param ilen     length of the input data
- * \param output   SHA-1 checksum result
- */
-void sha1( const unsigned char *input, int ilen, unsigned char output[20] );
-
-/**
- * \brief          Output = SHA-1( file contents )
- *
- * \param path     input file name
- * \param output   SHA-1 checksum result
- *
- * \return         0 if successful, 1 if fopen failed,
- *                 or 2 if fread failed
- */
-int sha1_file( const char *path, unsigned char output[20] );
-
-/**
- * \brief          SHA-1 HMAC context setup
- *
- * \param ctx      HMAC context to be initialized
- * \param key      HMAC secret key
- * \param keylen   length of the HMAC key
- */
-void sha1_hmac_starts( sha1_context *ctx, const unsigned char *key, int keylen );
-
-/**
- * \brief          SHA-1 HMAC process buffer
- *
- * \param ctx      HMAC context
- * \param input    buffer holding the  data
- * \param ilen     length of the input data
- */
-void sha1_hmac_update( sha1_context *ctx, const unsigned char *input, int ilen );
-
-/**
- * \brief          SHA-1 HMAC final digest
- *
- * \param ctx      HMAC context
- * \param output   SHA-1 HMAC checksum result
- */
-void sha1_hmac_finish( sha1_context *ctx, unsigned char output[20] );
-
-/**
- * \brief          SHA-1 HMAC context reset
- *
- * \param ctx      HMAC context to be reset
- */
-void sha1_hmac_reset( sha1_context *ctx );
-
-/**
- * \brief          Output = HMAC-SHA-1( hmac key, input buffer )
- *
- * \param key      HMAC secret key
- * \param keylen   length of the HMAC key
- * \param input    buffer holding the  data
- * \param ilen     length of the input data
- * \param output   HMAC-SHA-1 result
- */
-void sha1_hmac( const unsigned char *key, int keylen,
-                const unsigned char *input, int ilen,
-                unsigned char output[20] );
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* sha1.h */
diff --git a/src/pdkim/sha2.c b/src/pdkim/sha2.c
deleted file mode 100644 (file)
index e0beaf9..0000000
+++ /dev/null
@@ -1,441 +0,0 @@
-/*
- *  FIPS-180-2 compliant SHA-256 implementation
- *
- *  Copyright (C) 2006-2010, Brainspark B.V.
- *
- *  This file is part of PolarSSL (http://www.polarssl.org)
- *  Lead Maintainer: Paul Bakker <polarssl_maintainer at polarssl.org>
- *
- *  All rights reserved.
- *
- *  This program 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 program 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 program; if not, write to the Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-/*
- *  The SHA-256 Secure Hash Standard was published by NIST in 2002.
- *
- *  http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf
- */
-
-#include "sha2.h"
-
-#include <string.h>
-#include <stdio.h>
-
-/*
- * 32-bit integer manipulation macros (big endian)
- */
-#ifndef GET_ULONG_BE
-#define GET_ULONG_BE(n,b,i)                             \
-{                                                       \
-    (n) = ( (unsigned long) (b)[(i)    ] << 24 )        \
-        | ( (unsigned long) (b)[(i) + 1] << 16 )        \
-        | ( (unsigned long) (b)[(i) + 2] <<  8 )        \
-        | ( (unsigned long) (b)[(i) + 3]       );       \
-}
-#endif
-
-#ifndef PUT_ULONG_BE
-#define PUT_ULONG_BE(n,b,i)                             \
-{                                                       \
-    (b)[(i)    ] = (unsigned char) ( (n) >> 24 );       \
-    (b)[(i) + 1] = (unsigned char) ( (n) >> 16 );       \
-    (b)[(i) + 2] = (unsigned char) ( (n) >>  8 );       \
-    (b)[(i) + 3] = (unsigned char) ( (n)       );       \
-}
-#endif
-
-/*
- * SHA-256 context setup
- */
-void sha2_starts( sha2_context *ctx, int is224 )
-{
-    ctx->total[0] = 0;
-    ctx->total[1] = 0;
-
-    if( is224 == 0 )
-    {
-        /* SHA-256 */
-        ctx->state[0] = 0x6A09E667;
-        ctx->state[1] = 0xBB67AE85;
-        ctx->state[2] = 0x3C6EF372;
-        ctx->state[3] = 0xA54FF53A;
-        ctx->state[4] = 0x510E527F;
-        ctx->state[5] = 0x9B05688C;
-        ctx->state[6] = 0x1F83D9AB;
-        ctx->state[7] = 0x5BE0CD19;
-    }
-    else
-    {
-        /* SHA-224 */
-        ctx->state[0] = 0xC1059ED8;
-        ctx->state[1] = 0x367CD507;
-        ctx->state[2] = 0x3070DD17;
-        ctx->state[3] = 0xF70E5939;
-        ctx->state[4] = 0xFFC00B31;
-        ctx->state[5] = 0x68581511;
-        ctx->state[6] = 0x64F98FA7;
-        ctx->state[7] = 0xBEFA4FA4;
-    }
-
-    ctx->is224 = is224;
-}
-
-static void sha2_process( sha2_context *ctx, const unsigned char data[64] )
-{
-    unsigned long temp1, temp2, W[64];
-    unsigned long A, B, C, D, E, F, G, H;
-
-    GET_ULONG_BE( W[ 0], data,  0 );
-    GET_ULONG_BE( W[ 1], data,  4 );
-    GET_ULONG_BE( W[ 2], data,  8 );
-    GET_ULONG_BE( W[ 3], data, 12 );
-    GET_ULONG_BE( W[ 4], data, 16 );
-    GET_ULONG_BE( W[ 5], data, 20 );
-    GET_ULONG_BE( W[ 6], data, 24 );
-    GET_ULONG_BE( W[ 7], data, 28 );
-    GET_ULONG_BE( W[ 8], data, 32 );
-    GET_ULONG_BE( W[ 9], data, 36 );
-    GET_ULONG_BE( W[10], data, 40 );
-    GET_ULONG_BE( W[11], data, 44 );
-    GET_ULONG_BE( W[12], data, 48 );
-    GET_ULONG_BE( W[13], data, 52 );
-    GET_ULONG_BE( W[14], data, 56 );
-    GET_ULONG_BE( W[15], data, 60 );
-
-#define  SHR(x,n) ((x & 0xFFFFFFFF) >> n)
-#define ROTR(x,n) (SHR(x,n) | (x << (32 - n)))
-
-#define S0(x) (ROTR(x, 7) ^ ROTR(x,18) ^  SHR(x, 3))
-#define S1(x) (ROTR(x,17) ^ ROTR(x,19) ^  SHR(x,10))
-
-#define S2(x) (ROTR(x, 2) ^ ROTR(x,13) ^ ROTR(x,22))
-#define S3(x) (ROTR(x, 6) ^ ROTR(x,11) ^ ROTR(x,25))
-
-#define F0(x,y,z) ((x & y) | (z & (x | y)))
-#define F1(x,y,z) (z ^ (x & (y ^ z)))
-
-#define R(t)                                    \
-(                                               \
-    W[t] = S1(W[t -  2]) + W[t -  7] +          \
-           S0(W[t - 15]) + W[t - 16]            \
-)
-
-#define P(a,b,c,d,e,f,g,h,x,K)                  \
-{                                               \
-    temp1 = h + S3(e) + F1(e,f,g) + K + x;      \
-    temp2 = S2(a) + F0(a,b,c);                  \
-    d += temp1; h = temp1 + temp2;              \
-}
-
-    A = ctx->state[0];
-    B = ctx->state[1];
-    C = ctx->state[2];
-    D = ctx->state[3];
-    E = ctx->state[4];
-    F = ctx->state[5];
-    G = ctx->state[6];
-    H = ctx->state[7];
-
-    P( A, B, C, D, E, F, G, H, W[ 0], 0x428A2F98 );
-    P( H, A, B, C, D, E, F, G, W[ 1], 0x71374491 );
-    P( G, H, A, B, C, D, E, F, W[ 2], 0xB5C0FBCF );
-    P( F, G, H, A, B, C, D, E, W[ 3], 0xE9B5DBA5 );
-    P( E, F, G, H, A, B, C, D, W[ 4], 0x3956C25B );
-    P( D, E, F, G, H, A, B, C, W[ 5], 0x59F111F1 );
-    P( C, D, E, F, G, H, A, B, W[ 6], 0x923F82A4 );
-    P( B, C, D, E, F, G, H, A, W[ 7], 0xAB1C5ED5 );
-    P( A, B, C, D, E, F, G, H, W[ 8], 0xD807AA98 );
-    P( H, A, B, C, D, E, F, G, W[ 9], 0x12835B01 );
-    P( G, H, A, B, C, D, E, F, W[10], 0x243185BE );
-    P( F, G, H, A, B, C, D, E, W[11], 0x550C7DC3 );
-    P( E, F, G, H, A, B, C, D, W[12], 0x72BE5D74 );
-    P( D, E, F, G, H, A, B, C, W[13], 0x80DEB1FE );
-    P( C, D, E, F, G, H, A, B, W[14], 0x9BDC06A7 );
-    P( B, C, D, E, F, G, H, A, W[15], 0xC19BF174 );
-    P( A, B, C, D, E, F, G, H, R(16), 0xE49B69C1 );
-    P( H, A, B, C, D, E, F, G, R(17), 0xEFBE4786 );
-    P( G, H, A, B, C, D, E, F, R(18), 0x0FC19DC6 );
-    P( F, G, H, A, B, C, D, E, R(19), 0x240CA1CC );
-    P( E, F, G, H, A, B, C, D, R(20), 0x2DE92C6F );
-    P( D, E, F, G, H, A, B, C, R(21), 0x4A7484AA );
-    P( C, D, E, F, G, H, A, B, R(22), 0x5CB0A9DC );
-    P( B, C, D, E, F, G, H, A, R(23), 0x76F988DA );
-    P( A, B, C, D, E, F, G, H, R(24), 0x983E5152 );
-    P( H, A, B, C, D, E, F, G, R(25), 0xA831C66D );
-    P( G, H, A, B, C, D, E, F, R(26), 0xB00327C8 );
-    P( F, G, H, A, B, C, D, E, R(27), 0xBF597FC7 );
-    P( E, F, G, H, A, B, C, D, R(28), 0xC6E00BF3 );
-    P( D, E, F, G, H, A, B, C, R(29), 0xD5A79147 );
-    P( C, D, E, F, G, H, A, B, R(30), 0x06CA6351 );
-    P( B, C, D, E, F, G, H, A, R(31), 0x14292967 );
-    P( A, B, C, D, E, F, G, H, R(32), 0x27B70A85 );
-    P( H, A, B, C, D, E, F, G, R(33), 0x2E1B2138 );
-    P( G, H, A, B, C, D, E, F, R(34), 0x4D2C6DFC );
-    P( F, G, H, A, B, C, D, E, R(35), 0x53380D13 );
-    P( E, F, G, H, A, B, C, D, R(36), 0x650A7354 );
-    P( D, E, F, G, H, A, B, C, R(37), 0x766A0ABB );
-    P( C, D, E, F, G, H, A, B, R(38), 0x81C2C92E );
-    P( B, C, D, E, F, G, H, A, R(39), 0x92722C85 );
-    P( A, B, C, D, E, F, G, H, R(40), 0xA2BFE8A1 );
-    P( H, A, B, C, D, E, F, G, R(41), 0xA81A664B );
-    P( G, H, A, B, C, D, E, F, R(42), 0xC24B8B70 );
-    P( F, G, H, A, B, C, D, E, R(43), 0xC76C51A3 );
-    P( E, F, G, H, A, B, C, D, R(44), 0xD192E819 );
-    P( D, E, F, G, H, A, B, C, R(45), 0xD6990624 );
-    P( C, D, E, F, G, H, A, B, R(46), 0xF40E3585 );
-    P( B, C, D, E, F, G, H, A, R(47), 0x106AA070 );
-    P( A, B, C, D, E, F, G, H, R(48), 0x19A4C116 );
-    P( H, A, B, C, D, E, F, G, R(49), 0x1E376C08 );
-    P( G, H, A, B, C, D, E, F, R(50), 0x2748774C );
-    P( F, G, H, A, B, C, D, E, R(51), 0x34B0BCB5 );
-    P( E, F, G, H, A, B, C, D, R(52), 0x391C0CB3 );
-    P( D, E, F, G, H, A, B, C, R(53), 0x4ED8AA4A );
-    P( C, D, E, F, G, H, A, B, R(54), 0x5B9CCA4F );
-    P( B, C, D, E, F, G, H, A, R(55), 0x682E6FF3 );
-    P( A, B, C, D, E, F, G, H, R(56), 0x748F82EE );
-    P( H, A, B, C, D, E, F, G, R(57), 0x78A5636F );
-    P( G, H, A, B, C, D, E, F, R(58), 0x84C87814 );
-    P( F, G, H, A, B, C, D, E, R(59), 0x8CC70208 );
-    P( E, F, G, H, A, B, C, D, R(60), 0x90BEFFFA );
-    P( D, E, F, G, H, A, B, C, R(61), 0xA4506CEB );
-    P( C, D, E, F, G, H, A, B, R(62), 0xBEF9A3F7 );
-    P( B, C, D, E, F, G, H, A, R(63), 0xC67178F2 );
-
-    ctx->state[0] += A;
-    ctx->state[1] += B;
-    ctx->state[2] += C;
-    ctx->state[3] += D;
-    ctx->state[4] += E;
-    ctx->state[5] += F;
-    ctx->state[6] += G;
-    ctx->state[7] += H;
-}
-
-/*
- * SHA-256 process buffer
- */
-void sha2_update( sha2_context *ctx, const unsigned char *input, int ilen )
-{
-    int fill;
-    unsigned long left;
-
-    if( ilen <= 0 )
-        return;
-
-    left = ctx->total[0] & 0x3F;
-    fill = 64 - left;
-
-    ctx->total[0] += ilen;
-    ctx->total[0] &= 0xFFFFFFFF;
-
-    if( ctx->total[0] < (unsigned long) ilen )
-        ctx->total[1]++;
-
-    if( left && ilen >= fill )
-    {
-        memcpy( (void *) (ctx->buffer + left),
-                (void *) input, fill );
-        sha2_process( ctx, ctx->buffer );
-        input += fill;
-        ilen  -= fill;
-        left = 0;
-    }
-
-    while( ilen >= 64 )
-    {
-        sha2_process( ctx, input );
-        input += 64;
-        ilen  -= 64;
-    }
-
-    if( ilen > 0 )
-    {
-        memcpy( (void *) (ctx->buffer + left),
-                (void *) input, ilen );
-    }
-}
-
-static const unsigned char sha2_padding[64] =
-{
- 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
-};
-
-/*
- * SHA-256 final digest
- */
-void sha2_finish( sha2_context *ctx, unsigned char output[32] )
-{
-    unsigned long last, padn;
-    unsigned long high, low;
-    unsigned char msglen[8];
-
-    high = ( ctx->total[0] >> 29 )
-         | ( ctx->total[1] <<  3 );
-    low  = ( ctx->total[0] <<  3 );
-
-    PUT_ULONG_BE( high, msglen, 0 );
-    PUT_ULONG_BE( low,  msglen, 4 );
-
-    last = ctx->total[0] & 0x3F;
-    padn = ( last < 56 ) ? ( 56 - last ) : ( 120 - last );
-
-    sha2_update( ctx, (unsigned char *) sha2_padding, padn );
-    sha2_update( ctx, msglen, 8 );
-
-    PUT_ULONG_BE( ctx->state[0], output,  0 );
-    PUT_ULONG_BE( ctx->state[1], output,  4 );
-    PUT_ULONG_BE( ctx->state[2], output,  8 );
-    PUT_ULONG_BE( ctx->state[3], output, 12 );
-    PUT_ULONG_BE( ctx->state[4], output, 16 );
-    PUT_ULONG_BE( ctx->state[5], output, 20 );
-    PUT_ULONG_BE( ctx->state[6], output, 24 );
-
-    if( ctx->is224 == 0 )
-        PUT_ULONG_BE( ctx->state[7], output, 28 );
-}
-
-/*
- * output = SHA-256( input buffer )
- */
-void sha2( const unsigned char *input, int ilen,
-           unsigned char output[32], int is224 )
-{
-    sha2_context ctx;
-
-    sha2_starts( &ctx, is224 );
-    sha2_update( &ctx, input, ilen );
-    sha2_finish( &ctx, output );
-
-    memset( &ctx, 0, sizeof( sha2_context ) );
-}
-
-/*
- * output = SHA-256( file contents )
- */
-int sha2_file( const char *path, unsigned char output[32], int is224 )
-{
-    FILE *f;
-    size_t n;
-    sha2_context ctx;
-    unsigned char buf[1024];
-
-    if( ( f = fopen( path, "rb" ) ) == NULL )
-        return( 1 );
-
-    sha2_starts( &ctx, is224 );
-
-    while( ( n = fread( buf, 1, sizeof( buf ), f ) ) > 0 )
-        sha2_update( &ctx, buf, (int) n );
-
-    sha2_finish( &ctx, output );
-
-    memset( &ctx, 0, sizeof( sha2_context ) );
-
-    if( ferror( f ) != 0 )
-    {
-        fclose( f );
-        return( 2 );
-    }
-
-    fclose( f );
-    return( 0 );
-}
-
-/*
- * SHA-256 HMAC context setup
- */
-void sha2_hmac_starts( sha2_context *ctx, const unsigned char *key, int keylen,
-                       int is224 )
-{
-    int i;
-    unsigned char sum[32];
-
-    if( keylen > 64 )
-    {
-        sha2( key, keylen, sum, is224 );
-        keylen = ( is224 ) ? 28 : 32;
-        key = sum;
-    }
-
-    memset( ctx->ipad, 0x36, 64 );
-    memset( ctx->opad, 0x5C, 64 );
-
-    for( i = 0; i < keylen; i++ )
-    {
-        ctx->ipad[i] = (unsigned char)( ctx->ipad[i] ^ key[i] );
-        ctx->opad[i] = (unsigned char)( ctx->opad[i] ^ key[i] );
-    }
-
-    sha2_starts( ctx, is224 );
-    sha2_update( ctx, ctx->ipad, 64 );
-
-    memset( sum, 0, sizeof( sum ) );
-}
-
-/*
- * SHA-256 HMAC process buffer
- */
-void sha2_hmac_update( sha2_context *ctx, const unsigned char *input, int ilen )
-{
-    sha2_update( ctx, input, ilen );
-}
-
-/*
- * SHA-256 HMAC final digest
- */
-void sha2_hmac_finish( sha2_context *ctx, unsigned char output[32] )
-{
-    int is224, hlen;
-    unsigned char tmpbuf[32];
-
-    is224 = ctx->is224;
-    hlen = ( is224 == 0 ) ? 32 : 28;
-
-    sha2_finish( ctx, tmpbuf );
-    sha2_starts( ctx, is224 );
-    sha2_update( ctx, ctx->opad, 64 );
-    sha2_update( ctx, tmpbuf, hlen );
-    sha2_finish( ctx, output );
-
-    memset( tmpbuf, 0, sizeof( tmpbuf ) );
-}
-
-/*
- * SHA-256 HMAC context reset
- */
-void sha2_hmac_reset( sha2_context *ctx )
-{
-    sha2_starts( ctx, ctx->is224 );
-    sha2_update( ctx, ctx->ipad, 64 );
-}
-
-/*
- * output = HMAC-SHA-256( hmac key, input buffer )
- */
-void sha2_hmac( const unsigned char *key, int keylen,
-                const unsigned char *input, int ilen,
-                unsigned char output[32], int is224 )
-{
-    sha2_context ctx;
-
-    sha2_hmac_starts( &ctx, key, keylen, is224 );
-    sha2_hmac_update( &ctx, input, ilen );
-    sha2_hmac_finish( &ctx, output );
-
-    memset( &ctx, 0, sizeof( sha2_context ) );
-}
diff --git a/src/pdkim/sha2.h b/src/pdkim/sha2.h
deleted file mode 100644 (file)
index ec2acfa..0000000
+++ /dev/null
@@ -1,153 +0,0 @@
-/**
- * \file sha2.h
- *
- *  Copyright (C) 2006-2010, Brainspark B.V.
- *
- *  This file is part of PolarSSL (http://www.polarssl.org)
- *  Lead Maintainer: Paul Bakker <polarssl_maintainer at polarssl.org>
- *
- *  All rights reserved.
- *
- *  This program 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 program 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 program; if not, write to the Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef POLARSSL_SHA2_H
-#define POLARSSL_SHA2_H
-
-/**
- * \brief          SHA-256 context structure
- */
-#ifndef HAVE_SHA2_CONTEXT
-#define HAVE_SHA2_CONTEXT
-typedef struct sha2_context sha2_context;
-#endif
-
-struct sha2_context
-{
-    unsigned long total[2];     /*!< number of bytes processed  */
-    unsigned long state[8];     /*!< intermediate digest state  */
-    unsigned char buffer[64];   /*!< data block being processed */
-
-    unsigned char ipad[64];     /*!< HMAC: inner padding        */
-    unsigned char opad[64];     /*!< HMAC: outer padding        */
-    int is224;                  /*!< 0 => SHA-256, else SHA-224 */
-};
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
- * \brief          SHA-256 context setup
- *
- * \param ctx      context to be initialized
- * \param is224    0 = use SHA256, 1 = use SHA224
- */
-void sha2_starts( sha2_context *ctx, int is224 );
-
-/**
- * \brief          SHA-256 process buffer
- *
- * \param ctx      SHA-256 context
- * \param input    buffer holding the  data
- * \param ilen     length of the input data
- */
-void sha2_update( sha2_context *ctx, const unsigned char *input, int ilen );
-
-/**
- * \brief          SHA-256 final digest
- *
- * \param ctx      SHA-256 context
- * \param output   SHA-224/256 checksum result
- */
-void sha2_finish( sha2_context *ctx, unsigned char output[32] );
-
-/**
- * \brief          Output = SHA-256( input buffer )
- *
- * \param input    buffer holding the  data
- * \param ilen     length of the input data
- * \param output   SHA-224/256 checksum result
- * \param is224    0 = use SHA256, 1 = use SHA224
- */
-void sha2( const unsigned char *input, int ilen,
-           unsigned char output[32], int is224 );
-
-/**
- * \brief          Output = SHA-256( file contents )
- *
- * \param path     input file name
- * \param output   SHA-224/256 checksum result
- * \param is224    0 = use SHA256, 1 = use SHA224
- *
- * \return         0 if successful, 1 if fopen failed,
- *                 or 2 if fread failed
- */
-int sha2_file( const char *path, unsigned char output[32], int is224 );
-
-/**
- * \brief          SHA-256 HMAC context setup
- *
- * \param ctx      HMAC context to be initialized
- * \param key      HMAC secret key
- * \param keylen   length of the HMAC key
- * \param is224    0 = use SHA256, 1 = use SHA224
- */
-void sha2_hmac_starts( sha2_context *ctx, const unsigned char *key, int keylen,
-                       int is224 );
-
-/**
- * \brief          SHA-256 HMAC process buffer
- *
- * \param ctx      HMAC context
- * \param input    buffer holding the  data
- * \param ilen     length of the input data
- */
-void sha2_hmac_update( sha2_context *ctx, const unsigned char *input, int ilen );
-
-/**
- * \brief          SHA-256 HMAC final digest
- *
- * \param ctx      HMAC context
- * \param output   SHA-224/256 HMAC checksum result
- */
-void sha2_hmac_finish( sha2_context *ctx, unsigned char output[32] );
-
-/**
- * \brief          SHA-256 HMAC context reset
- *
- * \param ctx      HMAC context to be reset
- */
-void sha2_hmac_reset( sha2_context *ctx );
-
-/**
- * \brief          Output = HMAC-SHA-256( hmac key, input buffer )
- *
- * \param key      HMAC secret key
- * \param keylen   length of the HMAC key
- * \param input    buffer holding the  data
- * \param ilen     length of the input data
- * \param output   HMAC-SHA-224/256 result
- * \param is224    0 = use SHA256, 1 = use SHA224
- */
-void sha2_hmac( const unsigned char *key, int keylen,
-                const unsigned char *input, int ilen,
-                unsigned char output[32], int is224 );
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* sha2.h */
index 543b5d2..92218a6 100644 (file)
@@ -13,6 +13,7 @@
 /* This Perl add-on can be distributed under the same terms as Exim itself. */
 /* See the file NOTICE for conditions of use and distribution. */
 
+#include <assert.h>
 #include "exim.h"
 
 #define EXIM_TRUE TRUE
@@ -95,11 +96,17 @@ static void  xs_init(pTHX)
 uschar *
 init_perl(uschar *startup_code)
 {
-  static int argc = 2;
-  static char *argv[3] = { "exim-perl", "/dev/null", 0 };
+  static int argc = 1;
+  static char *argv[4] = { "exim-perl" };
   SV *sv;
   STRLEN len;
 
+  if (opt_perl_taintmode) argv[argc++] = "-T";
+  argv[argc++] = "/dev/null";
+  argv[argc] = 0;
+
+  assert(sizeof(argv)/sizeof(argv[0]) > argc);
+
   if (interp_perl) return 0;
   interp_perl = perl_alloc();
   perl_construct(interp_perl);
@@ -179,7 +186,7 @@ call_perl_cat(uschar *yield, int *sizep, int *ptrp, uschar **errstrp,
     return NULL;
     }
   str = US SvPV(sv, len);
-  yield = string_cat(yield, sizep, ptrp, str, (int)len);
+  yield = string_catn(yield, sizep, ptrp, str, (int)len);
   FREETMPS;
   LEAVE;
 
index 8876e09..50e4aae 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions that operate on the input queue. */
 
 
 
+/* Routines with knowledge of spool layout */
+
+#ifndef COMPILE_UTILITY
+static void
+spool_pname_buf(uschar * buf, int len)
+{
+snprintf(CS buf, len, "%s/%s/input", spool_directory, queue_name);
+}
+
+uschar *
+spool_dname(const uschar * purpose, uschar * subdir)
+{
+return string_sprintf("%s/%s/%s/%s",
+       spool_directory, queue_name, purpose, subdir);
+}
+#endif
+
+uschar *
+spool_sname(const uschar * purpose, uschar * subdir)
+{
+return string_sprintf("%s%s%s%s%s",
+                   queue_name, *queue_name ? "/" : "",
+                   purpose,
+                   *subdir ? "/" : "", subdir);
+}
+
+uschar *
+spool_fname(const uschar * purpose, const uschar * subdir, const uschar * fname,
+               const uschar * suffix)
+{
+return string_sprintf("%s/%s/%s/%s/%s%s",
+       spool_directory, queue_name, purpose, subdir, fname, suffix);
+}
+
+
+
+
+#ifndef COMPILE_UTILITY
+
 /* The number of nodes to use for the bottom-up merge sort when a list of queue
 items is to be ordered. The code for this sort was contributed as a patch by
 Michael Haardt. */
@@ -40,8 +79,7 @@ merge_queue_lists(queue_filename *a, queue_filename *b)
 queue_filename *first = NULL;
 queue_filename **append = &first;
 
-while (a != NULL && b != NULL)
-  {
+while (a && b)
   if (Ustrcmp(a->text, b->text) < 0)
     {
     *append = a;
@@ -54,9 +92,8 @@ while (a != NULL && b != NULL)
     append= &b->next;
     b = b->next;
     }
-  }
 
-*append=((a != NULL)? a : b);
+*append = a ? a : b;
 return first;
 }
 
@@ -125,8 +162,11 @@ according to the bits of the flags variable. Get a collection of bits from the
 current time. Use the bottom 16 and just keep re-using them if necessary. When
 not randomizing, initialize the sublists for the bottom-up merge sort. */
 
-if (randomize) resetflags = time(NULL) & 0xFFFF;
-  else for (i = 0; i < LOG2_MAXNODES; i++) root[i] = NULL;
+if (randomize)
+  resetflags = time(NULL) & 0xFFFF;
+else
+   for (i = 0; i < LOG2_MAXNODES; i++)
+     root[i] = NULL;
 
 /* If processing the full queue, or just the top-level, start at the base
 directory, and initialize the first subdirectory name (as none). Otherwise,
@@ -138,11 +178,13 @@ if (subdiroffset <= 0)
   subdirs[0] = 0;
   *subcount = 0;
   }
-else i = subdiroffset;
+else
+  i = subdiroffset;
 
 /* Set up prototype for the directory name. */
 
-sprintf(CS buffer, "%s/input", spool_directory);
+spool_pname_buf(buffer, sizeof(buffer));
+buffer[sizeof(buffer) - 3] = 0;
 subptr = Ustrlen(buffer);
 buffer[subptr+2] = 0;               /* terminator for lengthened name */
 
@@ -161,12 +203,13 @@ for (; i <= *subcount; i++)
     buffer[subptr+1] = subdirchar;
     }
 
-  dd = opendir(CS buffer);
-  if (dd == NULL) continue;
+  DEBUG(D_queue_run) debug_printf("looking in %s\n", buffer);
+  if (!(dd = opendir(CS buffer)))
+    continue;
 
   /* Now scan the directory. */
 
-  while ((ent = readdir(dd)) != NULL)
+  while ((ent = readdir(dd)))
     {
     uschar *name = US ent->d_name;
     int len = Ustrlen(name);
@@ -202,15 +245,15 @@ for (; i <= *subcount; i++)
       to store the number with each item. */
 
       if (randomize)
-        {
-        if (yield == NULL)
+        if (!yield)
           {
           next->next = NULL;
           yield = last = next;
           }
         else
           {
-          if (flags == 0) flags = resetflags;
+          if (flags == 0)
+           flags = resetflags;
           if ((flags & 1) == 0)
             {
             next->next = yield;
@@ -224,7 +267,6 @@ for (; i <= *subcount; i++)
             }
           flags = flags >> 1;
           }
-        }
 
       /* Otherwise do a bottom-up merge sort based on the name. */
 
@@ -233,8 +275,7 @@ for (; i <= *subcount; i++)
         int j;
         next->next = NULL;
         for (j = 0; j < LOG2_MAXNODES; j++)
-          {
-          if (root[j] != NULL)
+          if (root[j])
             {
             next = merge_queue_lists(next, root[j]);
             root[j] = (j == LOG2_MAXNODES - 1)? next : NULL;
@@ -244,7 +285,6 @@ for (; i <= *subcount; i++)
             root[j] = next;
             break;
             }
-          }
         }
       }
     }
@@ -264,9 +304,11 @@ for (; i <= *subcount; i++)
     {
     if (!split_spool_directory && count <= 2)
       {
+      uschar subdir[2];
+
       rmdir(CS buffer);
-      sprintf(CS big_buffer, "%s/msglog/%c", spool_directory, subdirchar);
-      rmdir(CS big_buffer);
+      subdir[0] = subdirchar; subdir[1] = 0;
+      rmdir(CS spool_dname(US"msglog", subdir));
       }
     if (subdiroffset > 0) break;    /* Single sub-directory */
     }
@@ -274,10 +316,8 @@ for (; i <= *subcount; i++)
   /* If we have just scanned the base directory, and subdiroffset is 0,
   we do not want to continue scanning the sub-directories. */
 
-  else
-    {
-    if (subdiroffset == 0) break;
-    }
+  else if (subdiroffset == 0)
+    break;
   }    /* Loop for multiple subdirectories */
 
 /* When using a bottom-up merge sort, do the final merging of the sublists.
@@ -389,7 +429,11 @@ if (!recurse)
     }
 
   log_detail = string_copy(big_buffer);
-  log_write(L_queue_run, LOG_MAIN, "Start queue run: %s", log_detail);
+  if (*queue_name)
+    log_write(L_queue_run, LOG_MAIN, "Start '%s' queue run: %s",
+      queue_name, log_detail);
+  else
+    log_write(L_queue_run, LOG_MAIN, "Start queue run: %s", log_detail);
   }
 
 /* If deliver_selectstring is a regex, compile it. */
@@ -411,7 +455,7 @@ any messages therein), and then repeats for any subdirectories that were found.
 When the first argument of queue_get_spool_list() is 0, it scans the top
 directory, fills in subdirs, and sets subcount. The order of the directories is
 then randomized after the first time through, before they are scanned in
-subsqeuent iterations.
+subsequent iterations.
 
 When the first argument of queue_get_spool_list() is -1 (for queue_run_in_
 order), it scans all directories and makes a single message list. */
@@ -434,7 +478,7 @@ for (i  = (queue_run_in_order? -1 : 0);
     }
 
   for (f = queue_get_spool_list(i, subdirs, &subcount, !queue_run_in_order);
-       f != NULL;
+       f;
        f = f->next)
     {
     pid_t pid;
@@ -447,9 +491,7 @@ for (i  = (queue_run_in_order? -1 : 0);
     check that the load average is low enough to permit deliveries. */
 
     if (!queue_run_force && deliver_queue_load_max >= 0)
-      {
-      load_average = os_getloadavg();
-      if (load_average > deliver_queue_load_max)
+      if ((load_average = os_getloadavg()) > deliver_queue_load_max)
         {
         log_write(L_queue_run, LOG_MAIN, "Abandon queue run: %s (load %.2f, max %.2f)",
           log_detail,
@@ -459,26 +501,22 @@ for (i  = (queue_run_in_order? -1 : 0);
         break;
         }
       else
-        {
         DEBUG(D_load) debug_printf("load average = %.2f max = %.2f\n",
           (double)load_average/1000.0,
           (double)deliver_queue_load_max/1000.0);
-        }
-      }
 
     /* Skip this message unless it's within the ID limits */
 
-    if (stop_id != NULL && Ustrncmp(f->text, stop_id, MESSAGE_ID_LENGTH) > 0)
+    if (stop_id && Ustrncmp(f->text, stop_id, MESSAGE_ID_LENGTH) > 0)
       continue;
-    if (start_id != NULL && Ustrncmp(f->text, start_id, MESSAGE_ID_LENGTH) < 0)
+    if (start_id && Ustrncmp(f->text, start_id, MESSAGE_ID_LENGTH) < 0)
       continue;
 
     /* Check that the message still exists */
 
     message_subdir[0] = f->dir_uschar;
-    sprintf(CS buffer, "%s/input/%s/%s", spool_directory, message_subdir,
-      f->text);
-    if (Ustat(buffer, &statbuf) < 0) continue;
+    if (Ustat(spool_fname(US"input", message_subdir, f->text, US""), &statbuf) < 0)
+      continue;
 
     /* There are some tests that require the reading of the header file. Ensure
     the store used is scavenged afterwards so that this process doesn't keep
@@ -486,7 +524,7 @@ for (i  = (queue_run_in_order? -1 : 0);
     delivering, but it's cheaper than forking a delivery process for each
     message when many are not going to be delivered. */
 
-    if (deliver_selectstring != NULL || deliver_selectstring_sender != NULL ||
+    if (deliver_selectstring || deliver_selectstring_sender ||
         queue_run_first_delivery)
       {
       BOOL wanted = TRUE;
@@ -519,19 +557,20 @@ for (i  = (queue_run_in_order? -1 : 0);
         wanted = FALSE;
         }
 
-      /* Check for a matching address if deliver_selectstring[_sender} is set.
+      /* Check for a matching address if deliver_selectstring[_sender] is set.
       If so, we do a fully delivery - don't want to omit other addresses since
       their routing might trigger re-writing etc. */
 
       /* Sender matching */
 
-      else if (deliver_selectstring_sender != NULL &&
-              !(deliver_selectstring_sender_regex?
-                (pcre_exec(selectstring_regex_sender, NULL, CS sender_address,
-                  Ustrlen(sender_address), 0, PCRE_EOPT, NULL, 0) >= 0)
-                :
-                (strstric(sender_address, deliver_selectstring_sender, FALSE)
-                  != NULL)))
+      else if (  deliver_selectstring_sender
+             && !(deliver_selectstring_sender_regex
+                 ? (pcre_exec(selectstring_regex_sender, NULL,
+                     CS sender_address, Ustrlen(sender_address), 0, PCRE_EOPT,
+                     NULL, 0) >= 0)
+                 : (strstric(sender_address, deliver_selectstring_sender, FALSE)
+                     != NULL)
+             )   )
         {
         DEBUG(D_queue_run) debug_printf("%s: sender address did not match %s\n",
           f->text, deliver_selectstring_sender);
@@ -540,19 +579,19 @@ for (i  = (queue_run_in_order? -1 : 0);
 
       /* Recipient matching */
 
-      else if (deliver_selectstring != NULL)
+      else if (deliver_selectstring)
         {
         int i;
         for (i = 0; i < recipients_count; i++)
           {
           uschar *address = recipients_list[i].address;
-          if ((deliver_selectstring_regex?
-               (pcre_exec(selectstring_regex, NULL, CS address,
-                 Ustrlen(address), 0, PCRE_EOPT, NULL, 0) >= 0)
-               :
-               (strstric(address, deliver_selectstring, FALSE) != NULL))
-              &&
-              tree_search(tree_nonrecipients, address) == NULL)
+          if (  (deliver_selectstring_regex
+               (pcre_exec(selectstring_regex, NULL, CS address,
+                    Ustrlen(address), 0, PCRE_EOPT, NULL, 0) >= 0)
+                : (strstric(address, deliver_selectstring, FALSE) != NULL)
+               )
+             && tree_search(tree_nonrecipients, address) == NULL
+            )
             break;
           }
 
@@ -567,6 +606,9 @@ for (i  = (queue_run_in_order? -1 : 0);
 
       /* Recover store used when reading the header */
 
+      received_protocol = NULL;
+      sender_address = sender_ident = NULL;
+      authenticated_id = authenticated_sender = NULL;
       store_reset(reset_point2);
       if (!wanted) continue;      /* With next message */
       }
@@ -581,10 +623,8 @@ for (i  = (queue_run_in_order? -1 : 0);
     pretty cheap. */
 
     if (pipe(pfd) < 0)
-      {
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to create pipe in queue "
         "runner process %d: %s", queue_run_pid, strerror(errno));
-      }
     queue_run_pipe = pfd[pipe_write];  /* To ensure it gets passed on. */
 
     /* Make sure it isn't stdin. This seems unlikely, but just to be on the
@@ -638,11 +678,9 @@ for (i  = (queue_run_in_order? -1 : 0);
     /* If the process crashed, tell somebody */
 
     else if ((status & 0x00ff) != 0)
-      {
       log_write(0, LOG_MAIN|LOG_PANIC,
         "queue run: process %d crashed with signal %d while delivering %s",
         (int)pid, status & 0x00ff, f->text);
-      }
 
     /* Before continuing, wait till the pipe gets closed at the far end. This
     tells us that any children created by the delivery to re-use any SMTP
@@ -650,8 +688,9 @@ for (i  = (queue_run_in_order? -1 : 0);
     the mere fact that read() unblocks is enough. */
 
     set_process_info("running queue: waiting for children of %d", pid);
-    if (read(pfd[pipe_read], buffer, sizeof(buffer)) > 0)
-      log_write(0, LOG_MAIN|LOG_PANIC, "queue run: unexpected data on pipe");
+    if ((status = read(pfd[pipe_read], buffer, sizeof(buffer))) != 0)
+      log_write(0, LOG_MAIN|LOG_PANIC, "queue run: %s on pipe",
+               status > 0 ? "unexpected data" : "error");
     (void)close(pfd[pipe_read]);
     set_process_info("running queue");
 
@@ -672,18 +711,15 @@ for (i  = (queue_run_in_order? -1 : 0);
 
   if (i == 0 && subcount > 1 && !queue_run_in_order)
     {
-    int j;
+    int j, r;
     for (j = 1; j <= subcount; j++)
-      {
-      int r = random_number(100);
-      if (r >= 50)
+      if ((r = random_number(100)) >= 50)
         {
         int k = (r % subcount) + 1;
         int x = subdirs[j];
         subdirs[j] = subdirs[k];
         subdirs[k] = x;
         }
-      }
     }
   }                                    /* End loop for multiple directories */
 
@@ -698,7 +734,12 @@ if (queue_2stage)
 
 /* At top level, log the end of the run. */
 
-if (!recurse) log_write(L_queue_run, LOG_MAIN, "End queue run: %s", log_detail);
+if (!recurse)
+  if (*queue_name)
+    log_write(L_queue_run, LOG_MAIN, "End '%s' queue run: %s",
+      queue_name, log_detail);
+  else
+    log_write(L_queue_run, LOG_MAIN, "End queue run: %s", log_detail);
 }
 
 
@@ -819,19 +860,16 @@ if (option >= 8) option -= 8;
 /* Now scan the chain and print information, resetting store used
 each time. */
 
-reset_point = store_get(0);
-
-for (; f != NULL; f = f->next)
+for (reset_point = store_get(0); f; f = f->next)
   {
   int rc, save_errno;
   int size = 0;
   BOOL env_read;
 
-  store_reset(reset_point);
   message_size = 0;
   message_subdir[0] = f->dir_uschar;
   rc = spool_read_header(f->text, FALSE, count <= 0);
-  if (rc == spool_read_notopen && errno == ENOENT && count <= 0) continue;
+  if (rc == spool_read_notopen && errno == ENOENT && count <= 0) goto next;
   save_errno = errno;
 
   env_read = (rc == spool_read_OK || rc == spool_read_hdrerror);
@@ -841,17 +879,16 @@ for (; f != NULL; f = f->next)
     int ptr;
     FILE *jread;
     struct stat statbuf;
+    uschar * fname = spool_fname(US"input", message_subdir, f->text, US"");
 
-    sprintf(CS big_buffer, "%s/input/%s/%s", spool_directory, message_subdir,
-      f->text);
-    ptr = Ustrlen(big_buffer)-1;
-    big_buffer[ptr] = 'D';
+    ptr = Ustrlen(fname)-1;
+    fname[ptr] = 'D';
 
     /* Add the data size to the header size; don't count the file name
     at the start of the data file, but add one for the notional blank line
     that precedes the data. */
 
-    if (Ustat(big_buffer, &statbuf) == 0)
+    if (Ustat(fname, &statbuf) == 0)
       size = message_size + statbuf.st_size - SPOOL_DATA_START_OFFSET + 1;
     i = (now - received_time)/60;  /* minutes on queue */
     if (i > 90)
@@ -863,9 +900,8 @@ for (; f != NULL; f = f->next)
 
     /* Collect delivered addresses from any J file */
 
-    big_buffer[ptr] = 'J';
-    jread = Ufopen(big_buffer, "rb");
-    if (jread != NULL)
+    fname[ptr] = 'J';
+    if ((jread = Ufopen(fname, "rb")))
       {
       while (Ufgets(big_buffer, big_buffer_size, jread) != NULL)
         {
@@ -880,7 +916,7 @@ for (; f != NULL; f = f->next)
   fprintf(stdout, "%s ", string_format_size(size, big_buffer));
   for (i = 0; i < 16; i++) fputc(f->text[i], stdout);
 
-  if (env_read && sender_address != NULL)
+  if (env_read && sender_address)
     {
     printf(" <%s>", sender_address);
     if (sender_set_untrusted) printf(" (%s)", originator_login);
@@ -892,9 +928,9 @@ for (; f != NULL; f = f->next)
     if (save_errno == ERRNO_SPOOLFORMAT)
       {
       struct stat statbuf;
-      sprintf(CS big_buffer, "%s/input/%s/%s", spool_directory, message_subdir,
-        f->text);
-      if (Ustat(big_buffer, &statbuf) == 0)
+      uschar * fname = spool_fname(US"input", message_subdir, f->text, US"");
+
+      if (Ustat(fname, &statbuf) == 0)
         printf("*** spool format error: size=" OFF_T_FMT " ***",
           statbuf.st_size);
       else printf("*** spool format error ***");
@@ -903,7 +939,7 @@ for (; f != NULL; f = f->next)
     if (rc != spool_read_hdrerror)
       {
       printf("\n\n");
-      continue;
+      goto next;
       }
     }
 
@@ -911,7 +947,7 @@ for (; f != NULL; f = f->next)
 
   printf("\n");
 
-  if (recipients_list != NULL)
+  if (recipients_list)
     {
     for (i = 0; i < recipients_count; i++)
       {
@@ -920,12 +956,22 @@ for (; f != NULL; f = f->next)
       if (!delivered || option != 1)
         printf("        %s %s\n", (delivered != NULL)? "D":" ",
           recipients_list[i].address);
-      if (delivered != NULL) delivered->data.val = TRUE;
+      if (delivered) delivered->data.val = TRUE;
       }
-    if (option == 2 && tree_nonrecipients != NULL)
+    if (option == 2 && tree_nonrecipients)
       queue_list_extras(tree_nonrecipients);
     printf("\n");
     }
+
+next:
+  received_protocol = NULL;
+  sender_fullhost = sender_helo_name =
+  sender_rcvhost = sender_host_address = sender_address = sender_ident = NULL;
+  sender_host_authenticated = authenticated_sender = authenticated_id = NULL;
+  interface_address = NULL;
+  acl_var_m = NULL;
+
+  store_reset(reset_point);
   }
 }
 
@@ -959,7 +1005,7 @@ struct passwd *pw;
 uschar *doing = NULL;
 uschar *username;
 uschar *errmsg;
-uschar spoolname[256];
+uschar spoolname[32];
 
 /* Set the global message_id variable, used when re-writing spool files. This
 also causes message ids to be added to log messages. */
@@ -1004,12 +1050,13 @@ if (action >= MSG_SHOW_BODY)
 
   for (i = 0; i < 2; i++)
     {
-    message_subdir[0] = (split_spool_directory == (i == 0))? id[5] : 0;
-    sprintf(CS spoolname, "%s/%s/%s/%s%s", spool_directory, subdirectory,
-      message_subdir, id, suffix);
-    fd = Uopen(spoolname, O_RDONLY, 0);
-    if (fd >= 0) break;
-    if (i == 0) continue;
+    message_subdir[0] = split_spool_directory == (i == 0) ? id[5] : 0;
+    if ((fd = Uopen(spool_fname(subdirectory, message_subdir, id, suffix),
+                   O_RDONLY, 0)) >= 0)
+      break;
+    if (i == 0)
+      continue;
+
     printf("Failed to open %s file for %s%s: %s\n", subdirectory, id, suffix,
       strerror(errno));
     if (action == MSG_SHOW_LOG && !message_logs)
@@ -1030,8 +1077,7 @@ other process is working on this message. If the file does not exist, continue
 only if the action is remove and the user is an admin user, to allow for
 tidying up broken states. */
 
-if (!spool_open_datafile(id))
-  {
+if ((deliver_datafile = spool_open_datafile(id)) < 0)
   if (errno == ENOENT)
     {
     yield = FALSE;
@@ -1046,7 +1092,6 @@ if (!spool_open_datafile(id))
         strerror(errno));
     return FALSE;
     }
-  }
 
 /* Read the spool header file for the message. Again, continue after an
 error only in the case of deleting by an administrator. Setting the third
@@ -1099,7 +1144,7 @@ switch(action)
   case MSG_SHOW_COPY:
   deliver_in_buffer = store_malloc(DELIVER_IN_BUFFER_SIZE);
   deliver_out_buffer = store_malloc(DELIVER_OUT_BUFFER_SIZE);
-  transport_write_message(NULL, 1, 0, 0, NULL, NULL, NULL, NULL, NULL, 0);
+  transport_write_message(1, NULL, 0);
   break;
 
 
@@ -1159,49 +1204,70 @@ switch(action)
   operation, just run everything twice. */
 
   case MSG_REMOVE:
-  message_subdir[0] = id[5];
-  for (j = 0; j < 2; message_subdir[0] = 0, j++)
     {
-    sprintf(CS spoolname, "%s/msglog/%s/%s", spool_directory, message_subdir, id);
-    if (Uunlink(spoolname) < 0)
-      {
-      if (errno != ENOENT)
-        {
-        yield = FALSE;
-        printf("Error while removing %s: %s\n", spoolname,
-          strerror(errno));
-        }
-      }
-    else removed = TRUE;
+    uschar suffix[3];
 
-    for (i = 0; i < 3; i++)
+    suffix[0] = '-';
+    suffix[2] = 0;
+    message_subdir[0] = id[5];
+
+    for (j = 0; j < 2; message_subdir[0] = 0, j++)
       {
-      sprintf(CS spoolname, "%s/input/%s/%s-%c", spool_directory, message_subdir,
-        id, "DHJ"[i]);
-      if (Uunlink(spoolname) < 0)
-        {
-        if (errno != ENOENT)
-          {
-          yield = FALSE;
-          printf("Error while removing %s: %s\n", spoolname,
-            strerror(errno));
-          }
-        }
-      else removed = TRUE;
+      uschar * fname = spool_fname(US"msglog", message_subdir, id, US"");
+
+      DEBUG(D_any) debug_printf(" removing %s", fname);
+      if (Uunlink(fname) < 0)
+       {
+       if (errno != ENOENT)
+         {
+         yield = FALSE;
+         printf("Error while removing %s: %s\n", fname, strerror(errno));
+         }
+       else DEBUG(D_any) debug_printf(" (no file)\n");
+       }
+      else
+       {
+       removed = TRUE;
+       DEBUG(D_any) debug_printf(" (ok)\n");
+       }
+
+      for (i = 0; i < 3; i++)
+       {
+       uschar * fname;
+
+       suffix[1] = (US"DHJ")[i];
+       fname = spool_fname(US"input", message_subdir, id, suffix);
+
+       DEBUG(D_any) debug_printf(" removing %s", fname);
+       if (Uunlink(fname) < 0)
+         {
+         if (errno != ENOENT)
+           {
+           yield = FALSE;
+           printf("Error while removing %s: %s\n", fname, strerror(errno));
+           }
+         else DEBUG(D_any) debug_printf(" (no file)\n");
+         }
+       else
+         {
+         removed = TRUE;
+         DEBUG(D_any) debug_printf(" (done)\n");
+         }
+       }
       }
-    }
 
-  /* In the common case, the datafile is open (and locked), so give the
-  obvious message. Otherwise be more specific. */
+    /* In the common case, the datafile is open (and locked), so give the
+    obvious message. Otherwise be more specific. */
 
-  if (deliver_datafile >= 0) printf("has been removed\n");
-    else printf("has been removed or did not exist\n");
-  if (removed)
-    {
-    log_write(0, LOG_MAIN, "removed by %s", username);
-    log_write(0, LOG_MAIN, "Completed");
+    if (deliver_datafile >= 0) printf("has been removed\n");
+      else printf("has been removed or did not exist\n");
+    if (removed)
+      {
+      log_write(0, LOG_MAIN, "removed by %s", username);
+      log_write(0, LOG_MAIN, "Completed");
+      }
+    break;
     }
-  break;
 
 
   case MSG_MARK_ALL_DELIVERED:
@@ -1274,6 +1340,9 @@ switch(action)
       {
       if (action == MSG_ADD_RECIPIENT)
         {
+#ifdef SUPPORT_I18N
+       if (string_is_utf8(recipient)) allow_utf8_domains = message_smtputf8 = TRUE;
+#endif
         receive_add_recipient(recipient, -1);
         log_write(0, LOG_MAIN, "recipient <%s> added by %s",
           recipient, username);
@@ -1297,6 +1366,9 @@ switch(action)
         }
       else  /* MSG_EDIT_SENDER */
         {
+#ifdef SUPPORT_I18N
+       if (string_is_utf8(recipient)) allow_utf8_domains = message_smtputf8 = TRUE;
+#endif
         sender_address = recipient;
         log_write(0, LOG_MAIN, "sender address changed to <%s> by %s",
           recipient, username);
@@ -1305,7 +1377,6 @@ switch(action)
     }
 
   if (yield)
-    {
     if (spool_write_header(id, SW_MODIFYING, &errmsg) >= 0)
       printf("has been modified\n");
     else
@@ -1313,7 +1384,6 @@ switch(action)
       yield = FALSE;
       printf("- while %s: %s\n", doing, errmsg);
       }
-    }
 
   break;
   }
@@ -1321,8 +1391,11 @@ switch(action)
 /* Closing the datafile releases the lock and permits other processes
 to operate on the message (if it still exists). */
 
-(void)close(deliver_datafile);
-deliver_datafile = -1;
+if (deliver_datafile >= 0)
+  {
+  (void)close(deliver_datafile);
+  deliver_datafile = -1;
+  }
 return yield;
 }
 
@@ -1345,7 +1418,8 @@ queue_check_only(void)
 BOOL *set;
 int sep = 0;
 struct stat statbuf;
-uschar *s, *ss, *name;
+const uschar *s;
+uschar *ss, *name;
 uschar buffer[1024];
 
 if (queue_only_file == NULL) return;
@@ -1373,4 +1447,6 @@ while ((ss = string_nextinlist(&s, &sep, buffer, sizeof(buffer))) != NULL)
   }
 }
 
+#endif /*!COMPILE_UTILITY*/
+
 /* End of queue.c */
index f915ce7..995909b 100644 (file)
--- a/src/rda.c
+++ b/src/rda.c
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* This module contains code for extracting addresses from a forwarding list
@@ -444,7 +444,7 @@ Returns:     -1 on error, else 0
 */
 
 static int
-rda_write_string(int fd, uschar *s)
+rda_write_string(int fd, const uschar *s)
 {
 int len = (s == NULL)? 0 : Ustrlen(s) + 1;
 return (  write(fd, &len, sizeof(int)) != sizeof(int)
@@ -474,11 +474,12 @@ rda_read_string(int fd, uschar **sp)
 int len;
 
 if (read(fd, &len, sizeof(int)) != sizeof(int)) return FALSE;
-if (len == 0) *sp = NULL; else
-  {
-  *sp = store_get(len);
-  if (read(fd, *sp, len) != len) return FALSE;
-  }
+if (len == 0)
+  *sp = NULL;
+else
+  /* We know we have enough memory so disable the error on "len" */
+  /* coverity[tainted_data] */
+  if (read(fd, *sp = store_get(len), len) != len) return FALSE;
 return TRUE;
 }
 
@@ -635,7 +636,7 @@ if ((pid = fork()) == 0)
     {
     DEBUG(D_rewrite) debug_printf("turned off address rewrite logging (not "
       "root or exim in this process)\n");
-    log_write_selector &= ~L_address_rewrite;
+    BIT_CLEAR(log_selector, log_selector_size, Li_address_rewrite);
     }
 
   /* Now do the business */
@@ -723,7 +724,7 @@ if ((pid = fork()) == 0)
            != sizeof(addr->mode)
          || write(fd, &(addr->flags), sizeof(addr->flags))
            != sizeof(addr->flags)
-         || rda_write_string(fd, addr->p.errors_address) != 0
+         || rda_write_string(fd, addr->prop.errors_address) != 0
         )
        goto bad;
 
@@ -805,22 +806,21 @@ if (read(fd, filtertype, sizeof(int)) != sizeof(int) ||
 
 /* Read the contents of any syntax error blocks if we have a pointer */
 
-if (eblockp != NULL)
+if (eblockp)
   {
-  uschar *s;
   error_block *e;
-  error_block **p = eblockp;
-  for (;;)
+  error_block **p;
+  for (p = eblockp; ; p = &e->next)
     {
+    uschar *s;
     if (!rda_read_string(fd, &s)) goto DISASTER;
-    if (s == NULL) break;
+    if (!s) break;
     e = store_get(sizeof(error_block));
     e->next = NULL;
     e->text1 = s;
     if (!rda_read_string(fd, &s)) goto DISASTER;
     e->text2 = s;
     *p = e;
-    p = &(e->next);
     }
   }
 
@@ -840,8 +840,7 @@ if (system_filtering)
     while (hn < n)
       {
       hn++;
-      h = h->next;
-      if (h == NULL) goto DISASTER_NO_HEADER;
+      if (!(h = h->next)) goto DISASTER_NO_HEADER;
       }
     h->type = htype_old;
     }
@@ -851,7 +850,7 @@ if (system_filtering)
     uschar *s;
     int type;
     if (!rda_read_string(fd, &s)) goto DISASTER;
-    if (s == NULL) break;
+    if (!s) break;
     if (read(fd, &type, sizeof(type)) != sizeof(type)) goto DISASTER;
     header_add(type, "%s", s);
     }
@@ -892,7 +891,7 @@ if (yield == FF_DELIVERED || yield == FF_NOTDELIVERED ||
 
     if (read(fd, &(addr->mode), sizeof(addr->mode)) != sizeof(addr->mode) ||
         read(fd, &(addr->flags), sizeof(addr->flags)) != sizeof(addr->flags) ||
-        !rda_read_string(fd, &(addr->p.errors_address))) goto DISASTER;
+        !rda_read_string(fd, &(addr->prop.errors_address))) goto DISASTER;
 
     /* Next comes a possible setting for $thisaddress and any numerical
     variables for pipe expansion, terminated by a NULL string. The maximum
@@ -911,7 +910,7 @@ if (yield == FF_DELIVERED || yield == FF_NOTDELIVERED ||
 
     if (i > 0)
       {
-      addr->pipe_expandn = store_get((i+1) * sizeof(uschar **));
+      addr->pipe_expandn = store_get((i+1) * sizeof(uschar *));
       addr->pipe_expandn[i] = NULL;
       while (--i >= 0) addr->pipe_expandn[i] = expandn[i];
       }
index adb538c..790f073 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for reading the configuration file, and for displaying
@@ -11,18 +11,39 @@ implementation of the conditional .ifdef etc. */
 
 #include "exim.h"
 
+extern char **environ;
+
+static void fn_smtp_receive_timeout(const uschar * name, const uschar * str);
+static void save_config_line(const uschar* line);
+static void save_config_position(const uschar *file, int line);
+static void print_config(BOOL admin, BOOL terse);
+static void readconf_options_auths(void);
+
+
 #define CSTATE_STACK_SIZE 10
 
+const uschar *config_directory = NULL;
+
 
 /* Structure for chain (stack) of .included files */
 
 typedef struct config_file_item {
   struct config_file_item *next;
-  uschar *filename;
+  const uschar *filename;
+  const uschar *directory;
   FILE *file;
   int lineno;
 } config_file_item;
 
+/* Structure for chain of configuration lines (-bP config) */
+
+typedef struct config_line_item {
+  struct config_line_item *next;
+  uschar *line;
+} config_line_item;
+
+static config_line_item* config_lines;
+
 /* Structure of table of conditional words and their state transitions */
 
 typedef struct cond_item {
@@ -40,6 +61,8 @@ typedef struct syslog_fac_item {
   int    value;
 } syslog_fac_item;
 
+/* constants */
+static const char * const hidden = "<value not displayable>";
 
 /* Static variables */
 
@@ -162,6 +185,7 @@ static optionlist optionlist_config[] = {
   { "acl_smtp_starttls",        opt_stringptr,   &acl_smtp_starttls },
 #endif
   { "acl_smtp_vrfy",            opt_stringptr,   &acl_smtp_vrfy },
+  { "add_environment",          opt_stringptr,   &add_environment },
   { "admin_groups",             opt_gidlist,     &admin_groups },
   { "allow_domain_literals",    opt_bool,        &allow_domain_literals },
   { "allow_mx_to_ip",           opt_bool,        &allow_mx_to_ip },
@@ -178,6 +202,7 @@ static optionlist optionlist_config[] = {
   { "bounce_message_file",      opt_stringptr,   &bounce_message_file },
   { "bounce_message_text",      opt_stringptr,   &bounce_message_text },
   { "bounce_return_body",       opt_bool,        &bounce_return_body },
+  { "bounce_return_linesize_limit", opt_mkint,   &bounce_return_linesize_limit },
   { "bounce_return_message",    opt_bool,        &bounce_return_message },
   { "bounce_return_size_limit", opt_mkint,       &bounce_return_size_limit },
   { "bounce_sender_authentication",opt_stringptr,&bounce_sender_authentication },
@@ -191,6 +216,7 @@ static optionlist optionlist_config[] = {
   { "check_rfc2047_length",     opt_bool,        &check_rfc2047_length },
   { "check_spool_inodes",       opt_int,         &check_spool_inodes },
   { "check_spool_space",        opt_Kint,        &check_spool_space },
+  { "chunking_advertise_hosts", opt_stringptr,  &chunking_advertise_hosts },
   { "daemon_smtp_port",         opt_stringptr|opt_hidden, &daemon_smtp_port },
   { "daemon_smtp_ports",        opt_stringptr,   &daemon_smtp_port },
   { "daemon_startup_retries",   opt_int,         &daemon_startup_retries },
@@ -200,6 +226,7 @@ static optionlist optionlist_config[] = {
   { "dccifd_address",           opt_stringptr,   &dccifd_address },
   { "dccifd_options",           opt_stringptr,   &dccifd_options },
 #endif
+  { "debug_store",              opt_bool,        &debug_store },
   { "delay_warning",            opt_timelist,    &delay_warning },
   { "delay_warning_condition",  opt_stringptr,   &delay_warning_condition },
   { "deliver_drop_privilege",   opt_bool,        &deliver_drop_privilege },
@@ -225,17 +252,19 @@ static optionlist optionlist_config[] = {
   { "dns_ipv4_lookup",          opt_stringptr,   &dns_ipv4_lookup },
   { "dns_retrans",              opt_time,        &dns_retrans },
   { "dns_retry",                opt_int,         &dns_retry },
+  { "dns_trust_aa",             opt_stringptr,   &dns_trust_aa },
   { "dns_use_edns0",            opt_int,         &dns_use_edns0 },
- /* This option is now a no-op, retained for compability */
+ /* This option is now a no-op, retained for compatibility */
   { "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 },
   { "errors_reply_to",          opt_stringptr,   &errors_reply_to },
+#ifndef DISABLE_EVENT
+  { "event_action",             opt_stringptr,   &event_action },
+#endif
   { "exim_group",               opt_gid,         &exim_gid },
   { "exim_path",                opt_stringptr,   &exim_path },
   { "exim_user",                opt_uid,         &exim_uid },
@@ -248,11 +277,6 @@ static optionlist optionlist_config[] = {
 #ifdef SUPPORT_TLS
   { "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 },
 #endif
   { "header_line_maxsize",      opt_int,         &header_line_maxsize },
   { "header_maxsize",           opt_int,         &header_maxsize },
@@ -267,6 +291,9 @@ static optionlist optionlist_config[] = {
   { "host_lookup_order",        opt_stringptr,   &host_lookup_order },
   { "host_reject_connection",   opt_stringptr,   &host_reject_connection },
   { "hosts_connection_nolog",   opt_stringptr,   &hosts_connection_nolog },
+#ifdef SUPPORT_PROXY
+  { "hosts_proxy",              opt_stringptr,   &hosts_proxy },
+#endif
   { "hosts_treat_as_local",     opt_stringptr,   &hosts_treat_as_local },
 #ifdef LOOKUP_IBASE
   { "ibase_servers",            opt_stringptr,   &ibase_servers },
@@ -274,6 +301,7 @@ static optionlist optionlist_config[] = {
   { "ignore_bounce_errors_after", opt_time,      &ignore_bounce_errors_after },
   { "ignore_fromline_hosts",    opt_stringptr,   &ignore_fromline_hosts },
   { "ignore_fromline_local",    opt_bool,        &ignore_fromline_local },
+  { "keep_environment",         opt_stringptr,   &keep_environment },
   { "keep_malformed",           opt_time,        &keep_malformed },
 #ifdef LOOKUP_LDAP
   { "ldap_ca_cert_dir",         opt_stringptr,   &eldap_ca_cert_dir },
@@ -322,6 +350,7 @@ static optionlist optionlist_config[] = {
 #ifdef EXIM_PERL
   { "perl_at_start",            opt_bool,        &opt_perl_at_start },
   { "perl_startup",             opt_stringptr,   &opt_perl_startup },
+  { "perl_taintmode",           opt_bool,        &opt_perl_taintmode },
 #endif
 #ifdef LOOKUP_PGSQL
   { "pgsql_servers",            opt_stringptr,   &pgsql_servers },
@@ -336,9 +365,6 @@ static optionlist optionlist_config[] = {
   { "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 },
@@ -349,7 +375,7 @@ static optionlist optionlist_config[] = {
   { "queue_only_load_latch",    opt_bool,        &queue_only_load_latch },
   { "queue_only_override",      opt_bool,        &queue_only_override },
   { "queue_run_in_order",       opt_bool,        &queue_run_in_order },
-  { "queue_run_max",            opt_int,         &queue_run_max },
+  { "queue_run_max",            opt_stringptr,   &queue_run_max },
   { "queue_smtp_domains",       opt_stringptr,   &queue_smtp_domains },
   { "receive_timeout",          opt_time,        &receive_timeout },
   { "received_header_text",     opt_stringptr,   &received_header_text },
@@ -357,7 +383,7 @@ static optionlist optionlist_config[] = {
   { "recipient_unqualified_hosts", opt_stringptr, &recipient_unqualified_hosts },
   { "recipients_max",           opt_int,         &recipients_max },
   { "recipients_max_reject",    opt_bool,        &recipients_max_reject },
-#ifdef EXPERIMENTAL_REDIS
+#ifdef LOOKUP_REDIS
   { "redis_servers",            opt_stringptr,   &redis_servers },
 #endif
   { "remote_max_parallel",      opt_int,         &remote_max_parallel },
@@ -369,6 +395,7 @@ static optionlist optionlist_config[] = {
   { "rfc1413_hosts",            opt_stringptr,   &rfc1413_hosts },
   { "rfc1413_query_timeout",    opt_time,        &rfc1413_query_timeout },
   { "sender_unqualified_hosts", opt_stringptr,   &sender_unqualified_hosts },
+  { "slow_lookup_log",          opt_int,         &slow_lookup_log },
   { "smtp_accept_keepalive",    opt_bool,        &smtp_accept_keepalive },
   { "smtp_accept_max",          opt_int,         &smtp_accept_max },
   { "smtp_accept_max_nonmail",  opt_int,         &smtp_accept_max_nonmail },
@@ -391,9 +418,12 @@ static optionlist optionlist_config[] = {
   { "smtp_ratelimit_hosts",     opt_stringptr,   &smtp_ratelimit_hosts },
   { "smtp_ratelimit_mail",      opt_stringptr,   &smtp_ratelimit_mail },
   { "smtp_ratelimit_rcpt",      opt_stringptr,   &smtp_ratelimit_rcpt },
-  { "smtp_receive_timeout",     opt_time,        &smtp_receive_timeout },
+  { "smtp_receive_timeout",     opt_func,        &fn_smtp_receive_timeout },
   { "smtp_reserve_hosts",       opt_stringptr,   &smtp_reserve_hosts },
   { "smtp_return_error_details",opt_bool,        &smtp_return_error_details },
+#ifdef SUPPORT_I18N
+  { "smtputf8_advertise_hosts", opt_stringptr,   &smtputf8_advertise_hosts },
+#endif
 #ifdef WITH_CONTENT_SCAN
   { "spamd_address",            opt_stringptr,   &spamd_address },
 #endif
@@ -419,6 +449,7 @@ static optionlist optionlist_config[] = {
   { "strip_trailing_dot",       opt_bool,        &strip_trailing_dot },
   { "syslog_duplication",       opt_bool,        &syslog_duplication },
   { "syslog_facility",          opt_stringptr,   &syslog_facility_str },
+  { "syslog_pid",               opt_bool,        &syslog_pid },
   { "syslog_processname",       opt_stringptr,   &syslog_processname },
   { "syslog_timestamp",         opt_bool,        &syslog_timestamp },
   { "system_filter",            opt_stringptr,   &system_filter },
@@ -434,12 +465,13 @@ static optionlist optionlist_config[] = {
 #endif
   { "timeout_frozen_after",     opt_time,        &timeout_frozen_after },
   { "timezone",                 opt_stringptr,   &timezone_string },
-#ifdef SUPPORT_TLS
   { "tls_advertise_hosts",      opt_stringptr,   &tls_advertise_hosts },
+#ifdef SUPPORT_TLS
   { "tls_certificate",          opt_stringptr,   &tls_certificate },
   { "tls_crl",                  opt_stringptr,   &tls_crl },
   { "tls_dh_max_bits",          opt_int,         &tls_dh_max_bits },
   { "tls_dhparam",              opt_stringptr,   &tls_dhparam },
+  { "tls_eccurve",              opt_stringptr,   &tls_eccurve },
 # ifndef DISABLE_OCSP
   { "tls_ocsp_file",            opt_stringptr,   &tls_ocsp_file },
 # endif
@@ -462,8 +494,7 @@ static optionlist optionlist_config[] = {
   { "write_rejectlog",          opt_bool,        &write_rejectlog }
 };
 
-static int optionlist_config_size =
-  sizeof(optionlist_config)/sizeof(optionlist);
+static int optionlist_config_size = nelem(optionlist_config);
 
 
 
@@ -488,13 +519,13 @@ int i;
 router_instance *r;
 transport_instance *t;
 
-for (i = 0; i < optionlist_config_size; i++)
+for (i = 0; i < nelem(optionlist_config); i++)
   if (p == optionlist_config[i].value) return US optionlist_config[i].name;
 
-for (r = routers; r != NULL; r = r->next)
+for (r = routers; r; r = r->next)
   {
   router_info *ri = r->info;
-  for (i = 0; i < ri->options_count[0]; i++)
+  for (i = 0; i < *ri->options_count; i++)
     {
     if ((ri->options[i].type & opt_mask) != opt_stringptr) continue;
     if (p == (char *)(r->options_block) + (long int)(ri->options[i].value))
@@ -502,14 +533,19 @@ for (r = routers; r != NULL; r = r->next)
     }
   }
 
-for (t = transports; t != NULL; t = t->next)
+for (t = transports; t; t = t->next)
   {
   transport_info *ti = t->info;
-  for (i = 0; i < ti->options_count[0]; i++)
+  for (i = 0; i < *ti->options_count; i++)
     {
-    if ((ti->options[i].type & opt_mask) != opt_stringptr) continue;
-    if (p == (char *)(t->options_block) + (long int)(ti->options[i].value))
-      return US ti->options[i].name;
+    optionlist * op = &ti->options[i];
+    if ((op->type & opt_mask) != opt_stringptr) continue;
+    if (p == (  op->type & opt_public
+            ? (char *)t
+            : (char *)t->options_block
+            )
+            + (long int)op->value)
+       return US op->name;
     }
   }
 
@@ -523,6 +559,45 @@ return US"";
 *       Deal with an assignment to a macro       *
 *************************************************/
 
+/* We have a new definition. The macro_item structure includes a final vector
+called "name" which is one byte long. Thus, adding "namelen" gives us enough
+room to store the "name" string.
+If a builtin macro we place at head of list, else tail.  This lets us lazy-create
+builtins. */
+
+macro_item *
+macro_create(const uschar * name, const uschar * val,
+  BOOL command_line, BOOL builtin)
+{
+unsigned namelen = Ustrlen(name);
+macro_item * m = store_get(sizeof(macro_item) + namelen);
+
+/* fprintf(stderr, "%s: '%s' '%s'\n", __FUNCTION__, name, val) */
+if (!macros)
+  {
+  macros = m;
+  mlast = m;
+  m->next = NULL;
+  }
+else if (builtin)
+  {
+  m->next = macros;
+  macros = m;
+  }
+else
+  {
+  mlast->next = m;
+  mlast = m;
+  m->next = NULL;
+  }
+m->command_line = command_line;
+m->namelen = namelen;
+m->replacement = string_copy(val);
+Ustrcpy(m->name, name);
+return m;
+}
+
+
 /* This function is called when a line that starts with an upper case letter is
 encountered. The argument "line" should contain a complete logical line, and
 start with the first letter of the macro name. The macro name and the
@@ -542,7 +617,6 @@ uschar name[64];
 int namelen = 0;
 BOOL redef = FALSE;
 macro_item *m;
-macro_item *mlast = NULL;
 
 while (isalnum(*s) || *s == '_')
   {
@@ -568,13 +642,13 @@ while (isspace(*s)) s++;
 just skip this definition. It's an error to attempt to redefine a macro without
 redef set to TRUE, or to redefine a macro when it hasn't been defined earlier.
 It is also an error to define a macro whose name begins with the name of a
-previously defined macro. Note: it is documented that the other way round
-works. */
+previously defined macro.  This is the requirement that make using a tree
+for macros hard; we must check all macros for the substring.  Perhaps a
+sorted list, and a bsearch, would work?
+Note: it is documented that the other way round works. */
 
-for (m = macros; m != NULL; m = m->next)
+for (m = macros; m; m = m->next)
   {
-  int len = Ustrlen(m->name);
-
   if (Ustrcmp(m->name, name) == 0)
     {
     if (!m->command_line && !redef)
@@ -583,7 +657,7 @@ for (m = macros; m != NULL; m = m->next)
     break;
     }
 
-  if (len < namelen && Ustrstr(name, m->name) != NULL)
+  if (m->namelen < namelen && Ustrstr(name, m->name) != NULL)
     log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "\"%s\" cannot be defined as "
       "a macro because previously defined macro \"%s\" is a substring",
       name, m->name);
@@ -591,49 +665,243 @@ for (m = macros; m != NULL; m = m->next)
   /* We cannot have this test, because it is documented that a substring
   macro is permitted (there is even an example).
   *
-  * if (len > namelen && Ustrstr(m->name, name) != NULL)
+  * if (m->namelen > namelen && Ustrstr(m->name, name) != NULL)
   *   log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "\"%s\" cannot be defined as "
   *     "a macro because it is a substring of previously defined macro \"%s\"",
   *     name, m->name);
   */
-
-  mlast = m;
   }
 
 /* Check for an overriding command-line definition. */
 
-if (m != NULL && m->command_line) return;
+if (m && m->command_line) return;
 
 /* Redefinition must refer to an existing macro. */
 
 if (redef)
-  {
-  if (m == NULL)
+  if (m)
+    m->replacement = string_copy(s);
+  else
     log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "can't redefine an undefined macro "
       "\"%s\"", name);
-  }
-
-/* We have a new definition. The macro_item structure includes a final vector
-called "name" which is one byte long. Thus, adding "namelen" gives us enough
-room to store the "name" string. */
 
+/* We have a new definition. */
 else
-  {
-  m = store_get(sizeof(macro_item) + namelen);
-  if (macros == NULL) macros = m; else mlast->next = m;
-  Ustrncpy(m->name, name, namelen);
-  m->name[namelen] = 0;
-  m->next = NULL;
-  m->command_line = FALSE;
-  }
+  (void) macro_create(name, s, FALSE, FALSE);
+}
+
+
+
+
+
+/*************************************************/
+/* Create compile-time feature macros */
+static void
+readconf_features(void)
+{
+/* Probably we could work out a static initialiser for wherever
+macros are stored, but this will do for now. Some names are awkward
+due to conflicts with other common macros. */
+
+#ifdef SUPPORT_CRYPTEQ
+  macro_create(US"_HAVE_CRYPTEQ", US"y", FALSE, TRUE);
+#endif
+#if HAVE_ICONV
+  macro_create(US"_HAVE_ICONV", US"y", FALSE, TRUE);
+#endif
+#if HAVE_IPV6
+  macro_create(US"_HAVE_IPV6", US"y", FALSE, TRUE);
+#endif
+#ifdef HAVE_SETCLASSRESOURCES
+  macro_create(US"_HAVE_SETCLASSRESOURCES", US"y", FALSE, TRUE);
+#endif
+#ifdef SUPPORT_PAM
+  macro_create(US"_HAVE_PAM", US"y", FALSE, TRUE);
+#endif
+#ifdef EXIM_PERL
+  macro_create(US"_HAVE_PERL", US"y", FALSE, TRUE);
+#endif
+#ifdef EXPAND_DLFUNC
+  macro_create(US"_HAVE_DLFUNC", US"y", FALSE, TRUE);
+#endif
+#ifdef USE_TCP_WRAPPERS
+  macro_create(US"_HAVE_TCPWRAPPERS", US"y", FALSE, TRUE);
+#endif
+#ifdef SUPPORT_TLS
+  macro_create(US"_HAVE_TLS", US"y", FALSE, TRUE);
+# ifdef USE_GNUTLS
+  macro_create(US"_HAVE_GNUTLS", US"y", FALSE, TRUE);
+# else
+  macro_create(US"_HAVE_OPENSSL", US"y", FALSE, TRUE);
+# endif
+#endif
+#ifdef SUPPORT_TRANSLATE_IP_ADDRESS
+  macro_create(US"_HAVE_TRANSLATE_IP_ADDRESS", US"y", FALSE, TRUE);
+#endif
+#ifdef SUPPORT_MOVE_FROZEN_MESSAGES
+  macro_create(US"_HAVE_MOVE_FROZEN_MESSAGES", US"y", FALSE, TRUE);
+#endif
+#ifdef WITH_CONTENT_SCAN
+  macro_create(US"_HAVE_CONTENT_SCANNING", US"y", FALSE, TRUE);
+#endif
+#ifndef DISABLE_DKIM
+  macro_create(US"_HAVE_DKIM", US"y", FALSE, TRUE);
+#endif
+#ifndef DISABLE_DNSSEC
+  macro_create(US"_HAVE_DNSSEC", US"y", FALSE, TRUE);
+#endif
+#ifndef DISABLE_EVENT
+  macro_create(US"_HAVE_EVENT", US"y", FALSE, TRUE);
+#endif
+#ifdef SUPPORT_I18N
+  macro_create(US"_HAVE_I18N", US"y", FALSE, TRUE);
+#endif
+#ifndef DISABLE_OCSP
+  macro_create(US"_HAVE_OCSP", US"y", FALSE, TRUE);
+#endif
+#ifndef DISABLE_PRDR
+  macro_create(US"_HAVE_PRDR", US"y", FALSE, TRUE);
+#endif
+#ifdef SUPPORT_PROXY
+  macro_create(US"_HAVE_PROXY", US"y", FALSE, TRUE);
+#endif
+#ifdef SUPPORT_SOCKS
+  macro_create(US"_HAVE_SOCKS", US"y", FALSE, TRUE);
+#endif
+#ifdef TCP_FASTOPEN
+  macro_create(US"_HAVE_TCP_FASTOPEN", US"y", FALSE, TRUE);
+#endif
+#ifdef EXPERIMENTAL_LMDB
+  macro_create(US"_HAVE_LMDB", US"y", FALSE, TRUE);
+#endif
+#ifdef EXPERIMENTAL_SPF
+  macro_create(US"_HAVE_SPF", US"y", FALSE, TRUE);
+#endif
+#ifdef EXPERIMENTAL_SRS
+  macro_create(US"_HAVE_SRS", US"y", FALSE, TRUE);
+#endif
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+  macro_create(US"_HAVE_BRIGHTMAIL", US"y", FALSE, TRUE);
+#endif
+#ifdef EXPERIMENTAL_DANE
+  macro_create(US"_HAVE_DANE", US"y", FALSE, TRUE);
+#endif
+#ifdef EXPERIMENTAL_DCC
+  macro_create(US"_HAVE_DCC", US"y", FALSE, TRUE);
+#endif
+#ifdef EXPERIMENTAL_DMARC
+  macro_create(US"_HAVE_DMARC", US"y", FALSE, TRUE);
+#endif
+#ifdef EXPERIMENTAL_DSN_INFO
+  macro_create(US"_HAVE_DSN_INFO", US"y", FALSE, TRUE);
+#endif
+
+#ifdef LOOKUP_LSEARCH
+  macro_create(US"_HAVE_LOOKUP_LSEARCH", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_CDB
+  macro_create(US"_HAVE_LOOKUP_CDB", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_DBM
+  macro_create(US"_HAVE_LOOKUP_DBM", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_DNSDB
+  macro_create(US"_HAVE_LOOKUP_DNSDB", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_DSEARCH
+  macro_create(US"_HAVE_LOOKUP_DSEARCH", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_IBASE
+  macro_create(US"_HAVE_LOOKUP_IBASE", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_LDAP
+  macro_create(US"_HAVE_LOOKUP_LDAP", US"y", FALSE, TRUE);
+#endif
+#ifdef EXPERIMENTAL_LMDB
+  macro_create(US"_HAVE_LOOKUP_LMDB", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_MYSQL
+  macro_create(US"_HAVE_LOOKUP_MYSQL", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_NIS
+  macro_create(US"_HAVE_LOOKUP_NIS", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_NISPLUS
+  macro_create(US"_HAVE_LOOKUP_NISPLUS", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_ORACLE
+  macro_create(US"_HAVE_LOOKUP_ORACLE", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_PASSWD
+  macro_create(US"_HAVE_LOOKUP_PASSWD", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_PGSQL
+  macro_create(US"_HAVE_LOOKUP_PGSQL", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_REDIS
+  macro_create(US"_HAVE_LOOKUP_REDIS", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_SQLITE
+  macro_create(US"_HAVE_LOOKUP_SQLITE", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_TESTDB
+  macro_create(US"_HAVE_LOOKUP_TESTDB", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_WHOSON
+  macro_create(US"_HAVE_LOOKUP_WHOSON", US"y", FALSE, TRUE);
+#endif
+
+#ifdef TRANSPORT_APPENDFILE
+# ifdef SUPPORT_MAILDIR
+  macro_create(US"_HAVE_TRANSPORT_APPEND_MAILDIR", US"y", FALSE, TRUE);
+# endif
+# ifdef SUPPORT_MAILSTORE
+  macro_create(US"_HAVE_TRANSPORT_APPEND_MAILSTORE", US"y", FALSE, TRUE);
+# endif
+# ifdef SUPPORT_MBX
+  macro_create(US"_HAVE_TRANSPORT_APPEND_MBX", US"y", FALSE, TRUE);
+# endif
+#endif
+}
 
-/* Set the value of the new or redefined macro */
 
-m->replacement = string_copy(s);
+void
+readconf_options_from_list(optionlist * opts, unsigned nopt, const uschar * section, uschar * group)
+{
+int i;
+const uschar * s;
+
+/* The 'previously-defined-substring' rule for macros in config file
+lines is done so for these builtin macros: we know that the table
+we source from is in strict alpha order, hence the builtins portion
+of the macros list is in reverse-alpha (we prepend them) - so longer
+macros that have substrings are always discovered first during
+expansion. */
+
+for (i = 0; i < nopt; i++)  if (*(s = US opts[i].name) && *s != '*')
+  if (group)
+    macro_create(string_sprintf("_OPT_%T_%T_%T", section, group, s), US"y", FALSE, TRUE);
+  else
+    macro_create(string_sprintf("_OPT_%T_%T", section, s), US"y", FALSE, TRUE);
 }
 
 
+static void
+readconf_options(void)
+{
+readconf_options_from_list(optionlist_config, nelem(optionlist_config), US"MAIN", NULL);
+readconf_options_routers();
+readconf_options_transports();
+readconf_options_auths();
+}
 
+static void
+macros_create_builtin(void)
+{
+readconf_features();
+readconf_options();
+macros_builtin_created = TRUE;
+}
 
 
 /*************************************************
@@ -680,8 +948,11 @@ for (;;)
       (void)fclose(config_file);
       config_file = config_file_stack->file;
       config_filename = config_file_stack->filename;
+      config_directory = config_file_stack->directory;
       config_lineno = config_file_stack->lineno;
       config_file_stack = config_file_stack->next;
+      if (config_lines)
+        save_config_position(config_filename, config_lineno);
       continue;
       }
 
@@ -699,6 +970,9 @@ for (;;)
   config_lineno++;
   newlen = len + Ustrlen(big_buffer + len);
 
+  if (config_lines && config_lineno == 1)
+    save_config_position(config_filename, config_lineno);
+
   /* Handle pathologically long physical lines - yes, it did happen - by
   extending big_buffer at this point. The code also copes with very long
   logical lines. */
@@ -740,11 +1014,28 @@ for (;;)
     if (*s != '=') s = ss;          /* Not a macro definition */
     }
 
+  /* If the builtin macros are not yet defined, and the line contains an
+  underscrore followed by an one of the three possible chars used by
+  builtins, create them. */
+
+  if (!macros_builtin_created)
+    {
+    const uschar * t, * p;
+    uschar c;
+    for (t = s; (p = CUstrchr(t, '_')); t = p+1)
+      if (c = p[1], c == 'O' || c == 'D' || c == 'H')
+       {
+/* fprintf(stderr, "%s: builtins create triggered by '%s'\n", __FUNCTION__, s); */
+       macros_create_builtin();
+       break;
+       }
+    }
+
   /* For each defined macro, scan the line (from after XXX= if present),
   replacing all occurrences of the macro. */
 
   macro_found = FALSE;
-  for (m = macros; m != NULL; m = m->next)
+  for (m = macros; m; m = m->next)
     {
     uschar *p, *pp;
     uschar *t = s;
@@ -752,12 +1043,12 @@ for (;;)
     while ((p = Ustrstr(t, m->name)) != NULL)
       {
       int moveby;
-      int namelen = Ustrlen(m->name);
       int replen = Ustrlen(m->replacement);
 
+/* fprintf(stderr, "%s: matched '%s' in '%s'\n", __FUNCTION__, m->name, t) */
       /* Expand the buffer if necessary */
 
-      while (newlen - namelen + replen + 1 > big_buffer_size)
+      while (newlen - m->namelen + replen + 1 > big_buffer_size)
         {
         int newsize = big_buffer_size + BIG_BUFFER_SIZE;
         uschar *newbuffer = store_malloc(newsize);
@@ -775,9 +1066,8 @@ for (;;)
       copying in the replacement text. Don't rescan the replacement for this
       same macro. */
 
-      pp = p + namelen;
-      moveby = replen - namelen;
-      if (moveby != 0)
+      pp = p + m->namelen;
+      if ((moveby = replen - m->namelen) != 0)
         {
         memmove(p + replen, pp, (big_buffer + newlen) - pp + 1);
         newlen += moveby;
@@ -881,24 +1171,38 @@ for (;;)
       }
     *t = 0;
 
+    /* We allow relative file names. For security reasons currently
+    relative names not allowed with .include_if_exists. For .include_if_exists
+    we need to check the permissions/ownership of the containing folder */
     if (*ss != '/')
-      log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, ".include specifies a non-"
-        "absolute path \"%s\"", ss);
+      if (include_if_exists) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, ".include specifies a non-"
+          "absolute path \"%s\"", ss);
+      else
+        {
+        int offset = 0;
+        int size = 0;
+        ss = string_append(NULL, &size, &offset, 3, config_directory, "/", ss);
+        ss[offset] = '\0';  /* string_append() does not zero terminate the string! */
+        }
 
     if (include_if_exists != 0 && (Ustat(ss, &statbuf) != 0)) continue;
 
+    if (config_lines)
+      save_config_position(config_filename, config_lineno);
     save = store_get(sizeof(config_file_item));
     save->next = config_file_stack;
     config_file_stack = save;
     save->file = config_file;
     save->filename = config_filename;
+    save->directory = config_directory;
     save->lineno = config_lineno;
 
-    config_file = Ufopen(ss, "rb");
-    if (config_file == NULL)
+    if (!(config_file = Ufopen(ss, "rb")))
       log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "failed to open included "
         "configuration file %s", ss);
+
     config_filename = string_copy(ss);
+    config_directory = string_copyn(ss, CUstrrchr(ss, '/') - ss);
     config_lineno = 0;
     continue;
     }
@@ -943,6 +1247,10 @@ next_section, truncate it. It will be unrecognized later, because all the known
 section names do fit. Leave space for pluralizing. */
 
 s = big_buffer + startoffset;            /* First non-space character */
+
+if (config_lines)
+  save_config_line(s);
+
 if (strncmpic(s, US"begin ", 6) == 0)
   {
   s += 6;
@@ -1021,7 +1329,7 @@ Returns:        the time value, or -1 on syntax error
 */
 
 int
-readconf_readtime(uschar *s, int terminator, BOOL return_msec)
+readconf_readtime(const uschar *s, int terminator, BOOL return_msec)
 {
 int yield = 0;
 for (;;)
@@ -1030,7 +1338,7 @@ for (;;)
   double fraction;
 
   if (!isdigit(*s)) return -1;
-  (void)sscanf(CS s, "%d%n", &value, &count);
+  (void)sscanf(CCS s, "%d%n", &value, &count);
   s += count;
 
   switch (*s)
@@ -1044,7 +1352,7 @@ for (;;)
 
     case '.':
     if (!return_msec) return -1;
-    (void)sscanf(CS s, "%lf%n", &fraction, &count);
+    (void)sscanf(CCS s, "%lf%n", &fraction, &count);
     s += count;
     if (*s++ != 's') return -1;
     yield += (int)(fraction * 1000.0);
@@ -1076,7 +1384,7 @@ Returns:      the value, or -1 on error
 */
 
 static int
-readconf_readfixed(uschar *s, int terminator)
+readconf_readfixed(const uschar *s, int terminator)
 {
 int yield = 0;
 int value, count;
@@ -1121,9 +1429,10 @@ while (last > first)
   {
   int middle = (first + last)/2;
   int c = Ustrcmp(name, ol[middle].name);
+
   if (c == 0) return ol + middle;
-    else if (c > 0) first = middle + 1;
-      else last = middle;
+  else if (c > 0) first = middle + 1;
+  else last = middle;
   }
 return NULL;
 }
@@ -1182,7 +1491,7 @@ Returns:     doesn't return; dies
 */
 
 static void
-extra_chars_error(uschar *s, uschar *t1, uschar *t2, uschar *t3)
+extra_chars_error(const uschar *s, const uschar *t1, const uschar *t2, const uschar *t3)
 {
 uschar *comment = US"";
 if (*s == '#') comment = US" (# is comment only at line start)";
@@ -1218,7 +1527,7 @@ Returns:      the control block for the parsed rule.
 */
 
 static rewrite_rule *
-readconf_one_rewrite(uschar *p, int *existflags, BOOL isglobal)
+readconf_one_rewrite(const uschar *p, int *existflags, BOOL isglobal)
 {
 rewrite_rule *next = store_get(sizeof(rewrite_rule));
 
@@ -1324,10 +1633,10 @@ Returns:    pointer to the string
 */
 
 static uschar *
-read_string(uschar *s, uschar *name)
+read_string(const uschar *s, const uschar *name)
 {
 uschar *yield;
-uschar *ss;
+const uschar *ss;
 
 if (*s != '\"') return string_copy(s);
 
@@ -1344,6 +1653,24 @@ return yield;
 }
 
 
+/*************************************************
+*            Custom-handler options              *
+*************************************************/
+static void
+fn_smtp_receive_timeout(const uschar * name, const uschar * str)
+{
+if (*str == '$')
+  smtp_receive_timeout_s = string_copy(str);
+else
+  {
+  /* "smtp_receive_timeout",     opt_time,        &smtp_receive_timeout */
+  smtp_receive_timeout = readconf_readtime(str, 0, FALSE);
+  if (smtp_receive_timeout < 0)
+    log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "invalid time value for %s",
+      name);
+  }
+}
+
 /*************************************************
 *            Handle option line                  *
 *************************************************/
@@ -1402,7 +1729,6 @@ int intbase = 0;
 uschar *inttype = US"";
 uschar *sptr;
 uschar *s = buffer;
-uschar *saved_condition, *strtemp;
 uschar **str_target;
 uschar name[64];
 uschar name2[64];
@@ -1449,9 +1775,7 @@ if (Ustrncmp(name, "not_", 4) == 0)
 /* Search the list for the given name. A non-existent name, or an option that
 is set twice, is a disaster. */
 
-ol = find_option(name + offset, oltop, last);
-
-if (ol == NULL)
+if (!(ol = find_option(name + offset, oltop, last)))
   {
   if (unknown_txt == NULL) return FALSE;
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, CS unknown_txt, name);
@@ -1480,7 +1804,7 @@ if (type < opt_bool || type > opt_bool_last)
   }
 
 /* If a boolean wasn't preceded by "no[t]_" it can be followed by = and
-true/false/yes/no, or, in the case of opt_expanded_bool, a general string that
+true/false/yes/no, or, in the case of opt_expand_bool, a general string that
 ultimately expands to one of those values. */
 
 else if (*s != 0 && (offset != 0 || *s != '='))
@@ -1539,19 +1863,18 @@ switch (type)
     control block and flags word. */
 
     case opt_stringptr:
-    if (data_block == NULL)
-      str_target = (uschar **)(ol->value);
-    else
-      str_target = (uschar **)((uschar *)data_block + (long int)(ol->value));
+    str_target = data_block ? USS (US data_block + (long int)(ol->value))
+                           : USS (ol->value);
     if (ol->type & opt_rep_con)
       {
+      uschar * saved_condition;
       /* We already have a condition, we're conducting a crude hack to let
       multiple condition rules be chained together, despite storing them in
       text form. */
-      saved_condition = *str_target;
-      strtemp = string_sprintf("${if and{{bool_lax{%s}}{bool_lax{%s}}}}",
-          saved_condition, sptr);
-      *str_target = string_copy_malloc(strtemp);
+      *str_target = string_copy_malloc( (saved_condition = *str_target)
+       ? string_sprintf("${if and{{bool_lax{%s}}{bool_lax{%s}}}}",
+           saved_condition, sptr)
+       : sptr);
       /* TODO(pdp): there is a memory leak here and just below
       when we set 3 or more conditions; I still don't
       understand the store mechanism enough to know
@@ -1568,18 +1891,16 @@ switch (type)
       }
     else if (ol->type & opt_rep_str)
       {
-      uschar sep = Ustrncmp(name, "headers_add", 11)==0 ? '\n' : ':';
-      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);
+      uschar sep_o = Ustrncmp(name, "headers_add", 11)==0 ? '\n' : ':';
+      int    sep_i = -(int)sep_o;
+      const uschar * list = sptr;
+      uschar * s;
+      uschar * list_o = *str_target;
+
+      while ((s = string_nextinlist(&list, &sep_i, NULL, 0)))
+       list_o = string_append_listele(list_o, sep_o, s);
+      if (list_o)
+       *str_target = string_copy_malloc(list_o);
       }
     else
       {
@@ -1589,10 +1910,10 @@ switch (type)
     break;
 
     case opt_rewrite:
-    if (data_block == NULL)
-      *((uschar **)(ol->value)) = sptr;
+    if (data_block)
+      *USS (US data_block + (long int)(ol->value)) = sptr;
     else
-      *((uschar **)((uschar *)data_block + (long int)(ol->value))) = sptr;
+      *USS (ol->value) = sptr;
     freesptr = FALSE;
     if (type == opt_rewrite)
       {
@@ -1622,8 +1943,7 @@ switch (type)
         flagptr = (int *)((uschar *)data_block + (long int)(ol3->value));
         }
 
-      while ((p = string_nextinlist(&sptr, &sep, big_buffer, BIG_BUFFER_SIZE))
-              != NULL)
+      while ((p = string_nextinlist(CUSS &sptr, &sep, big_buffer, BIG_BUFFER_SIZE)))
         {
         rewrite_rule *next = readconf_one_rewrite(p, flagptr, FALSE);
         *chain = next;
@@ -1747,8 +2067,8 @@ switch (type)
       int count = 1;
       uid_t *list;
       int ptr = 0;
-      uschar *p;
-      uschar *op = expand_string (sptr);
+      const uschar *p;
+      const uschar *op = expand_string (sptr);
 
       if (op == NULL)
         log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "failed to expand %s: %s",
@@ -1788,8 +2108,8 @@ switch (type)
       int count = 1;
       gid_t *list;
       int ptr = 0;
-      uschar *p;
-      uschar *op = expand_string (sptr);
+      const uschar *p;
+      const uschar *op = expand_string (sptr);
 
       if (op == NULL)
         log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "failed to expand %s: %s",
@@ -1928,7 +2248,7 @@ switch (type)
   inttype = US"octal ";
 
   /*  Integer: a simple(ish) case; allow octal and hex formats, and
-  suffixes K and M. The different types affect output, not input. */
+  suffixes K, M and G. The different types affect output, not input. */
 
   case opt_mkint:
   case opt_int:
@@ -1944,7 +2264,6 @@ switch (type)
         inttype, name);
 
     if (errno != ERANGE)
-      {
       if (tolower(*endptr) == 'k')
         {
         if (lvalue > INT_MAX/1024 || lvalue < INT_MIN/1024) errno = ERANGE;
@@ -1958,7 +2277,13 @@ switch (type)
         else lvalue *= 1024*1024;
         endptr++;
         }
-      }
+      else if (tolower(*endptr) == 'g')
+        {
+        if (lvalue > INT_MAX/(1024*1024*1024) || lvalue < INT_MIN/(1024*1024*1024))
+          errno = ERANGE;
+        else lvalue *= 1024*1024*1024;
+        endptr++;
+        }
 
     if (errno == ERANGE || lvalue > INT_MAX || lvalue < INT_MIN)
       log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
@@ -1977,8 +2302,9 @@ switch (type)
     *((int *)((uschar *)data_block + (long int)(ol->value))) = value;
   break;
 
-  /*  Integer held in K: again, allow octal and hex formats, and suffixes K and
-  M. */
+  /*  Integer held in K: again, allow octal and hex formats, and suffixes K, M
+  and G. */
+  /*XXX consider moving to int_eximarith_t (but mind the overflow test 0415) */
 
   case opt_Kint:
     {
@@ -1991,22 +2317,26 @@ switch (type)
         inttype, name);
 
     if (errno != ERANGE)
-      {
-      if (tolower(*endptr) == 'm')
+      if (tolower(*endptr) == 'g')
         {
-        if (value > INT_MAX/1024 || value < INT_MIN/1024) errno = ERANGE;
-          else value *= 1024;
+        if (value > INT_MAX/(1024*1024) || value < INT_MIN/(1024*1024))
+         errno = ERANGE;
+       else
+         value *= 1024*1024;
         endptr++;
         }
-      else if (tolower(*endptr) == 'k')
+      else if (tolower(*endptr) == 'm')
         {
+        if (value > INT_MAX/1024 || value < INT_MIN/1024)
+         errno = ERANGE;
+       else
+         value *= 1024;
         endptr++;
         }
+      else if (tolower(*endptr) == 'k')
+        endptr++;
       else
-        {
         value = (value + 512)/1024;
-        }
-      }
 
     if (errno == ERANGE) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
       "absolute value of integer \"%s\" is too large (overflow)", s);
@@ -2037,6 +2367,11 @@ switch (type)
   if (value < 0) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
     "integer \"%s\" is too large (overflow)", s);
 
+  /* We get a coverity error here for using count, as it derived
+  from the tainted buffer pointed to by s, as parsed by sscanf().
+  By the definition of sscanf we must be accessing between start
+  and end of s (assuming it is nul-terminated...) so ignore the error.  */
+  /* coverity[tainted_data] */
   if (s[count] == '.')
     {
     int d = 100;
@@ -2110,9 +2445,15 @@ switch (type)
         name);
     if (count > 0 && list[2] == 0) count = 0;
     list[1] = count;
+    break;
     }
 
-  break;
+  case opt_func:
+    {
+    void (*fn)() = ol->value;
+    fn(name, s);
+    break;
+    }
   }
 
 return TRUE;
@@ -2210,7 +2551,6 @@ if (ol == NULL)
 
 if (!admin_user && (ol->type & opt_secure) != 0)
   {
-  const char * const hidden = "<value not displayable>";
   if (no_labels)
     printf("%s\n", hidden);
   else
@@ -2475,7 +2815,9 @@ causes the value of any main configuration variable to be output if the
 second argument is NULL. There are some special values:
 
   all                print all main configuration options
-  configure_file     print the name of the configuration file
+  config_file        print the name of the configuration file
+                     (configure_file will still work, for backward
+                     compatibility)
   routers            print the routers' configurations
   transports         print the transports' configuration
   authenticators     print the authenticators' configuration
@@ -2486,6 +2828,8 @@ second argument is NULL. There are some special values:
   macro_list         print a list of macro names
   +name              print a named list item
   local_scan         print the local_scan options
+  config             print the configuration as it is parsed
+  environment        print the used execution environment
 
 If the second argument is not NULL, it must be one of "router", "transport",
 "authenticator" or "macro" in which case the first argument identifies the
@@ -2542,7 +2886,8 @@ if (type == NULL)
     return;
     }
 
-  if (Ustrcmp(name, "configure_file") == 0)
+  if (  Ustrcmp(name, "configure_file") == 0
+     || Ustrcmp(name, "config_file") == 0)
     {
     printf("%s\n", CS config_main_filename);
     return;
@@ -2551,11 +2896,11 @@ if (type == NULL)
   if (Ustrcmp(name, "all") == 0)
     {
     for (ol = optionlist_config;
-         ol < optionlist_config + optionlist_config_size; ol++)
+         ol < optionlist_config + nelem(optionlist_config); ol++)
       {
       if ((ol->type & opt_hidden) == 0)
         print_ol(ol, US ol->name, NULL,
-            optionlist_config, optionlist_config_size,
+            optionlist_config, nelem(optionlist_config),
             no_labels);
       }
     return;
@@ -2576,6 +2921,12 @@ if (type == NULL)
     return;
     }
 
+  if (Ustrcmp(name, "config") == 0)
+    {
+    print_config(admin_user, no_labels);
+    return;
+    }
+
   if (Ustrcmp(name, "routers") == 0)
     {
     type = US"router";
@@ -2627,10 +2978,28 @@ if (type == NULL)
     names_only = TRUE;
     }
 
+  else if (Ustrcmp(name, "environment") == 0)
+    {
+    if (environ)
+      {
+      uschar ** p;
+      for (p = USS environ; *p; p++) ;
+      qsort(environ, p - USS environ, sizeof(*p), string_compare_by_pointer);
+
+      for (p = USS environ; *p; p++)
+        {
+       uschar * q;
+        if (no_labels && (q = Ustrchr(*p, '='))) *q  = '\0';
+        puts(CS *p);
+        }
+      }
+    return;
+    }
+
   else
     {
-    print_ol(find_option(name, optionlist_config, optionlist_config_size),
-      name, NULL, optionlist_config, optionlist_config_size, no_labels);
+    print_ol(find_option(name, optionlist_config, nelem(optionlist_config)),
+      name, NULL, optionlist_config, nelem(optionlist_config), no_labels);
     return;
     }
   }
@@ -2669,19 +3038,18 @@ else if (Ustrcmp(type, "macro") == 0)
     fprintf(stderr, "exim: permission denied\n");
     exit(EXIT_FAILURE);
     }
-  for (m = macros; m != NULL; m = m->next)
-    {
-    if (name == NULL || Ustrcmp(name, m->name) == 0)
+  if (!macros_builtin_created) macros_create_builtin();
+  for (m = macros; m; m = m->next)
+    if (!name || Ustrcmp(name, m->name) == 0)
       {
       if (names_only)
         printf("%s\n", CS m->name);
       else
         printf("%s=%s\n", CS m->name, CS m->replacement);
-      if (name != NULL)
+      if (name)
         return;
       }
-    }
-  if (name != NULL)
+  if (name)
     printf("%s %s not found\n", type, name);
   return;
   }
@@ -2852,13 +3220,25 @@ Returns:  bool for "okay"; false will cause caller to immediately exit.
 
 #ifdef SUPPORT_TLS
 static BOOL
-tls_dropprivs_validate_require_cipher(void)
+tls_dropprivs_validate_require_cipher(BOOL nowarn)
 {
 const uschar *errmsg;
 pid_t pid;
 int rc, status;
 void (*oldsignal)(int);
 
+/* If TLS will never be used, no point checking ciphers */
+
+if (  !tls_advertise_hosts
+   || !*tls_advertise_hosts
+   || Ustrcmp(tls_advertise_hosts, ":") == 0
+   )
+  return TRUE;
+else if (!nowarn && !tls_certificate)
+  log_write(0, LOG_MAIN,
+    "Warning: No server certificate defined; will use a selfsigned one.\n"
+    " Suggested action: either install a certificate or change tls_advertise_hosts option");
+
 oldsignal = signal(SIGCHLD, SIG_DFL);
 
 fflush(NULL);
@@ -2928,18 +3308,18 @@ systems. Therefore they are available only when requested by compile-time
 options. */
 
 void
-readconf_main(void)
+readconf_main(BOOL nowarn)
 {
 int sep = 0;
 struct stat statbuf;
 uschar *s, *filename;
-uschar *list = config_main_filelist;
+const uschar *list = config_main_filelist;
 
 /* Loop through the possible file names */
 
-while((filename = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))
-       != NULL)
+while((filename = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
   {
+
   /* Cut out all the fancy processing unless specifically wanted */
 
   #if defined(CONFIGURE_FILE_USE_NODE) || defined(CONFIGURE_FILE_USE_EUID)
@@ -2998,9 +3378,45 @@ logging configuration errors (it changes for .included files) whereas
 config_main_filename is the name shown by -bP. Failure to open a configuration
 file is a serious disaster. */
 
-if (config_file != NULL)
+if (config_file)
   {
+  uschar *last_slash = Ustrrchr(filename, '/');
   config_filename = config_main_filename = string_copy(filename);
+
+  /* The config_main_directory we need for the $config_dir expansion.
+  config_main_filename we need for $config_file expansion.
+  And config_dir is the directory of the current configuration, used for
+  relative .includes. We do need to know it's name, as we change our working
+  directory later. */
+
+  if (filename[0] == '/')
+    config_main_directory = last_slash == filename ? US"/" : string_copyn(filename, last_slash - filename);
+  else
+    {
+      /* relative configuration file name: working dir + / + basename(filename) */
+
+      uschar buf[PATH_MAX];
+      int offset = 0;
+      int size = 0;
+
+      if (os_getcwd(buf, PATH_MAX) == NULL)
+        {
+        perror("exim: getcwd");
+        exit(EXIT_FAILURE);
+        }
+      config_main_directory = string_cat(NULL, &size, &offset, buf);
+
+      /* If the dir does not end with a "/", append one */
+      if (config_main_directory[offset-1] != '/')
+        config_main_directory = string_catn(config_main_directory, &size, &offset, US"/", 1);
+
+      /* If the config file contains a "/", extract the directory part */
+      if (last_slash)
+        config_main_directory = string_catn(config_main_directory, &size, &offset, filename, last_slash - filename);
+
+      config_main_directory[offset] = '\0';
+    }
+  config_directory = config_main_directory;
   }
 else
   {
@@ -3012,6 +3428,15 @@ else
       "configuration file %s", filename));
   }
 
+/* Now, once we found and opened our configuration file, we change the directory
+to a safe place. Later we change to $spool_directory. */
+
+if (Uchdir("/") < 0)
+  {
+  perror("exim: chdir `/': ");
+  exit(EXIT_FAILURE);
+  }
+
 /* Check the status of the file we have opened, if we have retained root
 privileges and the file isn't /dev/null (which *should* be 0666). */
 
@@ -3100,7 +3525,7 @@ don't force the case. */
 
 if (primary_hostname == NULL)
   {
-  uschar *hostname;
+  const uschar *hostname;
   struct utsname uts;
   if (uname(&uts) < 0)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "uname() failed to yield host name");
@@ -3113,8 +3538,8 @@ if (primary_hostname == NULL)
 
     #if HAVE_IPV6
     if (!disable_ipv6 && (dns_ipv4_lookup == NULL ||
-         match_isinlist(hostname, &dns_ipv4_lookup, 0, NULL, NULL, MCL_DOMAIN,
-           TRUE, NULL) != OK))
+         match_isinlist(hostname, CUSS &dns_ipv4_lookup, 0, NULL, NULL,
+           MCL_DOMAIN, TRUE, NULL) != OK))
       af = AF_INET6;
     #else
     af = AF_INET;
@@ -3174,7 +3599,7 @@ or %M. However, it must NOT contain % followed by anything else. */
 
 if (*log_file_path != 0)
   {
-  uschar *ss, *sss;
+  const uschar *ss, *sss;
   int sep = ':';                       /* Fixed for log file path */
   s = expand_string(log_file_path);
   if (s == NULL)
@@ -3351,7 +3776,7 @@ if ((tls_verify_hosts != NULL || tls_try_verify_hosts != NULL) &&
 
 /* This also checks that the library linkage is working and we can call
 routines in it, so call even if tls_require_ciphers is unset */
-if (!tls_dropprivs_validate_require_cipher())
+if (!tls_dropprivs_validate_require_cipher(nowarn))
   exit(1);
 
 /* Magic number: at time of writing, 1024 has been the long-standing value
@@ -3374,12 +3799,12 @@ if (openssl_options != NULL)
       "openssl_options parse error: %s", openssl_options);
 # 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*/
+
+if (!nowarn && !keep_environment && environ && *environ)
+  log_write(0, LOG_MAIN,
+      "Warning: purging the environment.\n"
+      " Suggested action: use keep_environment.");
 }
 
 
@@ -3492,9 +3917,9 @@ while ((buffer = get_config_line()) != NULL)
 
   if (isupper(*name) && *s == '=')
     {
-    if (d != NULL)
+    if (d)
       {
-      if (d->driver_name == NULL)
+      if (!d->driver_name)
         log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
           "no driver defined for %s \"%s\"", class, d->name);
       (d->info->init)(d);
@@ -3514,9 +3939,9 @@ while ((buffer = get_config_line()) != NULL)
 
     /* Finish off initializing the previous driver. */
 
-    if (d != NULL)
+    if (d)
       {
-      if (d->driver_name == NULL)
+      if (!d->driver_name)
         log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
           "no driver defined for %s \"%s\"", class, d->name);
       (d->info->init)(d);
@@ -3524,7 +3949,7 @@ while ((buffer = get_config_line()) != NULL)
 
     /* Check that we haven't already got a driver of this name */
 
-    for (d = *anchor; d != NULL; d = d->next)
+    for (d = *anchor; d; d = d->next)
       if (Ustrcmp(name, d->name) == 0)
         log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
           "there are two %ss called \"%s\"", class, name);
@@ -3535,7 +3960,7 @@ while ((buffer = get_config_line()) != NULL)
     d = store_get(instance_size);
     memcpy(d, instance_default, instance_size);
     *p = d;
-    p = &(d->next);
+    p = &d->next;
     d->name = string_copy(name);
 
     /* Clear out the "set" bits in the generic options */
@@ -3553,8 +3978,8 @@ while ((buffer = get_config_line()) != NULL)
   /* Not the start of a new driver. Give an error if we have not set up a
   current driver yet. */
 
-  if (d == NULL) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
-    "%s name missing", class);
+  if (!d)
+    log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "%s name missing", class);
 
   /* First look to see if this is a generic option; if it is "driver",
   initialize the driver. If is it not a generic option, we can look for a
@@ -3563,7 +3988,7 @@ while ((buffer = get_config_line()) != NULL)
   if (readconf_handle_option(buffer, driver_optionlist,
         driver_optionlist_count, d, NULL))
     {
-    if (d->info == NULL && d->driver_name != NULL)
+    if (!d->info && d->driver_name)
       init_driver(d, drivers_available, size_of_info, class);
     }
 
@@ -3571,11 +3996,9 @@ while ((buffer = get_config_line()) != NULL)
   live therein. A flag with each option indicates if it is in the public
   block. */
 
-  else if (d->info != NULL)
-    {
+  else if (d->info)
     readconf_handle_option(buffer, d->info->options,
       *(d->info->options_count), d, US"option \"%s\" unknown");
-    }
 
   /* The option is not generic and the driver name has not yet been given. */
 
@@ -3585,9 +4008,9 @@ while ((buffer = get_config_line()) != NULL)
 
 /* Run the initialization function for the final driver. */
 
-if (d != NULL)
+if (d)
   {
-  if (d->driver_name == NULL)
+  if (!d->driver_name)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
       "no driver defined for %s \"%s\"", class, d->name);
   (d->info->init)(d);
@@ -3661,10 +4084,11 @@ Returns:       NULL if decoded correctly; else points to error text
 */
 
 uschar *
-readconf_retry_error(uschar *pp, uschar *p, int *basic_errno, int *more_errno)
+readconf_retry_error(const uschar *pp, const uschar *p,
+  int *basic_errno, int *more_errno)
 {
 int len;
-uschar *q = pp;
+const uschar *q = pp;
 while (q < p && *q != '_') q++;
 len = q - pp;
 
@@ -3693,7 +4117,7 @@ else if (len == 7 && strncmpic(pp, US"timeout", len) == 0)
     {
     int i;
     int xlen = p - q - 1;
-    uschar *x = q + 1;
+    const uschar *x = q + 1;
 
     static uschar *extras[] =
       { US"A", US"MX", US"connect", US"connect_A",  US"connect_MX" };
@@ -3701,24 +4125,19 @@ else if (len == 7 && strncmpic(pp, US"timeout", len) == 0)
       { 'A',   'M',    RTEF_CTOUT,  RTEF_CTOUT|'A', RTEF_CTOUT|'M' };
 
     for (i = 0; i < sizeof(extras)/sizeof(uschar *); i++)
-      {
       if (strncmpic(x, extras[i], xlen) == 0)
         {
         *more_errno = values[i];
         break;
         }
-      }
 
     if (i >= sizeof(extras)/sizeof(uschar *))
-      {
       if (strncmpic(x, US"DNS", xlen) == 0)
-        {
         log_write(0, LOG_MAIN|LOG_PANIC, "\"timeout_dns\" is no longer "
           "available in retry rules (it has never worked) - treated as "
           "\"timeout\"");
-        }
-      else return US"\"A\", \"MX\", or \"connect\" expected after \"timeout\"";
-      }
+      else
+        return US"\"A\", \"MX\", or \"connect\" expected after \"timeout\"";
     }
   }
 
@@ -3745,8 +4164,8 @@ else if (strncmpic(pp, US"mail_4", 6) == 0 ||
     return string_sprintf("%.4s_4 must be followed by xx, dx, or dd, where "
       "x is literal and d is any digit", pp);
 
-  *basic_errno = (*pp == 'm')? ERRNO_MAIL4XX :
-                 (*pp == 'r')? ERRNO_RCPT4XX : ERRNO_DATA4XX;
+  *basic_errno = *pp == 'm' ? ERRNO_MAIL4XX :
+                 *pp == 'r' ? ERRNO_RCPT4XX : ERRNO_DATA4XX;
   *more_errno = x << 8;
   }
 
@@ -3760,6 +4179,9 @@ else if (strncmpic(pp, US"lost_connection", p - pp) == 0)
 else if (strncmpic(pp, US"tls_required", p - pp) == 0)
   *basic_errno = ERRNO_TLSREQUIRED;
 
+else if (strncmpic(pp, US"lookup", p - pp) == 0)
+  *basic_errno = ERRNO_UNKNOWNHOST;
+
 else if (len != 1 || Ustrncmp(pp, "*", 1) != 0)
   return string_sprintf("unknown or malformed retry error \"%.*s\"", (int) (p-pp), pp);
 
@@ -3797,10 +4219,10 @@ Returns:    time in seconds or fixed point number * 1000
 */
 
 static int
-retry_arg(uschar **paddr, int type)
+retry_arg(const uschar **paddr, int type)
 {
-uschar *p = *paddr;
-uschar *pp;
+const uschar *p = *paddr;
+const uschar *pp;
 
 if (*p++ != ',') log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "comma expected");
 
@@ -3814,10 +4236,8 @@ if (*p != 0 && !isspace(*p) && *p != ',' && *p != ';')
 *paddr = p;
 switch (type)
   {
-  case 0:
-  return readconf_readtime(pp, *p, FALSE);
-  case 1:
-  return readconf_readfixed(pp, *p);
+  case 0: return readconf_readtime(pp, *p, FALSE);
+  case 1: return readconf_readfixed(pp, *p);
   }
 return 0;    /* Keep picky compilers happy */
 }
@@ -3829,12 +4249,13 @@ readconf_retries(void)
 {
 retry_config **chain = &retries;
 retry_config *next;
-uschar *p;
+const uschar *p;
 
-while ((p = get_config_line()) != NULL)
+while ((p = get_config_line()))
   {
   retry_rule **rchain;
-  uschar *pp, *error;
+  const uschar *pp;
+  uschar *error;
 
   next = store_get(sizeof(retry_config));
   next->next = NULL;
@@ -3854,8 +4275,8 @@ while ((p = get_config_line()) != NULL)
 
   /* Test error names for things we understand. */
 
-  if ((error = readconf_retry_error(pp, p, &(next->basic_errno),
-       &(next->more_errno))) != NULL)
+  if ((error = readconf_retry_error(pp, p, &next->basic_errno,
+       &next->more_errno)))
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "%s", error);
 
   /* There may be an optional address list of senders to be used as another
@@ -3892,18 +4313,18 @@ while ((p = get_config_line()) != NULL)
     switch (rule->rule)
       {
       case 'F':   /* Fixed interval */
-      rule->p1 = retry_arg(&p, 0);
-      break;
+       rule->p1 = retry_arg(&p, 0);
+       break;
 
       case 'G':   /* Geometrically increasing intervals */
       case 'H':   /* Ditto, but with randomness */
-      rule->p1 = retry_arg(&p, 0);
-      rule->p2 = retry_arg(&p, 1);
-      break;
+       rule->p1 = retry_arg(&p, 0);
+       rule->p2 = retry_arg(&p, 1);
+       break;
 
       default:
-      log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "unknown retry rule letter");
-      break;
+       log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "unknown retry rule letter");
+       break;
       }
 
     if (rule->timeout <= 0 || rule->p1 <= 0 ||
@@ -3929,6 +4350,21 @@ while ((p = get_config_line()) != NULL)
 *         Initialize authenticators              *
 *************************************************/
 
+static void
+readconf_options_auths(void)
+{
+struct auth_info * ai;
+
+readconf_options_from_list(optionlist_auths, optionlist_auths_size, US"AUTHENTICATORS", NULL);
+
+for (ai = auths_available; ai->driver_name[0]; ai++)
+  {
+  macro_create(string_sprintf("_DRIVER_AUTHENTICATOR_%T", ai->driver_name), US"y", FALSE, TRUE);
+  readconf_options_from_list(ai->options, (unsigned)*ai->options_count, US"AUTHENTICATOR", ai->driver_name);
+  }
+}
+
+
 /* Read the authenticators section of the configuration file.
 
 Arguments:   none
@@ -3939,6 +4375,7 @@ static void
 auths_init(void)
 {
 auth_instance *au, *bu;
+
 readconf_driver_init(US"authenticator",
   (driver_instance **)(&auths),      /* chain anchor */
   (driver_info *)auths_available,    /* available drivers */
@@ -3948,22 +4385,19 @@ readconf_driver_init(US"authenticator",
   optionlist_auths,                  /* generic options */
   optionlist_auths_size);
 
-for (au = auths; au != NULL; au = au->next)
+for (au = auths; au; au = au->next)
   {
-  if (au->public_name == NULL)
+  if (!au->public_name)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "no public name specified for "
       "the %s authenticator", au->name);
-  for (bu = au->next; bu != NULL; bu = bu->next)
-    {
+
+  for (bu = au->next; bu; bu = bu->next)
     if (strcmpic(au->public_name, bu->public_name) == 0)
-      {
       if ((au->client && bu->client) || (au->server && bu->server))
         log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "two %s authenticators "
           "(%s and %s) have the same public name (%s)",
-          (au->client)? US"client" : US"server", au->name, bu->name,
+          au->client ? US"client" : US"server", au->name, bu->name,
           au->public_name);
-      }
-    }
   }
 }
 
@@ -4146,6 +4580,122 @@ while(next_section[0] != 0)
 (void)fclose(config_file);
 }
 
+/* Init the storage for the pre-parsed config lines */
+void
+readconf_save_config(const uschar *s)
+{
+  save_config_line(string_sprintf("# Exim Configuration (%s)",
+    running_in_test_harness ? US"X" : s));
+}
+
+static void
+save_config_position(const uschar *file, int line)
+{
+  save_config_line(string_sprintf("# %d \"%s\"", line, file));
+}
+
+/* Append a pre-parsed logical line to the config lines store,
+this operates on a global (static) list that holds all the pre-parsed
+config lines, we do no further processing here, output formatting and
+honouring of <hide> or macros will be done during output */
+static void
+save_config_line(const uschar* line)
+{
+static config_line_item *current;
+config_line_item *next;
+
+next = (config_line_item*) store_get(sizeof(config_line_item));
+next->line = string_copy(line);
+next->next = NULL;
+
+if (!config_lines) config_lines = next;
+else current->next = next;
+
+current = next;
+}
+
+/* List the parsed config lines, care about nice formatting and
+hide the <hide> values unless we're the admin user */
+void
+print_config(BOOL admin, BOOL terse)
+{
+config_line_item *i;
+const int TS = terse ? 0 : 2;
+int indent = 0;
+
+for (i = config_lines; i; i = i->next)
+  {
+  uschar *current;
+  uschar *p;
+
+  /* skip over to the first non-space */
+  for (current = i->line; *current && isspace(*current); ++current)
+    ;
+
+  if (*current == '\0')
+    continue;
+
+  /* Collapse runs of spaces. We stop this if we encounter one of the
+   * following characters: "'$, as this may indicate careful formatting */
+  for (p = current; *p; ++p)
+    {
+    uschar *next;
+    if (!isspace(*p)) continue;
+    if (*p != ' ') *p = ' ';
+
+    for (next = p; isspace(*next); ++next)
+      ;
+
+    if (next - p > 1)
+      memmove(p+1, next, Ustrlen(next)+1);
+
+    if (*next == '"' || *next == '\'' || *next == '$')
+      break;
+    }
+
+  /* # lines */
+  if (current[0] == '#')
+    puts(CCS current);
+
+  /* begin lines are left aligned */
+  else if (Ustrncmp(current, "begin", 5) == 0 && isspace(current[5]))
+    {
+    if (!terse) puts("");
+    puts(CCS current);
+    indent = TS;
+    }
+
+  /* router/acl/transport block names */
+  else if (current[Ustrlen(current)-1] == ':' && !Ustrchr(current, '='))
+    {
+    if (!terse) puts("");
+    printf("%*s%s\n", TS, "", current);
+    indent = 2 * TS;
+    }
+
+  /* hidden lines (all MACROS or lines prefixed with "hide") */
+  else if (  !admin
+         && (  isupper(*current)
+            || Ustrncmp(current, "hide", 4) == 0 && isspace(current[4])
+            )
+         )
+    {
+    if ((p = Ustrchr(current, '=')))
+      {
+      *p = '\0';
+      printf("%*s%s= %s\n", indent, "", current, hidden);
+      }
+    /* e.g.: hide split_spool_directory */
+    else
+      printf("%*s\n", indent, hidden);
+    }
+
+  else
+    /* rest is public */
+    printf("%*s%s\n", indent, "", current);
+  }
+}
+
 /* vi: aw ai sw=2
 */
 /* End of readconf.c */
index f27dc42..7980c32 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2017 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Code for receiving a message and setting up spool files. */
@@ -23,8 +23,9 @@ extern int dcc_ok;
 
 static FILE   *data_file = NULL;
 static int     data_fd = -1;
-static uschar  spool_name[256];
+static uschar *spool_name = US"";
 
+enum CH_STATE {LF_SEEN, MID_LINE, CR_SEEN};
 
 
 /*************************************************
@@ -37,7 +38,7 @@ the file. (When SMTP input is occurring, different functions are used by
 changing the pointer variables.) */
 
 int
-stdin_getc(void)
+stdin_getc(unsigned lim)
 {
 return getc(stdin);
 }
@@ -87,7 +88,7 @@ if (newsender == NULL || untrusted_set_sender == NULL) return FALSE;
 qnewsender = (Ustrchr(newsender, '@') != NULL)?
   newsender : string_sprintf("%s@%s", newsender, qualify_domain_sender);
 return
-  match_address_list(qnewsender, TRUE, TRUE, &untrusted_set_sender, NULL, -1,
+  match_address_list(qnewsender, TRUE, TRUE, CUSS &untrusted_set_sender, NULL, -1,
     0, NULL) == OK;
 }
 
@@ -124,6 +125,7 @@ receive_statvfs(BOOL isspool, int *inodeptr)
 {
 #ifdef HAVE_STATFS
 struct STATVFS statbuf;
+struct stat dummy;
 uschar *path;
 uschar *name;
 uschar buffer[1024];
@@ -142,17 +144,16 @@ appearance of "syslog" in it. */
 else
   {
   int sep = ':';              /* Not variable - outside scripts use */
-  uschar *p = log_file_path;
+  const uschar *p = log_file_path;
   name = US"log";
 
   /* An empty log_file_path means "use the default". This is the same as an
   empty item in a list. */
 
   if (*p == 0) p = US":";
-  while ((path = string_nextinlist(&p, &sep, buffer, sizeof(buffer))) != NULL)
-    {
-    if (Ustrcmp(path, "syslog") != 0) break;
-    }
+  while ((path = string_nextinlist(&p, &sep, buffer, sizeof(buffer))))
+    if (Ustrcmp(path, "syslog") != 0)
+      break;
 
   if (path == NULL)  /* No log files */
     {
@@ -181,12 +182,18 @@ else
 memset(&statbuf, 0, sizeof(statbuf));
 
 if (STATVFS(CS path, &statbuf) != 0)
-  {
-  log_write(0, LOG_MAIN|LOG_PANIC, "cannot accept message: failed to stat "
-    "%s directory %s: %s", name, spool_directory, strerror(errno));
-  smtp_closedown(US"spool or log directory problem");
-  exim_exit(EXIT_FAILURE);
-  }
+  if (stat(CS path, &dummy) == -1 && errno == ENOENT)
+    {                          /* Can happen on first run after installation */
+    *inodeptr = -1;
+    return -1;
+    }
+  else
+    {
+    log_write(0, LOG_MAIN|LOG_PANIC, "cannot accept message: failed to stat "
+      "%s directory %s: %s", name, path, strerror(errno));
+    smtp_closedown(US"spool or log directory problem");
+    exim_exit(EXIT_FAILURE);
+    }
 
 *inodeptr = (statbuf.F_FILES > 0)? statbuf.F_FAVAIL : -1;
 
@@ -194,9 +201,9 @@ if (STATVFS(CS path, &statbuf) != 0)
 
 return (int)(((double)statbuf.F_BAVAIL * (double)statbuf.F_FRSIZE)/1024.0);
 
+#else
 /* Unable to find partition sizes in this environment. */
 
-#else
 *inodeptr = -1;
 return -1;
 #endif
@@ -497,10 +504,8 @@ recipients_list[recipients_count].bmi_optin = bmi_current_optin;
 /* 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;
 }
 
@@ -528,7 +533,7 @@ static void
 smtp_user_msg(uschar *code, uschar *user_msg)
 {
 int len = 3;
-smtp_message_code(&code, &len, &user_msg, NULL);
+smtp_message_code(&code, &len, &user_msg, NULL, TRUE);
 smtp_respond(code, len, TRUE, user_msg);
 }
 #endif
@@ -622,7 +627,7 @@ if (!dot_ends)
   {
   register int last_ch = '\n';
 
-  for (; (ch = (receive_getc)()) != EOF; last_ch = ch)
+  for (; (ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF; last_ch = ch)
     {
     if (ch == 0) body_zerocount++;
     if (last_ch == '\r' && ch != '\n')
@@ -664,7 +669,7 @@ if (!dot_ends)
 
 ch_state = 1;
 
-while ((ch = (receive_getc)()) != EOF)
+while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF)
   {
   if (ch == 0) body_zerocount++;
   switch (ch_state)
@@ -685,7 +690,8 @@ while ((ch = (receive_getc)()) != EOF)
     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;
+    if (ch == '\n') { body_linecount++; linelength = -1; }
+    else ch_state = 0;
     break;
 
     case 2:
@@ -779,9 +785,9 @@ read_message_data_smtp(FILE *fout)
 {
 int ch_state = 0;
 int ch;
-register int linelength = 0;
+int linelength = 0;
 
-while ((ch = (receive_getc)()) != EOF)
+while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF)
   {
   if (ch == 0) body_zerocount++;
   switch (ch_state)
@@ -838,7 +844,15 @@ while ((ch = (receive_getc)()) != EOF)
       ch_state = 4;
       continue;
       }
-    ch_state = 1;                       /* The dot itself is removed */
+    /* The dot was removed at state 3. For a doubled dot, here, reinstate
+    it to cutthrough. The current ch, dot or not, is passed both to cutthrough
+    and to file below. */
+    if (ch == '.')
+      {
+      uschar c= ch;
+      (void) cutthrough_puts(&c, 1);
+      }
+    ch_state = 1;
     break;
 
     case 4:                             /* After [CR] LF . CR */
@@ -861,7 +875,7 @@ while ((ch = (receive_getc)()) != EOF)
 
   message_size++;
   linelength++;
-  if (fout != NULL)
+  if (fout)
     {
     if (fputc(ch, fout) == EOF) return END_WERROR;
     if (message_size > thismessage_size_limit) return END_SIZE;
@@ -870,7 +884,7 @@ while ((ch = (receive_getc)()) != EOF)
     (void) cutthrough_put_nl();
   else
     {
-    uschar c= ch;
+    uschar c = ch;
     (void) cutthrough_puts(&c, 1);
     }
   }
@@ -884,6 +898,129 @@ return END_EOF;
 
 
 
+/* Variant of the above read_message_data_smtp() specialised for RFC 3030
+CHUNKING. Accept input lines separated by either CRLF or CR or LF and write
+LF-delimited spoolfile.  Until we have wireformat spoolfiles, we need the
+body_linecount accounting for proper re-expansion for the wire, so use
+a cut-down version of the state-machine above; we don't need to do leading-dot
+detection and unstuffing.
+
+Arguments:
+  fout      a FILE to which to write the message; NULL if skipping;
+            must be open for both writing and reading.
+
+Returns:    One of the END_xxx values indicating why it stopped reading
+*/
+
+static int
+read_message_bdat_smtp(FILE *fout)
+{
+int linelength = 0, ch;
+enum CH_STATE ch_state = LF_SEEN;
+BOOL fix_nl = FALSE;
+
+for(;;)
+  {
+  switch ((ch = (bdat_getc)(GETC_BUFFER_UNLIMITED)))
+    {
+    case EOF:  return END_EOF;
+    case ERR:  return END_PROTOCOL;
+    case EOD:
+      /* Nothing to get from the sender anymore. We check the last
+      character written to the spool.
+
+      RFC 3030 states, that BDAT chunks are normal text, terminated by CRLF.
+      If we would be strict, we would refuse such broken messages.
+      But we are liberal, so we fix it.  It would be easy just to append
+      the "\n" to the spool.
+
+      But there are some more things (line counting, message size calculation and such),
+      that would need to be duplicated here.  So we simply do some ungetc
+      trickery.
+      */
+      if (fout)
+       {
+       if (fseek(fout, -1, SEEK_CUR) < 0)      return END_PROTOCOL;
+       if (fgetc(fout) == '\n')                return END_DOT;
+       }
+
+      if (linelength == -1)    /* \r already seen (see below) */
+        {
+        DEBUG(D_receive) debug_printf("Add missing LF\n");
+        bdat_ungetc('\n');
+        continue;
+        }
+      DEBUG(D_receive) debug_printf("Add missing CRLF\n");
+      bdat_ungetc('\r');      /* not even \r was seen */
+      fix_nl = TRUE;
+
+      continue;
+    case '\0':  body_zerocount++; break;
+    }
+  switch (ch_state)
+    {
+    case LF_SEEN:                             /* After LF or CRLF */
+      ch_state = MID_LINE;
+      /* fall through to handle as normal uschar. */
+
+    case MID_LINE:                            /* Mid-line state */
+      if (ch == '\n')
+       {
+       ch_state = LF_SEEN;
+       body_linecount++;
+       if (linelength > max_received_linelength)
+         max_received_linelength = linelength;
+       linelength = -1;
+       }
+      else if (ch == '\r')
+       {
+       ch_state = CR_SEEN;
+       if (fix_nl) bdat_ungetc('\n');
+       continue;                       /* don't write CR */
+       }
+      break;
+
+    case CR_SEEN:                       /* After (unwritten) CR */
+      body_linecount++;
+      if (linelength > max_received_linelength)
+       max_received_linelength = linelength;
+      linelength = -1;
+      if (ch == '\n')
+       ch_state = LF_SEEN;
+      else
+       {
+       message_size++;
+       if (fout && fputc('\n', fout) == EOF) return END_WERROR;
+       (void) cutthrough_put_nl();
+       if (ch == '\r') continue;       /* don't write CR */
+       ch_state = MID_LINE;
+       }
+      break;
+    }
+
+  /* Add the character to the spool file, unless skipping */
+
+  message_size++;
+  linelength++;
+  if (fout)
+    {
+    if (fputc(ch, fout) == EOF) return END_WERROR;
+    if (message_size > thismessage_size_limit) return END_SIZE;
+    }
+  if(ch == '\n')
+    (void) cutthrough_put_nl();
+  else
+    {
+    uschar c = ch;
+    (void) cutthrough_puts(&c, 1);
+    }
+  }
+/*NOTREACHED*/
+}
+
+
+
+
 /*************************************************
 *             Swallow SMTP message               *
 *************************************************/
@@ -900,6 +1037,7 @@ Returns:     nothing
 void
 receive_swallow_smtp(void)
 {
+/*XXX CHUNKING: not enough.  read chunks until RSET? */
 if (message_ended >= END_NOTENDED)
   message_ended = read_message_data_smtp(NULL);
 }
@@ -922,6 +1060,7 @@ handle_lost_connection(uschar *s)
 {
 log_write(L_lost_incoming_connection | L_smtp_connection, LOG_MAIN,
   "%s lost while reading message data%s", smtp_get_connection_info(), s);
+smtp_notquit_exit(US"connection-lost", NULL, NULL);
 return US"421 Lost incoming connection";
 }
 
@@ -956,10 +1095,12 @@ if (error_handling == ERRORS_SENDER)
   error_block eblock;
   eblock.next = NULL;
   eblock.text1 = text1;
+  eblock.text2 = US"";
   if (!moan_to_sender(errcode, &eblock, hptr, f, FALSE))
     error_rc = EXIT_FAILURE;
   }
-else fprintf(stderr, "exim: %s%s\n", text2, text1);  /* Sic */
+else
+  fprintf(stderr, "exim: %s%s\n", text2, text1);  /* Sic */
 (void)fclose(f);
 exim_exit(error_rc);
 }
@@ -999,7 +1140,7 @@ switch(where)
   case ACL_WHERE_DKIM:
   case ACL_WHERE_MIME:
   case ACL_WHERE_DATA:
-    if (cutthrough_fd >= 0 && (acl_removed_headers || acl_added_headers))
+    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");
@@ -1009,38 +1150,28 @@ switch(where)
 
 if (acl_removed_headers != NULL)
   {
-  DEBUG(D_receive|D_acl) debug_printf(">>Headers removed by %s ACL:\n", acl_name);
+  DEBUG(D_receive|D_acl) debug_printf_indent(">>Headers removed by %s ACL:\n", acl_name);
 
-  for (h = header_list; h != NULL; h = h->next)
+  for (h = header_list; h != NULL; h = h->next) if (h->type != htype_old)
     {
-    uschar *list;
-    BOOL include_header;
-
-    if (h->type == htype_old) continue;
-
-    include_header = TRUE;
-    list = acl_removed_headers;
-
+    const uschar * list = acl_removed_headers;
     int sep = ':';         /* This is specified as a colon-separated list */
     uschar *s;
     uschar buffer[128];
-    while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))
-            != NULL)
-      {
-      int len = Ustrlen(s);
-      if (header_testname(h, s, len, FALSE))
+
+    while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
+      if (header_testname(h, s, Ustrlen(s), FALSE))
        {
        h->type = htype_old;
-        DEBUG(D_receive|D_acl) debug_printf("  %s", h->text);
+        DEBUG(D_receive|D_acl) debug_printf_indent("  %s", h->text);
        }
-      }
     }
   acl_removed_headers = NULL;
-  DEBUG(D_receive|D_acl) debug_printf(">>\n");
+  DEBUG(D_receive|D_acl) debug_printf_indent(">>\n");
   }
 
 if (acl_added_headers == NULL) return;
-DEBUG(D_receive|D_acl) debug_printf(">>Headers added by %s ACL:\n", acl_name);
+DEBUG(D_receive|D_acl) debug_printf_indent(">>Headers added by %s ACL:\n", acl_name);
 
 for (h = acl_added_headers; h != NULL; h = next)
   {
@@ -1051,7 +1182,7 @@ for (h = acl_added_headers; h != NULL; h = next)
     case htype_add_top:
     h->next = header_list;
     header_list = h;
-    DEBUG(D_receive|D_acl) debug_printf("  (at top)");
+    DEBUG(D_receive|D_acl) debug_printf_indent("  (at top)");
     break;
 
     case htype_add_rec:
@@ -1066,7 +1197,7 @@ for (h = acl_added_headers; h != NULL; h = next)
       }
     h->next = last_received->next;
     last_received->next = h;
-    DEBUG(D_receive|D_acl) debug_printf("  (after Received:)");
+    DEBUG(D_receive|D_acl) debug_printf_indent("  (after Received:)");
     break;
 
     case htype_add_rfc:
@@ -1081,7 +1212,7 @@ for (h = acl_added_headers; h != NULL; h = next)
        of all headers. Our current header must follow it. */
     h->next = last_received->next;
     last_received->next = h;
-    DEBUG(D_receive|D_acl) debug_printf("  (before any non-Received: or Resent-*: header)");
+    DEBUG(D_receive|D_acl) debug_printf_indent("  (before any non-Received: or Resent-*: header)");
     break;
 
     default:
@@ -1101,11 +1232,11 @@ for (h = acl_added_headers; h != NULL; h = next)
   h->type = header_checkname(h, FALSE);
   if (h->type >= 'a') h->type = htype_other;
 
-  DEBUG(D_receive|D_acl) debug_printf("  %s", header_last->text);
+  DEBUG(D_receive|D_acl) debug_printf_indent("  %s", header_last->text);
   }
 
 acl_added_headers = NULL;
-DEBUG(D_receive|D_acl) debug_printf(">>\n");
+DEBUG(D_receive|D_acl) debug_printf_indent(">>\n");
 }
 
 
@@ -1126,17 +1257,17 @@ Returns:      the extended string
 */
 
 static uschar *
-add_host_info_for_log(uschar *s, int *sizeptr, int *ptrptr)
+add_host_info_for_log(uschar * s, int * sizeptr, int * ptrptr)
 {
-if (sender_fullhost != NULL)
+if (sender_fullhost)
   {
+  if (LOGGING(dnssec) && sender_host_dnssec)   /*XXX sender_helo_dnssec? */
+    s = string_cat(s, sizeptr, ptrptr, US" DS");
   s = string_append(s, sizeptr, ptrptr, 2, US" H=", sender_fullhost);
-  if ((log_extra_selector & LX_incoming_interface) != 0 &&
-       interface_address != NULL)
+  if (LOGGING(incoming_interface) && interface_address != NULL)
     {
-    uschar *ss = string_sprintf(" I=[%s]:%d", interface_address,
-      interface_port);
-    s = string_cat(s, sizeptr, ptrptr, ss, Ustrlen(ss));
+    s = string_cat(s, sizeptr, ptrptr,
+      string_sprintf(" I=[%s]:%d", interface_address, interface_port));
     }
   }
 if (sender_ident != NULL)
@@ -1242,42 +1373,40 @@ if (Ustrlen(rfc822_file_path) > 0)
 if (rc == OK)
   {
   uschar temp_path[1024];
-  int n;
-  struct dirent *entry;
-  DIR *tempdir;
+  struct dirent * entry;
+  DIR * tempdir;
 
-  (void)string_format(temp_path, 1024, "%s/scan/%s", spool_directory,
-    message_id);
+  (void) string_format(temp_path, sizeof(temp_path), "%s/scan/%s",
+    spool_directory, message_id);
 
   tempdir = opendir(CS temp_path);
-  n = 0;
-  do
+  for (;;)
     {
-    entry = readdir(tempdir);
-    if (entry == NULL) break;
-    if (strncmpic(US entry->d_name,US"__rfc822_",9) == 0)
+    if (!(entry = readdir(tempdir)))
+      break;
+    if (strncmpic(US entry->d_name, US"__rfc822_", 9) == 0)
       {
-      (void)string_format(rfc822_file_path, 2048,"%s/scan/%s/%s", spool_directory, message_id, entry->d_name);
-      debug_printf("RFC822 attachment detected: running MIME ACL for '%s'\n", rfc822_file_path);
+      (void) string_format(rfc822_file_path, sizeof(rfc822_file_path),
+       "%s/scan/%s/%s", spool_directory, message_id, entry->d_name);
+      DEBUG(D_receive) debug_printf("RFC822 attachment detected: running MIME ACL for '%s'\n",
+       rfc822_file_path);
       break;
       }
-    } while (1);
+    }
   closedir(tempdir);
 
-  if (entry != NULL)
+  if (entry)
     {
-    mbox_file = Ufopen(rfc822_file_path,"rb");
-    if (mbox_file == NULL)
+    if ((mbox_file = Ufopen(rfc822_file_path, "rb")))
       {
-      log_write(0, LOG_PANIC,
-         "acl_smtp_mime: can't open RFC822 spool file, skipping.");
-      unlink(CS rfc822_file_path);
-      goto END_MIME_ACL;
+      /* set RFC822 expansion variable */
+      mime_is_rfc822 = 1;
+      mime_part_count_buffer = mime_part_count;
+      goto MIME_ACL_CHECK;
       }
-    /* set RFC822 expansion variable */
-    mime_is_rfc822 = 1;
-    mime_part_count_buffer = mime_part_count;
-    goto MIME_ACL_CHECK;
+    log_write(0, LOG_PANIC,
+       "acl_smtp_mime: can't open RFC822 spool file, skipping.");
+    unlink(CS rfc822_file_path);
     }
   }
 
@@ -1295,10 +1424,12 @@ else if (rc != OK)
 #ifdef EXPERIMENTAL_DCC
   dcc_ok = 0;
 #endif
-  if (smtp_input && smtp_handle_acl_fail(ACL_WHERE_MIME, rc, user_msg, log_msg) != 0) {
-    *smtp_yield_ptr = FALSE;    /* No more messsages after dropped connection */
+  if (  smtp_input
+     && smtp_handle_acl_fail(ACL_WHERE_MIME, rc, user_msg, log_msg) != 0)
+    {
+    *smtp_yield_ptr = FALSE;    /* No more messages after dropped connection */
     *smtp_reply_ptr = US"";     /* Indicate reply already sent */
-  }
+    }
   message_id[0] = 0;            /* Indicate no message accepted */
   return FALSE;                 /* Cause skip to end of receive function */
   }
@@ -1322,7 +1453,7 @@ if (recipients_count == 1) received_for = recipients_list[0].address;
 received = expand_string(received_header_text);
 received_for = NULL;
 
-if (received == NULL)
+if (!received)
   {
   if(spool_name[0] != 0)
     Uunlink(spool_name);           /* Lose the data file */
@@ -1546,7 +1677,7 @@ yet, initialize the size and warning count, and deal with no size limit. */
 message_id[0] = 0;
 data_file = NULL;
 data_fd = -1;
-spool_name[0] = 0;
+spool_name = US"";
 message_size = 0;
 warning_count = 0;
 received_count = 1;            /* For the one we will add */
@@ -1559,8 +1690,10 @@ message_linecount = body_linecount = body_zerocount =
   max_received_linelength = 0;
 
 #ifndef DISABLE_DKIM
-/* Call into DKIM to set up the context. */
-if (smtp_input && !smtp_batched_input && !dkim_disable_verify) dkim_exim_verify_init();
+/* Call into DKIM to set up the context.  In CHUNKING mode
+we clear the dot-stuffing flag */
+if (smtp_input && !smtp_batched_input && !dkim_disable_verify)
+  dkim_exim_verify_init(chunking_state <= CHUNKING_OFFERED);
 #endif
 
 #ifdef EXPERIMENTAL_DMARC
@@ -1616,7 +1749,7 @@ next->text. */
 
 for (;;)
   {
-  int ch = (receive_getc)();
+  int ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
 
   /* If we hit EOF on a SMTP connection, it's an error, since incoming
   SMTP must have a correct "." terminator. */
@@ -1695,10 +1828,10 @@ for (;;)
 
   if (ptr == 0 && ch == '.' && (smtp_input || dot_ends))
     {
-    ch = (receive_getc)();
+    ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
     if (ch == '\r')
       {
-      ch = (receive_getc)();
+      ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
       if (ch != '\n')
         {
         receive_ungetc(ch);
@@ -1729,7 +1862,7 @@ for (;;)
 
   if (ch == '\r')
     {
-    ch = (receive_getc)();
+    ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
     if (ch == '\n')
       {
       if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = TRUE;
@@ -1824,7 +1957,7 @@ for (;;)
 
   if (ch != EOF)
     {
-    int nextch = (receive_getc)();
+    int nextch = (receive_getc)(GETC_BUFFER_UNLIMITED);
     if (nextch == ' ' || nextch == '\t')
       {
       next->text[ptr++] = nextch;
@@ -2018,6 +2151,21 @@ for (;;)
       }
     }
 
+  /* Reject CHUNKING messages that do not CRLF their first header line */
+
+  if (!first_line_ended_crlf && chunking_state > CHUNKING_OFFERED)
+    {
+    log_write(L_size_reject, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: "
+      "Non-CRLF-terminated header, under CHUNKING: message abandoned",
+      sender_address,
+      sender_fullhost ? " H=" : "", sender_fullhost ? sender_fullhost : US"",
+      sender_ident ? " U=" : "",    sender_ident ? sender_ident : US"");
+    smtp_printf("552 Message header not CRLF terminated\r\n");
+    bdat_flush_data();
+    smtp_reply = US"";
+    goto TIDYUP;                             /* Skip to end of function */
+    }
+
   /* The line has been handled. If we have hit EOF, break out of the loop,
   indicating no pending data line. */
 
@@ -2042,7 +2190,7 @@ normal case). */
 DEBUG(D_receive)
   {
   debug_printf(">>Headers received:\n");
-  for (h = header_list->next; h != NULL; h = h->next)
+  for (h = header_list->next; h; h = h->next)
     debug_printf("%s", h->text);
   debug_printf("\n");
   }
@@ -2069,7 +2217,7 @@ if (filter_test != FTEST_NONE && header_list->next == NULL)
 /* Scan the headers to identify them. Some are merely marked for later
 processing; some are dealt with here. */
 
-for (h = header_list->next; h != NULL; h = h->next)
+for (h = header_list->next; h; h = h->next)
   {
   BOOL is_resent = strncmpic(h->text, US"resent-", 7) == 0;
   if (is_resent) contains_resent_headers = TRUE;
@@ -2285,7 +2433,7 @@ if (extract_recip)
 
   /* Now scan the headers */
 
-  for (h = header_list->next; h != NULL; h = h->next)
+  for (h = header_list->next; h; h = h->next)
     {
     if ((h->type == htype_to || h->type == htype_cc || h->type == htype_bcc) &&
         (!contains_resent_headers || strncmpic(h->text, US"resent-", 7) == 0))
@@ -2318,9 +2466,23 @@ if (extract_recip)
         pp = recipient = store_get(ss - s + 1);
         for (p = s; p < ss; p++) if (*p != '\n') *pp++ = *p;
         *pp = 0;
+
+#ifdef SUPPORT_I18N
+       {
+       BOOL b = allow_utf8_domains;
+       allow_utf8_domains = TRUE;
+#endif
         recipient = parse_extract_address(recipient, &errmess, &start, &end,
           &domain, FALSE);
 
+#ifdef SUPPORT_I18N
+       if (string_is_utf8(recipient))
+         message_smtputf8 = TRUE;
+       else
+         allow_utf8_domains = b;
+       }
+#endif
+
         /* Keep a list of all the bad addresses so we can send a single
         error message at the end. However, an empty address is not an error;
         just ignore it. This can come from an empty group list like
@@ -2461,7 +2623,7 @@ it will fit. */
 to be the least significant base-62 digit of the time of arrival. Otherwise
 ensure that it is an empty string. */
 
-message_subdir[0] = split_spool_directory? message_id[5] : 0;
+message_subdir[0] = split_spool_directory ? message_id[5] : 0;
 
 /* Now that we have the message-id, if there is no message-id: header, generate
 one, but only for local (without suppress_local_fixups) or submission mode
@@ -2530,7 +2692,7 @@ if (msgid_header == NULL &&
 rewriting. Must copy the count, because later ACLs and the local_scan()
 function may mess with the real recipients. */
 
-if ((log_extra_selector & LX_received_recipients) != 0)
+if (LOGGING(received_recipients))
   {
   raw_recipients = store_get(recipients_count * sizeof(uschar *));
   for (i = 0; i < recipients_count; i++)
@@ -2765,11 +2927,11 @@ We start at the second header, skipping our own Received:. This rewriting is
 documented as happening *after* recipient addresses are taken from the headers
 by the -t command line option. An added Sender: gets rewritten here. */
 
-for (h = header_list->next; h != NULL; h = h->next)
+for (h = header_list->next; h; h = h->next)
   {
   header_line *newh = rewrite_header(h, NULL, NULL, global_rewrite_rules,
     rewrite_existflags, TRUE);
-  if (newh != NULL) h = newh;
+  if (newh) h = newh;
   }
 
 
@@ -2820,13 +2982,20 @@ if (filter_test != FTEST_NONE)
   return message_ended == END_DOT;
   }
 
-/* Cutthrough delivery:
-       We have to create the Received header now rather than at the end of reception,
-       so the timestamp behaviour is a change to the normal case.
-       XXX Ensure this gets documented XXX.
-       Having created it, send the headers to the destination.
+/*XXX CHUNKING: need to cancel cutthrough under BDAT, for now.  In future,
+think more if it could be handled.  Cannot do onward CHUNKING unless
+inbound is, but inbound chunking ought to be ok with outbound plain.
+Could we do onward CHUNKING given inbound CHUNKING?
 */
-if (cutthrough_fd >= 0)
+if (chunking_state > CHUNKING_OFFERED)
+  cancel_cutthrough_connection("chunking active");
+
+/* Cutthrough delivery:
+We have to create the Received header now rather than at the end of reception,
+so the timestamp behaviour is a change to the normal case.
+XXX Ensure this gets documented XXX.
+Having created it, send the headers to the destination. */
+if (cutthrough.fd >= 0)
   {
   if (received_count > received_headers_max)
     {
@@ -2851,20 +3020,18 @@ if (cutthrough_fd >= 0)
 
 /* Open a new spool file for the data portion of the message. We need
 to access it both via a file descriptor and a stream. Try to make the
-directory if it isn't there. Note re use of sprintf: spool_directory
-is checked on input to be < 200 characters long. */
+directory if it isn't there. */
+
+spool_name = spool_fname(US"input", message_subdir, message_id, US"-D");
+DEBUG(D_receive) debug_printf("Data file name: %s\n", spool_name);
 
-sprintf(CS spool_name, "%s/input/%s/%s-D", spool_directory, message_subdir,
-  message_id);
-data_fd = Uopen(spool_name, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE);
-if (data_fd < 0)
+if ((data_fd = Uopen(spool_name, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE)) < 0)
   {
   if (errno == ENOENT)
     {
-    uschar temp[16];
-    sprintf(CS temp, "input/%s", message_subdir);
-    if (message_subdir[0] == 0) temp[5] = 0;
-    (void)directory_make(spool_directory, temp, INPUT_DIRECTORY_MODE, TRUE);
+    (void) directory_make(spool_directory,
+                       spool_sname(US"input", message_subdir),
+                       INPUT_DIRECTORY_MODE, TRUE);
     data_fd = Uopen(spool_name, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE);
     }
   if (data_fd < 0)
@@ -2920,7 +3087,9 @@ if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT)
   {
   if (smtp_input)
     {
-    message_ended = read_message_data_smtp(data_file);
+    message_ended = chunking_state > CHUNKING_OFFERED
+      ? read_message_bdat_smtp(data_file)
+      : read_message_data_smtp(data_file);
     receive_linecount++;                /* The terminating "." line */
     }
   else message_ended = read_message_data(data_file);
@@ -2928,51 +3097,64 @@ if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT)
   receive_linecount += body_linecount;  /* For BSMTP errors mainly */
   message_linecount += body_linecount;
 
-  /* Handle premature termination of SMTP */
-
-  if (smtp_input && message_ended == END_EOF)
+  switch (message_ended)
     {
-    Uunlink(spool_name);                     /* Lose data file when closed */
-    cancel_cutthrough_connection("sender closed connection");
-    message_id[0] = 0;                       /* Indicate no message accepted */
-    smtp_reply = handle_lost_connection(US"");
-    smtp_yield = FALSE;
-    goto TIDYUP;                             /* Skip to end of function */
-    }
+    /* Handle premature termination of SMTP */
 
-  /* Handle message that is too big. Don't use host_or_ident() in the log
-  message; we want to see the ident value even for non-remote messages. */
+    case END_EOF:
+      if (smtp_input)
+       {
+       Uunlink(spool_name);                 /* Lose data file when closed */
+       cancel_cutthrough_connection("sender closed connection");
+       message_id[0] = 0;                   /* Indicate no message accepted */
+       smtp_reply = handle_lost_connection(US"");
+       smtp_yield = FALSE;
+       goto TIDYUP;                         /* Skip to end of function */
+       }
+      break;
 
-  if (message_ended == END_SIZE)
-    {
-    Uunlink(spool_name);                /* Lose the data file when closed */
-    cancel_cutthrough_connection("mail too big");
-    if (smtp_input) receive_swallow_smtp();  /* Swallow incoming SMTP */
+    /* Handle message that is too big. Don't use host_or_ident() in the log
+    message; we want to see the ident value even for non-remote messages. */
 
-    log_write(L_size_reject, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: "
-      "message too big: read=%d max=%d",
-      sender_address,
-      (sender_fullhost == NULL)? "" : " H=",
-      (sender_fullhost == NULL)? US"" : sender_fullhost,
-      (sender_ident == NULL)? "" : " U=",
-      (sender_ident == NULL)? US"" : sender_ident,
-      message_size,
-      thismessage_size_limit);
+    case END_SIZE:
+      Uunlink(spool_name);                /* Lose the data file when closed */
+      cancel_cutthrough_connection("mail too big");
+      if (smtp_input) receive_swallow_smtp();  /* Swallow incoming SMTP */
 
-    if (smtp_input)
-      {
-      smtp_reply = US"552 Message size exceeds maximum permitted";
-      message_id[0] = 0;               /* Indicate no message accepted */
-      goto TIDYUP;                     /* Skip to end of function */
-      }
-    else
-      {
-      fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
-      give_local_error(ERRMESS_TOOBIG,
-        string_sprintf("message too big (max=%d)", thismessage_size_limit),
-        US"message rejected: ", error_rc, data_file, header_list);
-      /* Does not return */
-      }
+      log_write(L_size_reject, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: "
+       "message too big: read=%d max=%d",
+       sender_address,
+       (sender_fullhost == NULL)? "" : " H=",
+       (sender_fullhost == NULL)? US"" : sender_fullhost,
+       (sender_ident == NULL)? "" : " U=",
+       (sender_ident == NULL)? US"" : sender_ident,
+       message_size,
+       thismessage_size_limit);
+
+      if (smtp_input)
+       {
+       smtp_reply = US"552 Message size exceeds maximum permitted";
+       message_id[0] = 0;               /* Indicate no message accepted */
+       goto TIDYUP;                     /* Skip to end of function */
+       }
+      else
+       {
+       fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+       give_local_error(ERRMESS_TOOBIG,
+         string_sprintf("message too big (max=%d)", thismessage_size_limit),
+         US"message rejected: ", error_rc, data_file, header_list);
+       /* Does not return */
+       }
+      break;
+
+    /* Handle bad BDAT protocol sequence */
+
+    case END_PROTOCOL:
+      Uunlink(spool_name);             /* Lose the data file when closed */
+      cancel_cutthrough_connection("sender protocol error");
+      smtp_reply = US"";               /* Response already sent */
+      message_id[0] = 0;               /* Indicate no message accepted */
+      goto TIDYUP;                     /* Skip to end of function */
     }
   }
 
@@ -3155,9 +3337,8 @@ user_msg = NULL;
 enable_dollar_recipients = TRUE;
 
 if (recipients_count == 0)
-  {
-  blackholed_by = recipients_discarded? US"MAIL ACL" : US"RCPT ACL";
-  }
+  blackholed_by = recipients_discarded ? US"MAIL ACL" : US"RCPT ACL";
+
 else
   {
   /* Handle interactive SMTP messages */
@@ -3173,81 +3354,78 @@ else
       dkim_exim_verify_finish();
 
       /* Check if we must run the DKIM ACL */
-      if ((acl_smtp_dkim != NULL) &&
-          (dkim_verify_signers != NULL) &&
-          (dkim_verify_signers[0] != '\0'))
+      if (acl_smtp_dkim && dkim_verify_signers && *dkim_verify_signers)
         {
         uschar *dkim_verify_signers_expanded =
           expand_string(dkim_verify_signers);
-        if (dkim_verify_signers_expanded == NULL)
-          {
+        if (!dkim_verify_signers_expanded)
           log_write(0, LOG_MAIN|LOG_PANIC,
             "expansion of dkim_verify_signers option failed: %s",
             expand_string_message);
-          }
+
         else
           {
           int sep = 0;
-          uschar *ptr = dkim_verify_signers_expanded;
+          const uschar *ptr = dkim_verify_signers_expanded;
           uschar *item = NULL;
           uschar *seen_items = NULL;
           int     seen_items_size = 0;
           int     seen_items_offset = 0;
-          uschar itembuf[256];
           /* Default to OK when no items are present */
           rc = OK;
-          while ((item = string_nextinlist(&ptr, &sep,
-                                           itembuf,
-                                           sizeof(itembuf))) != NULL)
+          while ((item = string_nextinlist(&ptr, &sep, NULL, 0)))
             {
             /* Prevent running ACL for an empty item */
-            if (!item || (item[0] == '\0')) continue;
-            /* Only run ACL once for each domain or identity, no matter how often it
-               appears in the expanded list. */
-            if (seen_items != NULL)
+            if (!item || !*item) continue;
+
+            /* Only run ACL once for each domain or identity,
+           no matter how often it appears in the expanded list. */
+            if (seen_items)
               {
               uschar *seen_item = NULL;
-              uschar seen_item_buf[256];
-              uschar *seen_items_list = seen_items;
-              int seen_this_item = 0;
+              const uschar *seen_items_list = seen_items;
+              BOOL seen_this_item = FALSE;
 
               while ((seen_item = string_nextinlist(&seen_items_list, &sep,
-                                                    seen_item_buf,
-                                                    sizeof(seen_item_buf))) != NULL)
-                {
-                  if (Ustrcmp(seen_item,item) == 0)
-                    {
-                      seen_this_item = 1;
-                      break;
-                    }
-                }
-
-              if (seen_this_item > 0)
+                                                    NULL, 0)))
+               if (Ustrcmp(seen_item,item) == 0)
+                 {
+                 seen_this_item = TRUE;
+                 break;
+                 }
+
+              if (seen_this_item)
                 {
                 DEBUG(D_receive)
-                  debug_printf("acl_smtp_dkim: skipping signer %s, already seen\n", item);
+                  debug_printf("acl_smtp_dkim: skipping signer %s, "
+                   "already seen\n", item);
                 continue;
                 }
 
-              seen_items = string_append(seen_items,&seen_items_size,&seen_items_offset,1,":");
+              seen_items = string_append(seen_items, &seen_items_size,
+               &seen_items_offset, 1, ":");
               }
 
-            seen_items = string_append(seen_items,&seen_items_size,&seen_items_offset,1,item);
+            seen_items = string_append(seen_items, &seen_items_size,
+             &seen_items_offset, 1, item);
             seen_items[seen_items_offset] = '\0';
 
             DEBUG(D_receive)
-              debug_printf("calling acl_smtp_dkim for dkim_cur_signer=%s\n", item);
+              debug_printf("calling acl_smtp_dkim for dkim_cur_signer=%s\n",
+               item);
 
             dkim_exim_acl_setup(item);
-            rc = acl_check(ACL_WHERE_DKIM, NULL, acl_smtp_dkim, &user_msg, &log_msg);
+            rc = acl_check(ACL_WHERE_DKIM, NULL, acl_smtp_dkim,
+                 &user_msg, &log_msg);
 
             if (rc != OK)
-              {
-                DEBUG(D_receive)
-                  debug_printf("acl_smtp_dkim: acl_check returned %d on %s, skipping remaining items\n", rc, item);
-               cancel_cutthrough_connection("dkim acl not ok");
-                break;
-              }
+             {
+             DEBUG(D_receive)
+               debug_printf("acl_smtp_dkim: acl_check returned %d on %s, "
+                 "skipping remaining items\n", rc, item);
+             cancel_cutthrough_connection("dkim acl not ok");
+             break;
+             }
             }
           add_acl_headers(ACL_WHERE_DKIM, US"DKIM");
           if (rc == DISCARD)
@@ -3261,7 +3439,7 @@ else
             {
             Uunlink(spool_name);
             if (smtp_handle_acl_fail(ACL_WHERE_DKIM, rc, user_msg, log_msg) != 0)
-              smtp_yield = FALSE;    /* No more messsages after dropped connection */
+              smtp_yield = FALSE;    /* No more messages after dropped connection */
             smtp_reply = US"";       /* Indicate reply already sent */
             message_id[0] = 0;       /* Indicate no message accepted */
             goto TIDYUP;             /* Skip to end of function */
@@ -3379,7 +3557,7 @@ else
        dcc_ok = 0;
 #endif
         if (smtp_handle_acl_fail(ACL_WHERE_DATA, rc, user_msg, log_msg) != 0)
-          smtp_yield = FALSE;    /* No more messsages after dropped connection */
+          smtp_yield = FALSE;    /* No more messages after dropped connection */
         smtp_reply = US"";       /* Indicate reply already sent */
         message_id[0] = 0;       /* Indicate no message accepted */
         goto TIDYUP;             /* Skip to end of function */
@@ -3570,7 +3748,7 @@ else
     goto TEMPREJECT;
 
     case LOCAL_SCAN_REJECT_NOLOGHDR:
-    log_extra_selector &= ~LX_rejected_header;
+    BIT_CLEAR(log_selector, log_selector_size, Li_rejected_header);
     /* Fall through */
 
     case LOCAL_SCAN_REJECT:
@@ -3579,7 +3757,7 @@ else
     break;
 
     case LOCAL_SCAN_TEMPREJECT_NOLOGHDR:
-    log_extra_selector &= ~LX_rejected_header;
+    BIT_CLEAR(log_selector, log_selector_size, Li_rejected_header);
     /* Fall through */
 
     case LOCAL_SCAN_TEMPREJECT:
@@ -3635,14 +3813,14 @@ signal(SIGINT, SIG_IGN);
 deliver_firsttime = TRUE;
 
 #ifdef EXPERIMENTAL_BRIGHTMAIL
-if (bmi_run == 1) {
-  /* rewind data file */
+if (bmi_run == 1)
+  /* rewind data file */
   lseek(data_fd, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
   bmi_verdicts = bmi_process_message(header_list, data_fd);
-};
+  }
 #endif
 
-/* Update the timstamp in our Received: header to account for any time taken by
+/* Update the timestamp in our Received: header to account for any time taken by
 an ACL or by local_scan(). The new time is the time that all reception
 processing is complete. */
 
@@ -3677,7 +3855,6 @@ if (host_checking || blackholed_by != NULL)
 /* Write the -H file */
 
 else
-  {
   if ((msg_size = spool_write_header(message_id, SW_RECEIVING, &errmsg)) < 0)
     {
     log_write(0, LOG_MAIN, "Message abandoned: %s", errmsg);
@@ -3697,7 +3874,6 @@ else
       /* Does not return */
       }
     }
-  }
 
 
 /* The message has now been successfully received. */
@@ -3729,30 +3905,31 @@ string as required. Since we commonly want to add two items at a time, use a
 macro to simplify the coding. We log the arrival of a new message while the
 file is still locked, just in case the machine is *really* fast, and delivers
 it first! Include any message id that is in the message - since the syntax of a
-message id is actually an addr-spec, we can use the parse routine to canonicize
+message id is actually an addr-spec, we can use the parse routine to canonicalize
 it. */
 
 size = 256;
 sptr = 0;
 s = store_get(size);
 
-s = string_append(s, &size, &sptr, 2, US"<= ",
-  (sender_address[0] == 0)? US"<>" : sender_address);
-if (message_reference != NULL)
+s = string_append(s, &size, &sptr, 2,
+  fake_response == FAIL ? US"(= " : US"<= ",
+  sender_address[0] == 0 ? US"<>" : sender_address);
+if (message_reference)
   s = string_append(s, &size, &sptr, 2, US" R=", message_reference);
 
 s = add_host_info_for_log(s, &size, &sptr);
 
 #ifdef SUPPORT_TLS
-if (log_extra_selector & LX_tls_cipher && tls_in.cipher)
+if (LOGGING(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 && tls_in.cipher)
+if (LOGGING(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 && tls_in.peerdn)
+    tls_in.certificate_verified ? "yes":"no");
+if (LOGGING(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 && tls_in.sni)
+if (LOGGING(tls_sni) && tls_in.sni)
   s = string_append(s, &size, &sptr, 3, US" SNI=\"",
     string_printing(tls_in.sni), US"\"");
 #endif
@@ -3760,24 +3937,27 @@ if (log_extra_selector & LX_tls_sni && tls_in.sni)
 if (sender_host_authenticated)
   {
   s = string_append(s, &size, &sptr, 2, US" A=", sender_host_authenticated);
-  if (authenticated_id != NULL)
+  if (authenticated_id)
     {
     s = string_append(s, &size, &sptr, 2, US":", authenticated_id);
-    if (log_extra_selector & LX_smtp_mailauth  &&  authenticated_sender != NULL)
+    if (LOGGING(smtp_mailauth) && authenticated_sender)
       s = string_append(s, &size, &sptr, 2, US":", authenticated_sender);
     }
   }
 
 #ifndef DISABLE_PRDR
 if (prdr_requested)
-  s = string_append(s, &size, &sptr, 1, US" PRDR");
+  s = string_catn(s, &size, &sptr, US" PRDR", 5);
 #endif
 
-#ifdef EXPERIMENTAL_PROXY
-if (proxy_session &&  log_extra_selector & LX_proxy)
-  s = string_append(s, &size, &sptr, 2, US" PRX=", proxy_host_address);
+#ifdef SUPPORT_PROXY
+if (proxy_session && LOGGING(proxy))
+  s = string_append(s, &size, &sptr, 2, US" PRX=", proxy_local_address);
 #endif
 
+if (chunking_state > CHUNKING_OFFERED)
+  s = string_catn(s, &size, &sptr, US" K", 2);
+
 sprintf(CS big_buffer, "%d", msg_size);
 s = string_append(s, &size, &sptr, 2, US" S=", big_buffer);
 
@@ -3785,18 +3965,21 @@ s = string_append(s, &size, &sptr, 2, US" S=", big_buffer);
    0 ... no BODY= used
    7 ... 7BIT
    8 ... 8BITMIME */
-if (log_extra_selector & LX_8bitmime)
+if (LOGGING(8bitmime))
   {
   sprintf(CS big_buffer, "%d", body_8bitmime);
   s = string_append(s, &size, &sptr, 2, US" M8S=", big_buffer);
   }
 
+if (*queue_name)
+  s = string_append(s, &size, &sptr, 2, US" Q=", queue_name);
+
 /* If an addr-spec in a message-id contains a quoted string, it can contain
 any characters except " \ and CR and so in particular it can contain NL!
 Therefore, make sure we use a printing-characters only version for the log.
 Also, allow for domain literals in the message id. */
 
-if (msgid_header != NULL)
+if (msgid_header)
   {
   uschar *old_id;
   BOOL save_allow_domain_literals = allow_domain_literals;
@@ -3811,7 +3994,7 @@ if (msgid_header != NULL)
 /* If subject logging is turned on, create suitable printing-character
 text. By expanding $h_subject: we make use of the MIME decoding. */
 
-if ((log_extra_selector & LX_subject) != 0 && subject_header != NULL)
+if (LOGGING(subject) && subject_header != NULL)
   {
   int i;
   uschar *p = big_buffer;
@@ -3845,16 +4028,15 @@ if (message_logs && blackholed_by == NULL)
   {
   int fd;
 
-  sprintf(CS spool_name, "%s/msglog/%s/%s", spool_directory, message_subdir,
-    message_id);
-  fd = Uopen(spool_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE);
-
-  if (fd < 0 && errno == ENOENT)
+  spool_name = spool_fname(US"msglog", message_subdir, message_id, US"");
+  
+  if (  (fd = Uopen(spool_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE)) < 0
+     && errno == ENOENT
+     )
     {
-    uschar temp[16];
-    sprintf(CS temp, "msglog/%s", message_subdir);
-    if (message_subdir[0] == 0) temp[6] = 0;
-    (void)directory_make(spool_directory, temp, MSGLOG_DIRECTORY_MODE, TRUE);
+    (void)directory_make(spool_directory,
+                       spool_sname(US"msglog", message_subdir),
+                       MSGLOG_DIRECTORY_MODE, TRUE);
     fd = Uopen(spool_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE);
     }
 
@@ -3880,7 +4062,9 @@ if (message_logs && blackholed_by == NULL)
       if (deliver_freeze) fprintf(message_log, "%s frozen by %s\n", now,
         frozen_by);
       if (queue_only_policy) fprintf(message_log,
-        "%s no immediate delivery: queued by %s\n", now, queued_by);
+        "%s no immediate delivery: queued%s%s by %s\n", now,
+        *queue_name ? " in " : "", *queue_name ? CS queue_name : "",
+       queued_by);
       (void)fclose(message_log);
       }
     }
@@ -3922,34 +4106,26 @@ if (smtp_input && sender_host_address != NULL && !sender_host_notsocket &&
 
   if (select(fileno(smtp_in) + 1, &select_check, NULL, NULL, &tv) != 0)
     {
-    int c = (receive_getc)();
+    int c = (receive_getc)(GETC_BUFFER_UNLIMITED);
     if (c != EOF) (receive_ungetc)(c); else
       {
-      uschar *msg = US"SMTP connection lost after final dot";
+      smtp_notquit_exit(US"connection-lost", NULL, NULL);
       smtp_reply = US"";    /* No attempt to send a response */
       smtp_yield = FALSE;   /* Nothing more on this connection */
 
       /* Re-use the log line workspace */
 
       sptr = 0;
-      s = string_cat(s, &size, &sptr, msg, Ustrlen(msg));
+      s = string_cat(s, &size, &sptr, US"SMTP connection lost after final dot");
       s = add_host_info_for_log(s, &size, &sptr);
       s[sptr] = 0;
       log_write(0, LOG_MAIN, "%s", s);
 
       /* Delete the files for this aborted message. */
 
-      sprintf(CS spool_name, "%s/input/%s/%s-D", spool_directory,
-        message_subdir, message_id);
-      Uunlink(spool_name);
-
-      sprintf(CS spool_name, "%s/input/%s/%s-H", spool_directory,
-        message_subdir, message_id);
-      Uunlink(spool_name);
-
-      sprintf(CS spool_name, "%s/msglog/%s/%s", spool_directory,
-        message_subdir, message_id);
-      Uunlink(spool_name);
+      Uunlink(spool_fname(US"input", message_subdir, message_id, US"-D"));
+      Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H"));
+      Uunlink(spool_fname(US"msglog", message_subdir, message_id, US""));
 
       goto TIDYUP;
       }
@@ -3964,16 +4140,17 @@ for this message. */
 
    Send dot onward.  If accepted, wipe the spooled files, log as delivered and accept
    the sender's dot (below).
-   If rejected: copy response to sender, wipe the spooled files, log approriately.
-   If temp-reject: accept to sender, keep the spooled files.
+   If rejected: copy response to sender, wipe the spooled files, log appropriately.
+   If temp-reject: normally accept to sender, keep the spooled file - unless defer=pass
+   in which case pass temp-reject back to initiator and dump the files.
 
    Having the normal spool files lets us do data-filtering, and store/forward on temp-reject.
 
    XXX We do not handle queue-only, freezing, or blackholes.
 */
-if(cutthrough_fd >= 0)
+if(cutthrough.fd >= 0)
   {
-  uschar * msg= cutthrough_finaldot(); /* Ask the target system to accept the messsage */
+  uschar * msg= cutthrough_finaldot(); /* Ask the target system to accept the message */
                                        /* Logging was done in finaldot() */
   switch(msg[0])
     {
@@ -3981,13 +4158,17 @@ if(cutthrough_fd >= 0)
       cutthrough_done = ACCEPTED;
       break;                                   /* message_id needed for SMTP accept below */
 
+    case '4':  /* Temp-reject. Keep spoolfiles and accept, unless defer-pass mode.
+               ... for which, pass back the exact error */
+      if (cutthrough.defer_pass) smtp_reply = string_copy_malloc(msg);
+      /*FALLTRHOUGH*/
+
     default:   /* Unknown response, or error.  Treat as temp-reject.         */
-    case '4':  /* Temp-reject. Keep spoolfiles and accept. */
       cutthrough_done = TMP_REJ;               /* Avoid the usual immediate delivery attempt */
       break;                                   /* message_id needed for SMTP accept below */
 
     case '5':  /* Perm-reject.  Do the same to the source.  Dump any spoolfiles */
-      smtp_reply= msg;         /* Pass on the exact error */
+      smtp_reply = string_copy_malloc(msg);            /* Pass on the exact error */
       cutthrough_done = PERM_REJ;
       break;
     }
@@ -4000,15 +4181,17 @@ if(!smtp_reply)
 #endif
   {
   log_write(0, LOG_MAIN |
-    (((log_extra_selector & LX_received_recipients) != 0)? LOG_RECIPIENTS : 0) |
-    (((log_extra_selector & LX_received_sender) != 0)? LOG_SENDER : 0),
+    (LOGGING(received_recipients)? LOG_RECIPIENTS : 0) |
+    (LOGGING(received_sender)? LOG_SENDER : 0),
     "%s", s);
 
   /* Log any control actions taken by an ACL or local_scan(). */
 
   if (deliver_freeze) log_write(0, LOG_MAIN, "frozen by %s", frozen_by);
   if (queue_only_policy) log_write(L_delay_delivery, LOG_MAIN,
-    "no immediate delivery: queued by %s", queued_by);
+    "no immediate delivery: queued%s%s by %s",
+    *queue_name ? " in " : "", *queue_name ? CS queue_name : "",       
+    queued_by);
   }
 receive_call_bombout = FALSE;
 
@@ -4064,26 +4247,33 @@ if (smtp_input)
 
   if (!smtp_batched_input)
     {
-    if (smtp_reply == NULL)
+    if (!smtp_reply)
       {
       if (fake_response != OK)
-        smtp_respond((fake_response == DEFER)? US"450" : US"550", 3, TRUE,
-          fake_response_text);
+        smtp_respond(fake_response == DEFER ? US"450" : US"550",
+         3, TRUE, fake_response_text);
 
       /* An OK response is required; use "message" text if present. */
 
-      else if (user_msg != NULL)
+      else if (user_msg)
         {
         uschar *code = US"250";
         int len = 3;
-        smtp_message_code(&code, &len, &user_msg, NULL);
+        smtp_message_code(&code, &len, &user_msg, NULL, TRUE);
         smtp_respond(code, len, TRUE, user_msg);
         }
 
       /* Default OK response */
 
+      else if (chunking_state > CHUNKING_OFFERED)
+       {
+        smtp_printf("250- %u byte chunk, total %d\r\n250 OK id=%s\r\n",
+           chunking_datasize, message_size+message_linecount, message_id);
+       chunking_state = CHUNKING_OFFERED;
+       }
       else
         smtp_printf("250 OK id=%s\r\n", message_id);
+
       if (host_checking)
         fprintf(stdout,
           "\n**** SMTP testing: that is not a real message id!\n\n");
@@ -4092,39 +4282,45 @@ if (smtp_input)
     /* smtp_reply is set non-empty */
 
     else if (smtp_reply[0] != 0)
-      {
       if (fake_response != OK && (smtp_reply[0] == '2'))
         smtp_respond((fake_response == DEFER)? US"450" : US"550", 3, TRUE,
           fake_response_text);
       else
         smtp_printf("%.1024s\r\n", smtp_reply);
-      }
 
     switch (cutthrough_done)
       {
-      case ACCEPTED: log_write(0, LOG_MAIN, "Completed");/* Delivery was done */
-      case PERM_REJ: {                                 /* Delete spool files */
-             sprintf(CS spool_name, "%s/input/%s/%s-D", spool_directory,
-               message_subdir, message_id);
-             Uunlink(spool_name);
-             sprintf(CS spool_name, "%s/input/%s/%s-H", spool_directory,
-               message_subdir, message_id);
-             Uunlink(spool_name);
-             sprintf(CS spool_name, "%s/msglog/%s/%s", spool_directory,
-               message_subdir, message_id);
-             Uunlink(spool_name);
-             }
-      case TMP_REJ: message_id[0] = 0;   /* Prevent a delivery from starting */
-      default:break;
+      case ACCEPTED:
+       log_write(0, LOG_MAIN, "Completed");/* Delivery was done */
+      case PERM_REJ:
+                                                        /* Delete spool files */
+       Uunlink(spool_fname(US"input", message_subdir, message_id, US"-D"));
+       Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H"));
+       Uunlink(spool_fname(US"msglog", message_subdir, message_id, US""));
+       message_id[0] = 0;        /* Prevent a delivery from starting */
+       break;
+
+      case TMP_REJ:
+       if (cutthrough.defer_pass)
+         {
+         Uunlink(spool_fname(US"input", message_subdir, message_id, US"-D"));
+         Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H"));
+         Uunlink(spool_fname(US"msglog", message_subdir, message_id, US""));
+         }
+       message_id[0] = 0;        /* Prevent a delivery from starting */
+      default:
+       break;
       }
-    cutthrough_delivery = FALSE;
+    cutthrough.delivery = FALSE;
+    cutthrough.defer_pass = FALSE;
     }
 
   /* For batched SMTP, generate an error message on failure, and do
   nothing on success. The function moan_smtp_batch() does not return -
   it exits from the program with a non-zero return code. */
 
-  else if (smtp_reply != NULL) moan_smtp_batch(NULL, "%s", smtp_reply);
+  else if (smtp_reply)
+    moan_smtp_batch(NULL, "%s", smtp_reply);
   }
 
 
@@ -4133,11 +4329,11 @@ file has already been unlinked, and the header file was never written to disk.
 We must now indicate that nothing was received, to prevent a delivery from
 starting. */
 
-if (blackholed_by != NULL)
+if (blackholed_by)
   {
-  uschar *detail = (local_scan_data != NULL)?
-    string_printing(local_scan_data) :
-    string_sprintf("(%s discarded recipients)", blackholed_by);
+  const uschar *detail = local_scan_data
+    ? string_printing(local_scan_data)
+    string_sprintf("(%s discarded recipients)", blackholed_by);
   log_write(0, LOG_MAIN, "=> blackhole %s%s", detail, blackhole_log_msg);
   log_write(0, LOG_MAIN, "Completed");
   message_id[0] = 0;
dissimilarity index 88%
index de8ec68..9274f90 100644 (file)
-/*************************************************
-*     Exim - an Internet mail transport agent    *
-*************************************************/
-
-/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003-???? */
-/* License: GPL */
-
-/* Code for matching regular expressions against headers and body.
- Called from acl.c. */
-
-#include "exim.h"
-#ifdef WITH_CONTENT_SCAN
-#include <unistd.h>
-#include <sys/mman.h>
-
-/* Structure to hold a list of Regular expressions */
-typedef struct pcre_list {
-  pcre *re;
-  uschar *pcre_text;
-  struct pcre_list *next;
-} pcre_list;
-
-uschar regex_match_string_buffer[1024];
-
-extern FILE *mime_stream;
-extern uschar *mime_current_boundary;
-
-int regex(uschar **listptr) {
-  int sep = 0;
-  uschar *list = *listptr;
-  uschar *regex_string;
-  uschar regex_string_buffer[1024];
-  unsigned long mbox_size;
-  FILE *mbox_file;
-  pcre *re;
-  pcre_list *re_list_head = NULL;
-  pcre_list *re_list_item;
-  const char *pcre_error;
-  int pcre_erroffset;
-  uschar *linebuffer;
-  long f_pos = 0;
-
-  /* reset expansion variable */
-  regex_match_string = NULL;
-
-  if (mime_stream == NULL) {
-    /* We are in the DATA ACL */
-    mbox_file = spool_mbox(&mbox_size, NULL);
-    if (mbox_file == NULL) {
-      /* error while spooling */
-      log_write(0, LOG_MAIN|LOG_PANIC,
-             "regex acl condition: error while creating mbox spool file");
-      return DEFER;
-    };
-  }
-  else {
-    f_pos = ftell(mime_stream);
-    mbox_file = mime_stream;
-  };
-
-  /* precompile our regexes */
-  while ((regex_string = string_nextinlist(&list, &sep,
-                                           regex_string_buffer,
-                                           sizeof(regex_string_buffer))) != NULL) {
-
-    /* parse option */
-    if ( (strcmpic(regex_string,US"false") == 0) ||
-         (Ustrcmp(regex_string,"0") == 0) ) {
-      /* explicitly no matching */
-      continue;
-    };
-
-    /* compile our regular expression */
-    re = pcre_compile( CS regex_string,
-                       0,
-                       &pcre_error,
-                       &pcre_erroffset,
-                       NULL );
-
-    if (re == NULL) {
-      log_write(0, LOG_MAIN,
-           "regex acl condition warning - error in regex '%s': %s at offset %d, skipped.", regex_string, pcre_error, pcre_erroffset);
-      continue;
-    }
-    else {
-      re_list_item = store_get(sizeof(pcre_list));
-      re_list_item->re = re;
-      re_list_item->pcre_text = string_copy(regex_string);
-      re_list_item->next = re_list_head;
-      re_list_head = re_list_item;
-    };
-  };
-
-  /* no regexes -> nothing to do */
-  if (re_list_head == NULL) {
-    return FAIL;
-  };
-
-  /* match each line against all regexes */
-  linebuffer = store_get(32767);
-  while (fgets(CS linebuffer, 32767, mbox_file) != NULL) {
-    if ( (mime_stream != NULL) && (mime_current_boundary != NULL) ) {
-      /* check boundary */
-      if (Ustrncmp(linebuffer,"--",2) == 0) {
-        if (Ustrncmp((linebuffer+2),mime_current_boundary,Ustrlen(mime_current_boundary)) == 0)
-          /* found boundary */
-          break;
-      };
-    };
-    re_list_item = re_list_head;
-    do {
-      /* try matcher on the line */
-      if (pcre_exec(re_list_item->re, NULL, CS linebuffer,
-      (int)Ustrlen(linebuffer), 0, 0, NULL, 0) >= 0) {
-        Ustrncpy(regex_match_string_buffer, re_list_item->pcre_text, 1023);
-        regex_match_string = regex_match_string_buffer;
-        if (mime_stream == NULL)
-          (void)fclose(mbox_file);
-        else {
-          clearerr(mime_stream);
-          fseek(mime_stream,f_pos,SEEK_SET);
-        };
-        return OK;
-      };
-      re_list_item = re_list_item->next;
-    } while (re_list_item != NULL);
-  };
-
-  if (mime_stream == NULL)
-    (void)fclose(mbox_file);
-  else {
-    clearerr(mime_stream);
-    fseek(mime_stream,f_pos,SEEK_SET);
-  };
-
-  /* no matches ... */
-  return FAIL;
-}
-
-
-int mime_regex(uschar **listptr) {
-  int sep = 0;
-  uschar *list = *listptr;
-  uschar *regex_string;
-  uschar regex_string_buffer[1024];
-  pcre *re;
-  pcre_list *re_list_head = NULL;
-  pcre_list *re_list_item;
-  const char *pcre_error;
-  int pcre_erroffset;
-  FILE *f;
-  uschar *mime_subject = NULL;
-  int mime_subject_len = 0;
-
-  /* reset expansion variable */
-  regex_match_string = NULL;
-
-  /* precompile our regexes */
-  while ((regex_string = string_nextinlist(&list, &sep,
-                                           regex_string_buffer,
-                                           sizeof(regex_string_buffer))) != NULL) {
-
-    /* parse option */
-    if ( (strcmpic(regex_string,US"false") == 0) ||
-         (Ustrcmp(regex_string,"0") == 0) ) {
-      /* explicitly no matching */
-      continue;
-    };
-
-    /* compile our regular expression */
-    re = pcre_compile( CS regex_string,
-                       0,
-                       &pcre_error,
-                       &pcre_erroffset,
-                       NULL );
-
-    if (re == NULL) {
-      log_write(0, LOG_MAIN,
-           "regex acl condition warning - error in regex '%s': %s at offset %d, skipped.", regex_string, pcre_error, pcre_erroffset);
-      continue;
-    }
-    else {
-      re_list_item = store_get(sizeof(pcre_list));
-      re_list_item->re = re;
-      re_list_item->pcre_text = string_copy(regex_string);
-      re_list_item->next = re_list_head;
-      re_list_head = re_list_item;
-    };
-  };
-
-  /* no regexes -> nothing to do */
-  if (re_list_head == NULL) {
-    return FAIL;
-  };
-
-  /* check if the file is already decoded */
-  if (mime_decoded_filename == NULL) {
-    uschar *empty = US"";
-    /* no, decode it first */
-    mime_decode(&empty);
-    if (mime_decoded_filename == NULL) {
-      /* decoding failed */
-      log_write(0, LOG_MAIN,
-           "mime_regex acl condition warning - could not decode MIME part to file.");
-      return DEFER;
-    };
-  };
-
-
-  /* open file */
-  f = fopen(CS mime_decoded_filename, "rb");
-  if (f == NULL) {
-    /* open failed */
-    log_write(0, LOG_MAIN,
-         "mime_regex acl condition warning - can't open '%s' for reading.", mime_decoded_filename);
-    return DEFER;
-  };
-
-  /* get 32k memory */
-  mime_subject = (uschar *)store_get(32767);
-
-  /* read max 32k chars from file */
-  mime_subject_len = fread(mime_subject, 1, 32766, f);
-
-  re_list_item = re_list_head;
-  do {
-    /* try matcher on the mmapped file */
-    debug_printf("Matching '%s'\n", re_list_item->pcre_text);
-    if (pcre_exec(re_list_item->re, NULL, CS mime_subject,
-                  mime_subject_len, 0, 0, NULL, 0) >= 0) {
-      Ustrncpy(regex_match_string_buffer, re_list_item->pcre_text, 1023);
-      regex_match_string = regex_match_string_buffer;
-      (void)fclose(f);
-      return OK;
-    };
-    re_list_item = re_list_item->next;
-  } while (re_list_item != NULL);
-
-  (void)fclose(f);
-
-  /* no matches ... */
-  return FAIL;
-}
-
-#endif
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003-2015
+ * License: GPL
+ * Copyright (c) The Exim Maintainers 2016
+ */
+
+/* Code for matching regular expressions against headers and body.
+ Called from acl.c. */
+
+#include "exim.h"
+#ifdef WITH_CONTENT_SCAN
+#include <unistd.h>
+#include <sys/mman.h>
+
+/* Structure to hold a list of Regular expressions */
+typedef struct pcre_list {
+  pcre *re;
+  uschar *pcre_text;
+  struct pcre_list *next;
+} pcre_list;
+
+uschar regex_match_string_buffer[1024];
+
+extern FILE *mime_stream;
+extern uschar *mime_current_boundary;
+
+static pcre_list *
+compile(const uschar * list)
+{
+int sep = 0;
+uschar *regex_string;
+const char *pcre_error;
+int pcre_erroffset;
+pcre_list *re_list_head = NULL;
+pcre_list *ri;
+
+/* precompile our regexes */
+while ((regex_string = string_nextinlist(&list, &sep, NULL, 0)))
+  if (strcmpic(regex_string, US"false") != 0 && Ustrcmp(regex_string, "0") != 0)
+    {
+    pcre *re;
+
+    /* compile our regular expression */
+    if (!(re = pcre_compile( CS regex_string,
+                      0, &pcre_error, &pcre_erroffset, NULL )))
+      {
+      log_write(0, LOG_MAIN,
+          "regex acl condition warning - error in regex '%s': %s at offset %d, skipped.",
+          regex_string, pcre_error, pcre_erroffset);
+      continue;
+      }
+
+    ri = store_get(sizeof(pcre_list));
+    ri->re = re;
+    ri->pcre_text = regex_string;
+    ri->next = re_list_head;
+    re_list_head = ri;
+    }
+return re_list_head;
+}
+
+static int
+matcher(pcre_list * re_list_head, uschar * linebuffer, int len)
+{
+pcre_list * ri;
+
+for(ri = re_list_head; ri; ri = ri->next)
+  {
+  int ovec[3*(REGEX_VARS+1)];
+  int n, nn;
+
+  /* try matcher on the line */
+  n = pcre_exec(ri->re, NULL, CS linebuffer, len, 0, 0, ovec, nelem(ovec));
+  if (n > 0)
+    {
+    Ustrncpy(regex_match_string_buffer, ri->pcre_text,
+             sizeof(regex_match_string_buffer)-1);
+    regex_match_string = regex_match_string_buffer;
+
+    for (nn = 1; nn < n; nn++)
+      regex_vars[nn-1] =
+       string_copyn(linebuffer + ovec[nn*2], ovec[nn*2+1] - ovec[nn*2]);
+
+    return OK;
+    }
+  }
+return FAIL;
+}
+
+int
+regex(const uschar **listptr)
+{
+unsigned long mbox_size;
+FILE *mbox_file;
+pcre_list *re_list_head;
+uschar *linebuffer;
+long f_pos = 0;
+int ret = FAIL;
+
+/* reset expansion variable */
+regex_match_string = NULL;
+
+if (!mime_stream)                              /* We are in the DATA ACL */
+  {
+  if (!(mbox_file = spool_mbox(&mbox_size, NULL)))
+    {                                          /* error while spooling */
+    log_write(0, LOG_MAIN|LOG_PANIC,
+          "regex acl condition: error while creating mbox spool file");
+    return DEFER;
+    }
+  }
+else
+  {
+  if ((f_pos = ftell(mime_stream)) < 0)
+    {
+    log_write(0, LOG_MAIN|LOG_PANIC,
+          "regex acl condition: mime_stream: %s", strerror(errno));
+    return DEFER;
+    }
+  mbox_file = mime_stream;
+  }
+
+/* precompile our regexes */
+if (!(re_list_head = compile(*listptr)))
+  return FAIL;                 /* no regexes -> nothing to do */
+
+/* match each line against all regexes */
+linebuffer = store_get(32767);
+while (fgets(CS linebuffer, 32767, mbox_file))
+  {
+  if (  mime_stream && mime_current_boundary           /* check boundary */
+     && Ustrncmp(linebuffer, "--", 2) == 0
+     && Ustrncmp((linebuffer+2), mime_current_boundary,
+                 Ustrlen(mime_current_boundary)) == 0)
+      break;                                           /* found boundary */
+
+  if ((ret = matcher(re_list_head, linebuffer, (int)Ustrlen(linebuffer))) == OK)
+    goto done;
+  }
+/* no matches ... */
+
+done:
+if (!mime_stream)
+  (void)fclose(mbox_file);
+else
+  {
+  clearerr(mime_stream);
+  if (fseek(mime_stream, f_pos, SEEK_SET) == -1)
+    {
+    log_write(0, LOG_MAIN|LOG_PANIC,
+          "regex acl condition: mime_stream: %s", strerror(errno));
+    clearerr(mime_stream);
+    }
+  }
+
+return ret;
+}
+
+
+int
+mime_regex(const uschar **listptr)
+{
+pcre_list *re_list_head = NULL;
+FILE *f;
+uschar *mime_subject = NULL;
+int mime_subject_len = 0;
+int ret;
+
+/* reset expansion variable */
+regex_match_string = NULL;
+
+/* precompile our regexes */
+if (!(re_list_head = compile(*listptr)))
+  return FAIL;                 /* no regexes -> nothing to do */
+
+/* check if the file is already decoded */
+if (!mime_decoded_filename)
+  {                            /* no, decode it first */
+  const uschar *empty = US"";
+  mime_decode(&empty);
+  if (!mime_decoded_filename)
+    {                          /* decoding failed */
+    log_write(0, LOG_MAIN,
+       "mime_regex acl condition warning - could not decode MIME part to file");
+    return DEFER;
+    }
+  }
+
+/* open file */
+if (!(f = fopen(CS mime_decoded_filename, "rb")))
+  {
+  log_write(0, LOG_MAIN,
+       "mime_regex acl condition warning - can't open '%s' for reading",
+       mime_decoded_filename);
+  return DEFER;
+  }
+
+/* get 32k memory */
+mime_subject = store_get(32767);
+
+mime_subject_len = fread(mime_subject, 1, 32766, f);
+
+ret = matcher(re_list_head, mime_subject, mime_subject_len);
+(void)fclose(f);
+return ret;
+}
+
+#endif /* WITH_CONTENT_SCAN */
index 4809335..364591b 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions concerned with retrying unsuccessful deliveries. */
@@ -29,7 +29,7 @@ Returns:        TRUE if the ultimate timeout has been reached
 */
 
 BOOL
-retry_ultimate_address_timeout(uschar *retry_key, uschar *domain,
+retry_ultimate_address_timeout(uschar *retry_key, const uschar *domain,
   dbdata_retry *retry_record, time_t now)
 {
 BOOL address_timeout;
@@ -122,7 +122,7 @@ Returns:    TRUE if the host has expired but is usable because
 */
 
 BOOL
-retry_check_address(uschar *domain, host_item *host, uschar *portstring,
+retry_check_address(const uschar *domain, host_item *host, uschar *portstring,
   BOOL include_ip_address, uschar **retry_host_key, uschar **retry_message_key)
 {
 BOOL yield = FALSE;
@@ -294,12 +294,15 @@ void
 retry_add_item(address_item *addr, uschar *key, int flags)
 {
 retry_item *rti = store_get(sizeof(retry_item));
+host_item * host = addr->host_used;
 rti->next = addr->retries;
 addr->retries = rti;
 rti->key = key;
 rti->basic_errno = addr->basic_errno;
 rti->more_errno = addr->more_errno;
-rti->message = addr->message;
+rti->message = host
+  ? string_sprintf("H=%s [%s]: %s", host->name, host->address, addr->message)
+  : addr->message;
 rti->flags = flags;
 
 DEBUG(D_transport|D_retry)
@@ -340,12 +343,10 @@ Returns:       pointer to retry rule, or NULL
 */
 
 retry_config *
-retry_find_config(uschar *key, uschar *alternate, int basic_errno,
+retry_find_config(const uschar *key, const uschar *alternate, int basic_errno,
   int more_errno)
 {
-int replace = 0;
-uschar *use_key, *use_alternate;
-uschar *colon = Ustrchr(key, ':');
+const uschar *colon = Ustrchr(key, ':');
 retry_config *yield;
 
 /* If there's a colon in the key, there are two possibilities:
@@ -354,8 +355,7 @@ retry_config *yield;
 
       hostname:ip+port
 
-    In this case, we temporarily replace the colon with a zero, to terminate
-    the string after the host name.
+    In this case, we copy the host name.
 
 (2) This is a key for a pipe, file, or autoreply delivery, in the format
 
@@ -366,28 +366,22 @@ retry_config *yield;
     with a letter or a digit. In this case we want to use the original address
     to search for a retry rule. */
 
-if (colon != NULL)
-  {
-  if (isalnum(*key))
-    replace = ':';
-  else
-    key = Ustrrchr(key, ':') + 1;   /* Take from the last colon */
-  }
-
-if (replace == 0) colon = key + Ustrlen(key);
-*colon = 0;
+if (colon)
+  key = isalnum(*key)
+    ? string_copyn(key, colon-key)     /* the hostname */
+    : Ustrrchr(key, ':') + 1;          /* Take from the last colon */
 
 /* Sort out the keys */
 
-use_key = (Ustrchr(key, '@') != NULL)? key : string_sprintf("*@%s", key);
-use_alternate = (alternate == NULL)? NULL : string_sprintf("*@%s", alternate);
+if (!Ustrchr(key, '@')) key = string_sprintf("*@%s", key);
+if (alternate)    alternate = string_sprintf("*@%s", alternate);
 
 /* Scan the configured retry items. */
 
 for (yield = retries; yield != NULL; yield = yield->next)
   {
-  uschar *plist = yield->pattern;
-  uschar *slist = yield->senders;
+  const uschar *plist = yield->pattern;
+  const uschar *slist = yield->senders;
 
   /* If a specific error is set for this item, check that we are handling that
   specific error, and if so, check any additional error information if
@@ -486,15 +480,14 @@ for (yield = retries; yield != NULL; yield = yield->next)
   /* Check for a match between the address list item at the start of this retry
   rule and either the main or alternate keys. */
 
-  if (match_address_list(use_key, TRUE, TRUE, &plist, NULL, -1, UCHAR_MAX+1,
+  if (match_address_list(key, TRUE, TRUE, &plist, NULL, -1, UCHAR_MAX+1,
         NULL) == OK ||
-     (use_alternate != NULL &&
-      match_address_list(use_alternate, TRUE, TRUE, &plist, NULL, -1,
+     (alternate != NULL &&
+      match_address_list(alternate, TRUE, TRUE, &plist, NULL, -1,
         UCHAR_MAX+1, NULL) == OK))
     break;
   }
 
-*colon = replace;
 return yield;
 }
 
@@ -543,12 +536,12 @@ for (i = 0; i < 3; i++)
   {
   address_item *endaddr, *addr;
   address_item *last_first = NULL;
-  address_item **paddr = (i==0)? addr_succeed :
-    (i==1)? addr_failed : addr_defer;
+  address_item **paddr = i==0 ? addr_succeed :
+    i==1 ? addr_failed : addr_defer;
   address_item **saved_paddr = NULL;
 
-  DEBUG(D_retry) debug_printf("%s addresses:\n", (i == 0)? "Succeeded" :
-    (i == 1)? "Failed" : "Deferred");
+  DEBUG(D_retry) debug_printf("%s addresses:\n",
+    i == 0 ? "Succeeded" : i == 1 ? "Failed" : "Deferred");
 
   /* Loop for each address on the chain. For deferred addresses, the whole
   address times out unless one of its retry addresses has a retry rule that
@@ -560,22 +553,22 @@ for (i = 0; i < 3; i++)
   retry items for any parent addresses - these are typically "delete" items,
   because the parent must have succeeded in order to generate the child. */
 
-  while ((endaddr = *paddr) != NULL)
+  while ((endaddr = *paddr))
     {
     BOOL timed_out = FALSE;
     retry_item *rti;
 
-    for (addr = endaddr; addr != NULL; addr = addr->parent)
+    for (addr = endaddr; addr; addr = addr->parent)
       {
       int update_count = 0;
       int timedout_count = 0;
 
-      DEBUG(D_retry) debug_printf("%s%s\n", addr->address, (addr->retries == NULL)?
-        ": no retry items" : "");
+      DEBUG(D_retry) debug_printf(" %s%s\n", addr->address,
+               addr->retries ? "" : ": no retry items");
 
       /* Loop for each retry item. */
 
-      for (rti = addr->retries; rti != NULL; rti = rti->next)
+      for (rti = addr->retries; rti; rti = rti->next)
         {
         uschar *message;
         int message_length, message_space, failing_interval, next_try;
@@ -589,10 +582,10 @@ for (i = 0; i < 3; i++)
         opening if no addresses have retry items - common when none have yet
         reached their retry next try time. */
 
-        if (dbm_file == NULL)
+        if (!dbm_file)
           dbm_file = dbfn_open(US"retry", O_RDWR, &dbblock, TRUE);
 
-        if (dbm_file == NULL)
+        if (!dbm_file)
           {
           DEBUG(D_deliver|D_retry|D_hints_lookup)
             debug_printf("retry database not available for updating\n");
@@ -607,13 +600,13 @@ for (i = 0; i < 3; i++)
         but the address gets delivered to the second one. This optimization
         doesn't succeed in cleaning out all the dead entries, but it helps. */
 
-        if (*addr_defer == NULL && (rti->flags & rf_message) != 0)
+        if (!*addr_defer  &&  rti->flags & rf_message)
           rti->flags |= rf_delete;
 
         /* Handle the case of a request to delete the retry info for this
         destination. */
 
-        if ((rti->flags & rf_delete) != 0)
+        if (rti->flags & rf_delete)
           {
           (void)dbfn_delete(dbm_file, rti->key);
           DEBUG(D_retry)
@@ -633,21 +626,21 @@ for (i = 0; i < 3; i++)
         information is found, we can't generate a retry time, so there is
         no point updating the database. This retry item is timed out. */
 
-        if ((retry = retry_find_config(rti->key + 2,
-             ((rti->flags & rf_host) != 0)? addr->domain : NULL,
-             rti->basic_errno, rti->more_errno)) == NULL)
+        if (!(retry = retry_find_config(rti->key + 2,
+             rti->flags & rf_host ? addr->domain : NULL,
+             rti->basic_errno, rti->more_errno)))
           {
           DEBUG(D_retry) debug_printf("No configured retry item for %s%s%s\n",
             rti->key,
-            ((rti->flags & rf_host) != 0)? US" or " : US"",
-            ((rti->flags & rf_host) != 0)? addr->domain : US"");
+            rti->flags & rf_host ? US" or " : US"",
+            rti->flags & rf_host ? addr->domain : US"");
           if (addr == endaddr) timedout_count++;
           continue;
           }
 
         DEBUG(D_retry)
           {
-          if ((rti->flags & rf_host) != 0)
+          if (rti->flags & rf_host)
             debug_printf("retry for %s (%s) = %s %d %d\n", rti->key,
               addr->domain, retry->pattern, retry->basic_errno,
               retry->more_errno);
@@ -660,9 +653,11 @@ for (i = 0; i < 3; i++)
         records have a maximum data length, we enforce a limit. There isn't
         much point in keeping a huge message here, anyway. */
 
-        message = (rti->basic_errno > 0)? US strerror(rti->basic_errno) :
-          (rti->message == NULL)?
-          US"unknown error" : string_printing(rti->message);
+        message = rti->basic_errno > 0
+         ? US strerror(rti->basic_errno)
+         : rti->message
+         ? US string_printing(rti->message)
+         : US"unknown error";
         message_length = Ustrlen(message);
         if (message_length > 150) message_length = 150;
 
@@ -670,11 +665,11 @@ for (i = 0; i < 3; i++)
         Ignore an old one if it is too old since it was last updated. */
 
         retry_record = dbfn_read(dbm_file, rti->key);
-        if (retry_record != NULL &&
-            now - retry_record->time_stamp > retry_data_expire)
+        if (  retry_record
+          && now - retry_record->time_stamp > retry_data_expire)
           retry_record = NULL;
 
-        if (retry_record == NULL)
+        if (!retry_record)
           {
           retry_record = store_get(sizeof(dbdata_retry) + message_length);
           message_space = message_length;
@@ -698,7 +693,7 @@ for (i = 0; i < 3; i++)
         successful delivery will reset the first_failed time, and this can lead
         to a failing message being retried too often. */
 
-        if ((rti->flags & rf_host) == 0 && message_age > failing_interval)
+        if (!(rti->flags & rf_host) && message_age > failing_interval)
           failing_interval = message_age;
 
         /* Search for the current retry rule. The cutoff time of the
@@ -709,7 +704,7 @@ for (i = 0; i < 3; i++)
         always times out, but we can't compute a retry time. */
 
         final_rule = NULL;
-        for (rule = retry->rules; rule != NULL; rule = rule->next)
+        for (rule = retry->rules; rule; rule = rule->next)
           {
           if (failing_interval <= rule->timeout) break;
           final_rule = rule;
@@ -721,10 +716,8 @@ for (i = 0; i < 3; i++)
         flag is false (can be forced via fixdb from outside, but ensure it is
         consistent with the rules whenever we go through here). */
 
-        if (rule != NULL)
-          {
+        if (rule)
           retry_record->expired = FALSE;
-          }
 
         /* Otherwise, set the retry timeout expired, and set the final rule
         as the one from which to compute the next retry time. Subsequent
@@ -763,13 +756,14 @@ for (i = 0; i < 3; i++)
         this is a small bit of code, and it does no harm to leave it in place,
         just in case. */
 
-        if (received_time <= retry_record->first_failed &&
-            addr == endaddr && !retry_record->expired && rule != NULL)
+        if (  received_time <= retry_record->first_failed
+          && addr == endaddr
+          && !retry_record->expired
+          && rule)
           {
           retry_rule *last_rule;
-          for (last_rule = rule;
-               last_rule->next != NULL;
-               last_rule = last_rule->next);
+          for (last_rule = rule; last_rule->next; last_rule = last_rule->next)
+           ;
           if (now - received_time > last_rule->timeout)
             {
             DEBUG(D_retry) debug_printf("on queue longer than maximum retry\n");
@@ -785,9 +779,12 @@ for (i = 0; i < 3; i++)
         case set the next retry time to now, so that one delivery attempt
         happens for subsequent messages. */
 
-        if (rule == NULL) next_try = now; else
+        if (!rule)
+         next_try = now;
+       else
           {
-          if (rule->rule == 'F') next_try = now + rule->p1;
+          if (rule->rule == 'F')
+           next_try = now + rule->p1;
           else  /* rule = 'G' or 'H' */
             {
             int last_predicted_gap =
@@ -797,9 +794,7 @@ for (i = 0; i < 3; i++)
               last_predicted_gap : last_actual_gap;
             int next_gap = (lastgap * rule->p2)/1000;
             if (rule->rule == 'G')
-              {
               next_try = now + ((lastgap < rule->p1)? rule->p1 : next_gap);
-              }
             else  /* The 'H' rule */
               {
               next_try = now + rule->p1;
@@ -860,7 +855,6 @@ for (i = 0; i < 3; i++)
       time was not reached (or because of hosts_max_try). */
 
       if (update_count > 0 && update_count == timedout_count)
-        {
         if (!testflag(endaddr, af_retry_skipped))
           {
           DEBUG(D_retry) debug_printf("timed out: all retries expired\n");
@@ -871,7 +865,6 @@ for (i = 0; i < 3; i++)
           DEBUG(D_retry)
             debug_printf("timed out but some hosts were skipped\n");
           }
-        }
       }     /* Loop for an address and its parents */
 
     /* If this is a deferred address, and retry processing was requested by
@@ -887,7 +880,7 @@ for (i = 0; i < 3; i++)
 
     if (i == 2)   /* Handling defers */
       {
-      if (endaddr->retries != NULL && timed_out)
+      if (endaddr->retries && timed_out)
         {
         if (last_first == endaddr) paddr = saved_paddr;
         addr = *paddr;
@@ -935,7 +928,7 @@ for (i = 0; i < 3; i++)
 
 /* Close and unlock the database */
 
-if (dbm_file != NULL) dbfn_close(dbm_file);
+if (dbm_file) dbfn_close(dbm_file);
 
 DEBUG(D_retry) debug_printf("end of retry processing\n");
 }
index f9d29c0..830d2bb 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions concerned with rewriting headers */
@@ -118,7 +118,8 @@ for (rule = rewrite_rules;
   {
   int start, end, pdomain;
   int count = 0;
-  uschar *save_localpart, *save_domain;
+  uschar *save_localpart;
+  const uschar *save_domain;
   uschar *error, *new, *newparsed;
 
   /* Ensure that the flag matches the flags in the rule. */
@@ -162,7 +163,7 @@ for (rule = rewrite_rules;
     /* Use the general function for matching an address against a list (here
     just one item, so use the "impossible value" separator UCHAR_MAX+1). */
 
-    if (match_address_list(subject, FALSE, TRUE, &(rule->key), NULL, 0,
+    if (match_address_list(subject, FALSE, TRUE, CUSS &(rule->key), NULL, 0,
         UCHAR_MAX + 1, NULL) != OK)
       continue;
 
@@ -204,6 +205,9 @@ for (rule = rewrite_rules;
     {
     if (expand_string_forcedfail)
       { if ((rule->flags & rewrite_quit) != 0) break; else continue; }
+
+    expand_string_message = expand_hide_passwords(expand_string_message);
+
     log_write(0, LOG_MAIN|LOG_PANIC, "Expansion of %s failed while rewriting: "
       "%s", rule->replacement, expand_string_message);
     break;
@@ -246,8 +250,7 @@ for (rule = rewrite_rules;
 
   /* We have a validly rewritten address */
 
-  if ((log_write_selector & L_address_rewrite) != 0 ||
-      (debug_selector & D_rewrite) != 0)
+  if (LOGGING(address_rewrite) || (debug_selector & D_rewrite) != 0)
     {
     int i;
     const uschar *where = CUS"?";
@@ -292,7 +295,7 @@ for (rule = rewrite_rules;
         {
         uschar *p1 = new + start - 1;
         uschar *p2 = new + end + 1;
-        uschar *pf1, *pf2;
+        const uschar *pf1, *pf2;
         uschar buff1[256], buff2[256];
 
         while (p1 > new && p1[-1] == ' ') p1--;
@@ -449,8 +452,9 @@ Returns:         NULL if header unchanged; otherwise the rewritten header
 */
 
 static header_line *
-rewrite_one_header(header_line *h, int flag, uschar *routed_old,
-  uschar *routed_new, rewrite_rule *rewrite_rules, int existflags, BOOL replace)
+rewrite_one_header(header_line *h, int flag,
+  const uschar *routed_old, const uschar *routed_new,
+  rewrite_rule *rewrite_rules, int existflags, BOOL replace)
 {
 int lastnewline = 0;
 header_line *newh = NULL;
@@ -717,7 +721,8 @@ Returns:         NULL if header unchanged; otherwise the rewritten header
 */
 
 header_line *
-rewrite_header(header_line *h, uschar *routed_old, uschar *routed_new,
+rewrite_header(header_line *h,
+  const uschar *routed_old, const uschar *routed_new,
   rewrite_rule *rewrite_rules, int existflags, BOOL replace)
 {
 switch (h->type)
index 128e6b9..5c987e2 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* This file contains a function for decoding message header lines that may
@@ -120,7 +120,7 @@ for (;; string = mimeword + 2)
   encoding = toupper((*q1ptr)[1]);
   **endptr = 0;
   if (encoding == 'B')
-    dlen = auth_b64decode(*q2ptr+1, dptrptr);
+    dlen = b64decode(*q2ptr+1, dptrptr);
   else if (encoding == 'Q')
     dlen = rfc2047_qpdecode(*q2ptr+1, dptrptr);
   **endptr = '?';   /* restore */
@@ -218,7 +218,7 @@ while (mimeword != NULL)
   #endif
 
   if (mimeword != string)
-    yield = string_cat(yield, &size, &ptr, string, mimeword - string);
+    yield = string_catn(yield, &size, &ptr, string, mimeword - string);
 
   /* Do a charset translation if required. This is supported only on hosts
   that have the iconv() function. Translation errors set error, but carry on,
@@ -305,7 +305,7 @@ while (mimeword != NULL)
 
     /* Add the new string onto the result */
 
-    yield = string_cat(yield, &size, &ptr, tptr, tlen);
+    yield = string_catn(yield, &size, &ptr, tptr, tlen);
     }
 
   #if HAVE_ICONV
@@ -328,7 +328,7 @@ while (mimeword != NULL)
 /* Copy the remaining characters of the string, zero-terminate it, and return
 the length as well if requested. */
 
-yield = string_cat(yield, &size, &ptr, string, Ustrlen(string));
+yield = string_cat(yield, &size, &ptr, string);
 yield[ptr] = 0;
 if (lenptr != NULL) *lenptr = ptr;
 if (sizeptr != NULL) *sizeptr = size;
index 6ba1d9f..08b3e05 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions concerned with routing, and the list of generic router options. */
@@ -54,14 +54,16 @@ optionlist optionlist_routers[] = {
                  (void *)offsetof(router_instance, debug_string) },
   { "disable_logging",    opt_bool | opt_public,
                  (void *)offsetof(router_instance, disable_logging) },
+  { "dnssec_request_domains",            opt_stringptr|opt_public,
+                 (void *)offsetof(router_instance, dnssec.request) },
+  { "dnssec_require_domains",            opt_stringptr|opt_public,
+                 (void *)offsetof(router_instance, dnssec.require) },
   { "domains",            opt_stringptr|opt_public,
                  (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,
@@ -141,6 +143,19 @@ optionlist optionlist_routers[] = {
 int optionlist_routers_size = sizeof(optionlist_routers)/sizeof(optionlist);
 
 
+void
+readconf_options_routers(void)
+{
+struct router_info * ri;
+
+readconf_options_from_list(optionlist_routers, nelem(optionlist_routers), US"ROUTERS", NULL);
+
+for (ri = routers_available; ri->driver_name[0]; ri++)
+  {
+  macro_create(string_sprintf("_DRIVER_ROUTER_%T", ri->driver_name), US"y", FALSE, TRUE);
+  readconf_options_from_list(ri->options, (unsigned)*ri->options_count, US"ROUTER", ri->driver_name);
+  }
+}
 
 /*************************************************
 *          Set router pointer from name          *
@@ -164,7 +179,7 @@ set_router(router_instance *r, uschar *name, router_instance **ptr, BOOL after)
 BOOL afterthis = FALSE;
 router_instance *rr;
 
-for (rr = routers; rr != NULL; rr = rr->next)
+for (rr = routers; rr; rr = rr->next)
   {
   if (Ustrcmp(name, rr->name) == 0)
     {
@@ -174,7 +189,7 @@ for (rr = routers; rr != NULL; rr = rr->next)
   if (rr == r) afterthis = TRUE;
   }
 
-if (rr == NULL)
+if (!rr)
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
     "new_router \"%s\" not found for \"%s\" router", name, r->name);
 
@@ -210,7 +225,7 @@ readconf_driver_init(US"router",
   optionlist_routers,                 /* generic options */
   optionlist_routers_size);
 
-for (r = routers; r != NULL; r = r->next)
+for (r = routers; r; r = r->next)
   {
   uschar *s = r->self;
 
@@ -275,14 +290,8 @@ for (r = routers; r != NULL; r = r->next)
   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
+  DEBUG(D_route) debug_printf("DSN: %s %s\n", r->name,
+       r->dsn_lasthop ? "lasthop set" : "propagating DSN");
   }
 }
 
@@ -301,8 +310,8 @@ void
 route_tidyup(void)
 {
 router_instance *r;
-for (r = routers; r != NULL; r = r->next)
-  if (r->info->tidyup != NULL) (r->info->tidyup)(r);
+for (r = routers; r; r = r->next)
+  if (r->info->tidyup) (r->info->tidyup)(r);
 }
 
 
@@ -323,20 +332,19 @@ Returns:        length of matching prefix or zero
 */
 
 int
-route_check_prefix(uschar *local_part, uschar *prefixes)
+route_check_prefix(const uschar *local_part, const uschar *prefixes)
 {
 int sep = 0;
 uschar *prefix;
-uschar *listptr = prefixes;
+const uschar *listptr = prefixes;
 uschar prebuf[64];
 
-while ((prefix = string_nextinlist(&listptr, &sep, prebuf, sizeof(prebuf)))
-       != NULL)
+while ((prefix = string_nextinlist(&listptr, &sep, prebuf, sizeof(prebuf))))
   {
   int plen = Ustrlen(prefix);
   if (prefix[0] == '*')
     {
-    uschar *p;
+    const uschar *p;
     prefix++;
     for (p = local_part + Ustrlen(local_part) - (--plen);
          p >= local_part; p--)
@@ -367,21 +375,20 @@ Returns:        length of matching suffix or zero
 */
 
 int
-route_check_suffix(uschar *local_part, uschar *suffixes)
+route_check_suffix(const uschar *local_part, const uschar *suffixes)
 {
 int sep = 0;
 int alen = Ustrlen(local_part);
 uschar *suffix;
-uschar *listptr = suffixes;
+const uschar *listptr = suffixes;
 uschar sufbuf[64];
 
-while ((suffix = string_nextinlist(&listptr, &sep, sufbuf, sizeof(sufbuf)))
-     != NULL)
+while ((suffix = string_nextinlist(&listptr, &sep, sufbuf, sizeof(sufbuf))))
   {
   int slen = Ustrlen(suffix);
   if (suffix[slen-1] == '*')
     {
-    uschar *p, *pend;
+    const uschar *p, *pend;
     pend = local_part + alen - (--slen) + 1;
     for (p = local_part; p < pend; p++)
       if (strncmpic(suffix, p, slen) == 0) return alen - (p - local_part);
@@ -424,46 +431,37 @@ Returns:         OK     item is in list
 */
 
 static int
-route_check_dls(uschar *rname, uschar *type, uschar *list, tree_node
-  **anchorptr, unsigned int *cache_bits, int listtype, uschar *domloc,
-  uschar **ldata, BOOL caseless, uschar **perror)
+route_check_dls(uschar *rname, uschar *type, const uschar *list,
+  tree_node **anchorptr, unsigned int *cache_bits, int listtype,
+  const uschar *domloc, const uschar **ldata, BOOL caseless, uschar **perror)
 {
-int rc;
-
-if (list == NULL) return OK;   /* Empty list always succeeds */
+if (!list) return OK;   /* Empty list always succeeds */
 
 DEBUG(D_route) debug_printf("checking %s\n", type);
 
 /* The domain and local part use the same matching function, whereas sender
 has its own code. */
 
-if (domloc != NULL)
-  {
-  rc = match_isinlist(domloc, &list, 0, anchorptr, cache_bits, listtype,
-    caseless, ldata);
-  }
-else
-  {
-  uschar *address = (sender_address == NULL)? US"" : sender_address;
-  rc = match_address_list(address, TRUE, TRUE, &list, cache_bits, -1, 0,
-    &sender_data);
-  }
-
-switch(rc)
+switch(domloc
+  ? match_isinlist(domloc, &list, 0, anchorptr, cache_bits, listtype,
+    caseless, ldata)
+  : match_address_list(sender_address ? sender_address : US"",
+    TRUE, TRUE, &list, cache_bits, -1, 0, CUSS &sender_data)
+      )
   {
   case OK:
-  return OK;
+    return OK;
 
   case FAIL:
-  *perror = string_sprintf("%s router skipped: %s mismatch", rname, type);
-  DEBUG(D_route) debug_printf("%s\n", *perror);
-  return SKIP;
+    *perror = string_sprintf("%s router skipped: %s mismatch", rname, type);
+    DEBUG(D_route) debug_printf("%s\n", *perror);
+    return SKIP;
 
   default:      /* Paranoia, and keeps compilers happy */
   case DEFER:
-  *perror = string_sprintf("%s check lookup or other defer", type);
-  DEBUG(D_route) debug_printf("%s\n", *perror);
-  return DEFER;
+    *perror = string_sprintf("%s check lookup or other defer", type);
+    DEBUG(D_route) debug_printf("%s\n", *perror);
+    return DEFER;
   }
 }
 
@@ -510,9 +508,9 @@ uschar *sp = rp + 1;
 DEBUG(D_route) debug_printf("route_check_access(%s,%d,%d,%o)\n", path,
   (int)uid, (int)gid, bits);
 
-if (rp == NULL) return FALSE;
+if (!rp) return FALSE;
 
-while ((slash = Ustrchr(sp, '/')) != NULL)
+while ((slash = Ustrchr(sp, '/')))
   {
   *slash = 0;
   DEBUG(D_route) debug_printf("stat %s\n", rp);
@@ -574,23 +572,23 @@ Returns:   OK if s == NULL or all tests are as required
            SKIP otherwise
 */
 
-int
-check_files(uschar *s, uschar **perror)
+static int
+check_files(const uschar *s, uschar **perror)
 {
 int sep = 0;              /* List has default separators */
 uid_t uid = 0;            /* For picky compilers */
 gid_t gid = 0;            /* For picky compilers */
 BOOL ugid_set = FALSE;
-uschar *check, *listptr;
+const uschar *listptr;
+uschar *check;
 uschar buffer[1024];
 
-if (s == NULL) return OK;
+if (!s) return OK;
 
 DEBUG(D_route) debug_printf("checking require_files\n");
 
 listptr = s;
-while ((check = string_nextinlist(&listptr, &sep, buffer, sizeof(buffer)))
-        != NULL)
+while ((check = string_nextinlist(&listptr, &sep, buffer, sizeof(buffer))))
   {
   int rc;
   int eacces_code = 0;
@@ -598,7 +596,7 @@ while ((check = string_nextinlist(&listptr, &sep, buffer, sizeof(buffer)))
   struct stat statbuf;
   uschar *ss = expand_string(check);
 
-  if (ss == NULL)
+  if (!ss)
     {
     if (expand_string_forcedfail) continue;
     *perror = string_sprintf("failed to expand \"%s\" for require_files: %s",
@@ -716,7 +714,7 @@ while ((check = string_nextinlist(&listptr, &sep, buffer, sizeof(buffer)))
     pid = fork();
 
     /* If fork() fails, reinstate the original error and behave as if
-    this block of code were not present. This is the same behavious as happens
+    this block of code were not present. This is the same behaviour as happens
     when Exim is not running as root at this point. */
 
     if (pid < 0)
@@ -767,7 +765,7 @@ while ((check = string_nextinlist(&listptr, &sep, buffer, sizeof(buffer)))
     rc = -1;
     }
 
-  /* Handle error returns from stat() or route_check_access(). The EACESS error
+  /* Handle error returns from stat() or route_check_access(). The EACCES error
   is handled specially. At present, we can force it to be treated as
   non-existence. Write the code so that it will be easy to add forcing for
   existence if required later. */
@@ -889,8 +887,8 @@ if (verify == v_expn && !r->expn)
 /* Skip this router if there's a domain mismatch. */
 
 if ((rc = route_check_dls(r->name, US"domains", r->domains, &domainlist_anchor,
-     addr->domain_cache, TRUE, addr->domain, &deliver_domain_data, MCL_DOMAIN,
-     perror)) != OK)
+     addr->domain_cache, TRUE, addr->domain, CUSS &deliver_domain_data,
+     MCL_DOMAIN, perror)) != OK)
   return rc;
 
 /* Skip this router if there's a local part mismatch. We want to pass over the
@@ -900,7 +898,7 @@ because that doesn't have the prefix or suffix stripped. A bit of massaging is
 required. Also, we only use the match cache for local parts that have not had
 a prefix or suffix stripped. */
 
-if (addr->prefix == NULL && addr->suffix == NULL)
+if (!addr->prefix && !addr->suffix)
   {
   localpart_cache = addr->localpart_cache;
   check_local_part = addr->cc_local_part;
@@ -909,16 +907,16 @@ else
   {
   localpart_cache = NULL;
   check_local_part = string_copy(addr->cc_local_part);
-  if (addr->prefix != NULL)
+  if (addr->prefix)
     check_local_part += Ustrlen(addr->prefix);
-  if (addr->suffix != NULL)
+  if (addr->suffix)
     check_local_part[Ustrlen(check_local_part) - Ustrlen(addr->suffix)] = 0;
   }
 
 if ((rc = route_check_dls(r->name, US"local_parts", r->local_parts,
        &localpartlist_anchor, localpart_cache, MCL_LOCALPART,
-       check_local_part, &deliver_localpart_data, !r->caseful_local_part,
-       perror)) != OK)
+       check_local_part, CUSS &deliver_localpart_data,
+       !r->caseful_local_part, perror)) != OK)
   return rc;
 
 /* If the check_local_user option is set, check that the local_part is the
@@ -947,10 +945,10 @@ check_local_user before any subsequent expansions are done. Otherwise, $home
 could mean different things for different options, which would be extremely
 confusing. */
 
-if (r->router_home_directory != NULL)
+if (r->router_home_directory)
   {
   uschar *router_home = expand_string(r->router_home_directory);
-  if (router_home == NULL)
+  if (!router_home)
     {
     if (!expand_string_forcedfail)
       {
@@ -968,7 +966,7 @@ if (r->router_home_directory != NULL)
   }
 
 /* Skip if the sender condition is not met. We leave this one till after the
-local user check so that $home is set - enabling the possiblity of letting
+local user check so that $home is set - enabling the possibility of letting
 individual recipients specify lists of acceptable/unacceptable senders. */
 
 if ((rc = route_check_dls(r->name, US"senders", r->senders, NULL,
@@ -992,9 +990,9 @@ if ((rc = check_files(r->require_files, perror)) != OK)
 
 /* Now the general condition test. */
 
-if (r->condition != NULL)
+if (r->condition)
   {
-  DEBUG(D_route) debug_printf("checking \"condition\"\n");
+  DEBUG(D_route) debug_printf("checking \"condition\" \"%.80s\"...\n", r->condition);
   if (!expand_check_condition(r->condition, r->name, US"router"))
     {
     if (search_find_defer)
@@ -1011,42 +1009,44 @@ if (r->condition != NULL)
 
 #ifdef EXPERIMENTAL_BRIGHTMAIL
 /* check if a specific Brightmail AntiSpam rule fired on the message */
-if (r->bmi_rule != NULL) {
+if (r->bmi_rule)
+  {
   DEBUG(D_route) debug_printf("checking bmi_rule\n");
-  if (bmi_check_rule(bmi_base64_verdict, r->bmi_rule) == 0) {
-    /* none of the rules fired */
+  if (bmi_check_rule(bmi_base64_verdict, r->bmi_rule) == 0)
+    {    /* none of the rules fired */
     DEBUG(D_route)
       debug_printf("%s router skipped: none of bmi_rule rules fired\n", r->name);
     return SKIP;
-  };
-};
+    }
+  }
 
 /* check if message should not be delivered */
-if (r->bmi_dont_deliver) {
-  if (bmi_deliver == 1) {
-    DEBUG(D_route)
-      debug_printf("%s router skipped: bmi_dont_deliver is FALSE\n", r->name);
-    return SKIP;
-  };
-};
+if (r->bmi_dont_deliver && bmi_deliver == 1)
+  {
+  DEBUG(D_route)
+    debug_printf("%s router skipped: bmi_dont_deliver is FALSE\n", r->name);
+  return SKIP;
+  }
 
 /* check if message should go to an alternate location */
-if (r->bmi_deliver_alternate) {
-  if ((bmi_deliver == 0) || (bmi_alt_location == NULL)) {
-    DEBUG(D_route)
-      debug_printf("%s router skipped: bmi_deliver_alternate is FALSE\n", r->name);
-    return SKIP;
-  };
-};
+if (  r->bmi_deliver_alternate
+   && (bmi_deliver == 0 || !bmi_alt_location)
+   )
+  {
+  DEBUG(D_route)
+    debug_printf("%s router skipped: bmi_deliver_alternate is FALSE\n", r->name);
+  return SKIP;
+  }
 
 /* check if message should go to default location */
-if (r->bmi_deliver_default) {
-  if ((bmi_deliver == 0) || (bmi_alt_location != NULL)) {
-    DEBUG(D_route)
-      debug_printf("%s router skipped: bmi_deliver_default is FALSE\n", r->name);
-    return SKIP;
-  };
-};
+if (  r->bmi_deliver_default
+   && (bmi_deliver == 0 || bmi_alt_location)
+   )
+  {
+  DEBUG(D_route)
+    debug_printf("%s router skipped: bmi_deliver_default is FALSE\n", r->name);
+  return SKIP;
+  }
 #endif
 
 /* All the checks passed. */
@@ -1092,7 +1092,7 @@ static uschar lastgecos[128];
 static uschar lastshell[128];
 
 BOOL
-route_finduser(uschar *s, struct passwd **pw, uid_t *return_uid)
+route_finduser(const uschar *s, struct passwd **pw, uid_t *return_uid)
 {
 BOOL cache_set = (Ustrcmp(lastname, s) == 0);
 
@@ -1103,11 +1103,11 @@ if (!cache_set)
   {
   int i = 0;
 
-  if (return_uid != NULL && (isdigit(*s) || *s == '-') &&
+  if (return_uid && (isdigit(*s) || *s == '-') &&
        s[Ustrspn(s+1, "0123456789")+1] == 0)
     {
     *return_uid = (uid_t)Uatoi(s);
-    if (pw != NULL) *pw = NULL;
+    if (pw) *pw = NULL;
     return TRUE;
     }
 
@@ -1127,12 +1127,12 @@ if (!cache_set)
   else for (;;)
     {
     errno = 0;
-    if ((lastpw = getpwnam(CS s)) != NULL) break;
+    if ((lastpw = getpwnam(CS s))) break;
     if (++i > finduser_retries) break;
     sleep(1);
     }
 
-  if (lastpw != NULL)
+  if (lastpw)
     {
     pwcopy.pw_uid = lastpw->pw_uid;
     pwcopy.pw_gid = lastpw->pw_gid;
@@ -1146,26 +1146,21 @@ if (!cache_set)
     lastpw = &pwcopy;
     }
 
-  else DEBUG(D_uid)
-    {
-    if (errno != 0) debug_printf("getpwnam(%s) failed: %s\n", s,
-      strerror(errno));
-    }
+  else DEBUG(D_uid) if (errno != 0)
+    debug_printf("getpwnam(%s) failed: %s\n", s, strerror(errno));
   }
 
-if (lastpw == NULL)
+if (!lastpw)
   {
   DEBUG(D_uid) debug_printf("getpwnam() returned NULL (user not found)\n");
   return FALSE;
   }
-else
-  {
-  DEBUG(D_uid) debug_printf("getpwnam() succeeded uid=%d gid=%d\n",
+
+DEBUG(D_uid) debug_printf("getpwnam() succeeded uid=%d gid=%d\n",
     lastpw->pw_uid, lastpw->pw_gid);
-  }
 
-if (return_uid != NULL) *return_uid = lastpw->pw_uid;
-if (pw != NULL) *pw = lastpw;
+if (return_uid) *return_uid = lastpw->pw_uid;
+if (pw) *pw = lastpw;
 
 return TRUE;
 }
@@ -1182,7 +1177,7 @@ NIS or NFS whatever cause an incorrect refusal. It's a pity that getgrnam()
 doesn't have some kind of indication as to why it has failed.
 
 Arguments:
-  s           the group namd or textual form of the numerical gid
+  s           the group name or textual form of the numerical gid
   return_gid  return the gid via this address
 
 Returns:      TRUE if the group was found; FALSE otherwise
@@ -1203,7 +1198,7 @@ if ((isdigit(*s) || *s == '-') && s[Ustrspn(s+1, "0123456789")+1] == 0)
 
 for (;;)
   {
-  if ((gr = getgrnam(CS s)) != NULL)
+  if ((gr = getgrnam(CS s)))
     {
     *return_gid = gr->gr_gid;
     return TRUE;
@@ -1242,7 +1237,7 @@ route_find_expanded_user(uschar *string, uschar *driver_name,
 {
 uschar *user = expand_string(string);
 
-if (user == NULL)
+if (!user)
   {
   *errmsg = string_sprintf("Failed to expand user string \"%s\" for the "
     "%s %s: %s", string, driver_name, driver_type, expand_string_message);
@@ -1284,7 +1279,7 @@ route_find_expanded_group(uschar *string, uschar *driver_name, uschar *driver_ty
 BOOL yield = TRUE;
 uschar *group = expand_string(string);
 
-if (group == NULL)
+if (!group)
   {
   *errmsg = string_sprintf("Failed to expand group string \"%s\" for the "
     "%s %s: %s", string, driver_name, driver_type, expand_string_message);
@@ -1305,67 +1300,6 @@ return yield;
 
 
 
-/*************************************************
-*           Sort out "more" or "unseen"          *
-*************************************************/
-
-/* These values are usually fixed boolean values, but they are permitted to be
-expanded strings.
-
-Arguments:
-  addr       address being routed
-  rname      the router name
-  oname      the option name
-  bvalue     the router's boolean value
-  svalue     the router's string value
-  rvalue     where to put the returned value
-
-Returns:     OK     value placed in rvalue
-             DEFER  expansion failed
-*/
-
-static int
-exp_bool(address_item *addr, uschar *rname, uschar *oname, BOOL bvalue,
-  uschar *svalue, BOOL *rvalue)
-{
-uschar *expanded;
-if (svalue == NULL) { *rvalue = bvalue; return OK; }
-
-expanded = expand_string(svalue);
-if (expanded == NULL)
-  {
-  if (expand_string_forcedfail)
-    {
-    DEBUG(D_route) debug_printf("expansion of \"%s\" forced failure\n", oname);
-    *rvalue = bvalue;
-    return OK;
-    }
-  addr->message = string_sprintf("failed to expand \"%s\" in %s router: %s",
-      oname, rname, expand_string_message);
-  DEBUG(D_route) debug_printf("%s\n", addr->message);
-  return DEFER;
-  }
-
-DEBUG(D_route) debug_printf("expansion of \"%s\" yields \"%s\"\n", oname,
-  expanded);
-
-if (strcmpic(expanded, US"true") == 0 || strcmpic(expanded, US"yes") == 0)
-  *rvalue = TRUE;
-else if (strcmpic(expanded, US"false") == 0 || strcmpic(expanded, US"no") == 0)
-  *rvalue = FALSE;
-else
-  {
-  addr->message = string_sprintf("\"%s\" is not a valid value for the "
-    "\"%s\" option in the %s router", expanded, oname, rname);
-  return DEFER;
-  }
-
-return OK;
-}
-
-
-
-
 /*************************************************
 *            Handle an unseen routing            *
 *************************************************/
@@ -1407,8 +1341,8 @@ from the original address' parent, if present, otherwise unset. */
 
 *parent = *addr;
 parent->child_count = 2;
-parent->p.errors_address =
-  (addr->parent == NULL)? NULL : addr->parent->p.errors_address;
+parent->prop.errors_address =
+  addr->parent ? addr->parent->prop.errors_address : NULL;
 
 /* The routed address gets a new parent. */
 
@@ -1419,16 +1353,14 @@ was set from the original parent (or to NULL) - see above. We do NOT want to
 take the errors address from the unseen router. */
 
 new->parent = parent;
-new->p.errors_address = parent->p.errors_address;
+new->prop.errors_address = parent->prop.errors_address;
 
 /* Copy the propagated flags and address_data from the original. */
 
 copyflag(new, addr, af_propagate);
-new->p.address_data = addr->p.address_data;
-#ifdef EXPERIMENTAL_DSN
+new->prop.address_data = addr->prop.address_data;
 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
@@ -1457,8 +1389,7 @@ delivered. If so, we take it off the relevant queue so that it isn't delivered
 again. Otherwise, it was an alias or something, and the addresses it generated
 are handled in the normal way. */
 
-if (addr->transport != NULL &&
-    tree_search(tree_nonrecipients, addr->unique) != NULL)
+if (addr->transport && tree_search(tree_nonrecipients, addr->unique))
   {
   DEBUG(D_route)
     debug_printf("\"unseen\" delivery previously done - discarded\n");
@@ -1504,7 +1435,7 @@ route_address(address_item *addr, address_item **paddr_local,
 int yield = OK;
 BOOL unseen;
 router_instance *r, *nextr;
-uschar *old_domain = addr->domain;
+const uschar *old_domain = addr->domain;
 
 HDEBUG(D_route)
   {
@@ -1516,8 +1447,7 @@ HDEBUG(D_route)
 encounters an error. If the address has start_router set, we begin from there
 instead of at the first router. */
 
-for (r = (addr->start_router == NULL)? routers : addr->start_router;
-     r != NULL; r = nextr)
+for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
   {
   uschar *error;
   struct passwd *pw = NULL;
@@ -1553,10 +1483,10 @@ for (r = (addr->start_router == NULL)? routers : addr->start_router;
   by  this router, even if it was different to the current address.
 
   Just in case someone does put it into a loop (possible with redirection
-  continally adding to an address, for example), put a long stop counter on
+  continually adding to an address, for example), put a long stop counter on
   the number of parents. */
 
-  for (parent = addr->parent; parent != NULL; parent = parent->parent)
+  for (parent = addr->parent; parent; parent = parent->parent)
     {
     if (parent->router == r)
       {
@@ -1610,7 +1540,7 @@ for (r = (addr->start_router == NULL)? routers : addr->start_router;
   and setting the prefix. Skip the router if the prefix doesn't match,
   unless the prefix is optional. */
 
-  if (r->prefix != NULL)
+  if (r->prefix)
     {
     int plen = route_check_prefix(addr->local_part, r->prefix);
     if (plen > 0)
@@ -1629,7 +1559,7 @@ for (r = (addr->start_router == NULL)? routers : addr->start_router;
 
   /* Handle any configured suffix likewise. */
 
-  if (r->suffix != NULL)
+  if (r->suffix)
     {
     int slen = route_check_suffix(addr->local_part, r->suffix);
     if (slen > 0)
@@ -1676,11 +1606,11 @@ for (r = (addr->start_router == NULL)? routers : addr->start_router;
   success, the string is attached to the address for all subsequent processing.
   */
 
-  if (r->address_data != NULL)
+  if (r->address_data)
     {
     DEBUG(D_route) debug_printf("processing address_data\n");
     deliver_address_data = expand_string(r->address_data);
-    if (deliver_address_data == NULL)
+    if (!deliver_address_data)
       {
       if (expand_string_forcedfail)
         {
@@ -1689,8 +1619,8 @@ for (r = (addr->start_router == NULL)? routers : addr->start_router;
 
         /* Expand "more" if necessary; DEFER => an expansion failed */
 
-        yield = exp_bool(addr, r->name, US"more", r->more, r->expand_more,
-          &more);
+        yield = exp_bool(addr, US"router", r->name, D_route,
+                       US"more", r->more, r->expand_more, &more);
         if (yield != OK) goto ROUTE_EXIT;
 
         if (!more)
@@ -1712,7 +1642,7 @@ for (r = (addr->start_router == NULL)? routers : addr->start_router;
         goto ROUTE_EXIT;
         }
       }
-    addr->p.address_data = deliver_address_data;
+    addr->prop.address_data = deliver_address_data;
     }
 
   /* We are finally cleared for take-off with this router. Clear the the flag
@@ -1723,7 +1653,7 @@ for (r = (addr->start_router == NULL)? routers : addr->start_router;
 
   clearflag(addr, af_local_host_removed);
 
-  if (pw != NULL)
+  if (pw)
     {
     pwcopy.pw_name = CS string_copy(US pw->pw_name);
     pwcopy.pw_uid = pw->pw_uid;
@@ -1736,16 +1666,14 @@ for (r = (addr->start_router == NULL)? routers : addr->start_router;
 
   /* 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))
-  {
+  /* ... but let us check on DSN before. If this should be the last hop for DSN
+  set flag. */
+
+  if (r->dsn_lasthop && !(addr->dsn_flags & rf_dsnlasthop))
+    {
     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);
 
@@ -1799,7 +1727,8 @@ for (r = (addr->start_router == NULL)? routers : addr->start_router;
     {
     /* Expand "more" if necessary */
 
-    yield = exp_bool(addr, r->name, US"more", r->more, r->expand_more, &more);
+    yield = exp_bool(addr, US"router", r->name, D_route,
+                       US"more", r->more, r->expand_more, &more);
     if (yield != OK) goto ROUTE_EXIT;
 
     if (!more)
@@ -1818,16 +1747,16 @@ prematurely, either because a router succeeded, or because of some special
 router response. Note that FAIL errors and errors detected before actually
 running a router go direct to ROUTE_EXIT from code above. */
 
-if (r == NULL)
+if (!r)
   {
   HDEBUG(D_route) debug_printf("no more routers\n");
-  if (addr->message == NULL)
+  if (!addr->message)
     {
     uschar *message = US"Unrouteable address";
-    if (addr->router != NULL && addr->router->cannot_route_message != NULL)
+    if (addr->router && addr->router->cannot_route_message)
       {
       uschar *expmessage = expand_string(addr->router->cannot_route_message);
-      if (expmessage == NULL)
+      if (!expmessage)
         {
         if (!expand_string_forcedfail)
           log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
@@ -1880,23 +1809,23 @@ networking, so it is included in the binary only if requested. */
 
 #ifdef SUPPORT_TRANSLATE_IP_ADDRESS
 
-if (r->translate_ip_address != NULL)
+if (r->translate_ip_address)
   {
   int rc;
   int old_pool = store_pool;
   host_item *h;
-  for (h = addr->host_list; h != NULL; h = h->next)
+  for (h = addr->host_list; h; h = h->next)
     {
     uschar *newaddress;
     uschar *oldaddress, *oldname;
 
-    if (h->address == NULL) continue;
+    if (!h->address) continue;
 
     deliver_host_address = h->address;
     newaddress = expand_string(r->translate_ip_address);
     deliver_host_address = NULL;
 
-    if (newaddress == NULL)
+    if (!newaddress)
       {
       if (expand_string_forcedfail) continue;
       addr->basic_errno = ERRNO_EXPANDFAIL;
@@ -1939,17 +1868,14 @@ if (r->translate_ip_address != NULL)
 /* See if this is an unseen routing; first expand the option if necessary.
 DEFER can be given if the expansion fails */
 
-yield = exp_bool(addr, r->name, US"unseen", r->unseen, r->expand_unseen,
-  &unseen);
+yield = exp_bool(addr, US"router", r->name, D_route,
+               US"unseen", r->unseen, r->expand_unseen, &unseen);
 if (yield != OK) goto ROUTE_EXIT;
 
 /* Debugging output recording a successful routing */
 
-HDEBUG(D_route)
-  {
-  debug_printf("routed by %s router%s\n", r->name,
+HDEBUG(D_route) debug_printf("routed by %s router%s\n", r->name,
     unseen? " (unseen)" : "");
-  }
 
 DEBUG(D_route)
   {
@@ -1959,17 +1885,17 @@ DEBUG(D_route)
   debug_printf("  transport: %s\n", (addr->transport == NULL)?
     US"<none>" : addr->transport->name);
 
-  if (addr->p.errors_address != NULL)
-    debug_printf("  errors to %s\n", addr->p.errors_address);
+  if (addr->prop.errors_address)
+    debug_printf("  errors to %s\n", addr->prop.errors_address);
 
-  for (h = addr->host_list; h != NULL; h = h->next)
+  for (h = addr->host_list; h; h = h->next)
     {
     debug_printf("  host %s", h->name);
-    if (h->address != NULL) debug_printf(" [%s]", h->address);
+    if (h->address) debug_printf(" [%s]", h->address);
     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"); */
+    if (h->dnssec != DS_UNK) debug_printf(" dnssec=%s", h->dnssec==DS_YES ? "yes" : "no");
     debug_printf("\n");
     }
   }
@@ -1978,30 +1904,14 @@ DEBUG(D_route)
 the "unseen" option (ignore if there are no further routers). */
 
 addr->message = NULL;
-if (unseen && r->next != NULL)
+if (unseen && r->next)
   route_unseen(r->name, addr, paddr_local, paddr_remote, addr_new);
 
 /* Unset the address expansions, and return the final result. */
 
 ROUTE_EXIT:
-if (yield == DEFER) {
-  if (
-    ((Ustrstr(addr->message, "failed to expand") != NULL) || (Ustrstr(addr->message, "expansion of ") != NULL)) &&
-    (
-      Ustrstr(addr->message, "mysql") != NULL ||
-      Ustrstr(addr->message, "pgsql") != NULL ||
-#ifdef EXPERIMENTAL_REDIS
-      Ustrstr(addr->message, "redis") != NULL ||
-#endif
-      Ustrstr(addr->message, "sqlite") != NULL ||
-      Ustrstr(addr->message, "ldap:") != NULL ||
-      Ustrstr(addr->message, "ldapdn:") != NULL ||
-      Ustrstr(addr->message, "ldapm:") != NULL
-    )
-  ) {
-    addr->message = string_sprintf("Temporary internal error");
-  }
-}
+if (yield == DEFER && addr->message)
+  addr->message = expand_hide_passwords(addr->message);
 
 deliver_set_expansions(NULL);
 router_name = NULL;
index f2a73c8..a674e8b 100644 (file)
@@ -23,7 +23,7 @@ The yield of a router is one of:
 
   DISCARD         the address was discarded (:blackhole: or "seen finish")
 
-  FAIL            the address was not routed; do not pass to any subseqent
+  FAIL            the address was not routed; do not pass to any subsequent
                   routers, i.e. cause routing to fail.
 
   DEFER           retry this address later.
index 8049eaf..6c76c71 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -118,9 +118,9 @@ if (!rf_get_transport(rblock->transport_name, &(rblock->transport),
   addr, rblock->name, NULL)) return DEFER;
 
 addr->transport = rblock->transport;
-addr->p.errors_address = errors_to;
-addr->p.extra_headers = extra_headers;
-addr->p.remove_headers = remove_headers;
+addr->prop.errors_address = errors_to;
+addr->prop.extra_headers = extra_headers;
+addr->prop.remove_headers = remove_headers;
 
 return rf_queue_add(addr, addr_local, addr_remote, rblock, pw)? OK : DEFER;
 }
index c8fd3f9..d2be40e 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -18,10 +18,8 @@ optionlist dnslookup_router_options[] = {
       (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)) },
+  { "fail_defer_domains", opt_stringptr,
+      (void *)(offsetof(dnslookup_router_options_block, fail_defer_domains)) },
   { "mx_domains",         opt_stringptr,
       (void *)(offsetof(dnslookup_router_options_block, mx_domains)) },
   { "mx_fail_domains",    opt_stringptr,
@@ -58,8 +56,7 @@ dnslookup_router_options_block dnslookup_router_option_defaults = {
   NULL,            /* mx_fail_domains */
   NULL,            /* srv_fail_domains */
   NULL,            /* check_srv */
-  NULL,            /* dnssec_request_domains */
-  NULL             /* dnssec_require_domains */
+  NULL             /* fail_defer_domains */
 };
 
 
@@ -146,10 +143,10 @@ dnslookup_router_options_block *ob =
   (dnslookup_router_options_block *)(rblock->options_block);
 uschar *srv_service = NULL;
 uschar *widen = NULL;
-uschar *pre_widen = addr->domain;
-uschar *post_widen = NULL;
-uschar *fully_qualified_name;
-uschar *listptr;
+const uschar *pre_widen = addr->domain;
+const uschar *post_widen = NULL;
+const uschar *fully_qualified_name;
+const uschar *listptr;
 uschar widen_buffer[256];
 
 addr_new = addr_new;          /* Keep picky compilers happy */
@@ -161,10 +158,10 @@ DEBUG(D_route)
 
 /* If an SRV check is required, expand the service name */
 
-if (ob->check_srv != NULL)
+if (ob->check_srv)
   {
-  srv_service = expand_string(ob->check_srv);
-  if (srv_service == NULL && !expand_string_forcedfail)
+  if (  !(srv_service = expand_string(ob->check_srv))
+     && !expand_string_forcedfail)
     {
     addr->message = string_sprintf("%s router: failed to expand \"%s\": %s",
       rblock->name, ob->check_srv, expand_string_message);
@@ -195,8 +192,8 @@ does not appear in the message header so it is also OK to widen. The
 suppression of widening for sender addresses is silent because it is the
 normal desirable behaviour. */
 
-if (ob->widen_domains != NULL &&
-    (verify != v_sender || !ob->rewrite_headers || addr->parent != NULL))
+if (  ob->widen_domains
+   && (verify != v_sender || !ob->rewrite_headers || addr->parent))
   {
   listptr = ob->widen_domains;
   widen = string_nextinlist(&listptr, &widen_sep, widen_buffer,
@@ -220,12 +217,12 @@ for (;;)
   int flags = whichrrs;
   BOOL removed = FALSE;
 
-  if (pre_widen != NULL)
+  if (pre_widen)
     {
     h.name = pre_widen;
     pre_widen = NULL;
     }
-  else if (widen != NULL)
+  else if (widen)
     {
     h.name = string_sprintf("%s.%s", addr->domain, widen);
     widen = string_nextinlist(&listptr, &widen_sep, widen_buffer,
@@ -233,7 +230,7 @@ for (;;)
     DEBUG(D_route) debug_printf("%s router widened %s to %s\n", rblock->name,
       addr->domain, h.name);
     }
-  else if (post_widen != NULL)
+  else if (post_widen)
     {
     h.name = post_widen;
     post_widen = NULL;
@@ -260,16 +257,15 @@ for (;;)
   subsequently. We use the same logic as that for widen_domains above to avoid
   requesting a header rewrite that cannot work. */
 
-  if (verify != v_sender || !ob->rewrite_headers || addr->parent != NULL)
+  if (verify != v_sender || !ob->rewrite_headers || addr->parent)
     {
     if (ob->qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE;
     if (ob->search_parents) flags |= HOST_FIND_SEARCH_PARENTS;
     }
 
-  rc = host_find_bydns(&h, rblock->ignore_target_hosts, flags, srv_service,
+  rc = host_find_bydns(&h, CUS rblock->ignore_target_hosts, flags, srv_service,
     ob->srv_fail_domains, ob->mx_fail_domains,
-    ob->dnssec_request_domains, ob->dnssec_require_domains,
-    &fully_qualified_name, &removed);
+    &rblock->dnssec, &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
@@ -277,9 +273,9 @@ for (;;)
   the option is historical. */
 
   if ((rc == HOST_FOUND || rc == HOST_FOUND_LOCAL) && h.mx < 0 &&
-       ob->mx_domains != NULL)
-    {
-    switch(match_isinlist(fully_qualified_name, &(ob->mx_domains), 0,
+       ob->mx_domains)
+    switch(match_isinlist(fully_qualified_name,
+          CUSS &(ob->mx_domains), 0,
           &domainlist_anchor, addr->domain_cache, MCL_DOMAIN, TRUE, NULL))
       {
       case DEFER:
@@ -291,7 +287,6 @@ for (;;)
         rblock->name, fully_qualified_name);
       continue;
       }
-    }
 
   /* Deferral returns forthwith, and anything other than failure breaks the
   loop. */
@@ -310,6 +305,22 @@ for (;;)
 
   if (rc != HOST_FIND_FAILED) break;
 
+  if (ob->fail_defer_domains)
+    switch(match_isinlist(fully_qualified_name,
+         CUSS &ob->fail_defer_domains, 0,
+         &domainlist_anchor, addr->domain_cache, MCL_DOMAIN, TRUE, NULL))
+      {
+      case DEFER:
+       addr->message = US"lookup defer for fail_defer_domains option";
+       return DEFER;
+
+      case OK:
+       DEBUG(D_route) debug_printf("%s router: matched fail_defer_domains\n",
+         rblock->name);
+       addr->message = US"missing MX, or all MXs point to missing A records,"
+         " and defer requested";
+       return DEFER;
+      }
   /* Check to see if the failure is the result of MX records pointing to
   non-existent domains, and if so, set an appropriate error message; the case
   of an MX or SRV record pointing to "." is another special case that we can
@@ -328,6 +339,7 @@ for (;;)
       addr->message = US"an MX or SRV record indicated no SMTP service";
     else
       {
+      addr->basic_errno = ERRNO_UNKNOWNHOST;
       addr->message = US"all relevant MX records point to non-existent hosts";
       if (!allow_mx_to_ip && string_is_ip_address(h.name, NULL) != 0)
         {
@@ -402,13 +414,13 @@ else if (ob->check_secondary_mx && !testflag(addr, af_local_host_removed))
 
 /* Set up the errors address, if any. */
 
-rc = rf_get_errors_address(addr, rblock, verify, &(addr->p.errors_address));
+rc = rf_get_errors_address(addr, rblock, verify, &addr->prop.errors_address);
 if (rc != OK) return rc;
 
 /* Set up the additional and removeable headers for this address. */
 
-rc = rf_get_munge_headers(addr, rblock, &(addr->p.extra_headers),
-  &(addr->p.remove_headers));
+rc = rf_get_munge_headers(addr, rblock, &addr->prop.extra_headers,
+  &addr->prop.remove_headers);
 if (rc != OK) return rc;
 
 /* Get store in which to preserve the original host item, chained on
@@ -430,3 +442,5 @@ return rf_queue_add(addr, addr_local, addr_remote, rblock, pw)?
 }
 
 /* End of routers/dnslookup.c */
+/* vi: aw ai sw=2
+*/
index 518b7f4..3979ef2 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Private structure for the private options. */
@@ -17,8 +17,7 @@ typedef struct {
   uschar *mx_fail_domains;
   uschar *srv_fail_domains;
   uschar *check_srv;
-  uschar *dnssec_request_domains;
-  uschar *dnssec_require_domains;
+  uschar *fail_defer_domains;
 } dnslookup_router_options_block;
 
 /* Data for reading the private options. */
index 7be96e5..22d292d 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -99,8 +99,8 @@ ipliteral_router_options_block *ob =
   (ipliteral_router_options_block *)(rblock->options_block);
 */
 host_item *h;
-uschar *domain = addr->domain;
-uschar *ip;
+const uschar *domain = addr->domain;
+const uschar *ip;
 int len = Ustrlen(domain);
 int rc, ipv;
 
@@ -116,29 +116,23 @@ declines. Otherwise route to the single IP address, setting the host name to
 "(unnamed)". */
 
 if (domain[0] != '[' || domain[len-1] != ']') return DECLINE;
-domain[len-1] = 0;  /* temporarily */
-
-ip = domain + 1;
+ip = string_copyn(domain+1, len-2);
 if (strncmpic(ip, US"IPV6:", 5) == 0 || strncmpic(ip, US"IPV4:", 5) == 0)
   ip += 5;
 
 ipv = string_is_ip_address(ip, NULL);
 if (ipv == 0 || (disable_ipv6 && ipv == 6))
-  {
-  domain[len-1] = ']';
   return DECLINE;
-  }
 
 /* It seems unlikely that ignore_target_hosts will be used with this router,
 but if it is set, it should probably work. */
 
-if (verify_check_this_host(&(rblock->ignore_target_hosts), NULL, domain,
-      ip, NULL) == OK)
+if (verify_check_this_host(CUSS&rblock->ignore_target_hosts,
+               NULL, domain, ip, NULL) == OK)
   {
   DEBUG(D_route)
       debug_printf("%s is in ignore_target_hosts\n", ip);
   addr->message = US"IP literal host explicitly ignored";
-  domain[len-1] = ']';
   return DECLINE;
   }
 
@@ -149,8 +143,7 @@ h = store_get(sizeof(host_item));
 h->next = NULL;
 h->address = string_copy(ip);
 h->port = PORT_NONE;
-domain[len-1] = ']';   /* restore */
-h->name = string_copy(domain);
+h->name = domain;
 h->mx = MX_NONE;
 h->status = hstatus_unknown;
 h->why = hwhy_unknown;
@@ -172,13 +165,13 @@ addr->host_list = h;
 
 /* Set up the errors address, if any. */
 
-rc = rf_get_errors_address(addr, rblock, verify, &(addr->p.errors_address));
+rc = rf_get_errors_address(addr, rblock, verify, &addr->prop.errors_address);
 if (rc != OK) return rc;
 
 /* Set up the additional and removeable headers for this address. */
 
-rc = rf_get_munge_headers(addr, rblock, &(addr->p.extra_headers),
-  &(addr->p.remove_headers));
+rc = rf_get_munge_headers(addr, rblock, &addr->prop.extra_headers,
+  &addr->prop.remove_headers);
 if (rc != OK) return rc;
 
 /* Fill in the transport, queue the address for local or remote delivery, and
index 3728007..96e9626 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -143,7 +143,8 @@ iplookup_router_entry(
 {
 uschar *query = NULL;
 uschar *reply;
-uschar *hostname, *reroute, *domain, *listptr;
+uschar *hostname, *reroute, *domain;
+const uschar *listptr;
 uschar host_buffer[256];
 host_item *host = store_get(sizeof(host_item));
 address_item *new_addr;
@@ -190,7 +191,7 @@ being a host list. */
 
 listptr = ob->hosts;
 while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,
-       sizeof(host_buffer))) != NULL)
+       sizeof(host_buffer))))
   {
   host_item *h;
 
@@ -206,25 +207,27 @@ while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,
     host->address = host->name;
   else
     {
+/*XXX might want dnssec request/require on an iplookup router? */
     int rc = host_find_byname(host, NULL, HOST_FIND_QUALIFY_SINGLE, NULL, TRUE);
     if (rc == HOST_FIND_FAILED || rc == HOST_FIND_AGAIN) continue;
     }
 
   /* Loop for possible multiple IP addresses for the given name. */
 
-  for (h = host; h != NULL; h = h->next)
+  for (h = host; h; h = h->next)
     {
     int host_af, query_socket;
 
     /* Skip any hosts for which we have no address */
 
-    if (h->address == NULL) continue;
+    if (!h->address) continue;
 
     /* Create a socket, for UDP or TCP, as configured. IPv6 addresses are
     detected by checking for a colon in the address. */
 
     host_af = (Ustrchr(h->address, ':') != NULL)? AF_INET6 : AF_INET;
-    query_socket = ip_socket((ob->protocol == ip_udp)? SOCK_DGRAM:SOCK_STREAM,
+
+    query_socket = ip_socket(ob->protocol == ip_udp ? SOCK_DGRAM:SOCK_STREAM,
       host_af);
     if (query_socket < 0)
       {
@@ -238,7 +241,8 @@ while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,
     here only for TCP calls; for a UDP socket, "connect" always works (the
     router will timeout later on the read call). */
 
-    if (ip_connect(query_socket, host_af, h->address,ob->port, ob->timeout) < 0)
+    if (ip_connect(query_socket, host_af, h->address,ob->port, ob->timeout,
+                       ob->protocol != ip_udp) < 0)
       {
       close(query_socket);
       DEBUG(D_route)
@@ -280,7 +284,7 @@ while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,
   /* If h == NULL we have tried all the IP addresses and failed on all of them,
   so we must continue to try more host names. Otherwise we have succeeded. */
 
-  if (h != NULL) break;
+  if (h) break;
   }
 
 
@@ -376,11 +380,11 @@ new_addr = deliver_make_addr(reroute, TRUE);
 new_addr->parent = addr;
 
 copyflag(new_addr, addr, af_propagate);
-new_addr->p = addr->p;
+new_addr->prop = addr->prop;
 
-if (addr->child_count == SHRT_MAX)
+if (addr->child_count == USHRT_MAX)
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s router generated more than %d "
-    "child addresses for <%s>", rblock->name, SHRT_MAX, addr->address);
+    "child addresses for <%s>", rblock->name, USHRT_MAX, addr->address);
 addr->child_count++;
 new_addr->next = *addr_new;
 *addr_new = new_addr;
@@ -388,11 +392,11 @@ new_addr->next = *addr_new;
 /* Set up the errors address, if any, and the additional and removeable headers
 for this new address. */
 
-rc = rf_get_errors_address(addr, rblock, verify, &(new_addr->p.errors_address));
+rc = rf_get_errors_address(addr, rblock, verify, &new_addr->prop.errors_address);
 if (rc != OK) return rc;
 
-rc = rf_get_munge_headers(addr, rblock, &(new_addr->p.extra_headers),
-  &(new_addr->p.remove_headers));
+rc = rf_get_munge_headers(addr, rblock, &new_addr->prop.extra_headers,
+  &new_addr->prop.remove_headers);
 if (rc != OK) return rc;
 
 return OK;
index cb047e2..95c6932 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -139,8 +139,8 @@ Returns:    FALSE if domain expected and string is empty;
 */
 
 static BOOL
-parse_route_item(uschar *s, uschar **domain, uschar **hostlist,
-  uschar **options)
+parse_route_item(const uschar *s, const uschar **domain, const uschar **hostlist,
+  const uschar **options)
 {
 while (*s != 0 && isspace(*s)) s++;
 
@@ -226,9 +226,11 @@ manualroute_router_entry(
 {
 int rc, lookup_type;
 uschar *route_item = NULL;
-uschar *options = NULL;
-uschar *hostlist = NULL;
-uschar *domain, *newhostlist, *listptr;
+const uschar *options = NULL;
+const uschar *hostlist = NULL;
+const uschar *domain;
+uschar *newhostlist;
+const uschar *listptr;
 manualroute_router_options_block *ob =
   (manualroute_router_options_block *)(rblock->options_block);
 transport_instance *transport = NULL;
@@ -260,7 +262,7 @@ if (ob->route_list != NULL)
     /* Check the current domain; if it matches, break the loop */
 
     if ((rc = match_isinlist(addr->domain, &domain, UCHAR_MAX+1,
-           &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, &lookup_value)) == OK)
+           &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, CUSS &lookup_value)) == OK)
       break;
 
     /* If there was a problem doing the check, defer */
@@ -318,28 +320,26 @@ lookup_type = lk_default;
 
 while (*options != 0)
   {
-  int term;
-  uschar *s = options;
+  unsigned n;
+  const uschar *s = options;
   while (*options != 0 && !isspace(*options)) options++;
-  term = *options;
-  *options = 0;
+  n = options-s;
 
-  if (Ustrcmp(s, "randomize") == 0) randomize = TRUE;
-  else if (Ustrcmp(s, "no_randomize") == 0) randomize = FALSE;
-  else if (Ustrcmp(s, "byname") == 0) lookup_type = lk_byname;
-  else if (Ustrcmp(s, "bydns") == 0) lookup_type = lk_bydns;
+  if (Ustrncmp(s, "randomize", n) == 0) randomize = TRUE;
+  else if (Ustrncmp(s, "no_randomize", n) == 0) randomize = FALSE;
+  else if (Ustrncmp(s, "byname", n) == 0) lookup_type = lk_byname;
+  else if (Ustrncmp(s, "bydns", n) == 0) lookup_type = lk_bydns;
   else
     {
     transport_instance *t;
     for (t = transports; t != NULL; t = t->next)
-      {
       if (Ustrcmp(t->name, s) == 0)
         {
         transport = t;
         individual_transport_set = TRUE;
         break;
         }
-      }
+
     if (t == NULL)
       {
       s = string_sprintf("unknown routing option or transport name \"%s\"", s);
@@ -349,7 +349,7 @@ while (*options != 0)
       }
     }
 
-  if (term != 0)
+  if (*options)
     {
     options++;
     while (*options != 0 && isspace(*options)) options++;
@@ -358,13 +358,13 @@ while (*options != 0)
 
 /* Set up the errors address, if any. */
 
-rc = rf_get_errors_address(addr, rblock, verify, &(addr->p.errors_address));
+rc = rf_get_errors_address(addr, rblock, verify, &addr->prop.errors_address);
 if (rc != OK) return rc;
 
 /* Set up the additional and removeable headers for this address. */
 
-rc = rf_get_munge_headers(addr, rblock, &(addr->p.extra_headers),
-  &(addr->p.remove_headers));
+rc = rf_get_munge_headers(addr, rblock, &addr->prop.extra_headers,
+  &addr->prop.remove_headers);
 if (rc != OK) return rc;
 
 /* If an individual transport is not set, get the transport for this router, if
@@ -415,7 +415,7 @@ if (hostlist[0] == 0)
   {
   if (verify != v_none) goto ROUTED;
   addr->message = string_sprintf("error in %s router: no host(s) specified "
-    "for domain %s", rblock->name, domain);
+    "for domain %s", rblock->name, addr->domain);
   log_write(0, LOG_MAIN, "%s", addr->message);
   return DEFER;
   }
index 11e1fdc..cd02f36 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -114,15 +114,15 @@ while (generated != NULL)
 
   next->parent = addr;
   orflag(next, addr, af_propagate);
-  next->p = *addr_prop;
+  next->prop = *addr_prop;
   next->start_router = rblock->redirect_router;
 
   next->next = *addr_new;
   *addr_new = next;
 
-  if (addr->child_count == SHRT_MAX)
+  if (addr->child_count == USHRT_MAX)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s router generated more than %d "
-      "child addresses for <%s>", rblock->name, SHRT_MAX, addr->address);
+      "child addresses for <%s>", rblock->name, USHRT_MAX, addr->address);
   addr->child_count++;
 
   DEBUG(D_route)
@@ -192,7 +192,7 @@ int fd_in, fd_out, len, rc;
 pid_t pid;
 struct passwd *upw = NULL;
 uschar buffer[1024];
-uschar **argvptr;
+const uschar **argvptr;
 uschar *rword, *rdata, *s;
 address_item_propagated addr_prop;
 queryprogram_router_options_block *ob =
@@ -214,15 +214,20 @@ ugid.uid_set = ugid.gid_set = FALSE;
 /* Set up the propagated data block with the current address_data and the
 errors address and extra header stuff. */
 
+bzero(&addr_prop, sizeof(addr_prop));
 addr_prop.address_data = deliver_address_data;
 
-rc = rf_get_errors_address(addr, rblock, verify, &(addr_prop.errors_address));
+rc = rf_get_errors_address(addr, rblock, verify, &addr_prop.errors_address);
 if (rc != OK) return rc;
 
-rc = rf_get_munge_headers(addr, rblock, &(addr_prop.extra_headers),
-  &(addr_prop.remove_headers));
+rc = rf_get_munge_headers(addr, rblock, &addr_prop.extra_headers,
+  &addr_prop.remove_headers);
 if (rc != OK) return rc;
 
+#ifdef EXPERIMENTAL_SRS
+addr_prop.srs_sender = NULL;
+#endif
+
 /* Get the fixed or expanded uid under which the command is to run
 (initialization ensures that one or the other is set). */
 
@@ -526,7 +531,7 @@ lookup_value = NULL;
 
 /* Put the errors address, extra headers, and address_data into this address */
 
-addr->p = addr_prop;
+addr->prop = addr_prop;
 
 /* Queue the address for local or remote delivery. */
 
index c6705e5..29537ba 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -277,11 +277,11 @@ sort_errors_and_headers(router_instance *rblock, address_item *addr,
   int verify, address_item_propagated *addr_prop)
 {
 int frc = rf_get_errors_address(addr, rblock, verify,
-  &(addr_prop->errors_address));
+  &addr_prop->errors_address);
 if (frc != OK) return frc;
-addr->p.errors_address = addr_prop->errors_address;
-return rf_get_munge_headers(addr, rblock, &(addr_prop->extra_headers),
-  &(addr_prop->remove_headers));
+addr->prop.errors_address = addr_prop->errors_address;
+return rf_get_munge_headers(addr, rblock, &addr_prop->extra_headers,
+  &addr_prop->remove_headers);
 }
 
 
@@ -325,19 +325,19 @@ add_generated(router_instance *rblock, address_item **addr_new,
 redirect_router_options_block *ob =
   (redirect_router_options_block *)(rblock->options_block);
 
-while (generated != NULL)
+while (generated)
   {
   address_item *parent;
   address_item *next = generated;
-  uschar *errors_address = next->p.errors_address;
+  uschar *errors_address = next->prop.errors_address;
 
   generated = next->next;
   next->parent = addr;
   orflag(next, addr, af_ignore_error);
   next->start_router = rblock->redirect_router;
-  if (addr->child_count == SHRT_MAX)
+  if (addr->child_count == USHRT_MAX)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s router generated more than %d "
-      "child addresses for <%s>", rblock->name, SHRT_MAX, addr->address);
+      "child addresses for <%s>", rblock->name, USHRT_MAX, addr->address);
   addr->child_count++;
 
   next->next = *addr_new;
@@ -347,7 +347,7 @@ while (generated != NULL)
 
   if (ob->one_time && !queue_2stage)
     {
-    for (parent = addr; parent->parent != NULL; parent = parent->parent);
+    for (parent = addr; parent->parent; parent = parent->parent) ;
     next->onetime_parent = parent->address;
     }
 
@@ -358,28 +358,23 @@ while (generated != NULL)
   unless the ancestor was routed by a case-sensitive router. */
 
   if (ob->check_ancestor)
-    {
-    for (parent = addr; parent != NULL; parent = parent->parent)
-      {
-      if (((parent->router != NULL && parent->router->caseful_local_part)?
-           Ustrcmp(next->address, parent->address)
-           :
-           strcmpic(next->address, parent->address)
+    for (parent = addr; parent; parent = parent->parent)
+      if ((parent->router && parent->router->caseful_local_part
+          ? Ustrcmp(next->address, parent->address)
+           : strcmpic(next->address, parent->address)
           ) == 0)
         {
         DEBUG(D_route) debug_printf("generated parent replaced by child\n");
         next->address = string_copy(addr->address);
         break;
         }
-      }
-    }
 
   /* A user filter may, under some circumstances, set up an errors address.
   If so, we must take care to re-instate it when we copy in the propagated
   data so that it overrides any errors_to setting on the router. */
 
-  next->p = *addr_prop;
-  if (errors_address != NULL) next->p.errors_address = errors_address;
+  next->prop = *addr_prop;
+  if (errors_address != NULL) next->prop.errors_address = errors_address;
 
   /* For pipes, files, and autoreplies, record this router as handling them,
   because they don't go through the routing process again. Then set up uid,
@@ -451,13 +446,18 @@ while (generated != NULL)
       }
     }
 
+#ifdef SUPPORT_I18N
+    next->prop.utf8_msg = string_is_utf8(next->address)
+      || (sender_address && string_is_utf8(sender_address));
+#endif
+
   DEBUG(D_route)
     {
     debug_printf("%s router generated %s\n  %serrors_to=%s transport=%s\n",
       rblock->name,
       next->address,
       testflag(next, af_pfr)? "pipe, file, or autoreply\n  " : "",
-      next->p.errors_address,
+      next->prop.errors_address,
       (next->transport == NULL)? US"NULL" : next->transport->name);
 
     if (testflag(next, af_uid_set))
@@ -470,6 +470,10 @@ while (generated != NULL)
     else
       debug_printf("gid=unset ");
 
+#ifdef SUPPORT_I18N
+    if (next->prop.utf8_msg) debug_printf("utf8 ");
+#endif
+
     debug_printf("home=%s\n", next->home_dir);
     }
   }
@@ -517,7 +521,7 @@ int redirect_router_entry(
 redirect_router_options_block *ob =
   (redirect_router_options_block *)(rblock->options_block);
 address_item *generated = NULL;
-uschar *save_qualify_domain_recipient = qualify_domain_recipient;
+const uschar *save_qualify_domain_recipient = qualify_domain_recipient;
 uschar *discarded = US"discarded";
 address_item_propagated addr_prop;
 error_block *eblock = NULL;
@@ -544,6 +548,12 @@ addr_prop.remove_headers = NULL;
 #ifdef EXPERIMENTAL_SRS
 addr_prop.srs_sender = NULL;
 #endif
+#ifdef SUPPORT_I18N
+addr_prop.utf8_msg = FALSE;    /*XXX should we not copy this from the parent? */
+addr_prop.utf8_downcvt = FALSE;
+addr_prop.utf8_downcvt_maybe = FALSE;
+#endif
+
 
 /* When verifying and testing addresses, the "logwrite" command in filters
 must be bypassed. */
@@ -640,8 +650,8 @@ if (!ugid.gid_set && pw != NULL)
 //          eximsrs_db_set(FALSE, NULL);
 */
 
-        if(ob->srs_alias != NULL ? (usedomain = expand_string(ob->srs_alias)) == NULL : 1)
-          usedomain = deliver_domain;
+        if (!(usedomain = ob->srs_alias ? expand_string(ob->srs_alias) : NULL))
+          usedomain = string_copy(deliver_domain);
 
         if((n_srs = eximsrs_forward(&res, sender_address, usedomain)) == OK)
         {
@@ -891,7 +901,7 @@ else
   data that propagates. */
 
   copyflag(next, addr, af_propagate);
-  next->p = addr_prop;
+  next->prop = addr_prop;
 
   DEBUG(D_route) debug_printf("%s router autogenerated %s\n%s%s%s",
     rblock->name,
index 2343e3e..219e283 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -32,7 +32,7 @@ Returns:      nothing
 */
 
 void
-rf_change_domain(address_item *addr, uschar *domain, BOOL rewrite,
+rf_change_domain(address_item *addr, const uschar *domain, BOOL rewrite,
   address_item **addr_new)
 {
 address_item *parent = store_get(sizeof(address_item));
@@ -52,11 +52,12 @@ domain cache. Then copy over the propagating fields from the parent. Then set
 up the new fields. */
 
 *addr = address_defaults;
-addr->p = parent->p;
+addr->prop = parent->prop;
 
 addr->address = address;
 addr->unique = string_copy(address);
 addr->parent = parent;
+parent->child_count = 1;
 
 addr->next = *addr_new;
 *addr_new = addr;
index 29f37cf..f310d5a 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Header for the functions that are shared by the routers */
 extern void rf_add_generated(router_instance *, address_item **,
               address_item *, address_item *, uschar *, header_line *,
               uschar *, ugid_block *, struct passwd *);
-extern void rf_change_domain(address_item *, uschar *, BOOL, address_item **);
+extern void rf_change_domain(address_item *, const uschar *, BOOL, address_item **);
 extern uschar *rf_expand_data(address_item *, uschar *, int *);
 extern int  rf_get_errors_address(address_item *, router_instance *,
-              BOOL, uschar **);
+              int, uschar **);
 extern int  rf_get_munge_headers(address_item *, router_instance *,
               header_line **, uschar **);
 extern BOOL rf_get_transport(uschar *, transport_instance **,  address_item *,
index 457397a..d7172d7 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -38,7 +38,7 @@ rf_get_errors_address(address_item *addr, router_instance *rblock,
 {
 uschar *s;
 
-*errors_to = addr->p.errors_address;
+*errors_to = addr->prop.errors_address;
 if (rblock->errors_to == NULL) return OK;
 
 s = expand_string(rblock->errors_to);
@@ -84,8 +84,8 @@ else
   BOOL save_address_test_mode = address_test_mode;
   int save1 = 0;
   int i;
-  uschar ***p;
-  uschar *address_expansions_save[ADDRESS_EXPANSIONS_COUNT];
+  const uschar ***p;
+  const uschar *address_expansions_save[ADDRESS_EXPANSIONS_COUNT];
   address_item *snew = deliver_make_addr(s, FALSE);
 
   if (sender_address != NULL)
index a4a13b0..ecb4ee0 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -32,11 +32,11 @@ rf_get_munge_headers(address_item *addr, router_instance *rblock,
   header_line **extra_headers, uschar **remove_headers)
 {
 /* Default is to retain existing headers */
-*extra_headers = addr->p.extra_headers;
+*extra_headers = addr->prop.extra_headers;
 
 if (rblock->extra_headers)
   {
-  uschar * list = rblock->extra_headers;
+  const uschar * list = rblock->extra_headers;
   int sep = '\n';
   uschar * s;
   int slen;
@@ -46,8 +46,9 @@ if (rblock->extra_headers)
       {
       if (!expand_string_forcedfail)
        {
-       addr->message = string_sprintf("%s router failed to expand \"%s\": %s",
-         rblock->name, rblock->extra_headers, expand_string_message);
+       addr->message = string_sprintf(
+         "%s router failed to expand add_headers item \"%s\": %s",
+         rblock->name, s, expand_string_message);
        return DEFER;
        }
       }
@@ -82,23 +83,23 @@ if (rblock->extra_headers)
   }
 
 /* Default is to retain existing removes */
-*remove_headers = addr->p.remove_headers;
+*remove_headers = addr->prop.remove_headers;
 
 /* Expand items from colon-sep list separately, then build new list */
 if (rblock->remove_headers)
   {
-  uschar * list = rblock->remove_headers;
+  const uschar * list = rblock->remove_headers;
   int sep = ':';
   uschar * s;
-  uschar buffer[128];
 
-  while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
+  while ((s = string_nextinlist(&list, &sep, NULL, 0)))
     if (!(s = expand_string(s)))
       {
       if (!expand_string_forcedfail)
        {
-       addr->message = string_sprintf("%s router failed to expand \"%s\": %s",
-         rblock->name, rblock->remove_headers, expand_string_message);
+       addr->message = string_sprintf(
+         "%s router failed to expand remove_headers item \"%s\": %s",
+         rblock->name, s, expand_string_message);
        return DEFER;
        }
       }
@@ -109,4 +110,6 @@ if (rblock->remove_headers)
 return OK;
 }
 
+/* vi: aw ai sw=4
+*/
 /* End of rf_get_munge_headers.c */
index 0eae31e..78eda22 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -61,14 +61,13 @@ host, omit it and any subsequent hosts - i.e. treat the list like an ordered
 list of MX hosts. If the first host is the local host, act according to the
 "self" option in the configuration. */
 
-prev = NULL;
-for (h = addr->host_list; h != NULL; h = next_h)
+for (prev = NULL, h = addr->host_list; h; h = next_h)
   {
-  uschar *canonical_name;
-  int rc, len, port;
+  const uschar *canonical_name;
+  int rc, len, port, mx, sort_key;
 
   next_h = h->next;
-  if (h->address != NULL) { prev = h; continue; }
+  if (h->address) { prev = h; continue; }
 
   DEBUG(D_route|D_host_lookup)
     debug_printf("finding IP address for %s\n", h->name);
@@ -78,8 +77,15 @@ for (h = addr->host_list; h != NULL; h = next_h)
 
   port = host_item_get_port(h);
 
+  /* Store the previous mx and sort_key values, which were assigned in
+  host_build_hostlist and will be overwritten by host_find_bydns. */
+
+  mx = h->mx;
+  sort_key = h->sort_key;
+
   /* If the name ends with "/MX", we interpret it to mean "the list of hosts
-  pointed to by MX records with this name". */
+  pointed to by MX records with this name", and the MX record values override
+  the ordering from host_build_hostlist. */
 
   len = Ustrlen(h->name);
   if (len > 3 && strcmpic(h->name + len - 3, US"/mx") == 0)
@@ -87,6 +93,7 @@ for (h = addr->host_list; h != NULL; h = next_h)
     DEBUG(D_route|D_host_lookup)
       debug_printf("doing DNS MX lookup for %s\n", h->name);
 
+    mx = MX_NONE;
     h->name = string_copyn(h->name, len - 3);
     rc = host_find_bydns(h,
         ignore_target_hosts,
@@ -94,8 +101,7 @@ for (h = addr->host_list; h != NULL; h = next_h)
         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 ? */
+        &rblock->dnssec,               /* dnssec request/require */
         NULL,                           /* fully_qualified_name */
         NULL);                          /* indicate local host removed */
     }
@@ -118,23 +124,23 @@ for (h = addr->host_list; h != NULL; h = next_h)
     {
     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,
-      NULL, NULL,      /*XXX dnssec? */
-      &canonical_name, &removed);
-    if (rc == HOST_FOUND)
-      {
-      if (removed) setflag(addr, af_local_host_removed);
-      }
-    else if (rc == HOST_FIND_FAILED)
+    switch (rc = host_find_bydns(h, ignore_target_hosts, HOST_FIND_BY_A, NULL,
+       NULL, NULL,
+       &rblock->dnssec,                        /* domains for request/require */
+       &canonical_name, &removed))
       {
-      if (lookup_type == lk_default)
-        {
-        DEBUG(D_route|D_host_lookup)
-          debug_printf("DNS lookup failed: trying getipnodebyname\n");
-        rc = host_find_byname(h, ignore_target_hosts, HOST_FIND_QUALIFY_SINGLE,
-          &canonical_name, TRUE);
-        }
+      case HOST_FOUND:
+        if (removed) setflag(addr, af_local_host_removed);
+       break;
+      case HOST_FIND_FAILED:
+       if (lookup_type == lk_default)
+         {
+         DEBUG(D_route|D_host_lookup)
+           debug_printf("DNS lookup failed: trying getipnodebyname\n");
+         rc = host_find_byname(h, ignore_target_hosts, HOST_FIND_QUALIFY_SINGLE,
+           &canonical_name, TRUE);
+         }
+       break;
       }
     }
 
@@ -168,6 +174,7 @@ for (h = addr->host_list; h != NULL; h = next_h)
     if (hff_code == hff_pass) return PASS;
     if (hff_code == hff_decline) return DECLINE;
 
+    addr->basic_errno = ERRNO_UNKNOWNHOST;
     addr->message =
       string_sprintf("lookup of host \"%s\" failed in %s router%s",
         h->name, rblock->name,
@@ -180,7 +187,8 @@ for (h = addr->host_list; h != NULL; h = next_h)
     return DEFER;
     }
 
-  /* Deal with a port setting */
+  /* Deal with the settings that were previously cleared:
+  port, mx and sort_key. */
 
   if (port != PORT_NONE)
     {
@@ -188,13 +196,23 @@ for (h = addr->host_list; h != NULL; h = next_h)
     for (hh = h; hh != next_h; hh = hh->next) hh->port = port;
     }
 
+  if (mx != MX_NONE)
+    {
+    host_item *hh;
+    for (hh = h; hh != next_h; hh = hh->next)
+      {
+      hh->mx = mx;
+      hh->sort_key = sort_key;
+      }
+    }
+
   /* A local host gets chopped, with its successors, if there are previous
   hosts. Otherwise the self option is used. If it is set to "send", any
   subsequent hosts that are also the local host do NOT get chopped. */
 
   if (rc == HOST_FOUND_LOCAL && !self_send)
     {
-    if (prev != NULL)
+    if (prev)
       {
       DEBUG(D_route)
         {
index 06cdb6c..784a547 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -36,8 +36,8 @@ BOOL
 rf_queue_add(address_item *addr, address_item **paddr_local,
   address_item **paddr_remote, router_instance *rblock, struct passwd *pw)
 {
-addr->p.domain_data = deliver_domain_data;         /* Save these values for */
-addr->p.localpart_data = deliver_localpart_data;   /* use in the transport */
+addr->prop.domain_data = deliver_domain_data;         /* Save these values for */
+addr->prop.localpart_data = deliver_localpart_data;   /* use in the transport */
 
 /* Handle a local transport */
 
@@ -95,9 +95,9 @@ DEBUG(D_route)
   debug_printf("queued for %s transport: local_part = %s\ndomain = %s\n"
     "  errors_to=%s\n",
     (addr->transport == NULL)? US"<unset>" : addr->transport->name,
-    addr->local_part, addr->domain, addr->p.errors_address);
-  debug_printf("  domain_data=%s localpart_data=%s\n", addr->p.domain_data,
-    addr->p.localpart_data);
+    addr->local_part, addr->domain, addr->prop.errors_address);
+  debug_printf("  domain_data=%s localpart_data=%s\n", addr->prop.domain_data,
+    addr->prop.localpart_data);
   }
 
 return TRUE;
index 15db52a..b1dc884 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* A set of functions to search databases in various formats. An open
@@ -62,7 +62,7 @@ Returns:     +ve => valid lookup name; value is offset in lookup_list
 */
 
 int
-search_findtype(uschar *name, int len)
+search_findtype(const uschar *name, int len)
 {
 int bot = 0;
 int top = lookup_list_count;
@@ -121,12 +121,12 @@ Returns:     +ve => valid lookup name; value is offset in lookup_list
 */
 
 int
-search_findtype_partial(uschar *name, int *ptypeptr, uschar **ptypeaff,
+search_findtype_partial(const uschar *name, int *ptypeptr, const uschar **ptypeaff,
   int *afflen, int *starflags)
 {
 int len, stype;
 int pv = -1;
-uschar *ss = name;
+const uschar *ss = name;
 
 *starflags = 0;
 *ptypeaff = NULL;
@@ -466,6 +466,7 @@ internal_search_find(void *handle, uschar *filename, uschar *keystring)
 {
 tree_node *t = (tree_node *)handle;
 search_cache *c = (search_cache *)(t->data.ptr);
+expiring_data *e;
 uschar *data = NULL;
 int search_type = t->name[0] - '0';
 int old_pool = store_pool;
@@ -491,18 +492,27 @@ store_pool = POOL_SEARCH;
 /* Look up the data for the key, unless it is already in the cache for this
 file. No need to check c->item_cache for NULL, tree_search will do so. */
 
-if ((t = tree_search(c->item_cache, keystring)) == NULL)
+if (  (t = tree_search(c->item_cache, keystring))
+   && (!(e = t->data.ptr)->expiry || e->expiry > time(NULL))
+   )
+  { /* Data was in the cache already; set the pointer from the tree node */
+  data = e->ptr;
+  DEBUG(D_lookup) debug_printf("cached data used for lookup of %s%s%s\n",
+    keystring,
+    filename ? US"\n  in " : US"", filename ? filename : US"");
+  }
+else
   {
-  BOOL do_cache = TRUE;
+  uint do_cache = UINT_MAX;
   int keylength = Ustrlen(keystring);
 
   DEBUG(D_lookup)
     {
-    if (filename != NULL)
-      debug_printf("file lookup required for %s\n  in %s\n",
-        keystring, filename);
-    else
-      debug_printf("database lookup required for %s\n", keystring);
+    if (t) debug_printf("cached data found but past valid time; ");
+    debug_printf("%s lookup required for %s%s%s\n",
+      filename ? US"file" : US"database",
+      keystring,
+      filename ? US"\n  in " : US"", filename ? filename : US"");
     }
 
   /* Call the code for the different kinds of search. DEFER is handled
@@ -511,9 +521,7 @@ if ((t = tree_search(c->item_cache, keystring)) == NULL)
 
   if (lookup_list[search_type]->find(c->handle, filename, keystring, keylength,
       &data, &search_error_message, &do_cache) == DEFER)
-    {
     search_find_defer = TRUE;
-    }
 
   /* A record that has been found is now in data, which is either NULL
   or points to a bit of dynamic store. Cache the result of the lookup if
@@ -524,10 +532,22 @@ if ((t = tree_search(c->item_cache, keystring)) == NULL)
   else if (do_cache)
     {
     int len = keylength + 1;
-    t = store_get(sizeof(tree_node) + len);
-    memcpy(t->name, keystring, len);
-    t->data.ptr = data;
-    tree_insertnode(&c->item_cache, t);
+
+    if (t)     /* Previous, out-of-date cache entry.  Update with the */
+      {        /* new result and forget the old one */
+      e->expiry = do_cache == UINT_MAX ? 0 : time(NULL)+do_cache;
+      e->ptr = data;
+      }
+    else
+      {
+      e = store_get(sizeof(expiring_data) + sizeof(tree_node) + len);
+      e->expiry = do_cache == UINT_MAX ? 0 : time(NULL)+do_cache;
+      e->ptr = data;
+      t = (tree_node *)(e+1);
+      memcpy(t->name, keystring, len);
+      t->data.ptr = e;
+      tree_insertnode(&c->item_cache, t);
+      }
     }
 
   /* If caching was disabled, empty the cache tree. We just set the cache
@@ -540,34 +560,19 @@ if ((t = tree_search(c->item_cache, keystring)) == NULL)
     }
   }
 
-/* Data was in the cache already; set the pointer from the tree node */
-
-else
-  {
-  data = US t->data.ptr;
-  DEBUG(D_lookup) debug_printf("cached data used for lookup of %s%s%s\n",
-    keystring,
-    (filename == NULL)? US"" : US"\n  in ",
-    (filename == NULL)? US"" : filename);
-  }
-
-/* Debug: output the answer */
-
 DEBUG(D_lookup)
   {
-  if (data == NULL)
-    {
-    if (search_find_defer) debug_printf("lookup deferred: %s\n",
-      search_error_message);
-    else debug_printf("lookup failed\n");
-    }
-  else debug_printf("lookup yielded: %s\n", data);
+  if (data)
+    debug_printf("lookup yielded: %s\n", data);
+  else if (search_find_defer)
+    debug_printf("lookup deferred: %s\n", search_error_message);
+  else debug_printf("lookup failed\n");
   }
 
 /* Return it in new dynamic store in the regular pool */
 
 store_pool = old_pool;
-return (data == NULL)? NULL : string_copy(data);
+return data ? string_copy(data) : NULL;
 }
 
 
@@ -601,7 +606,7 @@ Returns:         a pointer to a dynamic string containing the answer,
 
 uschar *
 search_find(void *handle, uschar *filename, uschar *keystring, int partial,
-  uschar *affix, int affixlen, int starflags, int *expand_setup)
+  const uschar *affix, int affixlen, int starflags, int *expand_setup)
 {
 tree_node *t = (tree_node *)handle;
 BOOL set_null_wild = FALSE;
@@ -752,7 +757,7 @@ else if (partial >= 0)
   }
 
 /* If nothing has been matched, but the option to look for "*@" is set, try
-replacing everthing to the left of @ by *. After a match, the wild part
+replacing everything to the left of @ by *. After a match, the wild part
 is set to the string to the left of the @. */
 
 if (yield == NULL && (starflags & SEARCH_STARAT) != 0)
diff --git a/src/setenv.c b/src/setenv.c
new file mode 100644 (file)
index 0000000..ceeb8ef
--- /dev/null
@@ -0,0 +1,59 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Michael Haardt 2015
+ * Copyright (c) Jeremy Harris 2015 - 2016
+ * Copyright (c) The Exim Maintainers 2016 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* This module provides (un)setenv routines for those environments
+lacking them in libraries. It is #include'd by OS/os.c-foo files. */
+
+
+int
+setenv(const char * name, const char * val, int overwrite)
+{
+uschar * s;
+if (Ustrchr(name, '=')) return -1;
+if (overwrite || !getenv(name))
+  putenv(CS string_copy_malloc(string_sprintf("%s=%s", name, val)));
+return 0;
+}
+
+int
+unsetenv(const char *name)
+{
+size_t len;
+const char * end;
+char ** e;
+extern char ** environ;
+
+if (!name)
+  {
+  errno = EINVAL;
+  return -1;
+  }
+
+if (!environ)
+  return 0;
+
+for (end = name; *end != '=' && *end; ) end++;
+len = end - name;
+  
+/* Find name in environment and move remaining variables down.
+Do not early-out in case there are duplicate names. */
+
+for (e = environ; *e; e++)
+  if (strncmp(*e, name, len) == 0 && (*e)[len] == '=')
+    {
+    char ** sp = e;
+    do *sp = sp[1]; while (*++sp);
+    }
+
+return 0;
+}
+
+/* vi: aw ai sw=2
+*/
+/* End of setenv.c */
diff --git a/src/sha_ver.h b/src/sha_ver.h
new file mode 100644 (file)
index 0000000..387ac52
--- /dev/null
@@ -0,0 +1,42 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Jeremy Harris 2016 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* SHA routine selection */
+
+#include "exim.h"
+
+/* Please be aware that pulling in extra headers which are not in the system
+ * includes may require careful juggling of CFLAGS in
+ * scripts/Configure-Makefile -- that logic should be kept in sync with this.
+ * In particular, building with just something like USE_OPENSSL_PC=openssl
+ * and not massaging CFLAGS in Local/Makefile is fully supported.
+ */
+
+#ifdef SUPPORT_TLS
+
+# define EXIM_HAVE_SHA2
+
+# ifdef USE_GNUTLS
+#  include <gnutls/gnutls.h>
+
+#  if GNUTLS_VERSION_NUMBER >= 0x020a00
+#   define SHA_GNUTLS
+#   if GNUTLS_VERSION_NUMBER >= 0x030500
+#    define EXIM_HAVE_SHA3
+#   endif
+#  else
+#   define SHA_GCRYPT
+#  endif
+
+# else
+#  define SHA_OPENSSL
+# endif
+
+#else
+# define SHA_NATIVE
+#endif
+
index 1303646..96344c4 100644 (file)
@@ -2,8 +2,10 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) Michael Haardt 2003-2008 */
-/* See the file NOTICE for conditions of use and distribution. */
+/* Copyright (c) Michael Haardt 2003 - 2015
+ * Copyright (c) The Exim Maintainers 2016
+ * See the file NOTICE for conditions of use and distribution.
+ */
 
 /* This code was contributed by Michael Haardt. */
 
@@ -232,6 +234,9 @@ uschar *new = NULL;
 uschar ch;
 size_t line;
 
+/* Two passes: one to count output allocation size, second
+to do the encoding */
+
 for (pass=0; pass<=1; ++pass)
   {
   line=0;
@@ -245,54 +250,47 @@ for (pass=0; pass<=1; ++pass)
   for (start=src->character,end=start+src->length; start<end; ++start)
     {
     ch=*start;
-    if (line>=73)
+    if (line>=73)      /* line length limit */
       {
       if (pass==0)
         dst->length+=2;
       else
         {
-        *new++='=';
+        *new++='=';    /* line split */
         *new++='\n';
         }
       line=0;
       }
-    if
-      (
-      (ch>=33 && ch<=60)
-      || (ch>=62 && ch<=126)
-      ||
-        (
-        (ch==9 || ch==32)
-        && start+2<end
-        && (*(start+1)!='\r' || *(start+2)!='\n')
-        )
-      )
+    if (  (ch>='!' && ch<='<')
+       || (ch>='>' && ch<='~')
+       || (  (ch=='\t' || ch==' ')
+          && start+2<end
+          && (*(start+1)!='\r' || *(start+2)!='\n')    /* CRLF */
+          )
+       )
       {
       if (pass==0)
         ++dst->length;
       else
-        *new++=*start;
+        *new++=*start; /* copy char */
       ++line;
       }
-    else if (ch=='\r' && start+1<end && *(start+1)=='\n')
+    else if (ch=='\r' && start+1<end && *(start+1)=='\n') /* CRLF */
       {
       if (pass==0)
-        {
         ++dst->length;
-        line=0;
-        }
       else
-        *new++='\n';
-        line=0;
-      ++start;
+        *new++='\n';                                   /* NL */
+      line=0;
+      ++start; /* consume extra input char */
       }
     else
       {
       if (pass==0)
         dst->length+=3;
       else
-        {
-        sprintf(CS new,"=%02X",ch);
+        {              /* encoded char */
+        new += sprintf(CS new,"=%02X",ch);
         new+=3;
         }
       line+=3;
@@ -415,7 +413,8 @@ Returns
 static int parse_mailto_uri(struct Sieve *filter, const uschar *uri, string_item **recipient, struct String *header, struct String *subject, struct String *body)
 {
 const uschar *start;
-struct String to,hname,hvalue;
+struct String to, hname;
+struct String hvalue = {NULL, 0};
 int capacity;
 string_item *new;
 
@@ -424,6 +423,7 @@ if (Ustrncmp(uri,"mailto:",7))
   filter->errmsg=US "Unknown URI scheme";
   return 0;
   }
+
 uri+=7;
 if (*uri && *uri!='?')
   for (;;)
@@ -433,9 +433,9 @@ if (*uri && *uri!='?')
     if (uri>start)
       {
       capacity=0;
-      to.character=(uschar*)0;
+      to.character= NULL;
       to.length=0;
-      to.character=string_cat(to.character,&capacity,&to.length,start,uri-start);
+      to.character=string_catn(to.character, &capacity, &to.length, start, uri-start);
       to.character[to.length]='\0';
       if (uri_decode(&to)==-1)
         {
@@ -467,9 +467,9 @@ if (*uri=='?')
     if (uri>start)
       {
       capacity=0;
-      hname.character=(uschar*)0;
+      hname.character= NULL;
       hname.length=0;
-      hname.character=string_cat(hname.character,&capacity,&hname.length,start,uri-start);
+      hname.character = string_catn(hname.character, &capacity, &hname.length, start, uri-start);
       hname.character[hname.length]='\0';
       if (uri_decode(&hname)==-1)
         {
@@ -490,9 +490,9 @@ if (*uri=='?')
     if (uri>start)
       {
       capacity=0;
-      hvalue.character=(uschar*)0;
+      hvalue.character= NULL;
       hvalue.length=0;
-      hvalue.character=string_cat(hvalue.character,&capacity,&hvalue.length,start,uri-start);
+      hvalue.character=string_catn(hvalue.character,&capacity,&hvalue.length,start,uri-start);
       hvalue.character[hvalue.length]='\0';
       if (uri_decode(&hvalue)==-1)
         {
@@ -531,10 +531,10 @@ if (*uri=='?')
         {
         if (header->length==-1) header->length=0;
         capacity=header->length;
-        header->character=string_cat(header->character,&capacity,&header->length,hname.character,hname.length);
-        header->character=string_cat(header->character,&capacity,&header->length,CUS ": ",2);
-        header->character=string_cat(header->character,&capacity,&header->length,hvalue.character,hvalue.length);
-        header->character=string_cat(header->character,&capacity,&header->length,CUS "\n",1);
+        header->character=string_catn(header->character,&capacity,&header->length,hname.character,hname.length);
+        header->character=string_catn(header->character,&capacity,&header->length,CUS ": ",2);
+        header->character=string_catn(header->character,&capacity,&header->length,hvalue.character,hvalue.length);
+        header->character=string_catn(header->character,&capacity,&header->length,CUS "\n",1);
         header->character[header->length]='\0';
         }
       }
@@ -1008,24 +1008,24 @@ while (l)
     {
     case '\0':
       {
-      quoted=string_cat(quoted,&size,&ptr,CUS "\\0",2);
+      quoted=string_catn(quoted,&size,&ptr,CUS "\\0",2);
       break;
       }
     case '$':
     case '{':
     case '}':
       {
-      quoted=string_cat(quoted,&size,&ptr,CUS "\\",1);
+      quoted=string_catn(quoted,&size,&ptr,CUS "\\",1);
       }
     default:
       {
-      quoted=string_cat(quoted,&size,&ptr,h,1);
+      quoted=string_catn(quoted,&size,&ptr,h,1);
       }
     }
   ++h;
   --l;
   }
-quoted=string_cat(quoted,&size,&ptr,CUS "",1);
+quoted=string_catn(quoted,&size,&ptr,CUS "",1);
 return quoted;
 }
 
@@ -1072,7 +1072,7 @@ if (file)
   setflag(new_addr, af_pfr|af_file);
   new_addr->mode = 0;
   }
-new_addr->p.errors_address = NULL;
+new_addr->prop.errors_address = NULL;
 new_addr->next = *generated;
 *generated = new_addr;
 }
@@ -1489,7 +1489,7 @@ if (*filter->pc=='"') /* quoted string */
 
       ++filter->pc;
       /* that way, there will be at least one character allocated */
-      data->character=string_cat(data->character,&dataCapacity,&foo,CUS "",1);
+      data->character=string_catn(data->character,&dataCapacity,&foo,CUS "",1);
 #ifdef ENCODED_CHARACTER
       if (filter->require_encoded_character
           && string_decode(filter,data)==-1)
@@ -1499,7 +1499,7 @@ if (*filter->pc=='"') /* quoted string */
       }
     else if (*filter->pc=='\\' && *(filter->pc+1)) /* quoted character */
       {
-      data->character=string_cat(data->character,&dataCapacity,&data->length,filter->pc+1,1);
+      data->character=string_catn(data->character,&dataCapacity,&data->length,filter->pc+1,1);
       filter->pc+=2;
       }
     else /* regular character */
@@ -1509,11 +1509,11 @@ if (*filter->pc=='"') /* quoted string */
 #else
       if (*filter->pc=='\n')
         {
-        data->character=string_cat(data->character,&dataCapacity,&data->length,US"\r",1);
+        data->character=string_catn(data->character,&dataCapacity,&data->length,US"\r",1);
         ++filter->line;
         }
 #endif
-      data->character=string_cat(data->character,&dataCapacity,&data->length,filter->pc,1);
+      data->character=string_catn(data->character,&dataCapacity,&data->length,filter->pc,1);
       filter->pc++;
       }
     }
@@ -1555,7 +1555,7 @@ else if (Ustrncmp(filter->pc,CUS "text:",5)==0) /* multiline string */
     if (*filter->pc=='\n') /* end of line */
 #endif
       {
-      data->character=string_cat(data->character,&dataCapacity,&data->length,CUS "\r\n",2);
+      data->character=string_catn(data->character,&dataCapacity,&data->length,CUS "\r\n",2);
 #ifdef RFC_EOL
       filter->pc+=2;
 #else
@@ -1571,7 +1571,7 @@ else if (Ustrncmp(filter->pc,CUS "text:",5)==0) /* multiline string */
         int foo=data->length;
 
         /* that way, there will be at least one character allocated */
-        data->character=string_cat(data->character,&dataCapacity,&foo,CUS "",1);
+        data->character=string_catn(data->character,&dataCapacity,&foo,CUS "",1);
 #ifdef RFC_EOL
         filter->pc+=3;
 #else
@@ -1587,13 +1587,13 @@ else if (Ustrncmp(filter->pc,CUS "text:",5)==0) /* multiline string */
         }
       else if (*filter->pc=='.' && *(filter->pc+1)=='.') /* remove dot stuffing */
         {
-        data->character=string_cat(data->character,&dataCapacity,&data->length,CUS ".",1);
+        data->character=string_catn(data->character,&dataCapacity,&data->length,CUS ".",1);
         filter->pc+=2;
         }
       }
     else /* regular character */
       {
-      data->character=string_cat(data->character,&dataCapacity,&data->length,filter->pc,1);
+      data->character=string_catn(data->character,&dataCapacity,&data->length,filter->pc,1);
       filter->pc++;
       }
     }
@@ -1706,12 +1706,13 @@ Returns:      1                success
               -1               no string list found
 */
 
-static int parse_stringlist(struct Sieve *filter, struct String **data)
+static int
+parse_stringlist(struct Sieve *filter, struct String **data)
 {
 const uschar *orig=filter->pc;
-int dataCapacity=0;
-int dataLength=0;
-struct String *d=(struct String*)0;
+int dataCapacity = 0;
+int dataLength = 0;
+struct String *d = NULL;
 int m;
 
 if (*filter->pc=='[') /* string list */
@@ -1720,20 +1721,18 @@ if (*filter->pc=='[') /* string list */
   for (;;)
     {
     if (parse_white(filter)==-1) goto error;
-    if ((dataLength+1)>=dataCapacity) /* increase buffer */
+    if (dataLength+1 >= dataCapacity) /* increase buffer */
       {
       struct String *new;
       int newCapacity;          /* Don't amalgamate with next line; some compilers grumble */
-      newCapacity=dataCapacity?(dataCapacity*=2):(dataCapacity=4);
-      if ((new=(struct String*)store_get(sizeof(struct String)*newCapacity))==(struct String*)0)
-        {
-        filter->errmsg=CUstrerror(errno);
-        goto error;
-        }
+
+      dataCapacity = dataCapacity ? dataCapacity * 2 : 4;
+      new = store_get(sizeof(struct String) * dataCapacity);
+
       if (d) memcpy(new,d,sizeof(struct String)*dataLength);
-      d=new;
-      dataCapacity=newCapacity;
+      d = new;
       }
+
     m=parse_string(filter,&d[dataLength]);
     if (m==0)
       {
@@ -2737,8 +2736,8 @@ Returns:      2                success by stop
               1                other success
               -1               syntax or execution error
 */
-static int parse_commands(struct Sieve *filter, int exec,
-  address_item **generated)
+static int
+parse_commands(struct Sieve *filter, int exec, address_item **generated)
 {
 while (*filter->pc)
   {
@@ -2970,7 +2969,6 @@ while (*filter->pc)
     int m;
     struct String from;
     struct String importance;
-    struct String *options;
     struct String message;
     struct String method;
     struct Notification *already;
@@ -2991,7 +2989,6 @@ while (*filter->pc)
     from.length=-1;
     importance.character=(uschar*)0;
     importance.length=-1;
-    options=(struct String*)0;
     message.character=(uschar*)0;
     message.length=-1;
     recipient=NULL;
@@ -3107,7 +3104,7 @@ while (*filter->pc)
                 message.character=US"Notification";
                 message.length=Ustrlen(message.character);
                 }
-              /* Allocation is larger than neccessary, but enough even for split MIME words */
+              /* Allocation is larger than necessary, but enough even for split MIME words */
               buffer_capacity=32+4*message.length;
               buffer=store_get(buffer_capacity);
               if (message.length!=-1) fprintf(f,"Subject: %s\n",parse_quote_2047(message.character, message.length, US"utf-8", buffer, buffer_capacity, TRUE));
@@ -3301,10 +3298,10 @@ while (*filter->pc)
         capacity=0;
         if (handle.length==-1)
           {
-          if (subject.length!=-1) key.character=string_cat(key.character,&capacity,&key.length,subject.character,subject.length);
-          if (from.length!=-1) key.character=string_cat(key.character,&capacity,&key.length,from.character,from.length);
-          key.character=string_cat(key.character,&capacity,&key.length,reason_is_mime?US"1":US"0",1);
-          key.character=string_cat(key.character,&capacity,&key.length,reason.character,reason.length);
+          if (subject.length!=-1) key.character=string_catn(key.character,&capacity,&key.length,subject.character,subject.length);
+          if (from.length!=-1) key.character=string_catn(key.character,&capacity,&key.length,from.character,from.length);
+          key.character=string_catn(key.character,&capacity,&key.length,reason_is_mime?US"1":US"0",1);
+          key.character=string_catn(key.character,&capacity,&key.length,reason.character,reason.length);
           }
         else
           key=handle;
@@ -3319,8 +3316,8 @@ while (*filter->pc)
           {
           capacity=Ustrlen(filter->vacation_directory);
           start=capacity;
-          once=string_cat(filter->vacation_directory,&capacity,&start,US"/",1);
-          once=string_cat(once,&capacity,&start,hexdigest,33);
+          once=string_catn(filter->vacation_directory,&capacity,&start,US"/",1);
+          once=string_catn(once,&capacity,&start,hexdigest,33);
           once[start] = '\0';
 
           /* process subject */
@@ -3335,7 +3332,7 @@ while (*filter->pc)
               expand_header(&subject,&str_subject);
               capacity=6;
               start=6;
-              subject.character=string_cat(US"Auto: ",&capacity,&start,subject.character,subject.length);
+              subject.character=string_catn(US"Auto: ",&capacity,&start,subject.character,subject.length);
               subject.length=start;
               }
             else
@@ -3359,10 +3356,11 @@ while (*filter->pc)
             addr->reply->from = expand_string(US"$local_part@$domain");
           else
             addr->reply->from = from.character;
-          /* Allocation is larger than neccessary, but enough even for split MIME words */
+          /* Allocation is larger than necessary, but enough even for split MIME words */
           buffer_capacity=32+4*subject.length;
           buffer=store_get(buffer_capacity);
-          addr->reply->subject=parse_quote_2047(subject.character, subject.length, US"utf-8", buffer, buffer_capacity, TRUE);
+         /* deconst cast safe as we pass in a non-const item */
+          addr->reply->subject = US parse_quote_2047(subject.character, subject.length, US"utf-8", buffer, buffer_capacity, TRUE);
           addr->reply->oncelog=once;
           addr->reply->once_repeat=days*86400;
 
@@ -3381,13 +3379,13 @@ while (*filter->pc)
               );
             capacity = 0;
             start = 0;
-            addr->reply->headers = string_cat(NULL,&capacity,&start,reason.character,mime_body-reason.character);
+            addr->reply->headers = string_catn(NULL,&capacity,&start,reason.character,mime_body-reason.character);
             addr->reply->headers[start] = '\0';
             capacity = 0;
             start = 0;
             if (mime_body+(sizeof(nlnl)-1)<reason_end) mime_body+=(sizeof(nlnl)-1);
             else mime_body=reason_end-1;
-            addr->reply->text = string_cat(NULL,&capacity,&start,mime_body,reason_end-mime_body);
+            addr->reply->text = string_catn(NULL,&capacity,&start,mime_body,reason_end-mime_body);
             addr->reply->text[start] = '\0';
             }
           else
index dbaa328..1b45f84 100644 (file)
@@ -2,13 +2,14 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2017 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for handling an incoming SMTP call. */
 
 
 #include "exim.h"
+#include <assert.h>
 
 
 /* Initialize for TCP wrappers if so configured. It appears that the macro
@@ -36,18 +37,18 @@ uschar *tcp_wrappers_name;
 /* Size of buffer for reading SMTP commands. We used to use 512, as defined
 by RFC 821. However, RFC 1869 specifies that this must be increased for SMTP
 commands that accept arguments, and this in particular applies to AUTH, where
-the data can be quite long.  More recently this value was 2048 in Exim; 
+the data can be quite long.  More recently this value was 2048 in Exim;
 however, RFC 4954 (circa 2007) recommends 12288 bytes to handle AUTH.  Clients
-such as Thunderbird will send an AUTH with an initial-response for GSSAPI. 
-The maximum size of a Kerberos ticket under Windows 2003 is 12000 bytes, and 
+such as Thunderbird will send an AUTH with an initial-response for GSSAPI.
+The maximum size of a Kerberos ticket under Windows 2003 is 12000 bytes, and
 we need room to handle large base64-encoded AUTHs for GSSAPI.
 */
 
-#define smtp_cmd_buffer_size  16384
+#define SMTP_CMD_BUFFER_SIZE  16384
 
 /* Size of buffer for reading SMTP incoming packets */
 
-#define in_buffer_size  8192
+#define IN_BUFFER_SIZE  8192
 
 /* Structure for SMTP command list */
 
@@ -71,6 +72,7 @@ enum {
   VRFY_CMD, EXPN_CMD, NOOP_CMD, /* RFC as requiring synchronization */
   ETRN_CMD,                     /* This by analogy with TURN from the RFC */
   STARTTLS_CMD,                 /* Required by the STARTTLS RFC */
+  TLS_AUTH_CMD,                        /* auto-command at start of SSL */
 
   /* This is a dummy to identify the non-sync commands when pipelining */
 
@@ -84,6 +86,19 @@ enum {
 
   NON_SYNC_CMD_NON_PIPELINING,
 
+  /* RFC3030 section 2: "After all MAIL and RCPT responses are collected and
+  processed the message is sent using a series of BDAT commands"
+  implies that BDAT should be synchronized.  However, we see Google, at least,
+  sending MAIL,RCPT,BDAT-LAST in a single packet, clearly not waiting for
+  processing of the RCPT response(s).  We shall do the same, and not require
+  synch for BDAT.  Worse, as the chunk may (very likely will) follow the
+  command-header in the same packet we cannot do the usual "is there any
+  follow-on data after the command line" even for non-pipeline mode.
+  So we'll need an explicit check after reading the expected chunk amount
+  when non-pipe, before sending the ACK. */
+
+  BDAT_CMD,
+
   /* I have been unable to find a statement about the use of pipelining
   with AUTH, so to be on the safe side it is here, though I kind of feel
   it should be up there with the synchronized commands. */
@@ -94,7 +109,7 @@ enum {
 
   QUIT_CMD, HELP_CMD,
 
-#ifdef EXPERIMENTAL_PROXY
+#ifdef SUPPORT_PROXY
   PROXY_FAIL_IGNORE_CMD,
 #endif
 
@@ -121,9 +136,7 @@ static BOOL auth_advertised;
 #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;
@@ -135,6 +148,9 @@ static BOOL rcpt_smtp_response_same;
 static BOOL rcpt_in_progress;
 static int  nonmail_command_count;
 static BOOL smtp_exit_function_called = 0;
+#ifdef SUPPORT_I18N
+static BOOL smtputf8_advertised;
+#endif
 static int  synprot_error_count;
 static int  unknown_command_count;
 static int  sync_cmd_limit;
@@ -157,15 +173,21 @@ AUTH is already forbidden. After a TLS session is started, AUTH's flag is again
 forced TRUE, to allow for the re-authentication that can happen at that point.
 
 QUIT is also "falsely" labelled as a mail command so that it doesn't up the
-count of non-mail commands and possibly provoke an error. */
+count of non-mail commands and possibly provoke an error.
+
+tls_auth is a pseudo-command, never expected in input.  It is activated
+on TLS startup and looks for a tls authenticator. */
 
 static smtp_cmd_list cmd_list[] = {
+  /* name         len                     cmd     has_arg is_mail_cmd */
+
   { "rset",       sizeof("rset")-1,       RSET_CMD, FALSE, FALSE },  /* First */
   { "helo",       sizeof("helo")-1,       HELO_CMD, TRUE,  FALSE },
   { "ehlo",       sizeof("ehlo")-1,       EHLO_CMD, TRUE,  FALSE },
   { "auth",       sizeof("auth")-1,       AUTH_CMD, TRUE,  TRUE  },
   #ifdef SUPPORT_TLS
   { "starttls",   sizeof("starttls")-1,   STARTTLS_CMD, FALSE, FALSE },
+  { "tls_auth",   0,                      TLS_AUTH_CMD, FALSE, TRUE },
   #endif
 
 /* If you change anything above here, also fix the definitions below. */
@@ -173,6 +195,7 @@ static smtp_cmd_list cmd_list[] = {
   { "mail from:", sizeof("mail from:")-1, MAIL_CMD, TRUE,  TRUE  },
   { "rcpt to:",   sizeof("rcpt to:")-1,   RCPT_CMD, TRUE,  TRUE  },
   { "data",       sizeof("data")-1,       DATA_CMD, FALSE, TRUE  },
+  { "bdat",       sizeof("bdat")-1,       BDAT_CMD, TRUE,  TRUE  },
   { "quit",       sizeof("quit")-1,       QUIT_CMD, FALSE, TRUE  },
   { "noop",       sizeof("noop")-1,       NOOP_CMD, TRUE,  FALSE },
   { "etrn",       sizeof("etrn")-1,       ETRN_CMD, TRUE,  FALSE },
@@ -189,17 +212,18 @@ static smtp_cmd_list *cmd_list_end =
 #define CMD_LIST_EHLO      2
 #define CMD_LIST_AUTH      3
 #define CMD_LIST_STARTTLS  4
+#define CMD_LIST_TLS_AUTH  5
 
 /* This list of names is used for performing the smtp_no_mail logging action.
 It must be kept in step with the SCH_xxx enumerations. */
 
 static uschar *smtp_names[] =
   {
-  US"NONE", US"AUTH", US"DATA", US"EHLO", US"ETRN", US"EXPN", US"HELO",
-  US"HELP", US"MAIL", US"NOOP", US"QUIT", US"RCPT", US"RSET", US"STARTTLS",
-  US"VRFY" };
+  US"NONE", US"AUTH", US"DATA", US"BDAT", US"EHLO", US"ETRN", US"EXPN",
+  US"HELO", US"HELP", US"MAIL", US"NOOP", US"QUIT", US"RCPT", US"RSET",
+  US"STARTTLS", US"VRFY" };
 
-static uschar *protocols[] = {
+static uschar *protocols_local[] = {
   US"local-smtp",        /* HELO */
   US"local-smtps",       /* The rare case EHLO->STARTTLS->HELO */
   US"local-esmtp",       /* EHLO */
@@ -207,23 +231,31 @@ static uschar *protocols[] = {
   US"local-esmtpa",      /* EHLO->AUTH */
   US"local-esmtpsa"      /* EHLO->STARTTLS->EHLO->AUTH */
   };
+static uschar *protocols[] = {
+  US"smtp",              /* HELO */
+  US"smtps",             /* The rare case EHLO->STARTTLS->HELO */
+  US"esmtp",             /* EHLO */
+  US"esmtps",            /* EHLO->STARTTLS->EHLO */
+  US"esmtpa",            /* EHLO->AUTH */
+  US"esmtpsa"            /* EHLO->STARTTLS->EHLO->AUTH */
+  };
 
 #define pnormal  0
 #define pextend  2
 #define pcrpted  1  /* added to pextend or pnormal */
 #define pauthed  2  /* added to pextend */
-#define pnlocal  6  /* offset to remove "local" */
 
 /* Sanity check and validate optional args to MAIL FROM: envelope */
 enum {
+  ENV_MAIL_OPT_NULL,
   ENV_MAIL_OPT_SIZE, ENV_MAIL_OPT_BODY, ENV_MAIL_OPT_AUTH,
 #ifndef DISABLE_PRDR
   ENV_MAIL_OPT_PRDR,
 #endif
-#ifdef EXPERIMENTAL_DSN
   ENV_MAIL_OPT_RET, ENV_MAIL_OPT_ENVID,
+#ifdef SUPPORT_I18N
+  ENV_MAIL_OPT_UTF8,
 #endif
-  ENV_MAIL_OPT_NULL
   };
 typedef struct {
   uschar *   name;  /* option requested during MAIL cmd */
@@ -238,11 +270,13 @@ static env_mail_type_t env_mail_type_list[] = {
 #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 },
+#ifdef SUPPORT_I18N
+    { US"SMTPUTF8",ENV_MAIL_OPT_UTF8,  FALSE },                /* rfc6531 */
 #endif
-    { US"NULL",   ENV_MAIL_OPT_NULL,   FALSE }
+    /* keep this the last entry */
+    { US"NULL",   ENV_MAIL_OPT_NULL,   FALSE },
   };
 
 /* When reading SMTP from a remote host, we have to use our own versions of the
@@ -269,6 +303,103 @@ static int     smtp_had_eof;
 static int     smtp_had_error;
 
 
+/* forward declarations */
+static int smtp_read_command(BOOL check_sync, unsigned buffer_lim);
+static int synprot_error(int type, int code, uschar *data, uschar *errmess);
+static void smtp_quit_handler(uschar **, uschar **);
+static void smtp_rset_handler(void);
+
+/*************************************************
+*          Recheck synchronization               *
+*************************************************/
+
+/* Synchronization checks can never be perfect because a packet may be on its
+way but not arrived when the check is done. Such checks can in any case only be
+done when TLS is not in use. Normally, the checks happen when commands are
+read: Exim ensures that there is no more input in the input buffer. In normal
+cases, the response to the command will be fast, and there is no further check.
+
+However, for some commands an ACL is run, and that can include delays. In those
+cases, it is useful to do another check on the input just before sending the
+response. This also applies at the start of a connection. This function does
+that check by means of the select() function, as long as the facility is not
+disabled or inappropriate. A failure of select() is ignored.
+
+When there is unwanted input, we read it so that it appears in the log of the
+error.
+
+Arguments: none
+Returns:   TRUE if all is well; FALSE if there is input pending
+*/
+
+static BOOL
+check_sync(void)
+{
+int fd, rc;
+fd_set fds;
+struct timeval tzero;
+
+if (!smtp_enforce_sync || sender_host_address == NULL ||
+    sender_host_notsocket || tls_in.active >= 0)
+  return TRUE;
+
+fd = fileno(smtp_in);
+FD_ZERO(&fds);
+FD_SET(fd, &fds);
+tzero.tv_sec = 0;
+tzero.tv_usec = 0;
+rc = select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &tzero);
+
+if (rc <= 0) return TRUE;     /* Not ready to read */
+rc = smtp_getc(GETC_BUFFER_UNLIMITED);
+if (rc < 0) return TRUE;      /* End of file or error */
+
+smtp_ungetc(rc);
+rc = smtp_inend - smtp_inptr;
+if (rc > 150) rc = 150;
+smtp_inptr[rc] = 0;
+return FALSE;
+}
+
+
+
+/*************************************************
+*          Log incomplete transactions           *
+*************************************************/
+
+/* This function is called after a transaction has been aborted by RSET, QUIT,
+connection drops or other errors. It logs the envelope information received
+so far in order to preserve address verification attempts.
+
+Argument:   string to indicate what aborted the transaction
+Returns:    nothing
+*/
+
+static void
+incomplete_transaction_log(uschar *what)
+{
+if (sender_address == NULL ||                 /* No transaction in progress */
+    !LOGGING(smtp_incomplete_transaction))
+  return;
+
+/* Build list of recipients for logging */
+
+if (recipients_count > 0)
+  {
+  int i;
+  raw_recipients = store_get(recipients_count * sizeof(uschar *));
+  for (i = 0; i < recipients_count; i++)
+    raw_recipients[i] = recipients_list[i].address;
+  raw_recipients_count = recipients_count;
+  }
+
+log_write(L_smtp_incomplete_transaction, LOG_MAIN|LOG_SENDER|LOG_RECIPIENTS,
+  "%s incomplete transaction (%s)", host_and_ident(TRUE), what);
+}
+
+
+
+
 /*************************************************
 *          SMTP version of getc()                *
 *************************************************/
@@ -276,21 +407,25 @@ static int     smtp_had_error;
 /* This gets the next byte from the SMTP input buffer. If the buffer is empty,
 it flushes the output, and refills the buffer, with a timeout. The signal
 handler is set appropriately by the calling function. This function is not used
-after a connection has negotated itself into an TLS/SSL state.
+after a connection has negotiated itself into an TLS/SSL state.
 
-Arguments:  none
+Arguments:  lim                Maximum amount to read/buffer
 Returns:    the next character or EOF
 */
 
 int
-smtp_getc(void)
+smtp_getc(unsigned lim)
 {
 if (smtp_inptr >= smtp_inend)
   {
   int rc, save_errno;
+  if (!smtp_out) return EOF;
   fflush(smtp_out);
   if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
-  rc = read(fileno(smtp_in), smtp_inbuffer, in_buffer_size);
+
+  /* Limit amount read, so non-message data is not fed to DKIM */
+
+  rc = read(fileno(smtp_in), smtp_inbuffer, MIN(IN_BUFFER_SIZE, lim));
   save_errno = errno;
   alarm(0);
   if (rc <= 0)
@@ -315,6 +450,178 @@ if (smtp_inptr >= smtp_inend)
 return *smtp_inptr++;
 }
 
+void
+smtp_get_cache(void)
+{
+#ifndef DISABLE_DKIM
+int n = smtp_inend - smtp_inptr;
+if (n > 0)
+  dkim_exim_verify_feed(smtp_inptr, n);
+#endif
+}
+
+
+/* Get a byte from the smtp input, in CHUNKING mode.  Handle ack of the
+previous BDAT chunk and getting new ones when we run out.  Uses the
+underlying smtp_getc or tls_getc both for that and for getting the
+(buffered) data byte.  EOD signals (an expected) no further data.
+ERR signals a protocol error, and EOF a closed input stream.
+
+Called from read_bdat_smtp() in receive.c for the message body, but also
+by the headers read loop in receive_msg(); manipulates chunking_state
+to handle the BDAT command/response.
+Placed here due to the correlation with the above smtp_getc(), which it wraps,
+and also by the need to do smtp command/response handling.
+
+Arguments:  lim                (ignored)
+Returns:    the next character or ERR, EOD or EOF
+*/
+
+int
+bdat_getc(unsigned lim)
+{
+uschar * user_msg = NULL;
+uschar * log_msg;
+
+for(;;)
+  {
+#ifndef DISABLE_DKIM
+  BOOL dkim_save;
+#endif
+
+  if (chunking_data_left > 0)
+    return lwr_receive_getc(chunking_data_left--);
+
+  receive_getc = lwr_receive_getc;
+  receive_ungetc = lwr_receive_ungetc;
+#ifndef DISABLE_DKIM
+  dkim_save = dkim_collect_input;
+  dkim_collect_input = FALSE;
+#endif
+
+  /* Unless PIPELINING was offered, there should be no next command
+  until after we ack that chunk */
+
+  if (!pipelining_advertised && !check_sync())
+    {
+    incomplete_transaction_log(US"sync failure");
+    log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error "
+      "(next input sent too soon: pipelining was not advertised): "
+      "rejected \"%s\" %s next input=\"%s\"",
+      smtp_cmd_buffer, host_and_ident(TRUE),
+      string_printing(smtp_inptr));
+      (void) synprot_error(L_smtp_protocol_error, 554, NULL,
+       US"SMTP synchronization error");
+    goto repeat_until_rset;
+    }
+
+  /* If not the last, ack the received chunk.  The last response is delayed
+  until after the data ACL decides on it */
+
+  if (chunking_state == CHUNKING_LAST)
+    {
+#ifndef DISABLE_DKIM
+    dkim_exim_verify_feed(NULL, 0);    /* notify EOD */
+#endif
+    return EOD;
+    }
+
+  smtp_printf("250 %u byte chunk received\r\n", chunking_datasize);
+  chunking_state = CHUNKING_OFFERED;
+  DEBUG(D_receive) debug_printf("chunking state %d\n", (int)chunking_state);
+
+  /* Expect another BDAT cmd from input. RFC 3030 says nothing about
+  QUIT, RSET or NOOP but handling them seems obvious */
+
+next_cmd:
+  switch(smtp_read_command(TRUE, 1))
+    {
+    default:
+      (void) synprot_error(L_smtp_protocol_error, 503, NULL,
+       US"only BDAT permissible after non-LAST BDAT");
+
+  repeat_until_rset:
+      switch(smtp_read_command(TRUE, 1))
+       {
+       case QUIT_CMD:  smtp_quit_handler(&user_msg, &log_msg); /*FALLTHROUGH */
+       case EOF_CMD:   return EOF;
+       case RSET_CMD:  smtp_rset_handler(); return ERR;
+       default:        if (synprot_error(L_smtp_protocol_error, 503, NULL,
+                                         US"only RSET accepted now") > 0)
+                         return EOF;
+                       goto repeat_until_rset;
+       }
+
+    case QUIT_CMD:
+      smtp_quit_handler(&user_msg, &log_msg);
+      /*FALLTHROUGH*/
+    case EOF_CMD:
+      return EOF;
+
+    case RSET_CMD:
+      smtp_rset_handler();
+      return ERR;
+
+    case NOOP_CMD:
+      HAD(SCH_NOOP);
+      smtp_printf("250 OK\r\n");
+      goto next_cmd;
+
+    case BDAT_CMD:
+      {
+      int n;
+
+      if (sscanf(CS smtp_cmd_data, "%u %n", &chunking_datasize, &n) < 1)
+       {
+       (void) synprot_error(L_smtp_protocol_error, 501, NULL,
+         US"missing size for BDAT command");
+       return ERR;
+       }
+      chunking_state = strcmpic(smtp_cmd_data+n, US"LAST") == 0
+       ? CHUNKING_LAST : CHUNKING_ACTIVE;
+      chunking_data_left = chunking_datasize;
+      DEBUG(D_receive) debug_printf("chunking state %d, %d bytes\n",
+                                   (int)chunking_state, chunking_data_left);
+
+      if (chunking_datasize == 0)
+       if (chunking_state == CHUNKING_LAST)
+         return EOD;
+       else
+         {
+         (void) synprot_error(L_smtp_protocol_error, 504, NULL,
+           US"zero size for BDAT command");
+         goto repeat_until_rset;
+         }
+
+      receive_getc = bdat_getc;
+      receive_ungetc = bdat_ungetc;
+#ifndef DISABLE_DKIM
+      dkim_collect_input = dkim_save;
+#endif
+      break;   /* to top of main loop */
+      }
+    }
+  }
+}
+
+void
+bdat_flush_data(void)
+{
+while (chunking_data_left > 0)
+  if (lwr_receive_getc(chunking_data_left--) < 0)
+    break;
+
+receive_getc = lwr_receive_getc;
+receive_ungetc = lwr_receive_ungetc;
+
+if (chunking_state != CHUNKING_LAST)
+  {
+  chunking_state = CHUNKING_OFFERED;
+  DEBUG(D_receive) debug_printf("chunking state %d\n", (int)chunking_state);
+  }
+}
+
+
 
 
 /*************************************************
@@ -333,11 +640,18 @@ Returns:       the character
 int
 smtp_ungetc(int ch)
 {
-*(--smtp_inptr) = ch;
+*--smtp_inptr = ch;
 return ch;
 }
 
 
+int
+bdat_ungetc(int ch)
+{
+chunking_data_left++;
+return lwr_receive_ungetc(ch);
+}
+
 
 
 /*************************************************
@@ -563,7 +877,7 @@ exim_exit(EXIT_FAILURE);
 
 
 
-#ifdef EXPERIMENTAL_PROXY
+#ifdef SUPPORT_PROXY
 /*************************************************
 *     Restore socket timeout to previous value   *
 *************************************************/
@@ -577,17 +891,18 @@ Arguments: fd     - File descriptor for input
 Returns:   none
 */
 static void
-restore_socket_timeout(int fd, int get_ok, struct timeval tvtmp, socklen_t vslen)
+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);
+  (void) setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS 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.
+or if it is configured that it must use Proxy Protocol.  A local
+connection cannot.
 
 Arguments: none
 Returns:   bool
@@ -597,12 +912,10 @@ 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)
+if (  sender_host_address
+   && (rc = verify_check_this_host(CUSS &hosts_proxy, NULL, NULL,
+                           sender_host_address, NULL)) == OK)
   {
   DEBUG(D_receive)
     debug_printf("Detected proxy protocol configured host\n");
@@ -612,6 +925,69 @@ return proxy_session;
 }
 
 
+/*************************************************
+*    Read data until newline or end of buffer    *
+*************************************************/
+/* While SMTP is server-speaks-first, TLS is client-speaks-first, so we can't
+read an entire buffer and assume there will be nothing past a proxy protocol
+header.  Our approach normally is to use stdio, but again that relies upon
+"STARTTLS\r\n" and a server response before the client starts TLS handshake, or
+reading _nothing_ before client TLS handshake.  So we don't want to use the
+usual buffering reads which may read enough to block TLS starting.
+
+So unfortunately we're down to "read one byte at a time, with a syscall each,
+and expect a little overhead", for all proxy-opened connections which are v1,
+just to handle the TLS-on-connect case.  Since SSL functions wrap the
+underlying fd, we can't assume that we can feed them any already-read content.
+
+We need to know where to read to, the max capacity, and we'll read until we
+get a CR and one more character.  Let the caller scream if it's CR+!LF.
+
+Return the amount read.
+*/
+
+static int
+swallow_until_crlf(int fd, uschar *base, int already, int capacity)
+{
+  uschar *to = base + already;
+  uschar *cr;
+  int have = 0;
+  int ret;
+  int last = 0;
+
+  /* For "PROXY UNKNOWN\r\n" we, at time of writing, expect to have read
+  up through the \r; for the _normal_ case, we haven't yet seen the \r. */
+  cr = memchr(base, '\r', already);
+  if (cr != NULL)
+    {
+    if ((cr - base) < already - 1)
+      {
+      /* \r and presumed \n already within what we have; probably not
+      actually proxy protocol, but abort cleanly. */
+      return 0;
+      }
+    /* \r is last character read, just need one more. */
+    last = 1;
+    }
+
+  while (capacity > 0)
+    {
+    do { ret = recv(fd, to, 1, 0); } while (ret == -1 && errno == EINTR);
+    if (ret == -1)
+      return -1;
+    have++;
+    if (last)
+      return have;
+    if (*to == '\r')
+      last = 1;
+    capacity--;
+    to++;
+    }
+  // reached end without having room for a final newline, abort
+  errno = EOVERFLOW;
+  return -1;
+}
+
 /*************************************************
 *         Setup host for proxy protocol          *
 *************************************************/
@@ -621,10 +997,10 @@ 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
+Returns:   Boolean success
 */
 
-static BOOL
+static void
 setup_proxy_protocol_host()
 {
 union {
@@ -664,69 +1040,133 @@ struct sockaddr_in tmpaddr;
 char tmpip6[INET6_ADDRSTRLEN];
 struct sockaddr_in6 tmpaddr6;
 
+/* We can't read "all data until end" because while SMTP is
+server-speaks-first, the TLS handshake is client-speaks-first, so for
+TLS-on-connect ports the proxy protocol header will usually be immediately
+followed by a TLS handshake, and with N TLS libraries, we can't reliably
+reinject data for reading by those.  So instead we first read "enough to be
+safely read within the header, and figure out how much more to read".
+For v1 we will later read to the end-of-line, for v2 we will read based upon
+the stated length.
+
+The v2 sig is 12 octets, and another 4 gets us the length, so we know how much
+data is needed total.  For v1, where the line looks like:
+PROXY TCPn L3src L3dest SrcPort DestPort \r\n
+
+However, for v1 there's also `PROXY UNKNOWN\r\n` which is only 15 octets.
+We seem to support that.  So, if we read 14 octets then we can tell if we're
+v2 or v1.  If we're v1, we can continue reading as normal.
+
+If we're v2, we can't slurp up the entire header.  We need the length in the
+15th & 16th octets, then to read everything after that.
+
+So to safely handle v1 and v2, with client-sent-first supported correctly,
+we have to do a minimum of 3 read calls, not 1.  Eww.
+*/
+
+#define PROXY_INITIAL_READ 14
+#define PROXY_V2_HEADER_SIZE 16
+#if PROXY_INITIAL_READ > PROXY_V2_HEADER_SIZE
+# error Code bug in sizes of data to read for proxy usage
+#endif
+
 int get_ok = 0;
-int size, ret, fd;
+int size, ret;
+int fd = fileno(smtp_in);
 const char v2sig[12] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
-uschar *iptype;  /* To display debug info */
+uschar * iptype;  /* To display debug info */
 struct timeval tv;
-socklen_t vslen = 0;
 struct timeval tvtmp;
-
-vslen = sizeof(struct timeval);
-
-fd = fileno(smtp_in);
+socklen_t vslen = sizeof(struct timeval);
+BOOL yield = FALSE;
 
 /* Save current socket timeout values */
-get_ok = getsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tvtmp,
-                    &vslen);
+get_ok = getsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS &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));
+if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS &tv, sizeof(tv)) < 0)
+  goto bad;
 
 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);
+     don't do a PEEK into the data, actually slurp up enough to be
+     "safe". Can't take it all because TLS-on-connect clients follow
+     immediately with TLS handshake. */
+  ret = recv(fd, &hdr, PROXY_INITIAL_READ, 0);
   }
   while (ret == -1 && errno == EINTR);
 
 if (ret == -1)
-  {
-  restore_socket_timeout(fd, get_ok, tvtmp, vslen);
-  return (errno == EAGAIN) ? 0 : ERRNO_PROXYFAIL;
-  }
+  goto proxyfail;
 
-if (ret >= 16 &&
-    memcmp(&hdr.v2, v2sig, 12) == 0)
+/* For v2, handle reading the length, and then the rest. */
+if ((ret == PROXY_INITIAL_READ) && (memcmp(&hdr.v2, v2sig, sizeof(v2sig)) == 0))
   {
-  uint8_t ver, cmd;
+  int retmore;
+  uint8_t ver;
+
+  /* First get the length fields. */
+  do
+    {
+    retmore = recv(fd, (uschar*)&hdr + ret, PROXY_V2_HEADER_SIZE - PROXY_INITIAL_READ, 0);
+    } while (retmore == -1 && errno == EINTR);
+  if (retmore == -1)
+    goto proxyfail;
+  ret += retmore;
+
+  ver = (hdr.v2.ver_cmd & 0xf0) >> 4;
 
   /* 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);
+     must still separate values here. */
 
   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)
+  size = 16 + ntohs(hdr.v2.len);
+  DEBUG(D_receive) debug_printf("Detected PROXYv2 header, size %d (limit %d)\n",
+      size, (int)sizeof(hdr));
+
+  /* We should now have 16 octets (PROXY_V2_HEADER_SIZE), and we know the total
+  amount that we need.  Double-check that the size is not unreasonable, then
+  get the rest. */
+  if (size > sizeof(hdr))
     {
-    DEBUG(D_receive) debug_printf("Truncated or too large PROXYv2 header (%d/%d)\n",
-                                  ret, size);
+    DEBUG(D_receive) debug_printf("PROXYv2 header size unreasonably large; security attack?\n");
     goto proxyfail;
     }
+
+  do
+    {
+    do
+      {
+      retmore = recv(fd, (uschar*)&hdr + ret, size-ret, 0);
+      } while (retmore == -1 && errno == EINTR);
+    if (retmore == -1)
+      goto proxyfail;
+    ret += retmore;
+    DEBUG(D_receive) debug_printf("PROXYv2: have %d/%d required octets\n", ret, size);
+    } while (ret < size);
+
+  } /* end scope for getting rest of data for v2 */
+
+/* At this point: if PROXYv2, we've read the exact size required for all data;
+if PROXYv1 then we've read "less than required for any valid line" and should
+read the rest". */
+
+if (ret >= 16 && memcmp(&hdr.v2, v2sig, 12) == 0)
+  {
+  uint8_t cmd = (hdr.v2.ver_cmd & 0x0f);
+
   switch (cmd)
     {
     case 0x01: /* PROXY command */
@@ -735,54 +1175,54 @@ if (ret >= 16 &&
         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))
+          inet_ntop(AF_INET, &tmpaddr.sin_addr, CS &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;
+            goto proxyfail;
             }
-          proxy_host_address  = sender_host_address;
+          proxy_local_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;
+          proxy_local_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))
+          inet_ntop(AF_INET, &tmpaddr.sin_addr, CS &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;
+            goto proxyfail;
             }
-          proxy_target_address = string_copy(US tmpip);
+          proxy_external_address = string_copy(US tmpip);
           tmpport              = ntohs(hdr.v2.addr.ip4.dst_port);
-          proxy_target_port    = tmpport;
+          proxy_external_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))
+          inet_ntop(AF_INET6, &tmpaddr6.sin6_addr, CS &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;
+            goto proxyfail;
             }
-          proxy_host_address  = sender_host_address;
+          proxy_local_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;
+          proxy_local_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))
+          inet_ntop(AF_INET6, &tmpaddr6.sin6_addr, CS &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;
+            goto proxyfail;
             }
-          proxy_target_address = string_copy(US tmpip6);
+          proxy_external_address = string_copy(US tmpip6);
           tmpport              = ntohs(hdr.v2.addr.ip6.dst_port);
-          proxy_target_port    = tmpport;
+          proxy_external_port  = tmpport;
           goto done;
         default:
           DEBUG(D_receive)
@@ -794,6 +1234,7 @@ if (ret >= 16 &&
       break;
     case 0x00: /* LOCAL command */
       /* Keep local connection address for LOCAL */
+      iptype = US"local";
       break;
     default:
       DEBUG(D_receive)
@@ -801,25 +1242,35 @@ if (ret >= 16 &&
       goto proxyfail;
     }
   }
-else if (ret >= 8 &&
-         memcmp(hdr.v1.line, "PROXY", 5) == 0)
+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 *p;
+  uschar *end;
   uschar *sp;     /* Utility variables follow */
   int     tmp_port;
+  int     r2;
   char   *endc;
 
-  if (!end || end[1] != '\n')
+  /* get the rest of the line */
+  r2 = swallow_until_crlf(fd, (uschar*)&hdr, ret, sizeof(hdr)-ret);
+  if (r2 == -1)
+    goto proxyfail;
+  ret += r2;
+
+  p = string_copy(hdr.v1.line);
+  end = memchr(p, '\r', ret - 1);
+
+  if (!end || (end == (uschar*)&hdr + ret) || 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 */
+  size = end + 2 - p; /* Skip header + CRLF */
   DEBUG(D_receive) debug_printf("Detected PROXYv1 header\n");
+  DEBUG(D_receive) debug_printf("Bytes read not within PROXY header: %d\n", ret - size);
   /* Step through the string looking for the required fields. Ensure
-     strict adherance to required formatting, exit for any error. */
+     strict adherence to required formatting, exit for any error. */
   p += 5;
   if (!isspace(*(p++)))
     {
@@ -855,13 +1306,13 @@ else if (ret >= 8 &&
     goto proxyfail;
     }
   *sp = '\0';
-  if(!string_is_ip_address(p,NULL))
+  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;
+  proxy_local_address = sender_host_address;
   sender_host_address = p;
   p = sp + 1;
   if ((sp = Ustrchr(p, ' ')) == NULL)
@@ -871,13 +1322,13 @@ else if (ret >= 8 &&
     goto proxyfail;
     }
   *sp = '\0';
-  if(!string_is_ip_address(p,NULL))
+  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;
+  proxy_external_address = p;
   p = sp + 1;
   if ((sp = Ustrchr(p, ' ')) == NULL)
     {
@@ -885,14 +1336,14 @@ else if (ret >= 8 &&
     goto proxyfail;
     }
   *sp = '\0';
-  tmp_port = strtol(CCS p,&endc,10);
+  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;
+  proxy_local_port = sender_host_port;
   sender_host_port = tmp_port;
   p = sp + 1;
   if ((sp = Ustrchr(p, '\0')) == NULL)
@@ -900,35 +1351,50 @@ else if (ret >= 8 &&
     DEBUG(D_receive) debug_printf("Did not find proxy dest port\n");
     goto proxyfail;
     }
-  tmp_port = strtol(CCS p,&endc,10);
+  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;
+  proxy_external_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");
+  (void) swallow_until_crlf(fd, (uschar*)&hdr, ret, sizeof(hdr)-ret);
   goto proxyfail;
   }
 
+done:
+  DEBUG(D_receive)
+    debug_printf("Valid %s sender from Proxy Protocol header\n", iptype);
+  yield = proxy_session;
+
+/* Don't flush any potential buffer contents. Any input on proxyfail
+should cause a synchronization failure */
+
 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;
+  restore_socket_timeout(fd, get_ok, &tvtmp, vslen);
 
-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;
+bad:
+  if (yield)
+    {
+    sender_host_name = NULL;
+    (void) host_name_lookup();
+    host_build_sender_fullhost();
+    }
+  else
+    {
+    proxy_session_failed = TRUE;
+    DEBUG(D_receive)
+      debug_printf("Failure to extract proxied host, only QUIT allowed\n");
+    }
+
+return;
 }
 #endif
 
@@ -949,13 +1415,14 @@ signal handler that closes down the session on a timeout. Control does not
 return when it runs.
 
 Arguments:
-  check_sync   if TRUE, check synchronization rules if global option is TRUE
+  check_sync   if TRUE, check synchronization rules if global option is TRUE
+  buffer_lim   maximum to buffer in lower layer
 
 Returns:       a code identifying the command (enumerated above)
 */
 
 static int
-smtp_read_command(BOOL check_sync)
+smtp_read_command(BOOL check_sync, unsigned buffer_lim)
 {
 int c;
 int ptr = 0;
@@ -964,9 +1431,9 @@ BOOL hadnull = FALSE;
 
 os_non_restarting_signal(SIGALRM, command_timeout_handler);
 
-while ((c = (receive_getc)()) != '\n' && c != EOF)
+while ((c = (receive_getc)(buffer_lim)) != '\n' && c != EOF)
   {
-  if (ptr >= smtp_cmd_buffer_size)
+  if (ptr >= SMTP_CMD_BUFFER_SIZE)
     {
     os_non_restarting_signal(SIGALRM, sigalrm_handler);
     return OTHER_CMD;
@@ -1005,18 +1472,17 @@ if required. */
 
 for (p = cmd_list; p < cmd_list_end; p++)
   {
-  #ifdef EXPERIMENTAL_PROXY
+#ifdef SUPPORT_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 ||
-        smtp_cmd_buffer[p->len] == ' '))
+  if (proxy_session && proxy_session_failed && p->cmd != QUIT_CMD)
+    continue;
+#endif
+  if (  p->len
+     && 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
+       || smtp_cmd_buffer[p->len] == ' '
+     )  )
     {
     if (smtp_inptr < smtp_inend &&                     /* Outstanding input */
         p->cmd < sync_cmd_limit &&                     /* Command should sync */
@@ -1060,7 +1526,7 @@ for (p = cmd_list; p < cmd_list_end; p++)
     }
   }
 
-#ifdef EXPERIMENTAL_PROXY
+#ifdef SUPPORT_PROXY
 /* Only allow QUIT command if Proxy Protocol parsing failed */
 if (proxy_session && proxy_session_failed)
   return PROXY_FAIL_IGNORE_CMD;
@@ -1080,60 +1546,6 @@ return OTHER_CMD;
 
 
 
-/*************************************************
-*          Recheck synchronization               *
-*************************************************/
-
-/* Synchronization checks can never be perfect because a packet may be on its
-way but not arrived when the check is done. Such checks can in any case only be
-done when TLS is not in use. Normally, the checks happen when commands are
-read: Exim ensures that there is no more input in the input buffer. In normal
-cases, the response to the command will be fast, and there is no further check.
-
-However, for some commands an ACL is run, and that can include delays. In those
-cases, it is useful to do another check on the input just before sending the
-response. This also applies at the start of a connection. This function does
-that check by means of the select() function, as long as the facility is not
-disabled or inappropriate. A failure of select() is ignored.
-
-When there is unwanted input, we read it so that it appears in the log of the
-error.
-
-Arguments: none
-Returns:   TRUE if all is well; FALSE if there is input pending
-*/
-
-static BOOL
-check_sync(void)
-{
-int fd, rc;
-fd_set fds;
-struct timeval tzero;
-
-if (!smtp_enforce_sync || sender_host_address == NULL ||
-    sender_host_notsocket || tls_in.active >= 0)
-  return TRUE;
-
-fd = fileno(smtp_in);
-FD_ZERO(&fds);
-FD_SET(fd, &fds);
-tzero.tv_sec = 0;
-tzero.tv_usec = 0;
-rc = select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &tzero);
-
-if (rc <= 0) return TRUE;     /* Not ready to read */
-rc = smtp_getc();
-if (rc < 0) return TRUE;      /* End of file or error */
-
-smtp_ungetc(rc);
-rc = smtp_inend - smtp_inptr;
-if (rc > 150) rc = 150;
-smtp_inptr[rc] = 0;
-return FALSE;
-}
-
-
-
 /*************************************************
 *          Forced closedown of call              *
 *************************************************/
@@ -1158,26 +1570,23 @@ if (smtp_in == NULL || smtp_batched_input) return;
 receive_swallow_smtp();
 smtp_printf("421 %s\r\n", message);
 
-for (;;)
+for (;;) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
   {
-  switch(smtp_read_command(FALSE))
-    {
-    case EOF_CMD:
-    return;
+  case EOF_CMD:
+  return;
 
-    case QUIT_CMD:
-    smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
-    mac_smtp_fflush();
-    return;
+  case QUIT_CMD:
+  smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
+  mac_smtp_fflush();
+  return;
 
-    case RSET_CMD:
-    smtp_printf("250 Reset OK\r\n");
-    break;
+  case RSET_CMD:
+  smtp_printf("250 Reset OK\r\n");
+  break;
 
-    default:
-    smtp_printf("421 %s\r\n", message);
-    break;
-    }
+  default:
+  smtp_printf("421 %s\r\n", message);
+  break;
   }
 }
 
@@ -1200,8 +1609,8 @@ Returns:     a string describing the connection
 uschar *
 smtp_get_connection_info(void)
 {
-uschar *hostname = (sender_fullhost == NULL)?
-  sender_host_address : sender_fullhost;
+const uschar * hostname = sender_fullhost
+  ? sender_fullhost : sender_host_address;
 
 if (host_checking)
   return string_sprintf("SMTP connection from %s", hostname);
@@ -1212,8 +1621,7 @@ if (sender_host_unknown || sender_host_notsocket)
 if (is_inetd)
   return string_sprintf("SMTP connection from %s (via inetd)", hostname);
 
-if ((log_extra_selector & LX_incoming_interface) != 0 &&
-     interface_address != NULL)
+if (LOGGING(incoming_interface) && interface_address != NULL)
   return string_sprintf("SMTP connection from %s I=[%s]:%d", hostname,
     interface_address, interface_port);
 
@@ -1238,16 +1646,15 @@ 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)
+  if (LOGGING(tls_cipher) && 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)
+  if (LOGGING(tls_certificate_verified) && 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)
+  if (LOGGING(tls_peerdn) && 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)
+  if (LOGGING(tls_sni) && tls_in.sni != NULL)
     s = string_append(s, &size, &ptr, 3, US" SNI=\"",
       string_printing(tls_in.sni), US"\"");
 
@@ -1279,7 +1686,7 @@ smtp_log_no_mail(void)
 int size, ptr, i;
 uschar *s, *sep;
 
-if (smtp_mailcmd_count > 0 || (log_extra_selector & LX_smtp_no_mail) == 0)
+if (smtp_mailcmd_count > 0 || !LOGGING(smtp_no_mail))
   return;
 
 s = NULL;
@@ -1329,7 +1736,7 @@ log_write(0, LOG_MAIN, "no MAIL in SMTP connection from %s D=%s%s",
 
 /* Check the format of a HELO line. The data for HELO/EHLO is supposed to be
 the domain name of the sending host, or an ip literal in square brackets. The
-arrgument is placed in sender_helo_name, which is in malloc store, because it
+argument is placed in sender_helo_name, which is in malloc store, because it
 must persist over multiple incoming messages. If helo_accept_junk is set, this
 host is permitted to send any old junk (needed for some broken hosts).
 Otherwise, helo_allow_chars can be used for rogue characters in general
@@ -1430,7 +1837,13 @@ uschar *n;
 uschar *v = smtp_cmd_data + Ustrlen(smtp_cmd_data) - 1;
 while (isspace(*v)) v--;
 v[1] = 0;
-while (v > smtp_cmd_data && *v != '=' && !isspace(*v)) v--;
+while (v > smtp_cmd_data && *v != '=' && !isspace(*v))
+  {
+  /* Take care to not stop at a space embedded in a quoted local-part */
+
+  if (*v == '"') do v--; while (*v != '"' && v > smtp_cmd_data+1);
+  v--;
+  }
 
 n = v;
 if (*v == '=')
@@ -1469,7 +1882,6 @@ Returns:    nothing
 static void
 smtp_reset(void *reset_point)
 {
-store_reset(reset_point);
 recipients_list = NULL;
 rcpt_count = rcpt_defer_count = rcpt_fail_count =
   raw_recipients_count = recipients_count = recipients_list_max = 0;
@@ -1492,7 +1904,12 @@ submission_mode = FALSE;                             /* Can be set by ACL */
 suppress_local_fixups = suppress_local_fixups_default; /* Can be set by ACL */
 active_local_from_check = local_from_check;          /* Can be set by ACL */
 active_local_sender_retain = local_sender_retain;    /* Can be set by ACL */
-sender_address = NULL;
+sending_ip_address = NULL;
+return_path = sender_address = NULL;
+sender_data = NULL;                                 /* Can be set by ACL */
+deliver_localpart_orig = NULL;
+deliver_domain_orig = NULL;
+callout_address = NULL;
 submission_name = NULL;                              /* Can be set by ACL */
 raw_sender = NULL;                  /* After SMTP rewrite, before qualifying */
 sender_address_unrewritten = NULL;  /* Set only after verify rewrite */
@@ -1500,28 +1917,32 @@ 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;
 bmi_verdicts = NULL;
 #endif
+dnslist_domain = dnslist_matched = NULL;
 #ifndef DISABLE_DKIM
 dkim_signers = NULL;
 dkim_disable_verify = FALSE;
 dkim_collect_input = FALSE;
 #endif
+dsn_ret = 0;
+dsn_envid = NULL;
+deliver_host = deliver_host_address = NULL;    /* Can be set by ACL */
+#ifndef DISABLE_PRDR
+prdr_requested = FALSE;
+#endif
 #ifdef EXPERIMENTAL_SPF
 spf_header_comment = NULL;
 spf_received = NULL;
 spf_result = NULL;
 spf_smtp_comment = NULL;
 #endif
+#ifdef SUPPORT_I18N
+message_smtputf8 = FALSE;
+#endif
 body_linecount = body_zerocount = 0;
 
 sender_rate = sender_rate_limit = sender_rate_period = NULL;
@@ -1536,13 +1957,13 @@ acl_var_m = NULL;
 not the first message in an SMTP session and the previous message caused them
 to be referenced in an ACL. */
 
-if (message_body != NULL)
+if (message_body)
   {
   store_free(message_body);
   message_body = NULL;
   }
 
-if (message_body_end != NULL)
+if (message_body_end)
   {
   store_free(message_body_end);
   message_body_end = NULL;
@@ -1552,12 +1973,13 @@ if (message_body_end != NULL)
 repetition in the same message, but it seems right to repeat them for different
 messages. */
 
-while (acl_warn_logged != NULL)
+while (acl_warn_logged)
   {
   string_item *this = acl_warn_logged;
   acl_warn_logged = acl_warn_logged->next;
   store_free(this);
   }
+store_reset(reset_point);
 }
 
 
@@ -1605,7 +2027,7 @@ while (done <= 0)
   uschar *recipient = NULL;
   int start, end, sender_domain, recipient_domain;
 
-  switch(smtp_read_command(FALSE))
+  switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
     {
     /* The HELO/EHLO commands set sender_address_helo if they have
     valid data; otherwise they are ignored, except that they do
@@ -1630,6 +2052,7 @@ while (done <= 0)
     it is the canonical extracted address which is all that is kept. */
 
     case MAIL_CMD:
+    smtp_mailcmd_count++;              /* Count for no-mail log */
     if (sender_address != NULL)
       /* The function moan_smtp_batch() does not return. */
       moan_smtp_batch(smtp_cmd_buffer, "503 Sender already given");
@@ -1703,16 +2126,15 @@ while (done <= 0)
     /* Apply SMTP rewrite, then extract address. Don't allow "<>" as a
     recipient address */
 
-    recipient = ((rewrite_existflags & rewrite_smtp) != 0)?
-      rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
-        global_rewrite_rules) : smtp_cmd_data;
+    recipient = rewrite_existflags & rewrite_smtp
+      ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
+                   global_rewrite_rules)
+      : smtp_cmd_data;
 
-    /* rfc821_domains = TRUE; << no longer needed */
     recipient = parse_extract_address(recipient, &errmess, &start, &end,
       &recipient_domain, FALSE);
-    /* rfc821_domains = FALSE; << no longer needed */
 
-    if (recipient == NULL)
+    if (!recipient)
       /* The function moan_smtp_batch() does not return. */
       moan_smtp_batch(smtp_cmd_buffer, "501 %s", errmess);
 
@@ -1840,8 +2262,6 @@ pipelining_enable = TRUE;
 sync_cmd_limit = NON_SYNC_CMD_NON_PIPELINING;
 smtp_exit_function_called = FALSE;    /* For avoiding loop in not-quit exit */
 
-memset(sender_host_cache, 0, sizeof(sender_host_cache));
-
 /* If receiving by -bs from a trusted user, or testing with -bh, we allow
 authentication settings from -oMaa to remain in force. */
 
@@ -1855,8 +2275,9 @@ tls_in.sni = NULL;
 tls_in.ocsp = OCSP_NOT_REQ;
 tls_advertised = FALSE;
 #endif
-#ifdef EXPERIMENTAL_DSN
 dsn_advertised = FALSE;
+#ifdef SUPPORT_I18N
+smtputf8_advertised = FALSE;
 #endif
 
 /* Reset ACL connection variables */
@@ -1865,19 +2286,19 @@ acl_var_c = NULL;
 
 /* Allow for trailing 0 in the command and data buffers. */
 
-smtp_cmd_buffer = (uschar *)malloc(2*smtp_cmd_buffer_size + 2);
-if (smtp_cmd_buffer == NULL)
+if (!(smtp_cmd_buffer = US malloc(2*SMTP_CMD_BUFFER_SIZE + 2)))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE,
     "malloc() failed for SMTP command buffer");
+
 smtp_cmd_buffer[0] = 0;
-smtp_data_buffer = smtp_cmd_buffer + smtp_cmd_buffer_size + 1;
+smtp_data_buffer = smtp_cmd_buffer + SMTP_CMD_BUFFER_SIZE + 1;
 
 /* For batched input, the protocol setting can be overridden from the
 command line by a trusted caller. */
 
 if (smtp_batched_input)
   {
-  if (received_protocol == NULL) received_protocol = US"local-bsmtp";
+  if (!received_protocol) received_protocol = US"local-bsmtp";
   }
 
 /* For non-batched SMTP input, the protocol setting is forced here. It will be
@@ -1885,15 +2306,16 @@ reset later if any of EHLO/AUTH/STARTTLS are received. */
 
 else
   received_protocol =
-    protocols[pnormal] + ((sender_host_address != NULL)? pnlocal : 0);
+    (sender_host_address ? protocols : protocols_local) [pnormal];
 
 /* Set up the buffer for inputting using direct read() calls, and arrange to
 call the local functions instead of the standard C ones. */
 
-smtp_inbuffer = (uschar *)malloc(in_buffer_size);
-if (smtp_inbuffer == NULL)
+if (!(smtp_inbuffer = (uschar *)malloc(IN_BUFFER_SIZE)))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "malloc() failed for SMTP input buffer");
+
 receive_getc = smtp_getc;
+receive_get_cache = smtp_get_cache;
 receive_ungetc = smtp_ungetc;
 receive_feof = smtp_feof;
 receive_ferror = smtp_ferror;
@@ -2139,13 +2561,18 @@ if (!sender_host_unknown)
   set_process_info("handling incoming connection from %s",
     host_and_ident(FALSE));
 
-  /* Start up TLS if tls_on_connect is set. This is for supporting the legacy
-  smtps port for use with older style SSL MTAs. */
+  /* Expand smtp_receive_timeout, if needed */
 
-  #ifdef SUPPORT_TLS
-  if (tls_in.on_connect && tls_server_start(tls_require_ciphers) != OK)
-    return FALSE;
-  #endif
+  if (smtp_receive_timeout_s)
+    {
+    uschar * exp;
+    if (  !(exp = expand_string(smtp_receive_timeout_s))
+       || !(*exp)
+       || (smtp_receive_timeout = readconf_readtime(exp, 0, FALSE)) < 0
+       )
+      log_write(0, LOG_MAIN|LOG_PANIC,
+       "bad value for smtp_receive_timeout: '%s'", exp ? exp : US"");
+    }
 
   /* Test for explicit connection rejection */
 
@@ -2166,19 +2593,17 @@ if (!sender_host_unknown)
   value of errno is 0 or ENOENT (which happens if /etc/hosts.{allow,deny} does
   not exist). */
 
-  #ifdef USE_TCP_WRAPPERS
+#ifdef USE_TCP_WRAPPERS
   errno = 0;
-  tcp_wrappers_name = expand_string(tcp_wrappers_daemon_name);
-  if (tcp_wrappers_name == NULL)
-    {
+  if (!(tcp_wrappers_name = expand_string(tcp_wrappers_daemon_name)))
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Expansion of \"%s\" "
       "(tcp_wrappers_name) failed: %s", string_printing(tcp_wrappers_name),
         expand_string_message);
-    }
+
   if (!hosts_ctl(tcp_wrappers_name,
-         (sender_host_name == NULL)? STRING_UNKNOWN : CS sender_host_name,
-         (sender_host_address == NULL)? STRING_UNKNOWN : CS sender_host_address,
-         (sender_ident == NULL)? STRING_UNKNOWN : CS sender_ident))
+         sender_host_name ? CS sender_host_name : STRING_UNKNOWN,
+         sender_host_address ? CS sender_host_address : STRING_UNKNOWN,
+         sender_ident ? CS sender_ident : STRING_UNKNOWN))
     {
     if (errno == 0 || errno == ENOENT)
       {
@@ -2200,7 +2625,7 @@ if (!sender_host_unknown)
       }
     return FALSE;
     }
-  #endif
+#endif
 
   /* Check for reserved slots. The value of smtp_accept_count has already been
   incremented to include this process. */
@@ -2271,39 +2696,34 @@ if (!sender_host_unknown)
 
 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. */
+
+#ifdef SUPPORT_PROXY
 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();
-    }
-  }
+  setup_proxy_protocol_host();
 #endif
 
-/* Run the ACL if it exists */
+  /* Start up TLS if tls_on_connect is set. This is for supporting the legacy
+  smtps port for use with older style SSL MTAs. */
+
+#ifdef SUPPORT_TLS
+  if (tls_in.on_connect && tls_server_start(tls_require_ciphers) != OK)
+    return FALSE;
+#endif
+
+/* Run the connect ACL if it exists */
 
 user_msg = NULL;
-if (acl_smtp_connect != NULL)
+if (acl_smtp_connect)
   {
   int rc;
-  rc = acl_check(ACL_WHERE_CONNECT, NULL, acl_smtp_connect, &user_msg,
-    &log_msg);
-  if (rc != OK)
+  if ((rc = acl_check(ACL_WHERE_CONNECT, NULL, acl_smtp_connect, &user_msg,
+                     &log_msg)) != OK)
     {
-    (void)smtp_handle_acl_fail(ACL_WHERE_CONNECT, rc, user_msg, log_msg);
+    (void) smtp_handle_acl_fail(ACL_WHERE_CONNECT, rc, user_msg, log_msg);
     return FALSE;
     }
   }
@@ -2315,10 +2735,9 @@ code = US"220";   /* Default status code */
 esc = US"";       /* Default extended status code */
 esclen = 0;       /* Length of esc */
 
-if (user_msg == NULL)
+if (!user_msg)
   {
-  s = expand_string(smtp_banner);
-  if (s == NULL)
+  if (!(s = expand_string(smtp_banner)))
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Expansion of \"%s\" (smtp_banner) "
       "failed: %s", smtp_banner, expand_string_message);
   }
@@ -2326,7 +2745,7 @@ else
   {
   int codelen = 3;
   s = user_msg;
-  smtp_message_code(&code, &codelen, &s, NULL);
+  smtp_message_code(&code, &codelen, &s, NULL, TRUE);
   if (codelen > 4)
     {
     esc = code + 4;
@@ -2357,20 +2776,20 @@ do       /* At least once, in case we have an empty string */
   {
   int len;
   uschar *linebreak = Ustrchr(p, '\n');
-  ss = string_cat(ss, &size, &ptr, code, 3);
+  ss = string_catn(ss, &size, &ptr, code, 3);
   if (linebreak == NULL)
     {
     len = Ustrlen(p);
-    ss = string_cat(ss, &size, &ptr, US" ", 1);
+    ss = string_catn(ss, &size, &ptr, US" ", 1);
     }
   else
     {
     len = linebreak - p;
-    ss = string_cat(ss, &size, &ptr, US"-", 1);
+    ss = string_catn(ss, &size, &ptr, US"-", 1);
     }
-  ss = string_cat(ss, &size, &ptr, esc, esclen);
-  ss = string_cat(ss, &size, &ptr, p, len);
-  ss = string_cat(ss, &size, &ptr, US"\r\n", 2);
+  ss = string_catn(ss, &size, &ptr, esc, esclen);
+  ss = string_catn(ss, &size, &ptr, p, len);
+  ss = string_catn(ss, &size, &ptr, US"\r\n", 2);
   p += len;
   if (linebreak != NULL) p++;
   }
@@ -2435,7 +2854,7 @@ if (++synprot_error_count > smtp_max_synprot_errors)
   yield = 1;
   log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
     "syntax or protocol errors (last command was \"%s\")",
-    host_and_ident(FALSE), smtp_cmd_buffer);
+    host_and_ident(FALSE), string_printing(smtp_cmd_buffer));
   }
 
 if (code > 0)
@@ -2452,43 +2871,6 @@ return yield;
 
 
 
-/*************************************************
-*          Log incomplete transactions           *
-*************************************************/
-
-/* This function is called after a transaction has been aborted by RSET, QUIT,
-connection drops or other errors. It logs the envelope information received
-so far in order to preserve address verification attempts.
-
-Argument:   string to indicate what aborted the transaction
-Returns:    nothing
-*/
-
-static void
-incomplete_transaction_log(uschar *what)
-{
-if (sender_address == NULL ||                 /* No transaction in progress */
-    (log_write_selector & L_smtp_incomplete_transaction) == 0  /* Not logging */
-  ) return;
-
-/* Build list of recipients for logging */
-
-if (recipients_count > 0)
-  {
-  int i;
-  raw_recipients = store_get(recipients_count * sizeof(uschar *));
-  for (i = 0; i < recipients_count; i++)
-    raw_recipients[i] = recipients_list[i].address;
-  raw_recipients_count = recipients_count;
-  }
-
-log_write(L_smtp_incomplete_transaction, LOG_MAIN|LOG_SENDER|LOG_RECIPIENTS,
-  "%s incomplete transaction (%s)", host_and_ident(TRUE), what);
-}
-
-
-
-
 /*************************************************
 *    Send SMTP response, possibly multiline      *
 *************************************************/
@@ -2588,23 +2970,24 @@ Arguments:
   codelen       length of smtp code; if > 4 there's an ESC
   msg           message text
   log_msg       optional log message, to be adjusted with the new SMTP code
+  check_valid   if true, verify the response code
 
 Returns:        nothing
 */
 
 void
-smtp_message_code(uschar **code, int *codelen, uschar **msg, uschar **log_msg)
+smtp_message_code(uschar **code, int *codelen, uschar **msg, uschar **log_msg,
+  BOOL check_valid)
 {
 int n;
 int ovector[3];
 
-if (msg == NULL || *msg == NULL) return;
+if (!msg || !*msg) return;
 
-n = pcre_exec(regex_smtp_code, NULL, CS *msg, Ustrlen(*msg), 0,
-  PCRE_EOPT, ovector, sizeof(ovector)/sizeof(int));
-if (n < 0) return;
+if ((n = pcre_exec(regex_smtp_code, NULL, CS *msg, Ustrlen(*msg), 0,
+  PCRE_EOPT, ovector, sizeof(ovector)/sizeof(int))) < 0) return;
 
-if ((*msg)[0] != (*code)[0])
+if (check_valid && (*msg)[0] != (*code)[0])
   {
   log_write(0, LOG_MAIN|LOG_PANIC, "configured error code starts with "
     "incorrect digit (expected %c) in \"%s\"", (*code)[0], *msg);
@@ -2639,18 +3022,19 @@ defaults disabled in Exim. However, discussion in connection with RFC 821bis
 (aka RFC 2821) has concluded that the response should be 252 in the disabled
 state, because there are broken clients that try VRFY before RCPT. A 5xx
 response should be given only when the address is positively known to be
-undeliverable. Sigh. Also, for ETRN, 458 is given on refusal, and for AUTH,
-503.
+undeliverable. Sigh. We return 252 if there is no VRFY ACL or it provides
+no explicit code, but if there is one we let it know best.
+Also, for ETRN, 458 is given on refusal, and for AUTH, 503.
 
 From Exim 4.63, it is possible to override the response code details by
 providing a suitable response code string at the start of the message provided
 in user_msg. The code's first digit is checked for validity.
 
 Arguments:
-  where      where the ACL was called from
-  rc         the failure code
-  user_msg   a message that can be included in an SMTP response
-  log_msg    a message for logging
+  where        where the ACL was called from
+  rc           the failure code
+  user_msg     a message that can be included in an SMTP response
+  log_msg      a message for logging
 
 Returns:     0 in most cases
              2 if the failure code was FAIL_DROP, in which case the
@@ -2668,23 +3052,24 @@ uschar *lognl;
 uschar *sender_info = US"";
 uschar *what =
 #ifdef WITH_CONTENT_SCAN
-  (where == ACL_WHERE_MIME)? US"during MIME ACL checks" :
+  where == ACL_WHERE_MIME ? US"during MIME ACL checks" :
 #endif
-  (where == ACL_WHERE_PREDATA)? US"DATA" :
-  (where == ACL_WHERE_DATA)? US"after DATA" :
+  where == ACL_WHERE_PREDATA ? US"DATA" :
+  where == ACL_WHERE_DATA ? US"after DATA" :
 #ifndef DISABLE_PRDR
-  (where == ACL_WHERE_PRDR)? US"after DATA PRDR" :
+  where == ACL_WHERE_PRDR ? US"after DATA PRDR" :
 #endif
-  (smtp_cmd_data == NULL)?
-    string_sprintf("%s in \"connect\" ACL", acl_wherenames[where]) :
-    string_sprintf("%s %s", acl_wherenames[where], smtp_cmd_data);
+  smtp_cmd_data ?
+    string_sprintf("%s %s", acl_wherenames[where], smtp_cmd_data) :
+    string_sprintf("%s in \"connect\" ACL", acl_wherenames[where]);
 
 if (drop) rc = FAIL;
 
 /* Set the default SMTP code, and allow a user message to change it. */
 
-smtp_code = (rc != FAIL)? US"451" : acl_wherecodes[where];
-smtp_message_code(&smtp_code, &codelen, &user_msg, &log_msg);
+smtp_code = rc == FAIL ? acl_wherecodes[where] : US"451";
+smtp_message_code(&smtp_code, &codelen, &user_msg, &log_msg,
+  where != ACL_WHERE_VRFY);
 
 /* We used to have sender_address here; however, there was a bug that was not
 updating sender_address after a rewrite during a verify. When this bug was
@@ -2712,7 +3097,7 @@ we have not sent a response about it yet, do so now, as a preliminary line for
 failures, but not defers. However, always log it for defer, and log it for fail
 unless the sender_verify_fail log selector has been turned off. */
 
-if (sender_verified_failed != NULL &&
+if (sender_verified_failed &&
     !testflag(sender_verified_failed, af_sverify_told))
   {
   BOOL save_rcpt_in_progress = rcpt_in_progress;
@@ -2720,7 +3105,7 @@ if (sender_verified_failed != NULL &&
 
   setflag(sender_verified_failed, af_sverify_told);
 
-  if (rc != FAIL || (log_extra_selector & LX_sender_verify_fail) != 0)
+  if (rc != FAIL || LOGGING(sender_verify_fail))
     log_write(0, LOG_MAIN|LOG_REJECT, "%s sender verify %s for <%s>%s",
       host_and_ident(TRUE),
       ((sender_verified_failed->special_action & 255) == DEFER)? "defer":"fail",
@@ -2728,7 +3113,7 @@ if (sender_verified_failed != NULL &&
       (sender_verified_failed->message == NULL)? US"" :
       string_sprintf(": %s", sender_verified_failed->message));
 
-  if (rc == FAIL && sender_verified_failed->user_message != NULL)
+  if (rc == FAIL && sender_verified_failed->user_message)
     smtp_respond(smtp_code, codelen, FALSE, string_sprintf(
         testflag(sender_verified_failed, af_verify_pmfail)?
           "Postmaster verification failed while checking <%s>\n%s\n"
@@ -2753,16 +3138,16 @@ if (sender_verified_failed != NULL &&
 
 /* Sort out text for logging */
 
-log_msg = (log_msg == NULL)? US"" : string_sprintf(": %s", log_msg);
-lognl = Ustrchr(log_msg, '\n');
-if (lognl != NULL) *lognl = 0;
+log_msg = log_msg ? string_sprintf(": %s", log_msg) : US"";
+if ((lognl = Ustrchr(log_msg, '\n'))) *lognl = 0;
 
 /* Send permanent failure response to the command, but the code used isn't
 always a 5xx one - see comments at the start of this function. If the original
 rc was FAIL_DROP we drop the connection and yield 2. */
 
-if (rc == FAIL) smtp_respond(smtp_code, codelen, TRUE, (user_msg == NULL)?
-  US"Administrative prohibition" : user_msg);
+if (rc == FAIL)
+  smtp_respond(smtp_code, codelen, TRUE,
+    user_msg ? user_msg : US"Administrative prohibition");
 
 /* Send temporary failure response to the command. Don't give any details,
 unless acl_temp_details is set. This is TRUE for a callout defer, a "defer"
@@ -2773,21 +3158,19 @@ interactions between temp_details and return_error_details. One day it should
 be re-implemented in a tidier fashion. */
 
 else
-  {
-  if (acl_temp_details && user_msg != NULL)
+  if (acl_temp_details && user_msg)
     {
-    if (smtp_return_error_details &&
-        sender_verified_failed != NULL &&
-        sender_verified_failed->message != NULL)
-      {
+    if (  smtp_return_error_details
+       && sender_verified_failed
+       && sender_verified_failed->message
+       )
       smtp_respond(smtp_code, codelen, FALSE, sender_verified_failed->message);
-      }
+
     smtp_respond(smtp_code, codelen, TRUE, user_msg);
     }
   else
     smtp_respond(smtp_code, codelen, TRUE,
       US"Temporary local problem - please try later");
-  }
 
 /* Log the incident to the logs that are specified by log_reject_target
 (default main, reject). This can be empty to suppress logging of rejections. If
@@ -2797,14 +3180,19 @@ is closing if required and return 2.  */
 if (log_reject_target != 0)
   {
 #ifdef SUPPORT_TLS
-  uschar * s = s_tlslog(NULL, NULL, NULL);
-  if (!s) s = US"";
+  uschar * tls = s_tlslog(NULL, NULL, NULL);
+  if (!tls) tls = US"";
 #else
-  uschar * s = US"";
+  uschar * tls = 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);
+  log_write(where == ACL_WHERE_CONNECT ? L_connection_reject : 0,
+    log_reject_target, "%s%s%s %s%srejected %s%s",
+    LOGGING(dnssec) && sender_host_dnssec ? US" DS" : US"",
+    host_and_ident(TRUE),
+    tls,
+    sender_info,
+    rc == FAIL ? US"" : US"temporarily ",
+    what, log_msg);
   }
 
 if (!drop) return 0;
@@ -2837,7 +3225,7 @@ the ACL that obeyed "drop" has already supplied the custom message, and NULL is
 passed to this function.
 
 In case things go wrong while processing this function, causing an error that
-may re-enter this funtion, there is a recursion check.
+may re-enter this function, there is a recursion check.
 
 Arguments:
   reason          What $smtp_notquit_reason will be set to in the ACL;
@@ -2867,12 +3255,11 @@ smtp_exit_function_called = TRUE;
 
 /* Call the not-QUIT ACL, if there is one, unless no reason is given. */
 
-if (acl_smtp_notquit != NULL && reason != NULL)
+if (acl_smtp_notquit && reason)
   {
   smtp_notquit_reason = reason;
-  rc = acl_check(ACL_WHERE_NOTQUIT, NULL, acl_smtp_notquit, &user_msg,
-    &log_msg);
-  if (rc == ERROR)
+  if ((rc = acl_check(ACL_WHERE_NOTQUIT, NULL, acl_smtp_notquit, &user_msg,
+                     &log_msg)) == ERROR)
     log_write(0, LOG_MAIN|LOG_PANIC, "ACL for not-QUIT returned ERROR: %s",
       log_msg);
   }
@@ -2882,9 +3269,11 @@ responses are all internal, they should always fit in the buffer, but code a
 warning, just in case. Note that string_vformat() still leaves a complete
 string, even if it is incomplete. */
 
-if (code != NULL && defaultrespond != NULL)
+if (code && defaultrespond)
   {
-  if (user_msg == NULL)
+  if (user_msg)
+    smtp_respond(code, 3, TRUE, user_msg);
+  else
     {
     uschar buffer[128];
     va_list ap;
@@ -2894,8 +3283,6 @@ if (code != NULL && defaultrespond != NULL)
     smtp_printf("%s %s\r\n", code, buffer);
     va_end(ap);
     }
-  else
-    smtp_respond(code, 3, TRUE, user_msg);
   mac_smtp_fflush();
   }
 }
@@ -2974,29 +3361,25 @@ else
 
   /* If a host name is known, check it and all its aliases. */
 
-  if (sender_host_name != NULL)
-    {
-    helo_verified = strcmpic(sender_host_name, sender_helo_name) == 0;
-
-    if (helo_verified)
+  if (sender_host_name)
+    if ((helo_verified = strcmpic(sender_host_name, sender_helo_name) == 0))
       {
+      sender_helo_dnssec = sender_host_dnssec;
       HDEBUG(D_receive) debug_printf("matched host name\n");
       }
     else
       {
       uschar **aliases = sender_host_aliases;
-      while (*aliases != NULL)
-        {
-        helo_verified = strcmpic(*aliases++, sender_helo_name) == 0;
-        if (helo_verified) break;
-        }
-      HDEBUG(D_receive)
-        {
-        if (helo_verified)
+      while (*aliases)
+        if ((helo_verified = strcmpic(*aliases++, sender_helo_name) == 0))
+         {
+         sender_helo_dnssec = sender_host_dnssec;
+         break;
+         }
+
+      HDEBUG(D_receive) if (helo_verified)
           debug_printf("matched alias %s\n", *(--aliases));
-        }
       }
-    }
 
   /* Final attempt: try a forward lookup of the helo name */
 
@@ -3004,61 +3387,235 @@ else
     {
     int rc;
     host_item h;
+    dnssec_domains d;
+    host_item *hh;
+
     h.name = sender_helo_name;
     h.address = NULL;
     h.mx = MX_NONE;
     h.next = NULL;
+    d.request = US"*";
+    d.require = US"";
+
     HDEBUG(D_receive) debug_printf("getting IP address for %s\n",
       sender_helo_name);
-    rc = host_find_byname(&h, NULL, 0, NULL, TRUE);
+    rc = host_find_bydns(&h, NULL, HOST_FIND_BY_A,
+                         NULL, NULL, NULL, &d, NULL, NULL);
     if (rc == HOST_FOUND || rc == HOST_FOUND_LOCAL)
-      {
-      host_item *hh = &h;
-      while (hh != NULL)
-        {
+      for (hh = &h; hh; hh = hh->next)
         if (Ustrcmp(hh->address, sender_host_address) == 0)
           {
           helo_verified = TRUE;
+         if (h.dnssec == DS_YES) sender_helo_dnssec = TRUE;
           HDEBUG(D_receive)
-            debug_printf("IP address for %s matches calling address\n",
-              sender_helo_name);
+           {
+            debug_printf("IP address for %s matches calling address\n"
+             "Forward DNS security status: %sverified\n",
+              sender_helo_name, sender_helo_dnssec ? "" : "un");
+           }
           break;
           }
-        hh = hh->next;
-        }
-      }
     }
   }
-
-if (!helo_verified) helo_verify_failed = TRUE;  /* We've tried ... */
-return yield;
+
+if (!helo_verified) helo_verify_failed = TRUE;  /* We've tried ... */
+return yield;
+}
+
+
+
+
+/*************************************************
+*        Send user response message              *
+*************************************************/
+
+/* This function is passed a default response code and a user message. It calls
+smtp_message_code() to check and possibly modify the response code, and then
+calls smtp_respond() to transmit the response. I put this into a function
+just to avoid a lot of repetition.
+
+Arguments:
+  code         the response code
+  user_msg     the user message
+
+Returns:       nothing
+*/
+
+static void
+smtp_user_msg(uschar *code, uschar *user_msg)
+{
+int len = 3;
+smtp_message_code(&code, &len, &user_msg, NULL, TRUE);
+smtp_respond(code, len, TRUE, user_msg);
+}
+
+
+
+static int
+smtp_in_auth(auth_instance *au, uschar ** s, uschar ** ss)
+{
+const uschar *set_id = NULL;
+int rc, i;
+
+/* Run the checking code, passing the remainder of the command line as
+data. Initials the $auth<n> variables as empty. Initialize $0 empty and set
+it as the only set numerical variable. The authenticator may set $auth<n>
+and also set other numeric variables. The $auth<n> variables are preferred
+nowadays; the numerical variables remain for backwards compatibility.
+
+Afterwards, have a go at expanding the set_id string, even if
+authentication failed - for bad passwords it can be useful to log the
+userid. On success, require set_id to expand and exist, and put it in
+authenticated_id. Save this in permanent store, as the working store gets
+reset at HELO, RSET, etc. */
+
+for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;
+expand_nmax = 0;
+expand_nlength[0] = 0;   /* $0 contains nothing */
+
+rc = (au->info->servercode)(au, smtp_cmd_data);
+if (au->set_id) set_id = expand_string(au->set_id);
+expand_nmax = -1;        /* Reset numeric variables */
+for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;   /* Reset $auth<n> */
+
+/* The value of authenticated_id is stored in the spool file and printed in
+log lines. It must not contain binary zeros or newline characters. In
+normal use, it never will, but when playing around or testing, this error
+can (did) happen. To guard against this, ensure that the id contains only
+printing characters. */
+
+if (set_id) set_id = string_printing(set_id);
+
+/* For the non-OK cases, set up additional logging data if set_id
+is not empty. */
+
+if (rc != OK)
+  set_id = set_id && *set_id
+    ? string_sprintf(" (set_id=%s)", set_id) : US"";
+
+/* Switch on the result */
+
+switch(rc)
+  {
+  case OK:
+  if (!au->set_id || set_id)    /* Complete success */
+    {
+    if (set_id) authenticated_id = string_copy_malloc(set_id);
+    sender_host_authenticated = au->name;
+    authentication_failed = FALSE;
+    authenticated_fail_id = NULL;   /* Impossible to already be set? */
+
+    received_protocol =
+      (sender_host_address ? protocols : protocols_local)
+       [pextend + pauthed + (tls_in.active >= 0 ? pcrpted:0)];
+    *s = *ss = US"235 Authentication succeeded";
+    authenticated_by = au;
+    break;
+    }
+
+  /* Authentication succeeded, but we failed to expand the set_id string.
+  Treat this as a temporary error. */
+
+  auth_defer_msg = expand_string_message;
+  /* Fall through */
+
+  case DEFER:
+  if (set_id) authenticated_fail_id = string_copy_malloc(set_id);
+  *s = string_sprintf("435 Unable to authenticate at present%s",
+    auth_defer_user_msg);
+  *ss = string_sprintf("435 Unable to authenticate at present%s: %s",
+    set_id, auth_defer_msg);
+  break;
+
+  case BAD64:
+  *s = *ss = US"501 Invalid base64 data";
+  break;
+
+  case CANCELLED:
+  *s = *ss = US"501 Authentication cancelled";
+  break;
+
+  case UNEXPECTED:
+  *s = *ss = US"553 Initial data not expected";
+  break;
+
+  case FAIL:
+  if (set_id) authenticated_fail_id = string_copy_malloc(set_id);
+  *s = US"535 Incorrect authentication data";
+  *ss = string_sprintf("535 Incorrect authentication data%s", set_id);
+  break;
+
+  default:
+  if (set_id) authenticated_fail_id = string_copy_malloc(set_id);
+  *s = US"435 Internal error";
+  *ss = string_sprintf("435 Internal error%s: return %d from authentication "
+    "check", set_id, rc);
+  break;
+  }
+
+return rc;
+}
+
+
+
+
+
+static int
+qualify_recipient(uschar ** recipient, uschar * smtp_cmd_data, uschar * tag)
+{
+int rd;
+if (allow_unqualified_recipient || strcmpic(*recipient, US"postmaster") == 0)
+  {
+  DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
+    *recipient);
+  rd = Ustrlen(recipient) + 1;
+  *recipient = rewrite_address_qualify(*recipient, TRUE);
+  return rd;
+  }
+smtp_printf("501 %s: recipient address must contain a domain\r\n",
+  smtp_cmd_data);
+log_write(L_smtp_syntax_error,
+  LOG_MAIN|LOG_REJECT, "unqualified %s rejected: <%s> %s%s",
+  tag, *recipient, host_and_ident(TRUE), host_lookup_msg);
+return 0;
 }
 
 
 
 
-/*************************************************
-*        Send user response message              *
-*************************************************/
+static void
+smtp_quit_handler(uschar ** user_msgp, uschar ** log_msgp)
+{
+HAD(SCH_QUIT);
+incomplete_transaction_log(US"QUIT");
+if (acl_smtp_quit)
+  {
+  int rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, user_msgp, log_msgp);
+  if (rc == ERROR)
+    log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s",
+      *log_msgp);
+  }
+if (*user_msgp)
+  smtp_respond(US"221", 3, TRUE, *user_msgp);
+else
+  smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
 
-/* This function is passed a default response code and a user message. It calls
-smtp_message_code() to check and possibly modify the response code, and then
-calls smtp_respond() to transmit the response. I put this into a function
-just to avoid a lot of repetition.
+#ifdef SUPPORT_TLS
+tls_close(TRUE, TRUE);
+#endif
 
-Arguments:
-  code         the response code
-  user_msg     the user message
+log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
+  smtp_get_connection_info());
+}
 
-Returns:       nothing
-*/
 
 static void
-smtp_user_msg(uschar *code, uschar *user_msg)
+smtp_rset_handler(void)
 {
-int len = 3;
-smtp_message_code(&code, &len, &user_msg, NULL);
-smtp_respond(code, len, TRUE, user_msg);
+HAD(SCH_RSET);
+incomplete_transaction_log(US"RSET");
+smtp_printf("250 Reset OK\r\n");
+cmd_list[CMD_LIST_RSET].is_mail_cmd = FALSE;
 }
 
 
@@ -3109,11 +3666,14 @@ for the host). Note: we do NOT reset AUTH at this point. */
 smtp_reset(reset_point);
 message_ended = END_NOTSTARTED;
 
+chunking_state = chunking_offered ? CHUNKING_OFFERED : CHUNKING_NOT_OFFERED;
+
 cmd_list[CMD_LIST_RSET].is_mail_cmd = TRUE;
 cmd_list[CMD_LIST_HELO].is_mail_cmd = TRUE;
 cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE;
 #ifdef SUPPORT_TLS
 cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = TRUE;
+cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
 #endif
 
 /* Set the local signal handler for SIGTERM - it tries to end off tidily */
@@ -3129,7 +3689,7 @@ value. The values are 2 larger than the required yield of the function. */
 
 while (done <= 0)
   {
-  uschar **argv;
+  const uschar **argv;
   uschar *etrn_command;
   uschar *etrn_serialize_key;
   uschar *errmess;
@@ -3137,7 +3697,6 @@ while (done <= 0)
   uschar *user_msg = NULL;
   uschar *recipient = NULL;
   uschar *hello = NULL;
-  uschar *set_id = NULL;
   uschar *s, *ss;
   BOOL was_rej_mail = FALSE;
   BOOL was_rcpt = FALSE;
@@ -3145,14 +3704,50 @@ while (done <= 0)
   pid_t pid;
   int start, end, sender_domain, recipient_domain;
   int ptr, size, rc;
-  int c, i;
+  int c;
   auth_instance *au;
-#ifdef EXPERIMENTAL_DSN
   uschar *orcpt = NULL;
   int flags;
+
+#ifdef AUTH_TLS
+  /* Check once per STARTTLS or SSL-on-connect for a TLS AUTH */
+  if (  tls_in.active >= 0
+     && tls_in.peercert
+     && tls_in.certificate_verified
+     && cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd
+     )
+    {
+    cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = FALSE;
+    if (  acl_smtp_auth
+       && (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth,
+                 &user_msg, &log_msg)) != OK
+       )
+      {
+      done = smtp_handle_acl_fail(ACL_WHERE_AUTH, rc, user_msg, log_msg);
+      continue;
+      }
+
+    for (au = auths; au; au = au->next)
+      if (strcmpic(US"tls", au->driver_name) == 0)
+       {
+       smtp_cmd_data = NULL;
+
+       if (smtp_in_auth(au, &s, &ss) == OK)
+         { DEBUG(D_auth) debug_printf("tls auth succeeded\n"); }
+       else
+         { DEBUG(D_auth) debug_printf("tls auth not succeeded\n"); }
+       break;
+       }
+    }
+#endif
+
+#ifdef TCP_QUICKACK
+  if (smtp_in)         /* Avoid pure-ACKs while in cmd pingpong phase */
+    (void) setsockopt(fileno(smtp_in), IPPROTO_TCP, TCP_QUICKACK,
+           US &off, sizeof(off));
 #endif
 
-  switch(smtp_read_command(TRUE))
+  switch(smtp_read_command(TRUE, GETC_BUFFER_UNLIMITED))
     {
     /* The AUTH command is not permitted to occur inside a transaction, and may
     occur successfully only once per connection. Actually, that isn't quite
@@ -3179,13 +3774,13 @@ while (done <= 0)
         US"AUTH command used when not advertised");
       break;
       }
-    if (sender_host_authenticated != NULL)
+    if (sender_host_authenticated)
       {
       done = synprot_error(L_smtp_protocol_error, 503, NULL,
         US"already authenticated");
       break;
       }
-    if (sender_address != NULL)
+    if (sender_address)
       {
       done = synprot_error(L_smtp_protocol_error, 503, NULL,
         US"not permitted in mail transaction");
@@ -3194,14 +3789,13 @@ while (done <= 0)
 
     /* Check the ACL */
 
-    if (acl_smtp_auth != NULL)
+    if (  acl_smtp_auth
+       && (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth,
+                 &user_msg, &log_msg)) != OK
+       )
       {
-      rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth, &user_msg, &log_msg);
-      if (rc != OK)
-        {
-        done = smtp_handle_acl_fail(ACL_WHERE_AUTH, rc, user_msg, log_msg);
-        break;
-        }
+      done = smtp_handle_acl_fail(ACL_WHERE_AUTH, rc, user_msg, log_msg);
+      break;
       }
 
     /* Find the name of the requested authentication mechanism. */
@@ -3231,121 +3825,23 @@ while (done <= 0)
     as a server and which has been advertised (unless, sigh, allow_auth_
     unadvertised is set). */
 
-    for (au = auths; au != NULL; au = au->next)
-      {
+    for (au = auths; au; au = au->next)
       if (strcmpic(s, au->public_name) == 0 && au->server &&
-          (au->advertised || allow_auth_unadvertised)) break;
-      }
-
-    if (au == NULL)
-      {
-      done = synprot_error(L_smtp_protocol_error, 504, NULL,
-        string_sprintf("%s authentication mechanism not supported", s));
-      break;
-      }
-
-    /* Run the checking code, passing the remainder of the command line as
-    data. Initials the $auth<n> variables as empty. Initialize $0 empty and set
-    it as the only set numerical variable. The authenticator may set $auth<n>
-    and also set other numeric variables. The $auth<n> variables are preferred
-    nowadays; the numerical variables remain for backwards compatibility.
-
-    Afterwards, have a go at expanding the set_id string, even if
-    authentication failed - for bad passwords it can be useful to log the
-    userid. On success, require set_id to expand and exist, and put it in
-    authenticated_id. Save this in permanent store, as the working store gets
-    reset at HELO, RSET, etc. */
-
-    for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;
-    expand_nmax = 0;
-    expand_nlength[0] = 0;   /* $0 contains nothing */
-
-    c = (au->info->servercode)(au, smtp_cmd_data);
-    if (au->set_id != NULL) set_id = expand_string(au->set_id);
-    expand_nmax = -1;        /* Reset numeric variables */
-    for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;   /* Reset $auth<n> */
-
-    /* The value of authenticated_id is stored in the spool file and printed in
-    log lines. It must not contain binary zeros or newline characters. In
-    normal use, it never will, but when playing around or testing, this error
-    can (did) happen. To guard against this, ensure that the id contains only
-    printing characters. */
-
-    if (set_id != NULL) set_id = string_printing(set_id);
-
-    /* For the non-OK cases, set up additional logging data if set_id
-    is not empty. */
-
-    if (c != OK)
-      {
-      if (set_id != NULL && *set_id != 0)
-        set_id = string_sprintf(" (set_id=%s)", set_id);
-      else set_id = US"";
-      }
+          (au->advertised || allow_auth_unadvertised))
+       break;
 
-    /* Switch on the result */
-
-    switch(c)
+    if (au)
       {
-      case OK:
-      if (au->set_id == NULL || set_id != NULL)    /* Complete success */
-        {
-        if (set_id != NULL) authenticated_id = string_copy_malloc(set_id);
-        sender_host_authenticated = au->name;
-        authentication_failed = FALSE;
-        authenticated_fail_id = NULL;   /* Impossible to already be set? */
-        received_protocol =
-          protocols[pextend + pauthed + ((tls_in.active >= 0)? pcrpted:0)] +
-            ((sender_host_address != NULL)? pnlocal : 0);
-        s = ss = US"235 Authentication succeeded";
-        authenticated_by = au;
-        break;
-        }
-
-      /* Authentication succeeded, but we failed to expand the set_id string.
-      Treat this as a temporary error. */
-
-      auth_defer_msg = expand_string_message;
-      /* Fall through */
-
-      case DEFER:
-      if (set_id != NULL) authenticated_fail_id = string_copy_malloc(set_id);
-      s = string_sprintf("435 Unable to authenticate at present%s",
-        auth_defer_user_msg);
-      ss = string_sprintf("435 Unable to authenticate at present%s: %s",
-        set_id, auth_defer_msg);
-      break;
-
-      case BAD64:
-      s = ss = US"501 Invalid base64 data";
-      break;
-
-      case CANCELLED:
-      s = ss = US"501 Authentication cancelled";
-      break;
-
-      case UNEXPECTED:
-      s = ss = US"553 Initial data not expected";
-      break;
-
-      case FAIL:
-      if (set_id != NULL) authenticated_fail_id = string_copy_malloc(set_id);
-      s = US"535 Incorrect authentication data";
-      ss = string_sprintf("535 Incorrect authentication data%s", set_id);
-      break;
+      c = smtp_in_auth(au, &s, &ss);
 
-      default:
-      if (set_id != NULL) authenticated_fail_id = string_copy_malloc(set_id);
-      s = US"435 Internal error";
-      ss = string_sprintf("435 Internal error%s: return %d from authentication "
-        "check", set_id, c);
-      break;
+      smtp_printf("%s\r\n", s);
+      if (c != OK)
+       log_write(0, LOG_MAIN|LOG_REJECT, "%s authenticator failed for %s: %s",
+         au->name, host_and_ident(FALSE), ss);
       }
-
-    smtp_printf("%s\r\n", s);
-    if (c != OK)
-      log_write(0, LOG_MAIN|LOG_REJECT, "%s authenticator failed for %s: %s",
-        au->name, host_and_ident(FALSE), ss);
+    else
+      done = synprot_error(L_smtp_protocol_error, 504, NULL,
+        string_sprintf("%s authentication mechanism not supported", s));
 
     break;  /* AUTH_CMD */
 
@@ -3396,7 +3892,7 @@ while (done <= 0)
         {
         log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
           "syntax or protocol errors (last command was \"%s\")",
-          host_and_ident(FALSE), smtp_cmd_buffer);
+          host_and_ident(FALSE), string_printing(smtp_cmd_buffer));
         done = 1;
         }
 
@@ -3423,7 +3919,7 @@ while (done <= 0)
 
       if (sender_host_name == NULL &&
            (deliver_domain = sender_helo_name,  /* set $domain */
-            match_isinlist(sender_helo_name, &helo_lookup_domains, 0,
+            match_isinlist(sender_helo_name, CUSS &helo_lookup_domains, 0,
               &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL)) == OK)
         (void)host_name_lookup();
 
@@ -3441,7 +3937,7 @@ while (done <= 0)
       now obsolescent, since the verification can now be requested selectively
       at ACL time. */
 
-      helo_verified = helo_verify_failed = FALSE;
+      helo_verified = helo_verify_failed = sender_helo_dnssec = FALSE;
       if (helo_required || helo_verify)
         {
         BOOL tempfail = !smtp_verify_helo();
@@ -3471,10 +3967,9 @@ while (done <= 0)
     /* Apply an ACL check if one is defined; afterwards, recheck
     synchronization in case the client started sending in a delay. */
 
-    if (acl_smtp_helo != NULL)
-      {
-      rc = acl_check(ACL_WHERE_HELO, NULL, acl_smtp_helo, &user_msg, &log_msg);
-      if (rc != OK)
+    if (acl_smtp_helo)
+      if ((rc = acl_check(ACL_WHERE_HELO, NULL, acl_smtp_helo,
+               &user_msg, &log_msg)) != OK)
         {
         done = smtp_handle_acl_fail(ACL_WHERE_HELO, rc, user_msg, log_msg);
         sender_helo_name = NULL;
@@ -3482,7 +3977,6 @@ while (done <= 0)
         break;
         }
       else if (!check_sync()) goto SYNC_FAILURE;
-      }
 
     /* Generate an OK reply. The default string includes the ident if present,
     and also the IP address if present. Reflecting back the ident is intended
@@ -3492,12 +3986,13 @@ while (done <= 0)
 
     auth_advertised = FALSE;
     pipelining_advertised = FALSE;
-    #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
     tls_advertised = FALSE;
-    #endif
-    #ifdef EXPERIMENTAL_DSN
+#endif
     dsn_advertised = FALSE;
-    #endif
+#ifdef SUPPORT_I18N
+    smtputf8_advertised = FALSE;
+#endif
 
     smtp_code = US"250 ";        /* Default response code plus space*/
     if (user_msg == NULL)
@@ -3514,10 +4009,9 @@ while (done <= 0)
 
       if (sender_host_address != NULL)
         {
-        s = string_cat(s, &size, &ptr, US" [", 2);
-        s = string_cat(s, &size, &ptr, sender_host_address,
-          Ustrlen(sender_host_address));
-        s = string_cat(s, &size, &ptr, US"]", 1);
+        s = string_catn(s, &size, &ptr, US" [", 2);
+        s = string_cat (s, &size, &ptr, sender_host_address);
+        s = string_catn(s, &size, &ptr, US"]", 1);
         }
       }
 
@@ -3529,7 +4023,7 @@ while (done <= 0)
       {
       char *ss;
       int codelen = 4;
-      smtp_message_code(&smtp_code, &codelen, &user_msg, NULL);
+      smtp_message_code(&smtp_code, &codelen, &user_msg, NULL, TRUE);
       s = string_sprintf("%.*s%s", codelen, smtp_code, user_msg);
       if ((ss = strpbrk(CS s, "\r\n")) != NULL)
         {
@@ -3541,7 +4035,7 @@ while (done <= 0)
       size = ptr + 1;
       }
 
-    s = string_cat(s, &size, &ptr, US"\r\n", 2);
+    s = string_catn(s, &size, &ptr, US"\r\n", 2);
 
     /* If we received EHLO, we must create a multiline response which includes
     the functions supported. */
@@ -3560,12 +4054,12 @@ while (done <= 0)
         {
         sprintf(CS big_buffer, "%.3s-SIZE %d\r\n", smtp_code,
           thismessage_size_limit);
-        s = string_cat(s, &size, &ptr, big_buffer, Ustrlen(big_buffer));
+        s = string_cat(s, &size, &ptr, big_buffer);
         }
       else
         {
-        s = string_cat(s, &size, &ptr, smtp_code, 3);
-        s = string_cat(s, &size, &ptr, US"-SIZE\r\n", 7);
+        s = string_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(s, &size, &ptr, US"-SIZE\r\n", 7);
         }
 
       /* Exim does not do protocol conversion or data conversion. It is 8-bit
@@ -3577,36 +4071,35 @@ while (done <= 0)
 
       if (accept_8bitmime)
         {
-        s = string_cat(s, &size, &ptr, smtp_code, 3);
-        s = string_cat(s, &size, &ptr, US"-8BITMIME\r\n", 11);
+        s = string_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(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) 
+      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);
+        s = string_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(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. */
+      /* Advertise ETRN/VRFY/EXPN if there's are ACL checking whether a host is
+      permitted to issue them; a check is made when any host actually tries. */
 
-      if (acl_smtp_etrn != NULL)
+      if (acl_smtp_etrn)
         {
-        s = string_cat(s, &size, &ptr, smtp_code, 3);
-        s = string_cat(s, &size, &ptr, US"-ETRN\r\n", 7);
+        s = string_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(s, &size, &ptr, US"-ETRN\r\n", 7);
         }
-
-      /* Advertise EXPN if there's an ACL checking whether a host is
-      permitted to issue it; a check is made when any host actually tries. */
-
-      if (acl_smtp_expn != NULL)
+      if (acl_smtp_vrfy)
+        {
+        s = string_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(s, &size, &ptr, US"-VRFY\r\n", 7);
+        }
+      if (acl_smtp_expn)
         {
-        s = string_cat(s, &size, &ptr, smtp_code, 3);
-        s = string_cat(s, &size, &ptr, US"-EXPN\r\n", 7);
+        s = string_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(s, &size, &ptr, US"-EXPN\r\n", 7);
         }
 
       /* Exim is quite happy with pipelining, so let the other end know that
@@ -3615,8 +4108,8 @@ while (done <= 0)
       if (pipelining_enable &&
           verify_check_host(&pipelining_advertise_hosts) == OK)
         {
-        s = string_cat(s, &size, &ptr, smtp_code, 3);
-        s = string_cat(s, &size, &ptr, US"-PIPELINING\r\n", 13);
+        s = string_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(s, &size, &ptr, US"-PIPELINING\r\n", 13);
         sync_cmd_limit = NON_SYNC_CMD_PIPELINING;
         pipelining_advertised = TRUE;
         }
@@ -3632,37 +4125,48 @@ while (done <= 0)
       letters, so output the names in upper case, though we actually recognize
       them in either case in the AUTH command. */
 
-      if (auths != NULL)
+      if (  auths
+#ifdef AUTH_TLS
+        && !sender_host_authenticated
+#endif
+         && verify_check_host(&auth_advertise_hosts) == OK
+        )
+       {
+       auth_instance *au;
+       BOOL first = TRUE;
+       for (au = auths; au; au = au->next)
+         if (au->server && (au->advertise_condition == NULL ||
+             expand_check_condition(au->advertise_condition, au->name,
+             US"authenticator")))
+           {
+           int saveptr;
+           if (first)
+             {
+             s = string_catn(s, &size, &ptr, smtp_code, 3);
+             s = string_catn(s, &size, &ptr, US"-AUTH", 5);
+             first = FALSE;
+             auth_advertised = TRUE;
+             }
+           saveptr = ptr;
+           s = string_catn(s, &size, &ptr, US" ", 1);
+           s = string_cat (s, &size, &ptr, au->public_name);
+           while (++saveptr < ptr) s[saveptr] = toupper(s[saveptr]);
+           au->advertised = TRUE;
+           }
+         else
+           au->advertised = FALSE;
+
+       if (!first) s = string_catn(s, &size, &ptr, US"\r\n", 2);
+       }
+
+      /* RFC 3030 CHUNKING */
+
+      if (verify_check_host(&chunking_advertise_hosts) != FAIL)
         {
-        if (verify_check_host(&auth_advertise_hosts) == OK)
-          {
-          auth_instance *au;
-          BOOL first = TRUE;
-          for (au = auths; au != NULL; au = au->next)
-            {
-            if (au->server && (au->advertise_condition == NULL ||
-                expand_check_condition(au->advertise_condition, au->name,
-                US"authenticator")))
-              {
-              int saveptr;
-              if (first)
-                {
-                s = string_cat(s, &size, &ptr, smtp_code, 3);
-                s = string_cat(s, &size, &ptr, US"-AUTH", 5);
-                first = FALSE;
-                auth_advertised = TRUE;
-                }
-              saveptr = ptr;
-              s = string_cat(s, &size, &ptr, US" ", 1);
-              s = string_cat(s, &size, &ptr, au->public_name,
-                Ustrlen(au->public_name));
-              while (++saveptr < ptr) s[saveptr] = toupper(s[saveptr]);
-              au->advertised = TRUE;
-              }
-            else au->advertised = FALSE;
-            }
-          if (!first) s = string_cat(s, &size, &ptr, US"\r\n", 2);
-          }
+        s = string_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(s, &size, &ptr, US"-CHUNKING\r\n", 11);
+       chunking_offered = TRUE;
+       chunking_state = CHUNKING_OFFERED;
         }
 
       /* Advertise TLS (Transport Level Security) aka SSL (Secure Socket Layer)
@@ -3670,29 +4174,39 @@ while (done <= 0)
       tls_advertise_hosts. We must *not* advertise if we are already in a
       secure connection. */
 
-      #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
       if (tls_in.active < 0 &&
           verify_check_host(&tls_advertise_hosts) != FAIL)
         {
-        s = string_cat(s, &size, &ptr, smtp_code, 3);
-        s = string_cat(s, &size, &ptr, US"-STARTTLS\r\n", 11);
+        s = string_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(s, &size, &ptr, US"-STARTTLS\r\n", 11);
         tls_advertised = TRUE;
         }
-      #endif
+#endif
 
-      #ifndef DISABLE_PRDR
+#ifndef DISABLE_PRDR
       /* Per Recipient Data Response, draft by Eric A. Hall extending RFC */
       if (prdr_enable)
         {
-        s = string_cat(s, &size, &ptr, smtp_code, 3);
-        s = string_cat(s, &size, &ptr, US"-PRDR\r\n", 7);
+        s = string_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(s, &size, &ptr, US"-PRDR\r\n", 7);
        }
-      #endif
+#endif
+
+#ifdef SUPPORT_I18N
+      if (  accept_8bitmime
+         && verify_check_host(&smtputf8_advertise_hosts) != FAIL)
+       {
+        s = string_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(s, &size, &ptr, US"-SMTPUTF8\r\n", 11);
+        smtputf8_advertised = TRUE;
+       }
+#endif
 
       /* Finish off the multiline reply with one that is always available. */
 
-      s = string_cat(s, &size, &ptr, smtp_code, 3);
-      s = string_cat(s, &size, &ptr, US" HELP\r\n", 7);
+      s = string_catn(s, &size, &ptr, smtp_code, 3);
+      s = string_catn(s, &size, &ptr, US" HELP\r\n", 7);
       }
 
     /* Terminate the string (for debug), write it, and note that HELO/EHLO
@@ -3700,9 +4214,9 @@ while (done <= 0)
 
     s[ptr] = 0;
 
-    #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
     if (tls_in.active >= 0) (void)tls_write(TRUE, s, ptr); else
-    #endif
+#endif
 
       {
       int i = fwrite(s, 1, ptr, smtp_out); i = i; /* compiler quietening */
@@ -3717,16 +4231,13 @@ while (done <= 0)
     helo_seen = TRUE;
 
     /* Reset the protocol and the state, abandoning any previous message. */
-
-    received_protocol = (esmtp?
-      protocols[pextend +
-        ((sender_host_authenticated != NULL)? pauthed : 0) +
-        ((tls_in.active >= 0)? pcrpted : 0)]
-      :
-      protocols[pnormal + ((tls_in.active >= 0)? pcrpted : 0)])
-      +
-      ((sender_host_address != NULL)? pnlocal : 0);
-
+    received_protocol =
+      (sender_host_address ? protocols : protocols_local)
+       [ (esmtp
+         ? pextend + (sender_host_authenticated ? pauthed : 0)
+         : pnormal)
+       + (tls_in.active >= 0 ? pcrpted : 0)
+       ];
     smtp_reset(reset_point);
     toomany = FALSE;
     break;   /* HELO/EHLO */
@@ -3796,13 +4307,11 @@ while (done <= 0)
       if (!extract_option(&name, &value)) break;
 
       for (mail_args = env_mail_type_list;
-           (char *)mail_args < (char *)env_mail_type_list + sizeof(env_mail_type_list);
+           mail_args->value != ENV_MAIL_OPT_NULL;
            mail_args++
           )
-        {
         if (strcmpic(name, mail_args->name) == 0)
           break;
-        }
       if (mail_args->need_value && strcmpic(value, US"") == 0)
         break;
 
@@ -3830,60 +4339,66 @@ while (done <= 0)
         and "7BIT" as body types, but take no action. */
         case ENV_MAIL_OPT_BODY:
           if (accept_8bitmime) {
-            if (strcmpic(value, US"8BITMIME") == 0) {
+            if (strcmpic(value, US"8BITMIME") == 0)
               body_8bitmime = 8;
-            } else if (strcmpic(value, US"7BIT") == 0) {
+            else if (strcmpic(value, US"7BIT") == 0)
               body_8bitmime = 7;
-            } else {
+            else
+             {
               body_8bitmime = 0;
               done = synprot_error(L_smtp_syntax_error, 501, NULL,
                 US"invalid data for BODY");
               goto COMMAND_LOOP;
-            }
+              }
             DEBUG(D_receive) debug_printf("8BITMIME: %d\n", body_8bitmime);
            break;
           }
           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) {
+          if (dsn_advertised)
+           {
             /* Check if RET has already been set */
-            if (dsn_ret > 0) {
+            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;
+             }
+            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) {
+            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) {
+          if (dsn_advertised)
+           {
             /* Check if the dsn envid has been already set */
-            if (dsn_envid != NULL) {
+            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
@@ -3918,38 +4433,38 @@ while (done <= 0)
               rc = acl_check(ACL_WHERE_MAILAUTH, NULL, acl_smtp_mailauth,
                 &user_msg, &log_msg);
               }
-  
+
             switch (rc)
               {
               case OK:
-              if (authenticated_by == NULL ||
-                  authenticated_by->mail_auth_condition == NULL ||
-                  expand_check_condition(authenticated_by->mail_auth_condition,
-                      authenticated_by->name, US"authenticator"))
-                break;     /* Accept the AUTH */
-  
-              ignore_msg = US"server_mail_auth_condition failed";
-              if (authenticated_id != NULL)
-                ignore_msg = string_sprintf("%s: authenticated ID=\"%s\"",
-                  ignore_msg, authenticated_id);
-  
+               if (authenticated_by == NULL ||
+                   authenticated_by->mail_auth_condition == NULL ||
+                   expand_check_condition(authenticated_by->mail_auth_condition,
+                       authenticated_by->name, US"authenticator"))
+                 break;     /* Accept the AUTH */
+
+               ignore_msg = US"server_mail_auth_condition failed";
+               if (authenticated_id != NULL)
+                 ignore_msg = string_sprintf("%s: authenticated ID=\"%s\"",
+                   ignore_msg, authenticated_id);
+
               /* Fall through */
-  
+
               case FAIL:
-              authenticated_sender = NULL;
-              log_write(0, LOG_MAIN, "ignoring AUTH=%s from %s (%s)",
-                value, host_and_ident(TRUE), ignore_msg);
-              break;
-  
+               authenticated_sender = NULL;
+               log_write(0, LOG_MAIN, "ignoring AUTH=%s from %s (%s)",
+                 value, host_and_ident(TRUE), ignore_msg);
+               break;
+
               /* Should only get DEFER or ERROR here. Put back terminator
               overrides for error message */
-  
+
               default:
-              value[-1] = '=';
-              name[-1] = ' ';
-              (void)smtp_handle_acl_fail(ACL_WHERE_MAILAUTH, rc, user_msg,
-                log_msg);
-              goto COMMAND_LOOP;
+               value[-1] = '=';
+               name[-1] = ' ';
+               (void)smtp_handle_acl_fail(ACL_WHERE_MAILAUTH, rc, user_msg,
+                 log_msg);
+               goto COMMAND_LOOP;
               }
             }
             break;
@@ -3961,15 +4476,31 @@ while (done <= 0)
           break;
 #endif
 
-        /* Unknown option. Stick back the terminator characters and break
+#ifdef SUPPORT_I18N
+        case ENV_MAIL_OPT_UTF8:
+         if (smtputf8_advertised)
+           {
+           int old_pool = store_pool;
+
+           DEBUG(D_receive) debug_printf("smtputf8 requested\n");
+           message_smtputf8 = allow_utf8_domains = TRUE;
+           store_pool = POOL_PERM;
+           received_protocol = string_sprintf("utf8%s", received_protocol);
+           store_pool = old_pool;
+           }
+         break;
+#endif
+        /* No valid option. Stick back the terminator characters and break
         the loop.  Do the name-terminator second as extract_option sets
-       value==name when it found no equal-sign.
-       An error for a malformed address will occur. */
-        default:
+        value==name when it found no equal-sign.
+        An error for a malformed address will occur. */
+        case ENV_MAIL_OPT_NULL:
           value[-1] = '=';
           name[-1] = ' ';
           arg_error = TRUE;
           break;
+
+        default:  assert(0);
         }
       /* Break out of for loop if switch() had bad argument or
          when start of the email address is reached */
@@ -3993,17 +4524,16 @@ while (done <= 0)
     /* Now extract the address, first applying any SMTP-time rewriting. The
     TRUE flag allows "<>" as a sender address. */
 
-    raw_sender = ((rewrite_existflags & rewrite_smtp) != 0)?
-      rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
-        global_rewrite_rules) : smtp_cmd_data;
+    raw_sender = rewrite_existflags & rewrite_smtp
+      ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
+                   global_rewrite_rules)
+      : smtp_cmd_data;
 
-    /* rfc821_domains = TRUE; << no longer needed */
     raw_sender =
       parse_extract_address(raw_sender, &errmess, &start, &end, &sender_domain,
         TRUE);
-    /* rfc821_domains = FALSE; << no longer needed */
 
-    if (raw_sender == NULL)
+    if (!raw_sender)
       {
       done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_data, errmess);
       break;
@@ -4100,7 +4630,7 @@ while (done <= 0)
                     US"",
                   #endif
                     US"\r\n");
-      else 
+      else
         {
       #ifndef DISABLE_PRDR
         if (prdr_requested)
@@ -4161,26 +4691,23 @@ while (done <= 0)
       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;
+      uschar *name, *value;
 
       if (!extract_option(&name, &value))
-        {
         break;
-        }
 
       if (dsn_advertised && strcmpic(name, US"ORCPT") == 0)
         {
         /* Check whether orcpt has been already set */
-        if (orcpt != NULL) {
+        if (orcpt)
+         {
           synprot_error(L_smtp_syntax_error, 501, NULL,
             US"ORCPT can be specified once only");
           goto COMMAND_LOOP;
@@ -4192,32 +4719,39 @@ while (done <= 0)
       else if (dsn_advertised && strcmpic(name, US"NOTIFY") == 0)
         {
         /* Check if the notify flags have been already set */
-        if (flags > 0) {
+        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
+        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;
+           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"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 if (strcmpic(p, US"DELAY") == 0)
+             {
+             DEBUG(D_receive) debug_printf("DSN: Setting notify delay\n");
+             flags |= rf_notify_delay;
               }
-            else {
+            else
+             {
               /* Catch any strange values */
               synprot_error(L_smtp_syntax_error, 501, NULL,
                 US"Invalid value for NOTIFY parameter");
@@ -4240,21 +4774,17 @@ while (done <= 0)
         break;
         }
       }
-    #endif
 
     /* Apply SMTP rewriting then extract the working address. Don't allow "<>"
     as a recipient address */
 
-    recipient = ((rewrite_existflags & rewrite_smtp) != 0)?
-      rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
-        global_rewrite_rules) : smtp_cmd_data;
-
-    /* rfc821_domains = TRUE; << no longer needed */
-    recipient = parse_extract_address(recipient, &errmess, &start, &end,
-      &recipient_domain, FALSE);
-    /* rfc821_domains = FALSE; << no longer needed */
+    recipient = rewrite_existflags & rewrite_smtp
+      ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
+         global_rewrite_rules)
+      : smtp_cmd_data;
 
-    if (recipient == NULL)
+    if (!(recipient = parse_extract_address(recipient, &errmess, &start, &end,
+      &recipient_domain, FALSE)))
       {
       done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_data, errmess);
       rcpt_fail_count++;
@@ -4272,28 +4802,13 @@ while (done <= 0)
     friends now makes it absolutely clear that it means *mailbox*. Consequently
     we must always qualify this address, regardless. */
 
-    if (recipient_domain == 0)
-      {
-      if (allow_unqualified_recipient ||
-          strcmpic(recipient, US"postmaster") == 0)
-        {
-        DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
-          recipient);
-        recipient_domain = Ustrlen(recipient) + 1;
-        recipient = rewrite_address_qualify(recipient, TRUE);
-        }
-      else
+    if (!recipient_domain)
+      if (!(recipient_domain = qualify_recipient(&recipient, smtp_cmd_data,
+                                 US"recipient")))
         {
         rcpt_fail_count++;
-        smtp_printf("501 %s: recipient address must contain a domain\r\n",
-          smtp_cmd_data);
-        log_write(L_smtp_syntax_error,
-          LOG_MAIN|LOG_REJECT, "unqualified recipient rejected: "
-          "<%s> %s%s", recipient, host_and_ident(TRUE),
-          host_lookup_msg);
         break;
         }
-      }
 
     /* Check maximum allowed */
 
@@ -4340,53 +4855,48 @@ while (done <= 0)
     there may be a delay in this, re-check for a synchronization error
     afterwards, unless pipelining was advertised. */
 
-    if (recipients_discarded) rc = DISCARD; else
-      {
-      rc = acl_check(ACL_WHERE_RCPT, recipient, acl_smtp_rcpt, &user_msg,
-        &log_msg);
-      if (rc == OK && !pipelining_advertised && !check_sync())
+    if (recipients_discarded)
+      rc = DISCARD;
+    else
+      if (  (rc = acl_check(ACL_WHERE_RCPT, recipient, acl_smtp_rcpt, &user_msg,
+                   &log_msg)) == OK
+        && !pipelining_advertised && !check_sync())
         goto SYNC_FAILURE;
-      }
 
     /* The ACL was happy */
 
     if (rc == OK)
       {
-      if (user_msg == NULL) smtp_printf("250 Accepted\r\n");
-        else smtp_user_msg(US"250", user_msg);
+      if (user_msg)
+        smtp_user_msg(US"250", user_msg);
+      else
+        smtp_printf("250 Accepted\r\n");
       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;
+      recipients_list[recipients_count-1].orcpt = orcpt;
+      recipients_list[recipients_count-1].dsn_flags = flags;
 
-      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
-      
+      DEBUG(D_receive) debug_printf("DSN: orcpt: %s  flags: %d\n",
+       recipients_list[recipients_count-1].orcpt,
+       recipients_list[recipients_count-1].dsn_flags);
       }
 
     /* The recipient was discarded */
 
     else if (rc == DISCARD)
       {
-      if (user_msg == NULL) smtp_printf("250 Accepted\r\n");
-        else smtp_user_msg(US"250", user_msg);
+      if (user_msg)
+        smtp_user_msg(US"250", user_msg);
+      else
+        smtp_printf("250 Accepted\r\n");
       rcpt_fail_count++;
       discarded = TRUE;
-      log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> rejected RCPT %s: "
+      log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> RCPT %s: "
         "discarded by %s ACL%s%s", host_and_ident(TRUE),
-        (sender_address_unrewritten != NULL)?
-        sender_address_unrewritten : sender_address,
+        sender_address_unrewritten? sender_address_unrewritten : sender_address,
         smtp_cmd_argument, recipients_discarded? "MAIL" : "RCPT",
-        (log_msg == NULL)? US"" : US": ",
-        (log_msg == NULL)? US"" : log_msg);
+        log_msg ? US": " : US"", log_msg ? log_msg : US"");
       }
 
     /* Either the ACL failed the address, or it was deferred. */
@@ -4419,8 +4929,44 @@ while (done <= 0)
     (often indicating some kind of system error), it is helpful to include it
     with the DATA rejection (an idea suggested by Tony Finch). */
 
+    case BDAT_CMD:
+    HAD(SCH_BDAT);
+      {
+      int n;
+
+      if (chunking_state != CHUNKING_OFFERED)
+       {
+       done = synprot_error(L_smtp_protocol_error, 503, NULL,
+         US"BDAT command used when CHUNKING not advertised");
+       break;
+       }
+
+      /* grab size, endmarker */
+
+      if (sscanf(CS smtp_cmd_data, "%u %n", &chunking_datasize, &n) < 1)
+       {
+       done = synprot_error(L_smtp_protocol_error, 501, NULL,
+         US"missing size for BDAT command");
+       break;
+       }
+      chunking_state = strcmpic(smtp_cmd_data+n, US"LAST") == 0
+       ? CHUNKING_LAST : CHUNKING_ACTIVE;
+      chunking_data_left = chunking_datasize;
+      DEBUG(D_receive) debug_printf("chunking state %d, %d bytes\n",
+                                   (int)chunking_state, chunking_data_left);
+
+      lwr_receive_getc = receive_getc;
+      lwr_receive_ungetc = receive_ungetc;
+      receive_getc = bdat_getc;
+      receive_ungetc = bdat_ungetc;
+
+      goto DATA_BDAT;
+      }
+
     case DATA_CMD:
     HAD(SCH_DATA);
+
+    DATA_BDAT:         /* Common code for DATA and BDAT */
     if (!discarded && recipients_count <= 0)
       {
       if (rcpt_smtp_response_same && rcpt_smtp_response != NULL)
@@ -4435,10 +4981,16 @@ while (done <= 0)
         smtp_respond(code, 3, FALSE, rcpt_smtp_response);
         }
       if (pipelining_advertised && last_was_rcpt)
-        smtp_printf("503 Valid RCPT command must precede DATA\r\n");
+        smtp_printf("503 Valid RCPT command must precede %s\r\n",
+         smtp_names[smtp_connection_had[smtp_ch_index-1]]);
       else
         done = synprot_error(L_smtp_protocol_error, 503, NULL,
-          US"valid RCPT command must precede DATA");
+         smtp_connection_had[smtp_ch_index-1] == SCH_DATA
+         ? US"valid RCPT command must precede DATA"
+         : US"valid RCPT command must precede BDAT");
+
+      if (chunking_state > CHUNKING_OFFERED)
+       bdat_flush_data();
       break;
       }
 
@@ -4450,84 +5002,103 @@ while (done <= 0)
       break;
       }
 
-    /* If there is an ACL, re-check the synchronization afterwards, since the
-    ACL may have delayed.  To handle cutthrough delivery enforce a dummy call
-    to get the DATA command sent. */
-
-    if (acl_smtp_predata == NULL && cutthrough_fd < 0) rc = OK; else
+    if (chunking_state > CHUNKING_OFFERED)
+      rc = OK;                 /* No predata ACL or go-ahead output for BDAT */
+    else
       {
-      uschar * acl= acl_smtp_predata ? acl_smtp_predata : US"accept";
-      enable_dollar_recipients = TRUE;
-      rc = acl_check(ACL_WHERE_PREDATA, NULL, acl, &user_msg,
-        &log_msg);
-      enable_dollar_recipients = FALSE;
-      if (rc == OK && !check_sync()) goto SYNC_FAILURE;
-      }
+      /* If there is an ACL, re-check the synchronization afterwards, since the
+      ACL may have delayed.  To handle cutthrough delivery enforce a dummy call
+      to get the DATA command sent. */
 
-    if (rc == OK)
-      {
-      uschar * code;
-      code = US"354";
-      if (user_msg == NULL)
-        smtp_printf("%s Enter message, ending with \".\" on a line by itself\r\n", code);
-      else smtp_user_msg(code, user_msg);
-      done = 3;
-      message_ended = END_NOTENDED;   /* Indicate in middle of data */
+      if (acl_smtp_predata == NULL && cutthrough.fd < 0)
+       rc = OK;
+      else
+       {
+       uschar * acl = acl_smtp_predata ? acl_smtp_predata : US"accept";
+       enable_dollar_recipients = TRUE;
+       rc = acl_check(ACL_WHERE_PREDATA, NULL, acl, &user_msg,
+         &log_msg);
+       enable_dollar_recipients = FALSE;
+       if (rc == OK && !check_sync())
+         goto SYNC_FAILURE;
+
+       if (rc != OK)
+         {     /* Either the ACL failed the address, or it was deferred. */
+         done = smtp_handle_acl_fail(ACL_WHERE_PREDATA, rc, user_msg, log_msg);
+         break;
+         }
+       }
+
+      if (user_msg)
+       smtp_user_msg(US"354", user_msg);
+      else
+       smtp_printf(
+         "354 Enter message, ending with \".\" on a line by itself\r\n");
       }
 
-    /* Either the ACL failed the address, or it was deferred. */
+#ifdef TCP_QUICKACK
+    if (smtp_in)       /* all ACKs needed to ramp window up for bulk data */
+      (void) setsockopt(fileno(smtp_in), IPPROTO_TCP, TCP_QUICKACK,
+             US &on, sizeof(on));
+#endif
+    done = 3;
+    message_ended = END_NOTENDED;   /* Indicate in middle of data */
 
-    else
-      done = smtp_handle_acl_fail(ACL_WHERE_PREDATA, rc, user_msg, log_msg);
     break;
 
 
     case VRFY_CMD:
-    HAD(SCH_VRFY);
-    rc = acl_check(ACL_WHERE_VRFY, NULL, acl_smtp_vrfy, &user_msg, &log_msg);
-    if (rc != OK)
-      done = smtp_handle_acl_fail(ACL_WHERE_VRFY, rc, user_msg, log_msg);
-    else
       {
-      uschar *address;
-      uschar *s = NULL;
+      uschar * address;
 
-      /* rfc821_domains = TRUE; << no longer needed */
-      address = parse_extract_address(smtp_cmd_data, &errmess, &start, &end,
-        &recipient_domain, FALSE);
-      /* rfc821_domains = FALSE; << no longer needed */
+      HAD(SCH_VRFY);
 
-      if (address == NULL)
-        s = string_sprintf("501 %s", errmess);
+      if (!(address = parse_extract_address(smtp_cmd_data, &errmess,
+            &start, &end, &recipient_domain, FALSE)))
+       {
+       smtp_printf("501 %s\r\n", errmess);
+       break;
+       }
+
+      if (!recipient_domain)
+       if (!(recipient_domain = qualify_recipient(&address, smtp_cmd_data,
+                                   US"verify")))
+         break;
+
+      if ((rc = acl_check(ACL_WHERE_VRFY, address, acl_smtp_vrfy,
+                   &user_msg, &log_msg)) != OK)
+       done = smtp_handle_acl_fail(ACL_WHERE_VRFY, rc, user_msg, log_msg);
       else
-        {
-        address_item *addr = deliver_make_addr(address, FALSE);
-        switch(verify_address(addr, NULL, vopt_is_recipient | vopt_qualify, -1,
-               -1, -1, NULL, NULL, NULL))
-          {
-          case OK:
-          s = string_sprintf("250 <%s> is deliverable", address);
-          break;
+       {
+       uschar * s = NULL;
+       address_item * addr = deliver_make_addr(address, FALSE);
+
+       switch(verify_address(addr, NULL, vopt_is_recipient | vopt_qualify, -1,
+              -1, -1, NULL, NULL, NULL))
+         {
+         case OK:
+           s = string_sprintf("250 <%s> is deliverable", address);
+           break;
 
-          case DEFER:
-          s = (addr->user_message != NULL)?
-            string_sprintf("451 <%s> %s", address, addr->user_message) :
-            string_sprintf("451 Cannot resolve <%s> at this time", address);
-          break;
+         case DEFER:
+           s = (addr->user_message != NULL)?
+             string_sprintf("451 <%s> %s", address, addr->user_message) :
+             string_sprintf("451 Cannot resolve <%s> at this time", address);
+           break;
 
-          case FAIL:
-          s = (addr->user_message != NULL)?
-            string_sprintf("550 <%s> %s", address, addr->user_message) :
-            string_sprintf("550 <%s> is not deliverable", address);
-          log_write(0, LOG_MAIN, "VRFY failed for %s %s",
-            smtp_cmd_argument, host_and_ident(TRUE));
-          break;
-          }
-        }
+         case FAIL:
+           s = (addr->user_message != NULL)?
+             string_sprintf("550 <%s> %s", address, addr->user_message) :
+             string_sprintf("550 <%s> is not deliverable", address);
+           log_write(0, LOG_MAIN, "VRFY failed for %s %s",
+             smtp_cmd_argument, host_and_ident(TRUE));
+           break;
+         }
 
-      smtp_printf("%s\r\n", s);
+       smtp_printf("%s\r\n", s);
+       }
+      break;
       }
-    break;
 
 
     case EXPN_CMD:
@@ -4561,20 +5132,18 @@ while (done <= 0)
 
     /* Apply an ACL check if one is defined */
 
-    if (acl_smtp_starttls != NULL)
+    if (  acl_smtp_starttls
+       && (rc = acl_check(ACL_WHERE_STARTTLS, NULL, acl_smtp_starttls,
+                 &user_msg, &log_msg)) != OK
+       )
       {
-      rc = acl_check(ACL_WHERE_STARTTLS, NULL, acl_smtp_starttls, &user_msg,
-        &log_msg);
-      if (rc != OK)
-        {
-        done = smtp_handle_acl_fail(ACL_WHERE_STARTTLS, rc, user_msg, log_msg);
-        break;
-        }
+      done = smtp_handle_acl_fail(ACL_WHERE_STARTTLS, rc, user_msg, log_msg);
+      break;
       }
 
     /* RFC 2487 is not clear on when this command may be sent, though it
     does state that all information previously obtained from the client
-    must be discarded if a TLS session is started. It seems reasonble to
+    must be discarded if a TLS session is started. It seems reasonable to
     do an implied RSET when STARTTLS is received. */
 
     incomplete_transaction_log(US"STARTTLS");
@@ -4593,20 +5162,20 @@ while (done <= 0)
     if (receive_smtp_buffered())
       {
       DEBUG(D_any)
-        debug_printf("Non-empty input buffer after STARTTLS; naive attack?");
+        debug_printf("Non-empty input buffer after STARTTLS; naive attack?\n");
       if (tls_in.active < 0)
         smtp_inend = smtp_inptr = smtp_inbuffer;
       /* and if TLS is already active, tls_server_start() should fail */
       }
 
-    /* There is nothing we value in the input buffer and if TLS is succesfully
+    /* There is nothing we value in the input buffer and if TLS is successfully
     negotiated, we won't use this buffer again; if TLS fails, we'll just read
     fresh content into it.  The buffer contains arbitrary content from an
     untrusted remote source; eg: NOOP <shellcode>\r\nSTARTTLS\r\n
     It seems safest to just wipe away the content rather than leave it as a
     target to jump to. */
 
-    memset(smtp_inbuffer, 0, in_buffer_size);
+    memset(smtp_inbuffer, 0, IN_BUFFER_SIZE);
 
     /* Attempt to start up a TLS session, and if successful, discard all
     knowledge that was obtained previously. At least, that's what the RFC says,
@@ -4622,6 +5191,7 @@ while (done <= 0)
         helo_seen = esmtp = auth_advertised = pipelining_advertised = FALSE;
       cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE;
       cmd_list[CMD_LIST_AUTH].is_mail_cmd = TRUE;
+      cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
       if (sender_helo_name != NULL)
         {
         store_free(sender_helo_name);
@@ -4630,13 +5200,13 @@ while (done <= 0)
         set_process_info("handling incoming TLS connection from %s",
           host_and_ident(FALSE));
         }
-      received_protocol = (esmtp?
-        protocols[pextend + pcrpted +
-          ((sender_host_authenticated != NULL)? pauthed : 0)]
-        :
-        protocols[pnormal + pcrpted])
-        +
-        ((sender_host_address != NULL)? pnlocal : 0);
+      received_protocol =
+       (sender_host_address ? protocols : protocols_local)
+         [ (esmtp
+           ? pextend + (sender_host_authenticated ? pauthed : 0)
+           : pnormal)
+         + (tls_in.active >= 0 ? pcrpted : 0)
+         ];
 
       sender_host_authenticated = NULL;
       authenticated_id = NULL;
@@ -4659,45 +5229,39 @@ while (done <= 0)
     set, but we must still reject all incoming commands. */
 
     DEBUG(D_tls) debug_printf("TLS failed to start\n");
-    while (done <= 0)
+    while (done <= 0) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
       {
-      switch(smtp_read_command(FALSE))
-        {
-        case EOF_CMD:
-        log_write(L_smtp_connection, LOG_MAIN, "%s closed by EOF",
-          smtp_get_connection_info());
-        smtp_notquit_exit(US"tls-failed", NULL, NULL);
-        done = 2;
-        break;
-
-        /* It is perhaps arguable as to which exit ACL should be called here,
-        but as it is probably a situation that almost never arises, it
-        probably doesn't matter. We choose to call the real QUIT ACL, which in
-        some sense is perhaps "right". */
-
-        case QUIT_CMD:
-        user_msg = NULL;
-        if (acl_smtp_quit != NULL)
-          {
-          rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, &user_msg,
-            &log_msg);
-          if (rc == ERROR)
-            log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s",
-              log_msg);
-          }
-        if (user_msg == NULL)
-          smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
-        else
-          smtp_respond(US"221", 3, TRUE, user_msg);
-        log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
-          smtp_get_connection_info());
-        done = 2;
-        break;
+      case EOF_CMD:
+       log_write(L_smtp_connection, LOG_MAIN, "%s closed by EOF",
+         smtp_get_connection_info());
+       smtp_notquit_exit(US"tls-failed", NULL, NULL);
+       done = 2;
+       break;
+
+      /* It is perhaps arguable as to which exit ACL should be called here,
+      but as it is probably a situation that almost never arises, it
+      probably doesn't matter. We choose to call the real QUIT ACL, which in
+      some sense is perhaps "right". */
+
+      case QUIT_CMD:
+       user_msg = NULL;
+       if (  acl_smtp_quit
+          && ((rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, &user_msg,
+                             &log_msg)) == ERROR))
+           log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s",
+             log_msg);
+       if (user_msg)
+         smtp_respond(US"221", 3, TRUE, user_msg);
+       else
+         smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
+       log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
+         smtp_get_connection_info());
+       done = 2;
+       break;
 
-        default:
-        smtp_printf("554 Security failure\r\n");
-        break;
-        }
+      default:
+       smtp_printf("554 Security failure\r\n");
+       break;
       }
     tls_close(TRUE, TRUE);
     break;
@@ -4709,37 +5273,15 @@ while (done <= 0)
     message. */
 
     case QUIT_CMD:
-    HAD(SCH_QUIT);
-    incomplete_transaction_log(US"QUIT");
-    if (acl_smtp_quit != NULL)
-      {
-      rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, &user_msg, &log_msg);
-      if (rc == ERROR)
-        log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s",
-          log_msg);
-      }
-    if (user_msg == NULL)
-      smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
-    else
-      smtp_respond(US"221", 3, TRUE, user_msg);
-
-    #ifdef SUPPORT_TLS
-    tls_close(TRUE, TRUE);
-    #endif
-
+    smtp_quit_handler(&user_msg, &log_msg);
     done = 2;
-    log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
-      smtp_get_connection_info());
     break;
 
 
     case RSET_CMD:
-    HAD(SCH_RSET);
-    incomplete_transaction_log(US"RSET");
+    smtp_rset_handler();
     smtp_reset(reset_point);
     toomany = FALSE;
-    smtp_printf("250 Reset OK\r\n");
-    cmd_list[CMD_LIST_RSET].is_mail_cmd = FALSE;
     break;
 
 
@@ -4766,7 +5308,7 @@ while (done <= 0)
           verify_check_host(&tls_advertise_hosts) != FAIL)
         Ustrcat(buffer, " STARTTLS");
       #endif
-      Ustrcat(buffer, " HELO EHLO MAIL RCPT DATA");
+      Ustrcat(buffer, " HELO EHLO MAIL RCPT DATA BDAT");
       Ustrcat(buffer, " NOOP QUIT RSET HELP");
       if (acl_smtp_etrn != NULL) Ustrcat(buffer, " ETRN");
       if (acl_smtp_expn != NULL) Ustrcat(buffer, " EXPN");
@@ -4811,8 +5353,8 @@ while (done <= 0)
     log_write(L_etrn, LOG_MAIN, "ETRN %s received from %s", smtp_cmd_argument,
       host_and_ident(FALSE));
 
-    rc = acl_check(ACL_WHERE_ETRN, NULL, acl_smtp_etrn, &user_msg, &log_msg);
-    if (rc != OK)
+    if ((rc = acl_check(ACL_WHERE_ETRN, NULL, acl_smtp_etrn,
+               &user_msg, &log_msg)) != OK)
       {
       done = smtp_handle_acl_fail(ACL_WHERE_ETRN, rc, user_msg, log_msg);
       break;
@@ -4856,8 +5398,10 @@ while (done <= 0)
         break;
         }
       etrn_command = US"exim -R";
-      argv = child_exec_exim(CEE_RETURN_ARGV, TRUE, NULL, TRUE, 2, US"-R",
-        smtp_cmd_data);
+      argv = CUSS child_exec_exim(CEE_RETURN_ARGV, TRUE, NULL, TRUE,
+        *queue_name ? 4 : 2,
+       US"-R", smtp_cmd_data,
+       US"-MCG", queue_name);
       }
 
     /* If we are host-testing, don't actually do anything. */
@@ -4878,7 +5422,7 @@ while (done <= 0)
     /* If ETRN queue runs are to be serialized, check the database to
     ensure one isn't already running. */
 
-    if (smtp_etrn_serialize && !enq_start(etrn_serialize_key))
+    if (smtp_etrn_serialize && !enq_start(etrn_serialize_key, 1))
       {
       smtp_printf("458 Already processing %s\r\n", smtp_cmd_data);
       break;
@@ -4973,8 +5517,8 @@ while (done <= 0)
 
     case BADSYN_CMD:
     SYNC_FAILURE:
-    if (smtp_inend >= smtp_inbuffer + in_buffer_size)
-      smtp_inend = smtp_inbuffer + in_buffer_size - 1;
+    if (smtp_inend >= smtp_inbuffer + IN_BUFFER_SIZE)
+      smtp_inend = smtp_inbuffer + IN_BUFFER_SIZE - 1;
     c = smtp_inend - smtp_inptr;
     if (c > 150) c = 150;
     smtp_inptr[c] = 0;
@@ -5002,11 +5546,11 @@ while (done <= 0)
     done = 1;   /* Pretend eof - drops connection */
     break;
 
-    #ifdef EXPERIMENTAL_PROXY
+#ifdef SUPPORT_PROXY
     case PROXY_FAIL_IGNORE_CMD:
     smtp_printf("503 Command refused, required Proxy negotiation failed\r\n");
     break;
-    #endif
+#endif
 
     default:
     if (unknown_command_count++ >= smtp_max_unknown_commands)
@@ -5021,7 +5565,7 @@ while (done <= 0)
       done = 2;
       log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
         "unrecognized commands (last was \"%s\")", host_and_ident(FALSE),
-        smtp_cmd_buffer);
+        string_printing(smtp_cmd_buffer));
       }
     else
       done = synprot_error(L_smtp_syntax_error, 500, NULL,
index b6ff511..ba6153e 100644 (file)
@@ -2,13 +2,14 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* A number of functions for driving outgoing SMTP calls. */
 
 
 #include "exim.h"
+#include "transports/smtp.h"
 
 
 
@@ -25,7 +26,6 @@ Arguments:
                which case the function does nothing
   host_af    AF_INET or AF_INET6 for the outgoing IP address
   addr       the mail address being handled (for setting errors)
-  changed    if not NULL, set TRUE if expansion actually changed istring
   interface  point this to the interface
   msg        to add to any error message
 
@@ -35,16 +35,15 @@ Returns:     TRUE on success, FALSE on failure, with error message
 
 BOOL
 smtp_get_interface(uschar *istring, int host_af, address_item *addr,
-  BOOL *changed, uschar **interface, uschar *msg)
+  uschar **interface, uschar *msg)
 {
-uschar *expint;
+const uschar * expint;
 uschar *iface;
 int sep = 0;
 
-if (istring == NULL) return TRUE;
+if (!istring) return TRUE;
 
-expint = expand_string(istring);
-if (expint == NULL)
+if (!(expint = expand_string(istring)))
   {
   if (expand_string_forcedfail) return TRUE;
   addr->transport_return = PANIC;
@@ -53,13 +52,11 @@ if (expint == NULL)
   return FALSE;
   }
 
-if (changed != NULL) *changed = expint != istring;
-
 while (isspace(*expint)) expint++;
 if (*expint == 0) return TRUE;
 
 while ((iface = string_nextinlist(&expint, &sep, big_buffer,
-          big_buffer_size)) != NULL)
+          big_buffer_size)))
   {
   if (string_is_ip_address(iface, NULL) == 0)
     {
@@ -74,7 +71,7 @@ while ((iface = string_nextinlist(&expint, &sep, big_buffer,
     break;
   }
 
-if (iface != NULL) *interface = string_copy(iface);
+if (iface) *interface = string_copy(iface);
 return TRUE;
 }
 
@@ -143,68 +140,33 @@ return TRUE;
 
 
 
-/*************************************************
-*           Connect to remote host               *
-*************************************************/
-
-/* Create a socket, and connect it to a remote host. IPv6 addresses are
-detected by checking for a colon in the address. AF_INET6 is defined even on
-non-IPv6 systems, to enable the code to be less messy. However, on such systems
-host->address will always be an IPv4 address.
-
-The port field in the host item is used if it is set (usually router from SRV
-records or elsewhere). In other cases, the default passed as an argument is
-used, and the host item is updated with its value.
-
-Arguments:
-  host        host item containing name and address (and sometimes port)
-  host_af     AF_INET or AF_INET6
-  port        default remote port to connect to, in host byte order, for those
-                hosts whose port setting is PORT_NONE
-  interface   outgoing interface address or NULL
-  timeout     timeout value or 0
-  keepalive   TRUE to use keepalive
-  dscp        DSCP value to assign to socket
-
-Returns:      connected socket number, or -1 with errno set
-*/
-
 int
-smtp_connect(host_item *host, int host_af, int port, uschar *interface,
-  int timeout, BOOL keepalive, const uschar *dscp)
+smtp_sock_connect(host_item * host, int host_af, int port, uschar * interface,
+  transport_instance * tb, int timeout)
 {
-int on = 1;
-int save_errno = 0;
+smtp_transport_options_block * ob =
+  (smtp_transport_options_block *)tb->options_block;
+const uschar * dscp = ob->dscp;
 int dscp_value;
 int dscp_level;
 int dscp_option;
 int sock;
+int save_errno = 0;
+BOOL fastopen = FALSE;
 
-if (host->port != PORT_NONE)
-  {
-  HDEBUG(D_transport|D_acl|D_v)
-    debug_printf("Transport port=%d replaced by host-specific port=%d\n", port,
-      host->port);
-  port = host->port;
-  }
-else host->port = port;    /* Set the port actually used */
-
-HDEBUG(D_transport|D_acl|D_v)
-  {
-  if (interface == NULL)
-    debug_printf("Connecting to %s [%s]:%d ... ",host->name,host->address,port);
-  else
-    debug_printf("Connecting to %s [%s]:%d from %s ... ", host->name,
-      host->address, port, interface);
-  }
-
-/* Create the socket */
+#ifndef DISABLE_EVENT
+deliver_host_address = host->address;
+deliver_host_port = port;
+if (event_raise(tb->event_action, US"tcp:connect", NULL)) return -1;
+#endif
 
 if ((sock = ip_socket(SOCK_STREAM, host_af)) < 0) return -1;
 
 /* Set TCP_NODELAY; Exim does its own buffering. */
 
-setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (uschar *)(&on), sizeof(on));
+if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, US &on, sizeof(on)))
+  HDEBUG(D_transport|D_acl|D_v)
+    debug_printf_indent("failed to set NODELAY: %s ", strerror(errno));
 
 /* Set DSCP value, if we can. For now, if we fail to set the value, we don't
 bomb out, just log it and continue in default traffic class. */
@@ -212,34 +174,36 @@ bomb out, just log it and continue in default traffic class. */
 if (dscp && dscp_lookup(dscp, host_af, &dscp_level, &dscp_option, &dscp_value))
   {
   HDEBUG(D_transport|D_acl|D_v)
-    debug_printf("DSCP \"%s\"=%x ", dscp, dscp_value);
+    debug_printf_indent("DSCP \"%s\"=%x ", dscp, dscp_value);
   if (setsockopt(sock, dscp_level, dscp_option, &dscp_value, sizeof(dscp_value)) < 0)
     HDEBUG(D_transport|D_acl|D_v)
-      debug_printf("failed to set DSCP: %s ", strerror(errno));
+      debug_printf_indent("failed to set DSCP: %s ", strerror(errno));
   /* If the kernel supports IPv4 and IPv6 on an IPv6 socket, we need to set the
   option for both; ignore failures here */
   if (host_af == AF_INET6 &&
       dscp_lookup(dscp, AF_INET, &dscp_level, &dscp_option, &dscp_value))
-    {
     (void) setsockopt(sock, dscp_level, dscp_option, &dscp_value, sizeof(dscp_value));
-    }
   }
 
+#ifdef TCP_FASTOPEN
+if (verify_check_given_host (&ob->hosts_try_fastopen, host) == OK) fastopen = TRUE;
+#endif
+
 /* Bind to a specific interface if requested. Caller must ensure the interface
 is the same type (IPv4 or IPv6) as the outgoing address. */
 
-if (interface != NULL && ip_bind(sock, host_af, interface, 0) < 0)
+if (interface && ip_bind(sock, host_af, interface, 0) < 0)
   {
   save_errno = errno;
   HDEBUG(D_transport|D_acl|D_v)
-    debug_printf("unable to bind outgoing SMTP call to %s: %s", interface,
+    debug_printf_indent("unable to bind outgoing SMTP call to %s: %s", interface,
     strerror(errno));
   }
 
 /* Connect to the remote host, and add keepalive to the socket before returning
 it, if requested. */
 
-else if (ip_connect(sock, host_af, host->address, port, timeout) < 0)
+else if (ip_connect(sock, host_af, host->address, port, timeout, fastopen) < 0)
   save_errno = errno;
 
 /* Either bind() or connect() failed */
@@ -248,7 +212,7 @@ if (save_errno != 0)
   {
   HDEBUG(D_transport|D_acl|D_v)
     {
-    debug_printf("failed: %s", CUstrerror(save_errno));
+    debug_printf_indent("failed: %s", CUstrerror(save_errno));
     if (save_errno == ETIMEDOUT)
       debug_printf(" (timeout=%s)", readconf_printtime(timeout));
     debug_printf("\n");
@@ -264,7 +228,7 @@ else
   {
   union sockaddr_46 interface_sock;
   EXIM_SOCKLEN_T size = sizeof(interface_sock);
-  HDEBUG(D_transport|D_acl|D_v) debug_printf("connected\n");
+  HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("connected\n");
   if (getsockname(sock, (struct sockaddr *)(&interface_sock), &size) == 0)
     sending_ip_address = host_ntoa(-1, &interface_sock, NULL, &sending_port);
   else
@@ -274,11 +238,76 @@ else
     close(sock);
     return -1;
     }
-  if (keepalive) ip_keepalive(sock, host->address, TRUE);
+  if (ob->keepalive) ip_keepalive(sock, host->address, TRUE);
   return sock;
   }
 }
 
+/*************************************************
+*           Connect to remote host               *
+*************************************************/
+
+/* Create a socket, and connect it to a remote host. IPv6 addresses are
+detected by checking for a colon in the address. AF_INET6 is defined even on
+non-IPv6 systems, to enable the code to be less messy. However, on such systems
+host->address will always be an IPv4 address.
+
+The port field in the host item is used if it is set (usually router from SRV
+records or elsewhere). In other cases, the default passed as an argument is
+used, and the host item is updated with its value.
+
+Arguments:
+  host        host item containing name and address (and sometimes port)
+  host_af     AF_INET or AF_INET6
+  port        default remote port to connect to, in host byte order, for those
+                hosts whose port setting is PORT_NONE
+  interface   outgoing interface address or NULL
+  timeout     timeout value or 0
+  tb          transport
+
+Returns:      connected socket number, or -1 with errno set
+*/
+
+int
+smtp_connect(host_item *host, int host_af, int port, uschar *interface,
+  int timeout, transport_instance * tb)
+{
+#ifdef SUPPORT_SOCKS
+smtp_transport_options_block * ob =
+  (smtp_transport_options_block *)tb->options_block;
+#endif
+
+if (host->port != PORT_NONE)
+  {
+  HDEBUG(D_transport|D_acl|D_v)
+    debug_printf_indent("Transport port=%d replaced by host-specific port=%d\n", port,
+      host->port);
+  port = host->port;
+  }
+else host->port = port;    /* Set the port actually used */
+
+callout_address = string_sprintf("[%s]:%d", host->address, port);
+
+HDEBUG(D_transport|D_acl|D_v)
+  {
+  uschar * s = US" ";
+  if (interface) s = string_sprintf(" from %s ", interface);
+#ifdef SUPPORT_SOCKS
+  if (ob->socks_proxy) s = string_sprintf("%svia proxy ", s);
+#endif
+  debug_printf_indent("Connecting to %s %s%s... ", host->name, callout_address, s);
+  }
+
+/* Create and connect the socket */
+
+#ifdef SUPPORT_SOCKS
+if (ob->socks_proxy)
+  return socks_sock_connect(host, host_af, port, interface, tb, timeout);
+#endif
+
+return smtp_sock_connect(host, host_af, port, interface, tb, timeout);
+}
+
 
 /*************************************************
 *        Flush outgoing command buffer           *
@@ -298,17 +327,19 @@ static BOOL
 flush_buffer(smtp_outblock *outblock)
 {
 int rc;
+int n = outblock->ptr - outblock->buffer;
 
+HDEBUG(D_transport|D_acl) debug_printf_indent("cmd buf flush %d bytes\n", n);
 #ifdef SUPPORT_TLS
 if (tls_out.active == outblock->sock)
-  rc = tls_write(FALSE, outblock->buffer, outblock->ptr - outblock->buffer);
+  rc = tls_write(FALSE, outblock->buffer, n);
 else
 #endif
+  rc = send(outblock->sock, outblock->buffer, n, 0);
 
-rc = send(outblock->sock, outblock->buffer, outblock->ptr - outblock->buffer, 0);
 if (rc <= 0)
   {
-  HDEBUG(D_transport|D_acl) debug_printf("send failed: %s\n", strerror(errno));
+  HDEBUG(D_transport|D_acl) debug_printf_indent("send failed: %s\n", strerror(errno));
   return FALSE;
   }
 
@@ -331,6 +362,7 @@ Arguments:
   noflush    if TRUE, save the command in the output buffer, for pipelining
   format     a format, starting with one of
              of HELO, MAIL FROM, RCPT TO, DATA, ".", or QUIT.
+            If NULL, flush pipeline buffer only.
   ...        data for the format
 
 Returns:     0 if command added to pipelining buffer, with nothing transmitted
@@ -345,48 +377,51 @@ int count;
 int rc = 0;
 va_list ap;
 
-va_start(ap, format);
-if (!string_vformat(big_buffer, big_buffer_size, CS format, ap))
-  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing "
-    "SMTP");
-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))
+if (format)
   {
-  rc = outblock->cmd_count;                 /* flush resets */
-  if (!flush_buffer(outblock)) return -1;
-  }
+  va_start(ap, format);
+  if (!string_vformat(big_buffer, big_buffer_size, CS format, ap))
+    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing "
+      "SMTP");
+  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 */
+    if (!flush_buffer(outblock)) return -1;
+    }
 
-Ustrncpy(CS outblock->ptr, big_buffer, count);
-outblock->ptr += count;
-outblock->cmd_count++;
-count -= 2;
-big_buffer[count] = 0;     /* remove \r\n for error message */
+  Ustrncpy(CS outblock->ptr, big_buffer, count);
+  outblock->ptr += count;
+  outblock->cmd_count++;
+  count -= 2;
+  big_buffer[count] = 0;     /* remove \r\n for error message */
 
-/* We want to hide the actual data sent in AUTH transactions from reflections
-and logs. While authenticating, a flag is set in the outblock to enable this.
-The AUTH command itself gets any data flattened. Other lines are flattened
-completely. */
+  /* We want to hide the actual data sent in AUTH transactions from reflections
+  and logs. While authenticating, a flag is set in the outblock to enable this.
+  The AUTH command itself gets any data flattened. Other lines are flattened
+  completely. */
 
-if (outblock->authenticating)
-  {
-  uschar *p = big_buffer;
-  if (Ustrncmp(big_buffer, "AUTH ", 5) == 0)
+  if (outblock->authenticating)
     {
-    p += 5;
-    while (isspace(*p)) p++;
-    while (!isspace(*p)) p++;
-    while (isspace(*p)) p++;
+    uschar *p = big_buffer;
+    if (Ustrncmp(big_buffer, "AUTH ", 5) == 0)
+      {
+      p += 5;
+      while (isspace(*p)) p++;
+      while (!isspace(*p)) p++;
+      while (isspace(*p)) p++;
+      }
+    while (*p != 0) *p++ = '*';
     }
-  while (*p != 0) *p++ = '*';
-  }
 
-HDEBUG(D_transport|D_acl|D_v) debug_printf("  SMTP>> %s\n", big_buffer);
+  HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP>> %s\n", big_buffer);
+  }
 
 if (!noflush)
   {
@@ -463,15 +498,19 @@ for (;;)
 
   /* Need to read a new input packet. */
 
-  rc = ip_recv(sock, inblock->buffer, inblock->buffersize, timeout);
-  if (rc <= 0) break;
+  if((rc = ip_recv(sock, inblock->buffer, inblock->buffersize, timeout)) <= 0)
+    {
+    if (!errno)
+      DEBUG(D_deliver|D_transport|D_acl) debug_printf_indent("  SMTP(closed)<<\n");
+    break;
+    }
 
   /* Another block of data has been successfully read. Set up the pointers
   and let the loop continue. */
 
   ptrend = inblock->ptrend = inblock->buffer + rc;
   ptr = inblock->buffer;
-  DEBUG(D_transport|D_acl) debug_printf("read response data: size=%d\n", rc);
+  DEBUG(D_transport|D_acl) debug_printf_indent("read response data: size=%d\n", rc);
   }
 
 /* Get here if there has been some kind of recv() error; errno is set, but we
@@ -501,7 +540,7 @@ Arguments:
   buffer    where to put the response
   size      the size of the buffer
   okdigit   the expected first digit of the response
-  timeout   the timeout to use
+  timeout   the timeout to use, in seconds
 
 Returns:    TRUE if a valid, non-error response was received; else FALSE
 */
@@ -515,7 +554,7 @@ int count;
 
 errno = 0;  /* Ensure errno starts out zero */
 
-/* This is a loop to read and concatentate the lines that make up a multi-line
+/* This is a loop to read and concatenate the lines that make up a multi-line
 response. */
 
 for (;;)
@@ -524,7 +563,7 @@ for (;;)
     return FALSE;
 
   HDEBUG(D_transport|D_acl|D_v)
-    debug_printf("  %s %s\n", (ptr == buffer)? "SMTP<<" : "      ", ptr);
+    debug_printf_indent("  %s %s\n", (ptr == buffer)? "SMTP<<" : "      ", ptr);
 
   /* Check the format of the response: it must start with three digits; if
   these are followed by a space or end of line, the response is complete. If
@@ -569,3 +608,5 @@ return buffer[0] == okdigit;
 }
 
 /* End of smtp_out.c */
+/* vi: aw ai sw=2
+*/
dissimilarity index 88%
index 7eb6fbf..d4b95b2 100644 (file)
-/*************************************************
-*     Exim - an Internet mail transport agent    *
-*************************************************/
-
-/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003-???? */
-/* License: GPL */
-
-/* Code for calling spamassassin's spamd. Called from acl.c. */
-
-#include "exim.h"
-#ifdef WITH_CONTENT_SCAN
-#include "spam.h"
-
-uschar spam_score_buffer[16];
-uschar spam_score_int_buffer[16];
-uschar spam_bar_buffer[128];
-uschar spam_report_buffer[32600];
-uschar prev_user_name[128] = "";
-int spam_ok = 0;
-int spam_rc = 0;
-uschar *prev_spamd_address_work = NULL;
-
-int
-spam(uschar **listptr)
-{
-  int sep = 0;
-  uschar *list = *listptr;
-  uschar *user_name;
-  uschar user_name_buffer[128];
-  unsigned long mbox_size;
-  FILE *mbox_file;
-  int spamd_sock = -1;
-  uschar spamd_buffer[32600];
-  int i, j, offset, result;
-  uschar spamd_version[8];
-  uschar spamd_score_char;
-  double spamd_threshold, spamd_score;
-  int spamd_report_offset;
-  uschar *p,*q;
-  int override = 0;
-  time_t start;
-  size_t read, wrote;
-  struct sockaddr_un server;
-#ifndef NO_POLL_H
-  struct pollfd pollfd;
-#else                               /* Patch posted by Erik ? for OS X */
-  struct timeval select_tv;         /* and applied by PH */
-  fd_set select_fd;
-#endif
-  uschar *spamd_address_work;
-
-  /* stop compiler warning */
-  result = 0;
-
-  /* find the username from the option list */
-  if ((user_name = string_nextinlist(&list, &sep,
-                                     user_name_buffer,
-                                     sizeof(user_name_buffer))) == NULL) {
-    /* no username given, this means no scanning should be done */
-    return FAIL;
-  };
-
-  /* if username is "0" or "false", do not scan */
-  if ( (Ustrcmp(user_name,"0") == 0) ||
-       (strcmpic(user_name,US"false") == 0) ) {
-    return FAIL;
-  };
-
-  /* if there is an additional option, check if it is "true" */
-  if (strcmpic(list,US"true") == 0) {
-    /* in that case, always return true later */
-    override = 1;
-  };
-
-  /* expand spamd_address if needed */
-  if (*spamd_address == '$') {
-    spamd_address_work = expand_string(spamd_address);
-    if (spamd_address_work == NULL) {
-      log_write(0, LOG_MAIN|LOG_PANIC,
-        "spamassassin acl condition: spamd_address starts with $, but expansion failed: %s", expand_string_message);
-      return DEFER;
-    }
-  }
-  else
-    spamd_address_work = spamd_address;
-
-  /* check if previous spamd_address was expanded and has changed. dump cached results if so */
-  if ( spam_ok && ( prev_spamd_address_work != NULL) && (Ustrcmp(prev_spamd_address_work, spamd_address_work) != 0)) {
-    spam_ok = 0;
-  }
-
-  /* if we scanned for this username last time, just return */
-  if ( spam_ok && ( Ustrcmp(prev_user_name, user_name) == 0 ) ) {
-    if (override)
-      return OK;
-    else
-      return spam_rc;
-  };
-
-  /* make sure the eml mbox file is spooled up */
-  mbox_file = spool_mbox(&mbox_size, NULL);
-
-  if (mbox_file == NULL) {
-    /* error while spooling */
-    log_write(0, LOG_MAIN|LOG_PANIC,
-           "spam acl condition: error while creating mbox spool file");
-    return DEFER;
-  };
-
-  start = time(NULL);
-
-  /* socket does not start with '/' -> network socket */
-  if (*spamd_address_work != '/') {
-    int num_servers = 0;
-    int current_server;
-    uschar *address = NULL;
-    uschar *spamd_address_list_ptr = spamd_address_work;
-    uschar address_buffer[256];
-    spamd_address_container * spamd_address_vector[32];
-
-    /* Check how many spamd servers we have
-       and register their addresses */
-    while ((address = string_nextinlist(&spamd_address_list_ptr, &sep,
-                                        address_buffer,
-                                        sizeof(address_buffer))) != NULL) {
-
-      /* Potential memory leak as we never free the store. */
-      spamd_address_container *this_spamd =
-        (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 ) {
-        log_write(0, LOG_MAIN,
-          "spam acl condition: warning - invalid spamd address: '%s'", address);
-        continue;
-      };
-
-      spamd_address_vector[num_servers] = this_spamd;
-      num_servers++;
-      if (num_servers > 31)
-        break;
-    };
-
-    /* check if we have at least one server */
-    if (!num_servers) {
-      log_write(0, LOG_MAIN|LOG_PANIC,
-         "spam acl condition: no useable spamd server addresses in spamd_address configuration option.");
-      (void)fclose(mbox_file);
-      return DEFER;
-    };
-
-    while ( num_servers > 0 ) {
-      int i;
-
-      /* Randomly pick a server to try */
-      current_server = random_number( num_servers );
-
-      debug_printf("trying server %s, port %u\n",
-                   spamd_address_vector[current_server]->tcp_addr,
-                   spamd_address_vector[current_server]->tcp_port);
-
-      /* contact a spamd */
-      if ( (spamd_sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) {
-        log_write(0, LOG_MAIN|LOG_PANIC,
-           "spam acl condition: error creating IP socket for spamd");
-        (void)fclose(mbox_file);
-        return DEFER;
-      };
-
-      if (ip_connect( spamd_sock,
-                      AF_INET,
-                      spamd_address_vector[current_server]->tcp_addr,
-                      spamd_address_vector[current_server]->tcp_port,
-                      5 ) > -1) {
-        /* connection OK */
-        break;
-      };
-
-      log_write(0, LOG_MAIN|LOG_PANIC,
-         "spam acl condition: warning - spamd connection to %s, port %u failed: %s",
-         spamd_address_vector[current_server]->tcp_addr,
-         spamd_address_vector[current_server]->tcp_port,
-         strerror(errno));
-
-      (void)close(spamd_sock);
-
-      /* Remove the server from the list. XXX We should free the memory */
-      num_servers--;
-      for( i = current_server; i < num_servers; i++ )
-        spamd_address_vector[i] = spamd_address_vector[i+1];
-    }
-
-    if ( num_servers == 0 ) {
-      log_write(0, LOG_MAIN|LOG_PANIC, "spam acl condition: all spamd servers failed");
-      (void)fclose(mbox_file);
-      return DEFER;
-    }
-
-  }
-  else {
-    /* open the local socket */
-
-    if ((spamd_sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
-      log_write(0, LOG_MAIN|LOG_PANIC,
-                "malware acl condition: spamd: unable to acquire socket (%s)",
-                strerror(errno));
-      (void)fclose(mbox_file);
-      return DEFER;
-    }
-
-    server.sun_family = AF_UNIX;
-    Ustrcpy(server.sun_path, spamd_address_work);
-
-    if (connect(spamd_sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) {
-      log_write(0, LOG_MAIN|LOG_PANIC,
-                "malware acl condition: spamd: unable to connect to UNIX socket %s (%s)",
-                spamd_address_work, strerror(errno) );
-      (void)fclose(mbox_file);
-      (void)close(spamd_sock);
-      return DEFER;
-    }
-
-  }
-
-  if (spamd_sock == -1) {
-    log_write(0, LOG_MAIN|LOG_PANIC,
-        "programming fault, spamd_sock unexpectedly unset");
-    (void)fclose(mbox_file);
-    (void)close(spamd_sock);
-    return DEFER;
-  }
-
-  /* now we are connected to spamd on spamd_sock */
-  (void)string_format(spamd_buffer,
-           sizeof(spamd_buffer),
-           "REPORT SPAMC/1.2\r\nUser: %s\r\nContent-length: %ld\r\n\r\n",
-           user_name,
-           mbox_size);
-
-  /* send our request */
-  if (send(spamd_sock, spamd_buffer, Ustrlen(spamd_buffer), 0) < 0) {
-    (void)close(spamd_sock);
-    log_write(0, LOG_MAIN|LOG_PANIC,
-         "spam acl condition: spamd send failed: %s", strerror(errno));
-    (void)fclose(mbox_file);
-    (void)close(spamd_sock);
-    return DEFER;
-  };
-
-  /* now send the file */
-  /* spamd sometimes accepts conections but doesn't read data off
-   * the connection.  We make the file descriptor non-blocking so
-   * that the write will only write sufficient data without blocking
-   * and we poll the desciptor to make sure that we can write without
-   * blocking.  Short writes are gracefully handled and if the whole
-   * trasaction takes too long it is aborted.
-   * Note: poll() is not supported in OSX 10.2 and is reported to be
-   *       broken in more recent versions (up to 10.4).
-   */
-#ifndef NO_POLL_H
-  pollfd.fd = spamd_sock;
-  pollfd.events = POLLOUT;
-#endif
-  (void)fcntl(spamd_sock, F_SETFL, O_NONBLOCK);
-  do {
-    read = fread(spamd_buffer,1,sizeof(spamd_buffer),mbox_file);
-    if (read > 0) {
-      offset = 0;
-again:
-#ifndef NO_POLL_H
-      result = poll(&pollfd, 1, 1000);
-
-/* Patch posted by Erik ? for OS X and applied by PH */
-#else
-      select_tv.tv_sec = 1;
-      select_tv.tv_usec = 0;
-      FD_ZERO(&select_fd);
-      FD_SET(spamd_sock, &select_fd);
-      result = select(spamd_sock+1, NULL, &select_fd, NULL, &select_tv);
-#endif
-/* End Erik's patch */
-
-      if (result == -1 && errno == EINTR)
-        goto again;
-      else if (result < 1) {
-        if (result == -1)
-          log_write(0, LOG_MAIN|LOG_PANIC,
-            "spam acl condition: %s on spamd socket", strerror(errno));
-        else {
-          if (time(NULL) - start < SPAMD_TIMEOUT)
-            goto again;
-          log_write(0, LOG_MAIN|LOG_PANIC,
-            "spam acl condition: timed out writing spamd socket");
-        }
-        (void)close(spamd_sock);
-        (void)fclose(mbox_file);
-        return DEFER;
-      }
-
-      wrote = send(spamd_sock,spamd_buffer + offset,read - offset,0);
-      if (wrote == -1)
-      {
-          log_write(0, LOG_MAIN|LOG_PANIC,
-            "spam acl condition: %s on spamd socket", strerror(errno));
-        (void)close(spamd_sock);
-        (void)fclose(mbox_file);
-        return DEFER;
-      }
-      if (offset + wrote != read) {
-        offset += wrote;
-        goto again;
-      }
-    }
-  }
-  while (!feof(mbox_file) && !ferror(mbox_file));
-  if (ferror(mbox_file)) {
-    log_write(0, LOG_MAIN|LOG_PANIC,
-      "spam acl condition: error reading spool file: %s", strerror(errno));
-    (void)close(spamd_sock);
-    (void)fclose(mbox_file);
-    return DEFER;
-  }
-
-  (void)fclose(mbox_file);
-
-  /* we're done sending, close socket for writing */
-  shutdown(spamd_sock,SHUT_WR);
-
-  /* read spamd response using what's left of the timeout.
-   */
-  memset(spamd_buffer, 0, sizeof(spamd_buffer));
-  offset = 0;
-  while((i = ip_recv(spamd_sock,
-                     spamd_buffer + offset,
-                     sizeof(spamd_buffer) - offset - 1,
-                     SPAMD_TIMEOUT - time(NULL) + start)) > 0 ) {
-    offset += i;
-  }
-
-  /* error handling */
-  if((i <= 0) && (errno != 0)) {
-    log_write(0, LOG_MAIN|LOG_PANIC,
-         "spam acl condition: error reading from spamd socket: %s", strerror(errno));
-    (void)close(spamd_sock);
-    return DEFER;
-  }
-
-  /* reading done */
-  (void)close(spamd_sock);
-
-  /* dig in the spamd output and put the report in a multiline header, if requested */
-  if( sscanf(CS spamd_buffer,"SPAMD/%7s 0 EX_OK\r\nContent-length: %*u\r\n\r\n%lf/%lf\r\n%n",
-             spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3 ) {
-
-    /* try to fall back to pre-2.50 spamd output */
-    if( sscanf(CS spamd_buffer,"SPAMD/%7s 0 EX_OK\r\nSpam: %*s ; %lf / %lf\r\n\r\n%n",
-               spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3 ) {
-      log_write(0, LOG_MAIN|LOG_PANIC,
-         "spam acl condition: cannot parse spamd output");
-      return DEFER;
-    };
-  };
-
-  /* Create report. Since this is a multiline string,
-  we must hack it into shape first */
-  p = &spamd_buffer[spamd_report_offset];
-  q = spam_report_buffer;
-  while (*p != '\0') {
-    /* skip \r */
-    if (*p == '\r') {
-      p++;
-      continue;
-    };
-    *q = *p;
-    q++;
-    if (*p == '\n') {
-      /* add an extra space after the newline to ensure
-      that it is treated as a header continuation line */
-      *q = ' ';
-      q++;
-    };
-    p++;
-  };
-  /* NULL-terminate */
-  *q = '\0';
-  q--;
-  /* cut off trailing leftovers */
-  while (*q <= ' ') {
-    *q = '\0';
-    q--;
-  };
-  spam_report = spam_report_buffer;
-
-  /* create spam bar */
-  spamd_score_char = spamd_score > 0 ? '+' : '-';
-  j = abs((int)(spamd_score));
-  i = 0;
-  if( j != 0 ) {
-    while((i < j) && (i <= MAX_SPAM_BAR_CHARS))
-       spam_bar_buffer[i++] = spamd_score_char;
-  }
-  else{
-    spam_bar_buffer[0] = '/';
-    i = 1;
-  }
-  spam_bar_buffer[i] = '\0';
-  spam_bar = spam_bar_buffer;
-
-  /* create "float" spam score */
-  (void)string_format(spam_score_buffer, sizeof(spam_score_buffer),"%.1f", spamd_score);
-  spam_score = spam_score_buffer;
-
-  /* create "int" spam score */
-  j = (int)((spamd_score + 0.001)*10);
-  (void)string_format(spam_score_int_buffer, sizeof(spam_score_int_buffer), "%d", j);
-  spam_score_int = spam_score_int_buffer;
-
-  /* compare threshold against score */
-  if (spamd_score >= spamd_threshold) {
-    /* spam as determined by user's threshold */
-    spam_rc = OK;
-  }
-  else {
-    /* not spam */
-    spam_rc = FAIL;
-  };
-
-  /* remember expanded spamd_address if needed */
-  if (spamd_address_work != spamd_address) {
-    prev_spamd_address_work = string_copy(spamd_address_work);
-  }
-  /* remember user name and "been here" for it */
-  Ustrcpy(prev_user_name, user_name);
-  spam_ok = 1;
-
-  if (override) {
-    /* always return OK, no matter what the score */
-    return OK;
-  }
-  else {
-    return spam_rc;
-  };
-}
-
-#endif
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003 - 2015
+ * License: GPL
+ * Copyright (c) The Exim Maintainers 2016
+ */
+
+/* Code for calling spamassassin's spamd. Called from acl.c. */
+
+#include "exim.h"
+#ifdef WITH_CONTENT_SCAN
+#include "spam.h"
+
+uschar spam_score_buffer[16];
+uschar spam_score_int_buffer[16];
+uschar spam_bar_buffer[128];
+uschar spam_action_buffer[32];
+uschar spam_report_buffer[32600];
+uschar prev_user_name[128] = "";
+int spam_ok = 0;
+int spam_rc = 0;
+uschar *prev_spamd_address_work = NULL;
+
+static const uschar * loglabel = US"spam acl condition:";
+
+
+static int
+spamd_param_init(spamd_address_container *spamd)
+{
+/* default spamd server weight, time and priority value */
+spamd->is_rspamd = FALSE;
+spamd->is_failed = FALSE;
+spamd->weight = SPAMD_WEIGHT;
+spamd->timeout = SPAMD_TIMEOUT;
+spamd->retry = 0;
+spamd->priority = 1;
+return 0;
+}
+
+
+static int
+spamd_param(const uschar * param, spamd_address_container * spamd)
+{
+static int timesinceday = -1;
+const uschar * s;
+const uschar * name;
+
+/*XXX more clever parsing could discard embedded spaces? */
+
+if (sscanf(CCS param, "pri=%u", &spamd->priority))
+  return 0; /* OK */
+
+if (sscanf(CCS param, "weight=%u", &spamd->weight))
+  {
+  if (spamd->weight == 0) /* this server disabled: skip it */
+    return 1;
+  return 0; /* OK */
+  }
+
+if (Ustrncmp(param, "time=", 5) == 0)
+  {
+  unsigned int start_h = 0, start_m = 0, start_s = 0;
+  unsigned int end_h = 24, end_m = 0, end_s = 0;
+  unsigned int time_start, time_end;
+  const uschar * end_string;
+
+  name = US"time";
+  s = param+5;
+  if ((end_string = Ustrchr(s, '-')))
+    {
+    end_string++;
+    if (  sscanf(CS end_string, "%u.%u.%u", &end_h,   &end_m,   &end_s)   == 0
+       || sscanf(CS s,          "%u.%u.%u", &start_h, &start_m, &start_s) == 0
+       )
+      goto badval;
+    }
+  else
+    goto badval;
+
+  if (timesinceday < 0)
+    {
+    time_t now = time(NULL);
+    struct tm *tmp = localtime(&now);
+    timesinceday = tmp->tm_hour*3600 + tmp->tm_min*60 + tmp->tm_sec;
+    }
+
+  time_start = start_h*3600 + start_m*60 + start_s;
+  time_end = end_h*3600 + end_m*60 + end_s;
+
+  if (timesinceday < time_start || timesinceday >= time_end)
+    return 1; /* skip spamd server */
+
+  return 0; /* OK */
+  }
+
+if (Ustrcmp(param, "variant=rspamd") == 0)
+  {
+  spamd->is_rspamd = TRUE;
+  return 0;
+  }
+
+if (Ustrncmp(param, "tmo=", 4) == 0)
+  {
+  int sec = readconf_readtime((s = param+4), '\0', FALSE);
+  name = US"timeout";
+  if (sec < 0)
+    goto badval;
+  spamd->timeout = sec;
+  return 0;
+  }
+
+if (Ustrncmp(param, "retry=", 6) == 0)
+  {
+  int sec = readconf_readtime((s = param+6), '\0', FALSE);
+  name = US"retry";
+  if (sec < 0)
+    goto badval;
+  spamd->retry = sec;
+  return 0;
+  }
+
+log_write(0, LOG_MAIN, "%s warning - invalid spamd parameter: '%s'",
+  loglabel, param);
+return -1; /* syntax error */
+
+badval:
+  log_write(0, LOG_MAIN,
+    "%s warning - invalid spamd %s value: '%s'", loglabel, name, s);
+  return -1; /* syntax error */
+}
+
+
+static int
+spamd_get_server(spamd_address_container ** spamds, int num_servers)
+{
+unsigned int i;
+spamd_address_container * sd;
+long rnd, weights;
+unsigned pri;
+static BOOL srandomed = FALSE;
+
+/* speedup, if we have only 1 server */
+if (num_servers == 1)
+  return (spamds[0]->is_failed ? -1 : 0);
+
+/* init ranmod */
+if (!srandomed)
+  {
+  struct timeval tv;
+  gettimeofday(&tv, NULL);
+  srandom((unsigned int)(tv.tv_usec/1000));
+  srandomed = TRUE;
+  }
+
+/* scan for highest pri */
+for (pri = 0, i = 0; i < num_servers; i++)
+  {
+  sd = spamds[i];
+  if (!sd->is_failed && sd->priority > pri) pri = sd->priority;
+  }
+
+/* get sum of weights */
+for (weights = 0, i = 0; i < num_servers; i++)
+  {
+  sd = spamds[i];
+  if (!sd->is_failed && sd->priority == pri) weights += sd->weight;
+  }
+if (weights == 0)      /* all servers failed */
+  return -1;
+
+for (rnd = random() % weights, i = 0; i < num_servers; i++)
+  {
+  sd = spamds[i];
+  if (!sd->is_failed && sd->priority == pri)
+    if ((rnd -= sd->weight) <= 0)
+      return i;
+  }
+
+log_write(0, LOG_MAIN|LOG_PANIC,
+  "%s unknown error (memory/cpu corruption?)", loglabel);
+return -1;
+}
+
+
+int
+spam(const uschar **listptr)
+{
+int sep = 0;
+const uschar *list = *listptr;
+uschar *user_name;
+uschar user_name_buffer[128];
+unsigned long mbox_size;
+FILE *mbox_file;
+int spamd_sock = -1;
+uschar spamd_buffer[32600];
+int i, j, offset, result;
+uschar spamd_version[8];
+uschar spamd_short_result[8];
+uschar spamd_score_char;
+double spamd_threshold, spamd_score, spamd_reject_score;
+int spamd_report_offset;
+uschar *p,*q;
+int override = 0;
+time_t start;
+size_t read, wrote;
+#ifndef NO_POLL_H
+struct pollfd pollfd;
+#else                               /* Patch posted by Erik ? for OS X */
+struct timeval select_tv;         /* and applied by PH */
+fd_set select_fd;
+#endif
+uschar *spamd_address_work;
+spamd_address_container * sd;
+
+/* stop compiler warning */
+result = 0;
+
+/* find the username from the option list */
+if ((user_name = string_nextinlist(&list, &sep,
+                                  user_name_buffer,
+                                  sizeof(user_name_buffer))) == NULL)
+  {
+  /* no username given, this means no scanning should be done */
+  return FAIL;
+  }
+
+/* if username is "0" or "false", do not scan */
+if ( (Ustrcmp(user_name,"0") == 0) ||
+     (strcmpic(user_name,US"false") == 0) )
+  return FAIL;
+
+/* if there is an additional option, check if it is "true" */
+if (strcmpic(list,US"true") == 0)
+  /* in that case, always return true later */
+  override = 1;
+
+/* expand spamd_address if needed */
+if (*spamd_address == '$')
+  {
+  spamd_address_work = expand_string(spamd_address);
+  if (spamd_address_work == NULL)
+    {
+    log_write(0, LOG_MAIN|LOG_PANIC,
+      "%s spamd_address starts with $, but expansion failed: %s",
+      loglabel, expand_string_message);
+    return DEFER;
+    }
+  }
+else
+  spamd_address_work = spamd_address;
+
+DEBUG(D_acl) debug_printf_indent("spamd: addrlist '%s'\n", spamd_address_work);
+
+/* check if previous spamd_address was expanded and has changed. dump cached results if so */
+if (  spam_ok
+   && prev_spamd_address_work != NULL
+   && Ustrcmp(prev_spamd_address_work, spamd_address_work) != 0
+   )
+  spam_ok = 0;
+
+/* if we scanned for this username last time, just return */
+if (spam_ok && Ustrcmp(prev_user_name, user_name) == 0)
+  return override ? OK : spam_rc;
+
+/* make sure the eml mbox file is spooled up */
+mbox_file = spool_mbox(&mbox_size, NULL);
+
+if (mbox_file == NULL)
+  {
+  /* error while spooling */
+  log_write(0, LOG_MAIN|LOG_PANIC,
+        "%s error while creating mbox spool file", loglabel);
+  return DEFER;
+  }
+
+start = time(NULL);
+
+  {
+  int num_servers = 0;
+  int current_server;
+  uschar * address;
+  const uschar * spamd_address_list_ptr = spamd_address_work;
+  spamd_address_container * spamd_address_vector[32];
+
+  /* Check how many spamd servers we have
+     and register their addresses */
+  sep = 0;                             /* default colon-sep */
+  while ((address = string_nextinlist(&spamd_address_list_ptr, &sep,
+                                     NULL, 0)) != NULL)
+    {
+    const uschar * sublist;
+    int sublist_sep = -(int)' ';       /* default space-sep */
+    unsigned args;
+    uschar * s;
+
+    DEBUG(D_acl) debug_printf_indent("spamd: addr entry '%s'\n", address);
+    sd = (spamd_address_container *)store_get(sizeof(spamd_address_container));
+
+    for (sublist = address, args = 0, spamd_param_init(sd);
+        (s = string_nextinlist(&sublist, &sublist_sep, NULL, 0));
+        args++
+        )
+      {
+       DEBUG(D_acl) debug_printf_indent("spamd:  addr parm '%s'\n", s);
+       switch (args)
+       {
+       case 0:   sd->hostspec = s;
+                 if (*s == '/') args++;        /* local; no port */
+                 break;
+       case 1:   sd->hostspec = string_sprintf("%s %s", sd->hostspec, s);
+                 break;
+       default:  spamd_param(s, sd);
+                 break;
+       }
+      }
+    if (args < 2)
+      {
+      log_write(0, LOG_MAIN,
+       "%s warning - invalid spamd address: '%s'", loglabel, address);
+      continue;
+      }
+
+    spamd_address_vector[num_servers] = sd;
+    if (++num_servers > 31)
+      break;
+    }
+
+  /* check if we have at least one server */
+  if (!num_servers)
+    {
+    log_write(0, LOG_MAIN|LOG_PANIC,
+       "%s no useable spamd server addresses in spamd_address configuration option.",
+       loglabel);
+    goto defer;
+    }
+
+  current_server = spamd_get_server(spamd_address_vector, num_servers);
+  sd = spamd_address_vector[current_server];
+  for(;;)
+    {
+    uschar * errstr;
+
+    DEBUG(D_acl) debug_printf_indent("spamd: trying server %s\n", sd->hostspec);
+
+    for (;;)
+      {
+      if (  (spamd_sock = ip_streamsocket(sd->hostspec, &errstr, 5)) >= 0
+         || sd->retry <= 0
+        )
+       break;
+      DEBUG(D_acl) debug_printf_indent("spamd: server %s: retry conn\n", sd->hostspec);
+      while (sd->retry > 0) sd->retry = sleep(sd->retry);
+      }
+    if (spamd_sock >= 0)
+      break;
+
+    log_write(0, LOG_MAIN, "%s spamd: %s", loglabel, errstr);
+    sd->is_failed = TRUE;
+
+    current_server = spamd_get_server(spamd_address_vector, num_servers);
+    if (current_server < 0)
+      {
+      log_write(0, LOG_MAIN|LOG_PANIC, "%s all spamd servers failed", loglabel);
+      goto defer;
+      }
+    sd = spamd_address_vector[current_server];
+    }
+  }
+
+(void)fcntl(spamd_sock, F_SETFL, O_NONBLOCK);
+/* now we are connected to spamd on spamd_sock */
+if (sd->is_rspamd)
+  {                            /* rspamd variant */
+  uschar *req_str;
+  const uschar * helo;
+  const uschar * fcrdns;
+  const uschar * authid;
+
+  req_str = string_sprintf("CHECK RSPAMC/1.3\r\nContent-length: %lu\r\n"
+    "Queue-Id: %s\r\nFrom: <%s>\r\nRecipient-Number: %d\r\n",
+    mbox_size, message_id, sender_address, recipients_count);
+  for (i = 0; i < recipients_count; i ++)
+    req_str = string_sprintf("%sRcpt: <%s>\r\n", req_str, recipients_list[i].address);
+  if ((helo = expand_string(US"$sender_helo_name")) != NULL && *helo != '\0')
+    req_str = string_sprintf("%sHelo: %s\r\n", req_str, helo);
+  if ((fcrdns = expand_string(US"$sender_host_name")) != NULL && *fcrdns != '\0')
+    req_str = string_sprintf("%sHostname: %s\r\n", req_str, fcrdns);
+  if (sender_host_address != NULL)
+    req_str = string_sprintf("%sIP: %s\r\n", req_str, sender_host_address);
+  if ((authid = expand_string(US"$authenticated_id")) != NULL && *authid != '\0')
+    req_str = string_sprintf("%sUser: %s\r\n", req_str, authid);
+  req_str = string_sprintf("%s\r\n", req_str);
+  wrote = send(spamd_sock, req_str, Ustrlen(req_str), 0);
+  }
+else
+  {                            /* spamassassin variant */
+  (void)string_format(spamd_buffer,
+         sizeof(spamd_buffer),
+         "REPORT SPAMC/1.2\r\nUser: %s\r\nContent-length: %ld\r\n\r\n",
+         user_name,
+         mbox_size);
+  /* send our request */
+  wrote = send(spamd_sock, spamd_buffer, Ustrlen(spamd_buffer), 0);
+  }
+
+if (wrote == -1)
+  {
+  (void)close(spamd_sock);
+  log_write(0, LOG_MAIN|LOG_PANIC,
+       "%s spamd %s send failed: %s", loglabel, callout_address, strerror(errno));
+  goto defer;
+  }
+
+/* now send the file */
+/* spamd sometimes accepts connections but doesn't read data off
+ * the connection.  We make the file descriptor non-blocking so
+ * that the write will only write sufficient data without blocking
+ * and we poll the descriptor to make sure that we can write without
+ * blocking.  Short writes are gracefully handled and if the whole
+ * transaction takes too long it is aborted.
+ * Note: poll() is not supported in OSX 10.2 and is reported to be
+ *       broken in more recent versions (up to 10.4).
+ */
+#ifndef NO_POLL_H
+pollfd.fd = spamd_sock;
+pollfd.events = POLLOUT;
+#endif
+(void)fcntl(spamd_sock, F_SETFL, O_NONBLOCK);
+do
+  {
+  read = fread(spamd_buffer,1,sizeof(spamd_buffer),mbox_file);
+  if (read > 0)
+    {
+    offset = 0;
+again:
+#ifndef NO_POLL_H
+    result = poll(&pollfd, 1, 1000);
+
+/* Patch posted by Erik ? for OS X and applied by PH */
+#else
+    select_tv.tv_sec = 1;
+    select_tv.tv_usec = 0;
+    FD_ZERO(&select_fd);
+    FD_SET(spamd_sock, &select_fd);
+    result = select(spamd_sock+1, NULL, &select_fd, NULL, &select_tv);
+#endif
+/* End Erik's patch */
+
+    if (result == -1 && errno == EINTR)
+      goto again;
+    else if (result < 1)
+      {
+      if (result == -1)
+       log_write(0, LOG_MAIN|LOG_PANIC,
+         "%s %s on spamd %s socket", loglabel, callout_address, strerror(errno));
+      else
+       {
+       if (time(NULL) - start < sd->timeout)
+         goto again;
+       log_write(0, LOG_MAIN|LOG_PANIC,
+         "%s timed out writing spamd %s, socket", loglabel, callout_address);
+       }
+      (void)close(spamd_sock);
+      goto defer;
+      }
+
+    wrote = send(spamd_sock,spamd_buffer + offset,read - offset,0);
+    if (wrote == -1)
+      {
+      log_write(0, LOG_MAIN|LOG_PANIC,
+         "%s %s on spamd %s socket", loglabel, callout_address, strerror(errno));
+      (void)close(spamd_sock);
+      goto defer;
+      }
+    if (offset + wrote != read)
+      {
+      offset += wrote;
+      goto again;
+      }
+    }
+  }
+while (!feof(mbox_file) && !ferror(mbox_file));
+
+if (ferror(mbox_file))
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC,
+    "%s error reading spool file: %s", loglabel, strerror(errno));
+  (void)close(spamd_sock);
+  goto defer;
+  }
+
+(void)fclose(mbox_file);
+
+/* we're done sending, close socket for writing */
+if (!sd->is_rspamd)
+  shutdown(spamd_sock,SHUT_WR);
+
+/* read spamd response using what's left of the timeout.  */
+memset(spamd_buffer, 0, sizeof(spamd_buffer));
+offset = 0;
+while ((i = ip_recv(spamd_sock,
+                  spamd_buffer + offset,
+                  sizeof(spamd_buffer) - offset - 1,
+                  sd->timeout - time(NULL) + start)) > 0)
+  offset += i;
+spamd_buffer[offset] = '\0';   /* guard byte */
+
+/* error handling */
+if (i <= 0 && errno != 0)
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC,
+       "%s error reading from spamd %s, socket: %s", loglabel, callout_address, strerror(errno));
+  (void)close(spamd_sock);
+  return DEFER;
+  }
+
+/* reading done */
+(void)close(spamd_sock);
+
+if (sd->is_rspamd)
+  {                            /* rspamd variant of reply */
+  int r;
+  if (  (r = sscanf(CS spamd_buffer,
+         "RSPAMD/%7s 0 EX_OK\r\nMetric: default; %7s %lf / %lf / %lf\r\n%n",
+         spamd_version, spamd_short_result, &spamd_score, &spamd_threshold,
+         &spamd_reject_score, &spamd_report_offset)) != 5
+     || spamd_report_offset >= offset          /* verify within buffer */
+     )
+    {
+    log_write(0, LOG_MAIN|LOG_PANIC,
+             "%s cannot parse spamd %s, output: %d", loglabel, callout_address, r);
+    return DEFER;
+    }
+  /* now parse action */
+  p = &spamd_buffer[spamd_report_offset];
+
+  if (Ustrncmp(p, "Action: ", sizeof("Action: ") - 1) == 0)
+    {
+    p += sizeof("Action: ") - 1;
+    q = &spam_action_buffer[0];
+    while (*p && *p != '\r' && (q - spam_action_buffer) < sizeof(spam_action_buffer) - 1)
+      *q++ = *p++;
+    *q = '\0';
+    }
+  }
+else
+  {                            /* spamassassin */
+  /* dig in the spamd output and put the report in a multiline header,
+  if requested */
+  if (sscanf(CS spamd_buffer,
+       "SPAMD/%7s 0 EX_OK\r\nContent-length: %*u\r\n\r\n%lf/%lf\r\n%n",
+       spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3)
+    {
+      /* try to fall back to pre-2.50 spamd output */
+      if (sscanf(CS spamd_buffer,
+          "SPAMD/%7s 0 EX_OK\r\nSpam: %*s ; %lf / %lf\r\n\r\n%n",
+          spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3)
+       {
+       log_write(0, LOG_MAIN|LOG_PANIC,
+                 "%s cannot parse spamd %s output", loglabel, callout_address);
+       return DEFER;
+       }
+    }
+
+  Ustrcpy(spam_action_buffer,
+    spamd_score >= spamd_threshold ? "reject" : "no action");
+  }
+
+/* Create report. Since this is a multiline string,
+we must hack it into shape first */
+p = &spamd_buffer[spamd_report_offset];
+q = spam_report_buffer;
+while (*p != '\0')
+  {
+  /* skip \r */
+  if (*p == '\r')
+    {
+    p++;
+    continue;
+    }
+  *q++ = *p;
+  if (*p++ == '\n')
+    {
+    /* add an extra space after the newline to ensure
+    that it is treated as a header continuation line */
+    *q++ = ' ';
+    }
+  }
+/* NULL-terminate */
+*q-- = '\0';
+/* cut off trailing leftovers */
+while (*q <= ' ')
+  *q-- = '\0';
+
+spam_report = spam_report_buffer;
+spam_action = spam_action_buffer;
+
+/* create spam bar */
+spamd_score_char = spamd_score > 0 ? '+' : '-';
+j = abs((int)(spamd_score));
+i = 0;
+if (j != 0)
+  while ((i < j) && (i <= MAX_SPAM_BAR_CHARS))
+     spam_bar_buffer[i++] = spamd_score_char;
+else
+  {
+  spam_bar_buffer[0] = '/';
+  i = 1;
+  }
+spam_bar_buffer[i] = '\0';
+spam_bar = spam_bar_buffer;
+
+/* create "float" spam score */
+(void)string_format(spam_score_buffer, sizeof(spam_score_buffer),
+       "%.1f", spamd_score);
+spam_score = spam_score_buffer;
+
+/* create "int" spam score */
+j = (int)((spamd_score + 0.001)*10);
+(void)string_format(spam_score_int_buffer, sizeof(spam_score_int_buffer),
+       "%d", j);
+spam_score_int = spam_score_int_buffer;
+
+/* compare threshold against score */
+spam_rc = spamd_score >= spamd_threshold
+  ? OK /* spam as determined by user's threshold */
+  : FAIL;      /* not spam */
+
+/* remember expanded spamd_address if needed */
+if (spamd_address_work != spamd_address)
+  prev_spamd_address_work = string_copy(spamd_address_work);
+
+/* remember user name and "been here" for it */
+Ustrcpy(prev_user_name, user_name);
+spam_ok = 1;
+
+return override
+  ? OK         /* always return OK, no matter what the score */
+  : spam_rc;
+
+defer:
+  (void)fclose(mbox_file);
+  return DEFER;
+}
+
+#endif
+/* vi: aw ai sw=2
+*/
index ba700c8..2fe7380 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003-???? */
+/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003 - 2015 */
 /* License: GPL */
 
 /* spam defines */
 /* timeout for reading and writing spamd */
 #define SPAMD_TIMEOUT 120
 
-/* maximum length of the spam bar */
+/* maximum length of the spam bar, please update the
+ * spec, the max length is mentioned there */
 #define MAX_SPAM_BAR_CHARS 50
 
 /* SHUT_WR seems to be undefined on Unixware ? */
 #ifndef SHUT_WR
-#define SHUT_WR 1
+# define SHUT_WR 1
 #endif
 
-typedef struct spamd_address_container {
-  uschar tcp_addr[24];
-  unsigned int tcp_port;
+/* default weight */
+#define SPAMD_WEIGHT 1
+
+typedef struct spamd_address_container
+{
+  uschar * hostspec;
+  int is_rspamd:1;
+  int is_failed:1;
+  unsigned int weight;
+  unsigned int timeout;
+  unsigned int retry;
+  unsigned int priority;
 } spamd_address_container;
 
 #endif
index 7167f57..9ab56af 100644 (file)
--- a/src/spf.c
+++ b/src/spf.c
@@ -4,7 +4,9 @@
 
 /* Experimental SPF support.
    Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 - 2014
-   License: GPL */
+   License: GPL
+   Copyright (c) The Exim Maintainers 2016
+*/
 
 /* Code for calling spf checks via libspf-alt. Called from acl.c. */
 
@@ -74,9 +76,9 @@ int spf_init(uschar *spf_helo_domain, uschar *spf_remote_addr) {
    context (if any), retrieves the result, sets up expansion
    strings and evaluates the condition outcome. */
 
-int spf_process(uschar **listptr, uschar *spf_envelope_sender, int action) {
+int spf_process(const uschar **listptr, uschar *spf_envelope_sender, int action) {
   int sep = 0;
-  uschar *list = *listptr;
+  const uschar *list = *listptr;
   uschar *spf_result_id;
   uschar spf_result_id_buffer[128];
   int rc = SPF_RESULT_PERMERROR;
index 0ce5d00..2a7c040 100644 (file)
--- a/src/spf.h
+++ b/src/spf.h
@@ -4,13 +4,15 @@
 
 /* Experimental SPF support.
    Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004
-   License: GPL */
+   License: GPL
+   Copyright (c) The Exim Maintainers 2016
+*/
 
 #ifdef EXPERIMENTAL_SPF
 
 /* Yes, we do have ns_type. spf.h redefines it if we don't set this. Doh */
 #ifndef HAVE_NS_TYPE
-#define HAVE_NS_TYPE
+# define HAVE_NS_TYPE
 #endif
 #include <spf2/spf.h>
 
@@ -24,7 +26,7 @@ typedef struct spf_result_id {
 
 /* prototypes */
 int spf_init(uschar *,uschar *);
-int spf_process(uschar **, uschar *, int);
+int spf_process(const uschar **, uschar *, int);
 
 #define SPF_PROCESS_NORMAL  0
 #define SPF_PROCESS_GUESS   1
index 6dcb512..6ed5664 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for reading spool files. When compiling for a utility (eximon),
@@ -25,19 +25,21 @@ fact it won't be written to. Just in case there's a major disaster (e.g.
 overwriting some other file descriptor with the value of this one), open it
 with append.
 
+As called by deliver_message() (at least) we are operating as root.
+
 Argument: the id of the message
-Returns:  TRUE if file successfully opened and locked
+Returns:  fd if file successfully opened and locked, else -1
 
-Side effect: deliver_datafile is set to the fd of the open file.
+Side effect: message_subdir is set for the (possibly split) spool directory
 */
 
-BOOL
+int
 spool_open_datafile(uschar *id)
 {
 int i;
 struct stat statbuf;
 flock_t lock_data;
-uschar spoolname[256];
+int fd;
 
 /* If split_spool_directory is set, first look for the file in the appropriate
 sub-directory of the input directory. If it is not found there, try the input
@@ -48,22 +50,33 @@ splitting state. */
 
 for (i = 0; i < 2; i++)
   {
+  uschar * fname;
   int save_errno;
-  message_subdir[0] = (split_spool_directory == (i == 0))? id[5] : 0;
-  sprintf(CS spoolname, "%s/input/%s/%s-D", spool_directory, message_subdir, id);
-  deliver_datafile = Uopen(spoolname, O_RDWR | O_APPEND, 0);
-  if (deliver_datafile >= 0) break;
+
+  message_subdir[0] = split_spool_directory == i ? '\0' : id[5];
+  fname = spool_fname(US"input", message_subdir, id, US"-D");
+  DEBUG(D_deliver) debug_printf("Trying spool file %s\n", fname);
+
+  if ((fd = Uopen(fname,
+#ifdef O_CLOEXEC
+                     O_CLOEXEC |
+#endif
+                     O_RDWR | O_APPEND, 0)) >= 0)
+    break;
   save_errno = errno;
   if (errno == ENOENT)
     {
     if (i == 0) continue;
     if (!queue_running)
-      log_write(0, LOG_MAIN, "Spool file %s-D not found", id);
+      log_write(0, LOG_MAIN, "Spool%s%s file %s-D not found",
+       *queue_name ? US" Q=" : US"",
+       *queue_name ? queue_name : US"",
+       id);
     }
-  else log_write(0, LOG_MAIN, "Spool error for %s: %s", spoolname,
-    strerror(errno));
+  else
+    log_write(0, LOG_MAIN, "Spool error for %s: %s", fname, strerror(errno));
   errno = save_errno;
-  return FALSE;
+  return -1;
   }
 
 /* File is open and message_subdir is set. Set the close-on-exec flag, and lock
@@ -74,35 +87,35 @@ an open file descriptor (at least, I think that's the Cygwin story). On real
 Unix systems it doesn't make any difference as long as Exim is consistent in
 what it locks. */
 
-(void)fcntl(deliver_datafile, F_SETFD, fcntl(deliver_datafile, F_GETFD) |
-  FD_CLOEXEC);
+#ifndef O_CLOEXEC
+(void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
+#endif
 
 lock_data.l_type = F_WRLCK;
 lock_data.l_whence = SEEK_SET;
 lock_data.l_start = 0;
 lock_data.l_len = SPOOL_DATA_START_OFFSET;
 
-if (fcntl(deliver_datafile, F_SETLK, &lock_data) < 0)
+if (fcntl(fd, F_SETLK, &lock_data) < 0)
   {
   log_write(L_skip_delivery,
             LOG_MAIN,
             "Spool file is locked (another process is handling this message)");
-  (void)close(deliver_datafile);
-  deliver_datafile = -1;
+  (void)close(fd);
   errno = 0;
-  return FALSE;
+  return -1;
   }
 
 /* Get the size of the data; don't include the leading filename line
 in the count, but add one for the newline before the data. */
 
-if (fstat(deliver_datafile, &statbuf) == 0)
+if (fstat(fd, &statbuf) == 0)
   {
   message_body_size = statbuf.st_size - SPOOL_DATA_START_OFFSET;
   message_size = message_body_size + 1;
   }
 
-return TRUE;
+return fd;
 }
 #endif  /* COMPILE_UTILITY */
 
@@ -209,6 +222,8 @@ have been the cause of that incident, but in any case, this code must be robust
 against such an event, and if such a file is encountered, it must be treated as
 malformed.
 
+As called from deliver_message() (at least) we are running as root.
+
 Arguments:
   name          name of the header file, including the -H
   read_headers  TRUE if in-store header structures are to be built
@@ -217,7 +232,7 @@ Arguments:
 Returns:        spool_read_OK        success
                 spool_read_notopen   open failed
                 spool_read_enverror  error in the envelope portion
-                spool_read_hdrdrror  error in the header portion
+                spool_read_hdrerror  error in the header portion
 */
 
 int
@@ -284,22 +299,32 @@ dkim_collect_input = FALSE;
 
 #ifdef SUPPORT_TLS
 tls_in.certificate_verified = FALSE;
+# ifdef EXPERIMENTAL_DANE
+tls_in.dane_verified = FALSE;
+# endif
 tls_in.cipher = NULL;
-tls_in.ourcert = NULL;
-tls_in.peercert = NULL;
+# ifndef COMPILE_UTILITY       /* tls support fns not built in */
+tls_free_cert(&tls_in.ourcert);
+tls_free_cert(&tls_in.peercert);
+# endif
 tls_in.peerdn = NULL;
 tls_in.sni = NULL;
 tls_in.ocsp = OCSP_NOT_REQ;
 #endif
 
 #ifdef WITH_CONTENT_SCAN
+spam_bar = NULL;
+spam_score = NULL;
 spam_score_int = NULL;
 #endif
 
-#ifdef EXPERIMENTAL_DSN
+#if defined(SUPPORT_I18N) && !defined(COMPILE_UTILITY)
+message_smtputf8 = FALSE;
+message_utf8_downconvert = 0;
+#endif
+
 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
@@ -308,12 +333,12 @@ and unsplit directories, as for the data file above. */
 for (n = 0; n < 2; n++)
   {
   if (!subdir_set)
-    message_subdir[0] = (split_spool_directory == (n == 0))? name[5] : 0;
-  sprintf(CS big_buffer, "%s/input/%s/%s", spool_directory, message_subdir,
-    name);
-  f = Ufopen(big_buffer, "rb");
-  if (f != NULL) break;
-  if (n != 0 || subdir_set || errno != ENOENT) return spool_read_notopen;
+    message_subdir[0] = split_spool_directory == (n == 0) ? name[5] : 0;
+
+  if ((f = Ufopen(spool_fname(US"input", message_subdir, name, US""), "rb")))
+    break;
+  if (n != 0 || subdir_set || errno != ENOENT)
+    return spool_read_notopen;
   }
 
 errno = 0;
@@ -357,6 +382,7 @@ originator_login = string_copy(big_buffer);
 originator_uid = (uid_t)uid;
 originator_gid = (gid_t)gid;
 
+/* envelope from */
 if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
 n = Ustrlen(big_buffer);
 if (n < 3 || big_buffer[0] != '<' || big_buffer[n-2] != '>')
@@ -366,6 +392,7 @@ sender_address = store_get(n-2);
 Ustrncpy(sender_address, big_buffer+1, n-3);
 sender_address[n-3] = 0;
 
+/* time */
 if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
 if (sscanf(CS big_buffer, "%d %d", &received_time, &warning_count) != 2)
   goto SPOOL_FORMAT_ERROR;
@@ -394,9 +421,22 @@ version that left new-style flags written on the spool. */
 p = big_buffer + 2;
 for (;;)
   {
+  int len;
   if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
   if (big_buffer[0] != '-') break;
-  big_buffer[Ustrlen(big_buffer) - 1] = 0;
+  while (  (len = Ustrlen(big_buffer)) == big_buffer_size-1
+       && big_buffer[len-1] != '\n'
+       )
+    {  /* buffer not big enough for line; certs make this possible */
+    uschar * buf;
+    if (big_buffer_size >= BIG_BUFFER_SIZE*4) goto SPOOL_READ_ERROR;
+    buf = store_get_perm(big_buffer_size *= 2);
+    memcpy(buf, big_buffer, --len);
+    big_buffer = buf;
+    if (Ufgets(big_buffer+len, big_buffer_size-len, f) == NULL)
+      goto SPOOL_READ_ERROR;
+    }
+  big_buffer[len-1] = 0;
 
   switch(big_buffer[1])
     {
@@ -445,19 +485,24 @@ for (;;)
 
     else if (Ustrncmp(p, "cl ", 3) == 0)
       {
-      int index, count;
-      uschar name[20];   /* Need plenty of space for %d format */
-      tree_node *node;
-      if (sscanf(CS big_buffer + 5, "%d %d", &index, &count) != 2)
+      unsigned index, count;
+      uschar name[20];   /* Need plenty of space for %u format */
+      tree_node * node;
+      if (  sscanf(CS big_buffer + 5, "%u %u", &index, &count) != 2
+        || index >= 20
+        || count > 16384       /* arbitrary limit on variable size */
+         )
         goto SPOOL_FORMAT_ERROR;
       if (index < 10)
-        (void) string_format(name, sizeof(name), "%c%d", 'c', index);
-      else if (index < 20) /* ignore out-of-range index */
-        (void) string_format(name, sizeof(name), "%c%d", 'm', index - 10);
+        (void) string_format(name, sizeof(name), "%c%u", 'c', index);
+      else
+        (void) string_format(name, sizeof(name), "%c%u", 'm', index - 10);
       node = acl_var_create(name);
       node->data.ptr = store_get(count + 1);
+      /* We sanity-checked the count, so disable the Coverity error */
+      /* coverity[tainted_data] */
       if (fread(node->data.ptr, 1, count+1, f) < count) goto SPOOL_READ_ERROR;
-      ((uschar*)node->data.ptr)[count] = 0;
+      (US node->data.ptr)[count] = '\0';
       }
     break;
 
@@ -466,33 +511,28 @@ for (;;)
       body_linecount = Uatoi(big_buffer + 15);
     else if (Ustrncmp(p, "ody_zerocount", 13) == 0)
       body_zerocount = Uatoi(big_buffer + 15);
-    #ifdef EXPERIMENTAL_BRIGHTMAIL
+#ifdef EXPERIMENTAL_BRIGHTMAIL
     else if (Ustrncmp(p, "mi_verdicts ", 12) == 0)
       bmi_verdicts = string_copy(big_buffer + 14);
-    #endif
+#endif
     break;
 
     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);
-      }
+      dsn_ret= atoi(CS 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;
-      sscanf(big_buffer+7, TIME_T_FMT, &deliver_frozen_at);
+      if (sscanf(CS big_buffer+7, TIME_T_FMT, &deliver_frozen_at) != 1)
+       goto SPOOL_READ_ERROR;
       }
     break;
 
@@ -555,24 +595,32 @@ for (;;)
     case 's':
     if (Ustrncmp(p, "ender_set_untrusted", 19) == 0)
       sender_set_untrusted = TRUE;
-    #ifdef WITH_CONTENT_SCAN
+#ifdef WITH_CONTENT_SCAN
+    else if (Ustrncmp(p, "pam_bar ", 8) == 0)
+      spam_bar = string_copy(big_buffer + 10);
+    else if (Ustrncmp(p, "pam_score ", 10) == 0)
+      spam_score = string_copy(big_buffer + 12);
     else if (Ustrncmp(p, "pam_score_int ", 14) == 0)
       spam_score_int = string_copy(big_buffer + 16);
-    #endif
+#endif
+#if defined(SUPPORT_I18N) && !defined(COMPILE_UTILITY)
+    else if (Ustrncmp(p, "mtputf8", 7) == 0)
+      message_smtputf8 = TRUE;
+#endif
     break;
 
-    #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
     case 't':
     if (Ustrncmp(p, "ls_certificate_verified", 23) == 0)
       tls_in.certificate_verified = TRUE;
     else if (Ustrncmp(p, "ls_cipher", 9) == 0)
       tls_in.cipher = string_copy(big_buffer + 12);
-#ifndef COMPILE_UTILITY
+# ifndef COMPILE_UTILITY       /* tls support fns not built in */
     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
+# 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)
@@ -580,7 +628,16 @@ for (;;)
     else if (Ustrncmp(p, "ls_ocsp", 7) == 0)
       tls_in.ocsp = big_buffer[10] - '0';
     break;
-    #endif
+#endif
+
+#if defined(SUPPORT_I18N) && !defined(COMPILE_UTILITY)
+    case 'u':
+    if (Ustrncmp(p, "tf8_downcvt", 11) == 0)
+      message_utf8_downconvert = 1;
+    else if (Ustrncmp(p, "tf8_optdowncvt", 15) == 0)
+      message_utf8_downconvert = -1;
+    break;
+#endif
 
     default:    /* Present because some compilers complain if all */
     break;      /* possibilities are not covered. */
@@ -615,10 +672,12 @@ DEBUG(D_deliver)
 #endif  /* COMPILE_UTILITY */
 
 /* After reading the tree, the next line has not yet been read into the
-buffer. It contains the count of recipients which follow on separate lines. */
+buffer. It contains the count of recipients which follow on separate lines.
+Apply an arbitrary sanity check.*/
 
 if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
-if (sscanf(CS big_buffer, "%d", &rcount) != 1) goto SPOOL_FORMAT_ERROR;
+if (sscanf(CS big_buffer, "%d", &rcount) != 1 || rcount > 16384)
+  goto SPOOL_FORMAT_ERROR;
 
 #ifndef COMPILE_UTILITY
 DEBUG(D_deliver) debug_printf("recipients_count=%d\n", rcount);
@@ -627,14 +686,16 @@ DEBUG(D_deliver) debug_printf("recipients_count=%d\n", rcount);
 recipients_list_max = rcount;
 recipients_list = store_get(rcount * sizeof(recipient_item));
 
+/* We sanitised the count and know we have enough memory, so disable
+the Coverity error on recipients_count */
+/* coverity[tainted_data] */
+
 for (recipients_count = 0; recipients_count < rcount; recipients_count++)
   {
   int nn;
   int pno = -1;
-  #ifdef EXPERIMENTAL_DSN
   int dsn_flags = 0;
   uschar *orcpt = NULL;
-  #endif
   uschar *errors_to = NULL;
   uschar *p;
 
@@ -711,11 +772,9 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
     {
     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
+#if !defined (COMPILE_UTILITY)
+    DEBUG(D_deliver) debug_printf("**** SPOOL_IN - Exim 4 standard format spoolfile\n");
+#endif
 
     (void)sscanf(CS p+1, "%d", &flags);
 
@@ -729,11 +788,10 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
         {
         p -= len;
         errors_to = string_copy(p);
-        }      
+        }
       }
 
     *(--p) = 0;   /* Terminate address */
-#ifdef EXPERIMENTAL_DSN
     if ((flags & 0x02) != 0)      /* one_time data exists */
       {
       int len;
@@ -744,18 +802,14 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
         {
         p -= len;
         orcpt = string_copy(p);
-        }      
+        }
       }
 
     *(--p) = 0;   /* Terminate address */
-#endif  /* EXPERIMENTAL_DSN */
     }
-#ifdef EXPERIMENTAL_DSN
-  #ifndef COMPILE_UTILITY
+#if !defined(COMPILE_UTILITY)
   else
-    {
-       DEBUG(D_deliver) debug_printf("**** SPOOL_IN - No additional fields\n");
-    }
+    { DEBUG(D_deliver) debug_printf("**** SPOOL_IN - No additional fields\n"); }
 
   if ((orcpt != NULL) || (dsn_flags != 0))
     {
@@ -767,16 +821,13 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
     DEBUG(D_deliver) debug_printf("**** SPOOL_IN - address: |%s| errorsto: |%s|\n",
       big_buffer, errors_to);
     }
-  #endif  /* COMPILE_UTILITY */
-#endif  /* EXPERIMENTAL_DSN */
+#endif
 
   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,
@@ -859,9 +910,9 @@ if (errno != 0)
   {
   n = errno;
 
-  #ifndef COMPILE_UTILITY
+#ifndef COMPILE_UTILITY
   DEBUG(D_any) debug_printf("Error while reading spool file %s\n", name);
-  #endif  /* COMPILE_UTILITY */
+#endif  /* COMPILE_UTILITY */
 
   fclose(f);
   errno = n;
dissimilarity index 86%
index 12cf3d4..b1de39e 100644 (file)
-/*************************************************
-*     Exim - an Internet mail transport agent    *
-*************************************************/
-
-/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003-???? */
-/* License: GPL */
-
-/* Code for setting up a MBOX style spool file inside a /scan/<msgid>
-sub directory of exim's spool directory. */
-
-#include "exim.h"
-#ifdef WITH_CONTENT_SCAN
-
-/* externals, we must reset them on unspooling */
-#ifdef WITH_OLD_DEMIME
-extern int demime_ok;
-extern struct file_extension *file_extensions;
-#endif
-
-extern int malware_ok;
-extern int spam_ok;
-
-int spool_mbox_ok = 0;
-uschar spooled_message_id[17];
-
-/* returns a pointer to the FILE, and puts the size in bytes into mbox_file_size
- * normally, source_file_override is NULL */
-
-FILE *spool_mbox(unsigned long *mbox_file_size, uschar *source_file_override) {
-  uschar message_subdir[2];
-  uschar buffer[16384];
-  uschar *temp_string;
-  uschar *mbox_path;
-  FILE *mbox_file = NULL;
-  FILE *data_file = NULL;
-  FILE *yield = NULL;
-  header_line *my_headerlist;
-  struct stat statbuf;
-  int i, j;
-  void *reset_point = store_get(0);
-
-  mbox_path = string_sprintf("%s/scan/%s/%s.eml", spool_directory, message_id,
-    message_id);
-
-  /* Skip creation if already spooled out as mbox file */
-  if (!spool_mbox_ok) {
-    /* create temp directory inside scan dir, directory_make works recursively */
-    temp_string = string_sprintf("scan/%s", message_id);
-    if (!directory_make(spool_directory, temp_string, 0750, FALSE)) {
-      log_write(0, LOG_MAIN|LOG_PANIC, "%s", string_open_failed(errno,
-        "scan directory %s/scan/%s", spool_directory, temp_string));
-      goto OUT;
-    };
-
-    /* open [message_id].eml file for writing */
-    mbox_file = modefopen(mbox_path, "wb", SPOOL_MODE);
-    if (mbox_file == NULL) {
-      log_write(0, LOG_MAIN|LOG_PANIC, "%s", string_open_failed(errno,
-        "scan file %s", mbox_path));
-      goto OUT;
-    };
-
-    /* Generate mailbox headers. The $received_for variable is (up to at least
-    Exim 4.64) never set here, because it is only set when expanding the
-    contents of the Received: header line. However, the code below will use it
-    if it should become available in future. */
-
-    temp_string = expand_string(
-      US"From ${if def:return_path{$return_path}{MAILER-DAEMON}} ${tod_bsdinbox}\n"
-      "${if def:sender_address{X-Envelope-From: <${sender_address}>\n}}"
-      "${if def:recipients{X-Envelope-To: ${recipients}\n}}");
-
-    if (temp_string != NULL) {
-      i = fwrite(temp_string, Ustrlen(temp_string), 1, mbox_file);
-      if (i != 1) {
-        log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
-            mailbox headers to %s", mbox_path);
-        goto OUT;
-      };
-    };
-
-    /* write all header lines to mbox file */
-    my_headerlist = header_list;
-    for (my_headerlist = header_list; my_headerlist != NULL;
-      my_headerlist = my_headerlist->next)
-    {
-      /* skip deleted headers */
-      if (my_headerlist->type == '*') continue;
-
-      i = fwrite(my_headerlist->text, my_headerlist->slen, 1, mbox_file);
-      if (i != 1) {
-        log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
-            message headers to %s", mbox_path);
-        goto OUT;
-      };
-    };
-
-    /* End headers */
-    if (fwrite("\n", 1, 1, mbox_file) != 1) {
-      log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
-        message headers to %s", mbox_path);
-      goto OUT;
-    }
-
-    /* copy body file */
-    if (source_file_override == NULL) {
-      message_subdir[1] = '\0';
-      for (i = 0; i < 2; i++) {
-        message_subdir[0] = (split_spool_directory == (i == 0))? message_id[5] : 0;
-        temp_string = string_sprintf("%s/input/%s/%s-D", spool_directory,
-          message_subdir, message_id);
-        data_file = Ufopen(temp_string, "rb");
-        if (data_file != NULL) break;
-      };
-    } else {
-      data_file = Ufopen(source_file_override, "rb");
-    };
-
-    if (data_file == NULL) {
-      log_write(0, LOG_MAIN|LOG_PANIC, "Could not open datafile for message %s",
-        message_id);
-      goto OUT;
-    };
-
-    /* The code used to use this line, but it doesn't work in Cygwin.
-     *
-     *  (void)fread(data_buffer, 1, 18, data_file);
-     *
-     * What's happening is that spool_mbox used to use an fread to jump over the
-     * file header. That fails under Cygwin because the header is locked, but
-     * doing an fseek succeeds. We have to output the leading newline
-     * explicitly, because the one in the file is parted of the locked area.
-     */
-
-    if (!source_file_override)
-      (void)fseek(data_file, SPOOL_DATA_START_OFFSET, SEEK_SET);
-
-    do {
-      j = fread(buffer, 1, sizeof(buffer), data_file);
-
-      if (j > 0) {
-        i = fwrite(buffer, j, 1, mbox_file);
-        if (i != 1) {
-          log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
-              message body to %s", mbox_path);
-          goto OUT;
-        };
-      };
-    } while (j > 0);
-
-    (void)fclose(mbox_file);
-    mbox_file = NULL;
-
-    Ustrcpy(spooled_message_id, message_id);
-    spool_mbox_ok = 1;
-  };
-
-  /* get the size of the mbox message and open [message_id].eml file for reading*/
-  if (Ustat(mbox_path, &statbuf) != 0 ||
-      (yield = Ufopen(mbox_path,"rb")) == NULL) {
-    log_write(0, LOG_MAIN|LOG_PANIC, "%s", string_open_failed(errno,
-      "scan file %s", mbox_path));
-    goto OUT;
-  };
-
-  *mbox_file_size = statbuf.st_size;
-
-  OUT:
-  if (data_file) (void)fclose(data_file);
-  if (mbox_file) (void)fclose(mbox_file);
-  store_reset(reset_point);
-  return yield;
-}
-
-/* remove mbox spool file, demimed files and temp directory */
-void unspool_mbox(void) {
-
-  /* reset all exiscan state variables */
-  #ifdef WITH_OLD_DEMIME
-  demime_ok = 0;
-  demime_errorlevel = 0;
-  demime_reason = NULL;
-  file_extensions = NULL;
-  #endif
-
-  spam_ok = 0;
-  malware_ok = 0;
-
-  if (spool_mbox_ok && !no_mbox_unspool) {
-    uschar *mbox_path;
-    uschar *file_path;
-    int n;
-    struct dirent *entry;
-    DIR *tempdir;
-
-    mbox_path = string_sprintf("%s/scan/%s", spool_directory, spooled_message_id);
-
-    tempdir = opendir(CS mbox_path);
-    if (!tempdir) {
-      debug_printf("Unable to opendir(%s): %s\n", mbox_path, strerror(errno));
-      /* Just in case we still can: */
-      rmdir(CS mbox_path);
-      return;
-    }
-    /* loop thru dir & delete entries */
-    while((entry = readdir(tempdir)) != NULL) {
-      uschar *name = US entry->d_name;
-      if (Ustrcmp(name, US".") == 0 || Ustrcmp(name, US"..") == 0) continue;
-
-      file_path = string_sprintf("%s/%s", mbox_path, name);
-      debug_printf("unspool_mbox(): unlinking '%s'\n", file_path);
-      n = unlink(CS file_path);
-    };
-
-    closedir(tempdir);
-
-    /* remove directory */
-    rmdir(CS mbox_path);
-    store_reset(mbox_path);
-  };
-  spool_mbox_ok = 0;
-}
-
-#endif
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003 - 2015
+ * License: GPL
+ * Copyright (c) The Exim Maintainers 2016
+ */
+
+/* Code for setting up a MBOX style spool file inside a /scan/<msgid>
+sub directory of exim's spool directory. */
+
+#include "exim.h"
+#ifdef WITH_CONTENT_SCAN
+
+extern int malware_ok;
+extern int spam_ok;
+
+int spool_mbox_ok = 0;
+uschar spooled_message_id[MESSAGE_ID_LENGTH+1];
+
+/* returns a pointer to the FILE, and puts the size in bytes into mbox_file_size
+ * normally, source_file_override is NULL */
+
+FILE *
+spool_mbox(unsigned long *mbox_file_size, const uschar *source_file_override)
+{
+uschar message_subdir[2];
+uschar buffer[16384];
+uschar *temp_string;
+uschar *mbox_path;
+FILE *mbox_file = NULL;
+FILE *data_file = NULL;
+FILE *yield = NULL;
+header_line *my_headerlist;
+struct stat statbuf;
+int i, j;
+void *reset_point = store_get(0);
+
+mbox_path = string_sprintf("%s/scan/%s/%s.eml", spool_directory, message_id,
+  message_id);
+
+/* Skip creation if already spooled out as mbox file */
+if (!spool_mbox_ok)
+  {
+  /* create temp directory inside scan dir, directory_make works recursively */
+  temp_string = string_sprintf("scan/%s", message_id);
+  if (!directory_make(spool_directory, temp_string, 0750, FALSE))
+    {
+    log_write(0, LOG_MAIN|LOG_PANIC, "%s", string_open_failed(errno,
+      "scan directory %s/scan/%s", spool_directory, temp_string));
+    goto OUT;
+    }
+
+  /* open [message_id].eml file for writing */
+  mbox_file = modefopen(mbox_path, "wb", SPOOL_MODE);
+  if (mbox_file == NULL)
+    {
+    log_write(0, LOG_MAIN|LOG_PANIC, "%s", string_open_failed(errno,
+      "scan file %s", mbox_path));
+    goto OUT;
+    }
+
+  /* Generate mailbox headers. The $received_for variable is (up to at least
+  Exim 4.64) never set here, because it is only set when expanding the
+  contents of the Received: header line. However, the code below will use it
+  if it should become available in future. */
+
+  temp_string = expand_string(
+    US"From ${if def:return_path{$return_path}{MAILER-DAEMON}} ${tod_bsdinbox}\n"
+    "${if def:sender_address{X-Envelope-From: <${sender_address}>\n}}"
+    "${if def:recipients{X-Envelope-To: ${recipients}\n}}");
+
+  if (temp_string != NULL)
+    {
+    i = fwrite(temp_string, Ustrlen(temp_string), 1, mbox_file);
+    if (i != 1)
+      {
+      log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
+         mailbox headers to %s", mbox_path);
+      goto OUT;
+      }
+    }
+
+  /* write all header lines to mbox file */
+  my_headerlist = header_list;
+  for (my_headerlist = header_list; my_headerlist != NULL;
+    my_headerlist = my_headerlist->next)
+    {
+    /* skip deleted headers */
+    if (my_headerlist->type == '*') continue;
+
+    i = fwrite(my_headerlist->text, my_headerlist->slen, 1, mbox_file);
+    if (i != 1)
+      {
+      log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
+         message headers to %s", mbox_path);
+      goto OUT;
+      }
+    }
+
+  /* End headers */
+  if (fwrite("\n", 1, 1, mbox_file) != 1)
+    {
+    log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
+      message headers to %s", mbox_path);
+    goto OUT;
+    }
+
+  /* copy body file */
+  if (source_file_override == NULL)
+    {
+    message_subdir[1] = '\0';
+    for (i = 0; i < 2; i++)
+      {
+      message_subdir[0] = split_spool_directory == (i == 0) ? message_id[5] : 0;
+      temp_string = spool_fname(US"input", message_subdir, message_id, US"-D");
+      if ((data_file = Ufopen(temp_string, "rb"))) break;
+      }
+    }
+  else
+    data_file = Ufopen(source_file_override, "rb");
+
+  if (!data_file)
+    {
+    log_write(0, LOG_MAIN|LOG_PANIC, "Could not open datafile for message %s",
+      message_id);
+    goto OUT;
+    }
+
+  /* The code used to use this line, but it doesn't work in Cygwin.
+
+      (void)fread(data_buffer, 1, 18, data_file);
+    
+     What's happening is that spool_mbox used to use an fread to jump over the
+     file header. That fails under Cygwin because the header is locked, but
+     doing an fseek succeeds. We have to output the leading newline
+     explicitly, because the one in the file is parted of the locked area.  */
+
+  if (!source_file_override)
+    (void)fseek(data_file, SPOOL_DATA_START_OFFSET, SEEK_SET);
+
+  do
+    {
+    j = fread(buffer, 1, sizeof(buffer), data_file);
+
+    if (j > 0)
+      {
+      i = fwrite(buffer, j, 1, mbox_file);
+      if (i != 1)
+        {
+       log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
+           message body to %s", mbox_path);
+       goto OUT;
+       }
+      }
+    } while (j > 0);
+
+  (void)fclose(mbox_file);
+  mbox_file = NULL;
+
+  Ustrncpy(spooled_message_id, message_id, sizeof(spooled_message_id));
+  spooled_message_id[sizeof(spooled_message_id)-1] = '\0';
+  spool_mbox_ok = 1;
+  }
+
+/* get the size of the mbox message and open [message_id].eml file for reading*/
+
+if (  !(yield = Ufopen(mbox_path,"rb"))
+   || fstat(fileno(yield), &statbuf) != 0
+   )
+  log_write(0, LOG_MAIN|LOG_PANIC, "%s", string_open_failed(errno,
+    "scan file %s", mbox_path));
+else
+  *mbox_file_size = statbuf.st_size;
+
+OUT:
+if (data_file) (void)fclose(data_file);
+if (mbox_file) (void)fclose(mbox_file);
+store_reset(reset_point);
+return yield;
+}
+
+
+
+
+
+/* remove mbox spool file and temp directory */
+void
+unspool_mbox(void)
+{
+spam_ok = 0;
+malware_ok = 0;
+
+if (spool_mbox_ok && !no_mbox_unspool)
+  {
+  uschar *mbox_path;
+  uschar *file_path;
+  struct dirent *entry;
+  DIR *tempdir;
+
+  mbox_path = string_sprintf("%s/scan/%s", spool_directory, spooled_message_id);
+
+  tempdir = opendir(CS mbox_path);
+  if (!tempdir)
+    {
+    debug_printf("Unable to opendir(%s): %s\n", mbox_path, strerror(errno));
+    /* Just in case we still can: */
+    rmdir(CS mbox_path);
+    return;
+    }
+  /* loop thru dir & delete entries */
+  while((entry = readdir(tempdir)) != NULL)
+    {
+    uschar *name = US entry->d_name;
+    int dummy;
+    if (Ustrcmp(name, US".") == 0 || Ustrcmp(name, US"..") == 0) continue;
+
+    file_path = string_sprintf("%s/%s", mbox_path, name);
+    debug_printf("unspool_mbox(): unlinking '%s'\n", file_path);
+    dummy = unlink(CS file_path);
+    }
+
+  closedir(tempdir);
+
+  /* remove directory */
+  rmdir(CS mbox_path);
+  store_reset(mbox_path);
+  }
+spool_mbox_ok = 0;
+}
+
+#endif
index 67ac8bc..652506f 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for writing spool files, and moving them about. */
@@ -17,7 +17,7 @@
 *************************************************/
 
 /* This function is called immediately after errors in writing the spool, with
-errno still set. It creates and error message, depending on the circumstances.
+errno still set. It creates an error message, depending on the circumstances.
 If errmsg is NULL, it logs the message and panic-dies. Otherwise errmsg is set
 to point to the message, and -1 is returned. This function makes the code of
 spool_write_header() a bit neater.
@@ -36,22 +36,21 @@ static int
 spool_write_error(int where, uschar **errmsg, uschar *s, uschar *temp_name,
   FILE *f)
 {
-uschar *msg = (where == SW_RECEIVING)?
-  string_sprintf("spool file %s error while receiving from %s: %s", s,
-    (sender_fullhost != NULL)? sender_fullhost : sender_ident,
-    strerror(errno))
-  :
-  string_sprintf("spool file %s error while %s: %s", s,
-    (where == SW_DELIVERING)? "delivering" : "modifying",
-    strerror(errno));
-
-if (temp_name != NULL) Uunlink(temp_name);
-if (f != NULL) (void)fclose(f);
-
-if (errmsg == NULL)
-  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", msg);
-else
+uschar *msg = where == SW_RECEIVING
+  ? string_sprintf("spool file %s error while receiving from %s: %s", s,
+      sender_fullhost ? sender_fullhost : sender_ident,
+      strerror(errno))
+  : string_sprintf("spool file %s error while %s: %s", s,
+      where == SW_DELIVERING ? "delivering" : "modifying",
+      strerror(errno));
+
+if (temp_name) Uunlink(temp_name);
+if (f) (void)fclose(f);
+
+if (errmsg)
   *errmsg = msg;
+else
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", msg);
 
 return -1;
 }
@@ -134,15 +133,16 @@ int size_correction;
 FILE *f;
 header_line *h;
 struct stat statbuf;
-uschar name[256];
-uschar temp_name[256];
+uschar * tname;
+uschar * fname;
+
+tname = spool_fname(US"input", message_subdir,
+                   string_sprintf("hdr.%d", (int)getpid()), US"");
 
-sprintf(CS temp_name, "%s/input/%s/hdr.%d", spool_directory, message_subdir,
-  (int)getpid());
-fd = spool_open_temp(temp_name);
-if (fd < 0) return spool_write_error(where, errmsg, US"open", NULL, NULL);
+if ((fd = spool_open_temp(tname)) < 0)
+  return spool_write_error(where, errmsg, US"open", NULL, NULL);
 f = fdopen(fd, "wb");
-DEBUG(D_receive|D_deliver) debug_printf("Writing spool header file\n");
+DEBUG(D_receive|D_deliver) debug_printf("Writing spool header file: %s\n", tname);
 
 /* We now have an open file to which the header data is to be written. Start
 with the file's leaf name, to make the file self-identifying. Continue with the
@@ -218,7 +218,9 @@ if (sender_local) fprintf(f, "-local\n");
 if (local_error_message) fprintf(f, "-localerror\n");
 if (local_scan_data != NULL) fprintf(f, "-local_scan %s\n", local_scan_data);
 #ifdef WITH_CONTENT_SCAN
-if (spam_score_int != NULL) fprintf(f,"-spam_score_int %s\n", spam_score_int);
+if (spam_bar)       fprintf(f,"-spam_bar %s\n",       spam_bar);
+if (spam_score)     fprintf(f,"-spam_score %s\n",     spam_score);
+if (spam_score_int) fprintf(f,"-spam_score_int %s\n", spam_score_int);
 #endif
 if (deliver_manual_thaw) fprintf(f, "-manual_thaw\n");
 if (sender_set_untrusted) fprintf(f, "-sender_set_untrusted\n");
@@ -245,13 +247,20 @@ if (tls_in.ourcert)
 if (tls_in.ocsp)        fprintf(f, "-tls_ocsp %d\n",   tls_in.ocsp);
 #endif
 
-#ifdef EXPERIMENTAL_DSN
+#ifdef SUPPORT_I18N
+if (message_smtputf8)
+  {
+  fprintf(f, "-smtputf8\n");
+  if (message_utf8_downconvert)
+    fprintf(f, "-utf8_%sdowncvt\n", message_utf8_downconvert < 0 ? "opt" : "");
+  }
+#endif
+
 /* 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
 the list of recipients. These won't be disjoint the first time, when no
@@ -263,34 +272,25 @@ fprintf(f, "%d\n", recipients_count);
 for (i = 0; i < recipients_count; i++)
   {
   recipient_item *r = recipients_list + i;
-#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
-    )
+
+  DEBUG(D_deliver) debug_printf("DSN: Flags :%d\n", r->dsn_flags);
+
+  if (r->pno < 0 && r->errors_to == NULL && r->dsn_flags == 0)
     fprintf(f, "%s\n", r->address);
   else
     {
-    uschar *errors_to = (r->errors_to == NULL)? US"" : r->errors_to;
-    #ifdef EXPERIMENTAL_DSN
+    uschar * errors_to = r->errors_to ? r->errors_to : US"";
     /* for DSN SUPPORT extend exim 4 spool in a compatible way by
-       adding new values upfront and add flag 0x02 */
-    uschar *orcpt = (r->orcpt == 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
+    adding new values upfront and add flag 0x02 */
+    uschar * orcpt = r->orcpt ? r->orcpt : US"";
+
+    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);
     }
-    
-    #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
+
+    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);
   }
 
 /* Put a blank line before the headers */
@@ -301,7 +301,8 @@ fprintf(f, "\n");
 to get the actual size of the headers. */
 
 fflush(f);
-fstat(fd, &statbuf);
+if (fstat(fd, &statbuf))
+  return spool_write_error(where, errmsg, US"fstat", tname, f);
 size_correction = statbuf.st_size;
 
 /* Finally, write out the message's headers. To make it easier to read them
@@ -322,28 +323,30 @@ for (h = header_list; h != NULL; h = h->next)
 /* Flush and check for any errors while writing */
 
 if (fflush(f) != 0 || ferror(f))
-  return spool_write_error(where, errmsg, US"write", temp_name, f);
+  return spool_write_error(where, errmsg, US"write", tname, f);
 
 /* Force the file's contents to be written to disk. Note that fflush()
 just pushes it out of C, and fclose() doesn't guarantee to do the write
 either. That's just the way Unix works... */
 
 if (EXIMfsync(fileno(f)) < 0)
-  return spool_write_error(where, errmsg, US"sync", temp_name, f);
+  return spool_write_error(where, errmsg, US"sync", tname, f);
 
 /* Get the size of the file, and close it. */
 
-fstat(fd, &statbuf);
+if (fstat(fd, &statbuf) != 0)
+  return spool_write_error(where, errmsg, US"fstat", tname, NULL);
 if (fclose(f) != 0)
-  return spool_write_error(where, errmsg, US"close", temp_name, NULL);
+  return spool_write_error(where, errmsg, US"close", tname, NULL);
 
 /* Rename the file to its correct name, thereby replacing any previous
 incarnation. */
 
-sprintf(CS name, "%s/input/%s/%s-H", spool_directory, message_subdir, id);
+fname = spool_fname(US"input", message_subdir, id, US"-H");
+DEBUG(D_receive|D_deliver) debug_printf("Renaming spool header file: %s\n", fname);
 
-if (Urename(temp_name, name) < 0)
-  return spool_write_error(where, errmsg, US"rename", temp_name, NULL);
+if (Urename(tname, fname) < 0)
+  return spool_write_error(where, errmsg, US"rename", tname, NULL);
 
 /* Linux (and maybe other OS?) does not automatically sync a directory after
 an operation like rename. We therefore have to do it forcibly ourselves in
@@ -357,25 +360,25 @@ these cases. One hack on top of another... but that's life. */
 
 #ifdef NEED_SYNC_DIRECTORY
 
-sprintf(CS temp_name, "%s/input/%s/.", spool_directory, message_subdir);
+tname = spool_fname(US"input", message_subdir, US".", US"");
 
-#ifndef O_DIRECTORY
-#define O_DIRECTORY 0
-#endif
+# ifndef O_DIRECTORY
+#  define O_DIRECTORY 0
+# endif
 
-if ((fd = Uopen(temp_name, O_RDONLY|O_DIRECTORY, 0)) < 0)
-  return spool_write_error(where, errmsg, US"directory open", name, NULL);
+if ((fd = Uopen(tname, O_RDONLY|O_DIRECTORY, 0)) < 0)
+  return spool_write_error(where, errmsg, US"directory open", fname, NULL);
 
 if (EXIMfsync(fd) < 0 && errno != EINVAL)
-  return spool_write_error(where, errmsg, US"directory sync", name, NULL);
+  return spool_write_error(where, errmsg, US"directory sync", fname, NULL);
 
 if (close(fd) < 0)
-  return spool_write_error(where, errmsg, US"directory close", name, NULL);
+  return spool_write_error(where, errmsg, US"directory close", fname, NULL);
 
 #endif  /* NEED_SYNC_DIRECTORY */
 
 /* Return the number of characters in the headers, which is the file size, less
-the prelimary stuff, less the additional count fields on the headers. */
+the preliminary stuff, less the additional count fields on the headers. */
 
 DEBUG(D_receive) debug_printf("Size of headers = %d\n",
   (int)(statbuf.st_size - size_correction));
@@ -411,13 +414,12 @@ static BOOL
 make_link(uschar *dir, uschar *subdir, uschar *id, uschar *suffix, uschar *from,
   uschar *to, BOOL noentok)
 {
-uschar f[256], t[256];
-sprintf(CS f, "%s/%s%s/%s/%s%s", spool_directory, from, dir, subdir, id, suffix);
-sprintf(CS t, "%s/%s%s/%s/%s%s", spool_directory, to, dir, subdir, id, suffix);
-if (Ulink(f, t) < 0 && (!noentok || errno != ENOENT))
+uschar * fname = spool_fname(string_sprintf("%s%s", from, dir), subdir, id, suffix);
+uschar * tname = spool_fname(string_sprintf("%s%s", to,   dir), subdir, id, suffix);
+if (Ulink(fname, tname) < 0 && (!noentok || errno != ENOENT))
   {
   log_write(0, LOG_MAIN|LOG_PANIC, "link(\"%s\", \"%s\") failed while moving "
-    "message: %s", f, t, strerror(errno));
+    "message: %s", fname, tname, strerror(errno));
   return FALSE;
   }
 return TRUE;
@@ -449,12 +451,11 @@ static BOOL
 break_link(uschar *dir, uschar *subdir, uschar *id, uschar *suffix, uschar *from,
   BOOL noentok)
 {
-uschar f[256];
-sprintf(CS f, "%s/%s%s/%s/%s%s", spool_directory, from, dir, subdir, id, suffix);
-if (Uunlink(f) < 0 && (!noentok || errno != ENOENT))
+uschar * fname = spool_fname(string_sprintf("%s%s", from, dir), subdir, id, suffix);
+if (Uunlink(fname) < 0 && (!noentok || errno != ENOENT))
   {
   log_write(0, LOG_MAIN|LOG_PANIC, "unlink(\"%s\") failed while moving "
-    "message: %s", f, strerror(errno));
+    "message: %s", fname, strerror(errno));
   return FALSE;
   }
 return TRUE;
@@ -486,17 +487,19 @@ spool_move_message(uschar *id, uschar *subdir, uschar *from, uschar *to)
 {
 /* Create any output directories that do not exist. */
 
-sprintf(CS big_buffer, "%sinput/%s", to, subdir);
-(void)directory_make(spool_directory, big_buffer, INPUT_DIRECTORY_MODE, TRUE);
-sprintf(CS big_buffer, "%smsglog/%s", to, subdir);
-(void)directory_make(spool_directory, big_buffer, INPUT_DIRECTORY_MODE, TRUE);
+(void) directory_make(spool_directory,
+  spool_sname(string_sprintf("%sinput", to), subdir),
+  INPUT_DIRECTORY_MODE, TRUE);
+(void) directory_make(spool_directory,
+  spool_sname(string_sprintf("%smsglog", to), subdir),
+  INPUT_DIRECTORY_MODE, TRUE);
 
 /* Move the message by first creating new hard links for all the files, and
 then removing the old links. When moving messages onto the main spool, the -H
 file should be set up last, because that's the one that tells Exim there is a
 message to be delivered, so we create its new link last and remove its old link
 first. Programs that look at the alternate directories should follow the same
-rule of waiting for a -H file before doing anything. When moving messsages off
+rule of waiting for a -H file before doing anything. When moving messages off
 the mail spool, the -D file should be open and locked at the time, thus keeping
 Exim's hands off. */
 
@@ -519,3 +522,5 @@ return TRUE;
 #endif
 
 /* End of spool_out.c */
+/* vi: aw ai sw=2
+*/
index 0115c0b..1ff391f 100644 (file)
--- a/src/srs.c
+++ b/src/srs.c
@@ -4,6 +4,7 @@
 
 /* SRS - Sender rewriting scheme support
   (C)2004 Miles Wilton <miles@mirtol.com>
+  Copyright (c) The Exim Maintainers 2016
 
   SRS Support Version: 1.0a
 
@@ -25,7 +26,7 @@ uschar   *srs_db_reverse        = NULL;
 
 int eximsrs_init()
 {
-  uschar *list = srs_config;
+  const uschar *list = srs_config;
   uschar secret_buf[SRS_MAX_SECRET_LENGTH];
   uschar *secret = NULL;
   uschar sbuf[4];
index 3f0fec8..8ccef12 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) Phil Pennock 2012
+/* Copyright (c) Phil Pennock 2012, 2016
  * But almost everything here is fixed published constants from RFCs, so also:
  * Copyright (C) The Internet Society (2003)
  * Copyright (C) The IETF Trust (2008)
@@ -459,6 +459,497 @@ static const char dh_ike_24_pem[] =
 "KM3GfrYYS1I9HbJGwy9jB4SQ8A741kfRSNR5VFFeIyfP75jFgmZLTA9sxBZZ\n"
 "-----END DH PARAMETERS-----\n";
 
+/* ------------------------------------------------------------------------- */
+/* RFC 7919 Published August 2016, so strength estimates date from then.
+
+A.1.  ffdhe2048
+
+   The 2048-bit group has registry value 256 and is calculated from the
+   following formula:
+
+   The modulus is:
+
+   p = 2^2048 - 2^1984 + {[2^1918 * e] + 560316 } * 2^64 - 1
+
+   The hexadecimal representation of p is:
+
+    FFFFFFFF FFFFFFFF ADF85458 A2BB4A9A AFDC5620 273D3CF1
+    D8B9C583 CE2D3695 A9E13641 146433FB CC939DCE 249B3EF9
+    7D2FE363 630C75D8 F681B202 AEC4617A D3DF1ED5 D5FD6561
+    2433F51F 5F066ED0 85636555 3DED1AF3 B557135E 7F57C935
+    984F0C70 E0E68B77 E2A689DA F3EFE872 1DF158A1 36ADE735
+    30ACCA4F 483A797A BC0AB182 B324FB61 D108A94B B2C8E3FB
+    B96ADAB7 60D7F468 1D4F42A3 DE394DF4 AE56EDE7 6372BB19
+    0B07A7C8 EE0A6D70 9E02FCE1 CDF7E2EC C03404CD 28342F61
+    9172FE9C E98583FF 8E4F1232 EEF28183 C3FE3B1B 4C6FAD73
+    3BB5FCBC 2EC22005 C58EF183 7D1683B2 C6F34A26 C1B2EFFA
+    886B4238 61285C97 FFFFFFFF FFFFFFFF
+
+   The generator is: g = 2
+
+   The group size is: q = (p-1)/2
+
+   The hexadecimal representation of q is:
+
+    7FFFFFFF FFFFFFFF D6FC2A2C 515DA54D 57EE2B10 139E9E78
+    EC5CE2C1 E7169B4A D4F09B20 8A3219FD E649CEE7 124D9F7C
+    BE97F1B1 B1863AEC 7B40D901 576230BD 69EF8F6A EAFEB2B0
+    9219FA8F AF833768 42B1B2AA 9EF68D79 DAAB89AF 3FABE49A
+    CC278638 707345BB F15344ED 79F7F439 0EF8AC50 9B56F39A
+    98566527 A41D3CBD 5E0558C1 59927DB0 E88454A5 D96471FD
+    DCB56D5B B06BFA34 0EA7A151 EF1CA6FA 572B76F3 B1B95D8C
+    8583D3E4 770536B8 4F017E70 E6FBF176 601A0266 941A17B0
+    C8B97F4E 74C2C1FF C7278919 777940C1 E1FF1D8D A637D6B9
+    9DDAFE5E 17611002 E2C778C1 BE8B41D9 6379A513 60D977FD
+    4435A11C 30942E4B FFFFFFFF FFFFFFFF
+
+   The estimated symmetric-equivalent strength of this group is 103
+   bits.
+*/
+static const char dh_ffdhe2048_pem[] =
+"-----BEGIN DH PARAMETERS-----\n"
+"MIH+AoH4DfhUWKK7Spqv3FYgJz088di5xYPOLTaVqeE2QRRkM/vMk53OJJs++X0v\n"
+"42NjDHXY9oGyAq7EYXrT3x7V1f1lYSQz9R9fBm7QhWNlVT3tGvO1VxNef1fJNZhP\n"
+"DHDg5ot34qaJ2vPv6HId8VihNq3nNTCsyk9IOnl6vAqxgrMk+2HRCKlLssjj+7lq\n"
+"2rdg1/RoHU9Co945TfSuVu3nY3K7GQsHp8juCm1wngL84c334uzANATNKDQvYZFy\n"
+"/pzphYP/jk8SMu7ygYPD/jsbTG+tczu1/LwuwiAFxY7xg30Wg7LG80omwbLv+ohr\n"
+"QjhhKFyX//////////8CAQI=\n"
+"-----END DH PARAMETERS-----\n";
+
+/*
+A.2.  ffdhe3072
+
+   The 3072-bit prime has registry value 257 and is calculated from the
+   following formula:
+
+   The modulus is:
+
+   p = 2^3072 - 2^3008 + {[2^2942 * e] + 2625351} * 2^64 - 1
+
+   The hexadecimal representation of p is:
+
+    FFFFFFFF FFFFFFFF ADF85458 A2BB4A9A AFDC5620 273D3CF1
+    D8B9C583 CE2D3695 A9E13641 146433FB CC939DCE 249B3EF9
+    7D2FE363 630C75D8 F681B202 AEC4617A D3DF1ED5 D5FD6561
+    2433F51F 5F066ED0 85636555 3DED1AF3 B557135E 7F57C935
+    984F0C70 E0E68B77 E2A689DA F3EFE872 1DF158A1 36ADE735
+    30ACCA4F 483A797A BC0AB182 B324FB61 D108A94B B2C8E3FB
+    B96ADAB7 60D7F468 1D4F42A3 DE394DF4 AE56EDE7 6372BB19
+    0B07A7C8 EE0A6D70 9E02FCE1 CDF7E2EC C03404CD 28342F61
+    9172FE9C E98583FF 8E4F1232 EEF28183 C3FE3B1B 4C6FAD73
+    3BB5FCBC 2EC22005 C58EF183 7D1683B2 C6F34A26 C1B2EFFA
+    886B4238 611FCFDC DE355B3B 6519035B BC34F4DE F99C0238
+    61B46FC9 D6E6C907 7AD91D26 91F7F7EE 598CB0FA C186D91C
+    AEFE1309 85139270 B4130C93 BC437944 F4FD4452 E2D74DD3
+    64F2E21E 71F54BFF 5CAE82AB 9C9DF69E E86D2BC5 22363A0D
+    ABC52197 9B0DEADA 1DBF9A42 D5C4484E 0ABCD06B FA53DDEF
+    3C1B20EE 3FD59D7C 25E41D2B 66C62E37 FFFFFFFF FFFFFFFF
+
+   The generator is: g = 2
+
+   The group size is: q = (p-1)/2
+
+   The hexadecimal representation of q is:
+
+    7FFFFFFF FFFFFFFF D6FC2A2C 515DA54D 57EE2B10 139E9E78
+    EC5CE2C1 E7169B4A D4F09B20 8A3219FD E649CEE7 124D9F7C
+    BE97F1B1 B1863AEC 7B40D901 576230BD 69EF8F6A EAFEB2B0
+    9219FA8F AF833768 42B1B2AA 9EF68D79 DAAB89AF 3FABE49A
+    CC278638 707345BB F15344ED 79F7F439 0EF8AC50 9B56F39A
+    98566527 A41D3CBD 5E0558C1 59927DB0 E88454A5 D96471FD
+    DCB56D5B B06BFA34 0EA7A151 EF1CA6FA 572B76F3 B1B95D8C
+    8583D3E4 770536B8 4F017E70 E6FBF176 601A0266 941A17B0
+    C8B97F4E 74C2C1FF C7278919 777940C1 E1FF1D8D A637D6B9
+    9DDAFE5E 17611002 E2C778C1 BE8B41D9 6379A513 60D977FD
+    4435A11C 308FE7EE 6F1AAD9D B28C81AD DE1A7A6F 7CCE011C
+    30DA37E4 EB736483 BD6C8E93 48FBFBF7 2CC6587D 60C36C8E
+    577F0984 C289C938 5A098649 DE21BCA2 7A7EA229 716BA6E9
+    B279710F 38FAA5FF AE574155 CE4EFB4F 743695E2 911B1D06
+    D5E290CB CD86F56D 0EDFCD21 6AE22427 055E6835 FD29EEF7
+    9E0D9077 1FEACEBE 12F20E95 B363171B FFFFFFFF FFFFFFFF
+
+   The estimated symmetric-equivalent strength of this group is 125
+   bits.
+*/
+static const char dh_ffdhe3072_pem[] =
+"-----BEGIN DH PARAMETERS-----\n"
+"MIIBiAKCAYEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz\n"
+"+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a\n"
+"87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7\n"
+"YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi\n"
+"7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD\n"
+"ssbzSibBsu/6iGtCOGEfz9zeNVs7ZRkDW7w09N75nAI4YbRvydbmyQd62R0mkff3\n"
+"7lmMsPrBhtkcrv4TCYUTknC0EwyTvEN5RPT9RFLi103TZPLiHnH1S/9croKrnJ32\n"
+"nuhtK8UiNjoNq8Uhl5sN6todv5pC1cRITgq80Gv6U93vPBsg7j/VnXwl5B0rZsYu\n"
+"N///////////AgEC\n"
+"-----END DH PARAMETERS-----\n";
+
+/*
+A.3.  ffdhe4096
+
+   The 4096-bit group has registry value 258 and is calculated from the
+   following formula:
+
+   The modulus is:
+
+   p = 2^4096 - 2^4032 + {[2^3966 * e] + 5736041} * 2^64 - 1
+
+   The hexadecimal representation of p is:
+
+    FFFFFFFF FFFFFFFF ADF85458 A2BB4A9A AFDC5620 273D3CF1
+    D8B9C583 CE2D3695 A9E13641 146433FB CC939DCE 249B3EF9
+    7D2FE363 630C75D8 F681B202 AEC4617A D3DF1ED5 D5FD6561
+    2433F51F 5F066ED0 85636555 3DED1AF3 B557135E 7F57C935
+    984F0C70 E0E68B77 E2A689DA F3EFE872 1DF158A1 36ADE735
+    30ACCA4F 483A797A BC0AB182 B324FB61 D108A94B B2C8E3FB
+    B96ADAB7 60D7F468 1D4F42A3 DE394DF4 AE56EDE7 6372BB19
+    0B07A7C8 EE0A6D70 9E02FCE1 CDF7E2EC C03404CD 28342F61
+    9172FE9C E98583FF 8E4F1232 EEF28183 C3FE3B1B 4C6FAD73
+    3BB5FCBC 2EC22005 C58EF183 7D1683B2 C6F34A26 C1B2EFFA
+    886B4238 611FCFDC DE355B3B 6519035B BC34F4DE F99C0238
+    61B46FC9 D6E6C907 7AD91D26 91F7F7EE 598CB0FA C186D91C
+    AEFE1309 85139270 B4130C93 BC437944 F4FD4452 E2D74DD3
+    64F2E21E 71F54BFF 5CAE82AB 9C9DF69E E86D2BC5 22363A0D
+    ABC52197 9B0DEADA 1DBF9A42 D5C4484E 0ABCD06B FA53DDEF
+    3C1B20EE 3FD59D7C 25E41D2B 669E1EF1 6E6F52C3 164DF4FB
+    7930E9E4 E58857B6 AC7D5F42 D69F6D18 7763CF1D 55034004
+    87F55BA5 7E31CC7A 7135C886 EFB4318A ED6A1E01 2D9E6832
+    A907600A 918130C4 6DC778F9 71AD0038 092999A3 33CB8B7A
+    1A1DB93D 7140003C 2A4ECEA9 F98D0ACC 0A8291CD CEC97DCF
+    8EC9B55A 7F88A46B 4DB5A851 F44182E1 C68A007E 5E655F6A
+    FFFFFFFF FFFFFFFF
+
+   The generator is: g = 2
+
+   The group size is: q = (p-1)/2
+
+   The hexadecimal representation of q is:
+
+    7FFFFFFF FFFFFFFF D6FC2A2C 515DA54D 57EE2B10 139E9E78
+    EC5CE2C1 E7169B4A D4F09B20 8A3219FD E649CEE7 124D9F7C
+    BE97F1B1 B1863AEC 7B40D901 576230BD 69EF8F6A EAFEB2B0
+    9219FA8F AF833768 42B1B2AA 9EF68D79 DAAB89AF 3FABE49A
+    CC278638 707345BB F15344ED 79F7F439 0EF8AC50 9B56F39A
+    98566527 A41D3CBD 5E0558C1 59927DB0 E88454A5 D96471FD
+    DCB56D5B B06BFA34 0EA7A151 EF1CA6FA 572B76F3 B1B95D8C
+    8583D3E4 770536B8 4F017E70 E6FBF176 601A0266 941A17B0
+    C8B97F4E 74C2C1FF C7278919 777940C1 E1FF1D8D A637D6B9
+    9DDAFE5E 17611002 E2C778C1 BE8B41D9 6379A513 60D977FD
+    4435A11C 308FE7EE 6F1AAD9D B28C81AD DE1A7A6F 7CCE011C
+    30DA37E4 EB736483 BD6C8E93 48FBFBF7 2CC6587D 60C36C8E
+    577F0984 C289C938 5A098649 DE21BCA2 7A7EA229 716BA6E9
+    B279710F 38FAA5FF AE574155 CE4EFB4F 743695E2 911B1D06
+    D5E290CB CD86F56D 0EDFCD21 6AE22427 055E6835 FD29EEF7
+    9E0D9077 1FEACEBE 12F20E95 B34F0F78 B737A961 8B26FA7D
+    BC9874F2 72C42BDB 563EAFA1 6B4FB68C 3BB1E78E AA81A002
+    43FAADD2 BF18E63D 389AE443 77DA18C5 76B50F00 96CF3419
+    5483B005 48C09862 36E3BC7C B8D6801C 0494CCD1 99E5C5BD
+    0D0EDC9E B8A0001E 15276754 FCC68566 054148E6 E764BEE7
+    C764DAAD 3FC45235 A6DAD428 FA20C170 E345003F 2F32AFB5
+    7FFFFFFF FFFFFFFF
+
+   The estimated symmetric-equivalent strength of this group is 150
+   bits.
+*/
+static const char dh_ffdhe4096_pem[] =
+"-----BEGIN DH PARAMETERS-----\n"
+"MIICCAKCAgEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz\n"
+"+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a\n"
+"87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7\n"
+"YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi\n"
+"7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD\n"
+"ssbzSibBsu/6iGtCOGEfz9zeNVs7ZRkDW7w09N75nAI4YbRvydbmyQd62R0mkff3\n"
+"7lmMsPrBhtkcrv4TCYUTknC0EwyTvEN5RPT9RFLi103TZPLiHnH1S/9croKrnJ32\n"
+"nuhtK8UiNjoNq8Uhl5sN6todv5pC1cRITgq80Gv6U93vPBsg7j/VnXwl5B0rZp4e\n"
+"8W5vUsMWTfT7eTDp5OWIV7asfV9C1p9tGHdjzx1VA0AEh/VbpX4xzHpxNciG77Qx\n"
+"iu1qHgEtnmgyqQdgCpGBMMRtx3j5ca0AOAkpmaMzy4t6Gh25PXFAADwqTs6p+Y0K\n"
+"zAqCkc3OyX3Pjsm1Wn+IpGtNtahR9EGC4caKAH5eZV9q//////////8CAQI=\n"
+"-----END DH PARAMETERS-----\n";
+
+/*
+A.4.  ffdhe6144
+
+   The 6144-bit group has registry value 259 and is calculated from the
+   following formula:
+
+   The modulus is:
+
+   p = 2^6144 - 2^6080 + {[2^6014 * e] + 15705020} * 2^64 - 1
+
+   The hexadecimal representation of p is:
+
+    FFFFFFFF FFFFFFFF ADF85458 A2BB4A9A AFDC5620 273D3CF1
+    D8B9C583 CE2D3695 A9E13641 146433FB CC939DCE 249B3EF9
+    7D2FE363 630C75D8 F681B202 AEC4617A D3DF1ED5 D5FD6561
+    2433F51F 5F066ED0 85636555 3DED1AF3 B557135E 7F57C935
+    984F0C70 E0E68B77 E2A689DA F3EFE872 1DF158A1 36ADE735
+    30ACCA4F 483A797A BC0AB182 B324FB61 D108A94B B2C8E3FB
+    B96ADAB7 60D7F468 1D4F42A3 DE394DF4 AE56EDE7 6372BB19
+    0B07A7C8 EE0A6D70 9E02FCE1 CDF7E2EC C03404CD 28342F61
+    9172FE9C E98583FF 8E4F1232 EEF28183 C3FE3B1B 4C6FAD73
+    3BB5FCBC 2EC22005 C58EF183 7D1683B2 C6F34A26 C1B2EFFA
+    886B4238 611FCFDC DE355B3B 6519035B BC34F4DE F99C0238
+    61B46FC9 D6E6C907 7AD91D26 91F7F7EE 598CB0FA C186D91C
+    AEFE1309 85139270 B4130C93 BC437944 F4FD4452 E2D74DD3
+    64F2E21E 71F54BFF 5CAE82AB 9C9DF69E E86D2BC5 22363A0D
+    ABC52197 9B0DEADA 1DBF9A42 D5C4484E 0ABCD06B FA53DDEF
+    3C1B20EE 3FD59D7C 25E41D2B 669E1EF1 6E6F52C3 164DF4FB
+    7930E9E4 E58857B6 AC7D5F42 D69F6D18 7763CF1D 55034004
+    87F55BA5 7E31CC7A 7135C886 EFB4318A ED6A1E01 2D9E6832
+    A907600A 918130C4 6DC778F9 71AD0038 092999A3 33CB8B7A
+    1A1DB93D 7140003C 2A4ECEA9 F98D0ACC 0A8291CD CEC97DCF
+    8EC9B55A 7F88A46B 4DB5A851 F44182E1 C68A007E 5E0DD902
+    0BFD64B6 45036C7A 4E677D2C 38532A3A 23BA4442 CAF53EA6
+    3BB45432 9B7624C8 917BDD64 B1C0FD4C B38E8C33 4C701C3A
+    CDAD0657 FCCFEC71 9B1F5C3E 4E46041F 388147FB 4CFDB477
+    A52471F7 A9A96910 B855322E DB6340D8 A00EF092 350511E3
+    0ABEC1FF F9E3A26E 7FB29F8C 183023C3 587E38DA 0077D9B4
+    763E4E4B 94B2BBC1 94C6651E 77CAF992 EEAAC023 2A281BF6
+    B3A739C1 22611682 0AE8DB58 47A67CBE F9C9091B 462D538C
+    D72B0374 6AE77F5E 62292C31 1562A846 505DC82D B854338A
+    E49F5235 C95B9117 8CCF2DD5 CACEF403 EC9D1810 C6272B04
+    5B3B71F9 DC6B80D6 3FDD4A8E 9ADB1E69 62A69526 D43161C1
+    A41D570D 7938DAD4 A40E329C D0E40E65 FFFFFFFF FFFFFFFF
+
+   The generator is: g = 2
+
+   The group size is: q = (p-1)/2
+
+   The hexadecimal representation of q is:
+
+    7FFFFFFF FFFFFFFF D6FC2A2C 515DA54D 57EE2B10 139E9E78
+    EC5CE2C1 E7169B4A D4F09B20 8A3219FD E649CEE7 124D9F7C
+    BE97F1B1 B1863AEC 7B40D901 576230BD 69EF8F6A EAFEB2B0
+    9219FA8F AF833768 42B1B2AA 9EF68D79 DAAB89AF 3FABE49A
+    CC278638 707345BB F15344ED 79F7F439 0EF8AC50 9B56F39A
+    98566527 A41D3CBD 5E0558C1 59927DB0 E88454A5 D96471FD
+    DCB56D5B B06BFA34 0EA7A151 EF1CA6FA 572B76F3 B1B95D8C
+    8583D3E4 770536B8 4F017E70 E6FBF176 601A0266 941A17B0
+    C8B97F4E 74C2C1FF C7278919 777940C1 E1FF1D8D A637D6B9
+    9DDAFE5E 17611002 E2C778C1 BE8B41D9 6379A513 60D977FD
+    4435A11C 308FE7EE 6F1AAD9D B28C81AD DE1A7A6F 7CCE011C
+    30DA37E4 EB736483 BD6C8E93 48FBFBF7 2CC6587D 60C36C8E
+    577F0984 C289C938 5A098649 DE21BCA2 7A7EA229 716BA6E9
+    B279710F 38FAA5FF AE574155 CE4EFB4F 743695E2 911B1D06
+    D5E290CB CD86F56D 0EDFCD21 6AE22427 055E6835 FD29EEF7
+    9E0D9077 1FEACEBE 12F20E95 B34F0F78 B737A961 8B26FA7D
+    BC9874F2 72C42BDB 563EAFA1 6B4FB68C 3BB1E78E AA81A002
+    43FAADD2 BF18E63D 389AE443 77DA18C5 76B50F00 96CF3419
+    5483B005 48C09862 36E3BC7C B8D6801C 0494CCD1 99E5C5BD
+    0D0EDC9E B8A0001E 15276754 FCC68566 054148E6 E764BEE7
+    C764DAAD 3FC45235 A6DAD428 FA20C170 E345003F 2F06EC81
+    05FEB25B 2281B63D 2733BE96 1C29951D 11DD2221 657A9F53
+    1DDA2A19 4DBB1264 48BDEEB2 58E07EA6 59C74619 A6380E1D
+    66D6832B FE67F638 CD8FAE1F 2723020F 9C40A3FD A67EDA3B
+    D29238FB D4D4B488 5C2A9917 6DB1A06C 50077849 1A8288F1
+    855F60FF FCF1D137 3FD94FC6 0C1811E1 AC3F1C6D 003BECDA
+    3B1F2725 CA595DE0 CA63328F 3BE57CC9 77556011 95140DFB
+    59D39CE0 91308B41 05746DAC 23D33E5F 7CE4848D A316A9C6
+    6B9581BA 3573BFAF 31149618 8AB15423 282EE416 DC2A19C5
+    724FA91A E4ADC88B C66796EA E5677A01 F64E8C08 63139582
+    2D9DB8FC EE35C06B 1FEEA547 4D6D8F34 B1534A93 6A18B0E0
+    D20EAB86 BC9C6D6A 5207194E 68720732 FFFFFFFF FFFFFFFF
+
+   The estimated symmetric-equivalent strength of this group is 175
+   bits.
+*/
+static const char dh_ffdhe6144_pem[] =
+"-----BEGIN DH PARAMETERS-----\n"
+"MIIDCAKCAwEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz\n"
+"+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a\n"
+"87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7\n"
+"YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi\n"
+"7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD\n"
+"ssbzSibBsu/6iGtCOGEfz9zeNVs7ZRkDW7w09N75nAI4YbRvydbmyQd62R0mkff3\n"
+"7lmMsPrBhtkcrv4TCYUTknC0EwyTvEN5RPT9RFLi103TZPLiHnH1S/9croKrnJ32\n"
+"nuhtK8UiNjoNq8Uhl5sN6todv5pC1cRITgq80Gv6U93vPBsg7j/VnXwl5B0rZp4e\n"
+"8W5vUsMWTfT7eTDp5OWIV7asfV9C1p9tGHdjzx1VA0AEh/VbpX4xzHpxNciG77Qx\n"
+"iu1qHgEtnmgyqQdgCpGBMMRtx3j5ca0AOAkpmaMzy4t6Gh25PXFAADwqTs6p+Y0K\n"
+"zAqCkc3OyX3Pjsm1Wn+IpGtNtahR9EGC4caKAH5eDdkCC/1ktkUDbHpOZ30sOFMq\n"
+"OiO6RELK9T6mO7RUMpt2JMiRe91kscD9TLOOjDNMcBw6za0GV/zP7HGbH1w+TkYE\n"
+"HziBR/tM/bR3pSRx96mpaRC4VTIu22NA2KAO8JI1BRHjCr7B//njom5/sp+MGDAj\n"
+"w1h+ONoAd9m0dj5OS5Syu8GUxmUed8r5ku6qwCMqKBv2s6c5wSJhFoIK6NtYR6Z8\n"
+"vvnJCRtGLVOM1ysDdGrnf15iKSwxFWKoRlBdyC24VDOK5J9SNclbkReMzy3Vys70\n"
+"A+ydGBDGJysEWztx+dxrgNY/3UqOmtseaWKmlSbUMWHBpB1XDXk42tSkDjKc0OQO\n"
+"Zf//////////AgEC\n"
+"-----END DH PARAMETERS-----\n";
+
+/*
+A.5.  ffdhe8192
+
+   The 8192-bit group has registry value 260 and is calculated from the
+   following formula:
+
+   The modulus is:
+
+   p = 2^8192 - 2^8128 + {[2^8062 * e] + 10965728} * 2^64 - 1
+
+   The hexadecimal representation of p is:
+
+    FFFFFFFF FFFFFFFF ADF85458 A2BB4A9A AFDC5620 273D3CF1
+    D8B9C583 CE2D3695 A9E13641 146433FB CC939DCE 249B3EF9
+    7D2FE363 630C75D8 F681B202 AEC4617A D3DF1ED5 D5FD6561
+    2433F51F 5F066ED0 85636555 3DED1AF3 B557135E 7F57C935
+    984F0C70 E0E68B77 E2A689DA F3EFE872 1DF158A1 36ADE735
+    30ACCA4F 483A797A BC0AB182 B324FB61 D108A94B B2C8E3FB
+    B96ADAB7 60D7F468 1D4F42A3 DE394DF4 AE56EDE7 6372BB19
+    0B07A7C8 EE0A6D70 9E02FCE1 CDF7E2EC C03404CD 28342F61
+    9172FE9C E98583FF 8E4F1232 EEF28183 C3FE3B1B 4C6FAD73
+    3BB5FCBC 2EC22005 C58EF183 7D1683B2 C6F34A26 C1B2EFFA
+    886B4238 611FCFDC DE355B3B 6519035B BC34F4DE F99C0238
+    61B46FC9 D6E6C907 7AD91D26 91F7F7EE 598CB0FA C186D91C
+    AEFE1309 85139270 B4130C93 BC437944 F4FD4452 E2D74DD3
+    64F2E21E 71F54BFF 5CAE82AB 9C9DF69E E86D2BC5 22363A0D
+    ABC52197 9B0DEADA 1DBF9A42 D5C4484E 0ABCD06B FA53DDEF
+    3C1B20EE 3FD59D7C 25E41D2B 669E1EF1 6E6F52C3 164DF4FB
+    7930E9E4 E58857B6 AC7D5F42 D69F6D18 7763CF1D 55034004
+    87F55BA5 7E31CC7A 7135C886 EFB4318A ED6A1E01 2D9E6832
+    A907600A 918130C4 6DC778F9 71AD0038 092999A3 33CB8B7A
+    1A1DB93D 7140003C 2A4ECEA9 F98D0ACC 0A8291CD CEC97DCF
+    8EC9B55A 7F88A46B 4DB5A851 F44182E1 C68A007E 5E0DD902
+    0BFD64B6 45036C7A 4E677D2C 38532A3A 23BA4442 CAF53EA6
+    3BB45432 9B7624C8 917BDD64 B1C0FD4C B38E8C33 4C701C3A
+    CDAD0657 FCCFEC71 9B1F5C3E 4E46041F 388147FB 4CFDB477
+    A52471F7 A9A96910 B855322E DB6340D8 A00EF092 350511E3
+    0ABEC1FF F9E3A26E 7FB29F8C 183023C3 587E38DA 0077D9B4
+    763E4E4B 94B2BBC1 94C6651E 77CAF992 EEAAC023 2A281BF6
+    B3A739C1 22611682 0AE8DB58 47A67CBE F9C9091B 462D538C
+    D72B0374 6AE77F5E 62292C31 1562A846 505DC82D B854338A
+    E49F5235 C95B9117 8CCF2DD5 CACEF403 EC9D1810 C6272B04
+    5B3B71F9 DC6B80D6 3FDD4A8E 9ADB1E69 62A69526 D43161C1
+    A41D570D 7938DAD4 A40E329C CFF46AAA 36AD004C F600C838
+    1E425A31 D951AE64 FDB23FCE C9509D43 687FEB69 EDD1CC5E
+    0B8CC3BD F64B10EF 86B63142 A3AB8829 555B2F74 7C932665
+    CB2C0F1C C01BD702 29388839 D2AF05E4 54504AC7 8B758282
+    2846C0BA 35C35F5C 59160CC0 46FD8251 541FC68C 9C86B022
+    BB709987 6A460E74 51A8A931 09703FEE 1C217E6C 3826E52C
+    51AA691E 0E423CFC 99E9E316 50C1217B 624816CD AD9A95F9
+    D5B80194 88D9C0A0 A1FE3075 A577E231 83F81D4A 3F2FA457
+    1EFC8CE0 BA8A4FE8 B6855DFE 72B0A66E DED2FBAB FBE58A30
+    FAFABE1C 5D71A87E 2F741EF8 C1FE86FE A6BBFDE5 30677F0D
+    97D11D49 F7A8443D 0822E506 A9F4614E 011E2A94 838FF88C
+    D68C8BB7 C5C6424C FFFFFFFF FFFFFFFF
+
+   The generator is: g = 2
+
+   The group size is: q = (p-1)/2
+
+   The hexadecimal representation of q is:
+
+    7FFFFFFF FFFFFFFF D6FC2A2C 515DA54D 57EE2B10 139E9E78
+    EC5CE2C1 E7169B4A D4F09B20 8A3219FD E649CEE7 124D9F7C
+    BE97F1B1 B1863AEC 7B40D901 576230BD 69EF8F6A EAFEB2B0
+    9219FA8F AF833768 42B1B2AA 9EF68D79 DAAB89AF 3FABE49A
+    CC278638 707345BB F15344ED 79F7F439 0EF8AC50 9B56F39A
+    98566527 A41D3CBD 5E0558C1 59927DB0 E88454A5 D96471FD
+    DCB56D5B B06BFA34 0EA7A151 EF1CA6FA 572B76F3 B1B95D8C
+    8583D3E4 770536B8 4F017E70 E6FBF176 601A0266 941A17B0
+    C8B97F4E 74C2C1FF C7278919 777940C1 E1FF1D8D A637D6B9
+    9DDAFE5E 17611002 E2C778C1 BE8B41D9 6379A513 60D977FD
+    4435A11C 308FE7EE 6F1AAD9D B28C81AD DE1A7A6F 7CCE011C
+    30DA37E4 EB736483 BD6C8E93 48FBFBF7 2CC6587D 60C36C8E
+    577F0984 C289C938 5A098649 DE21BCA2 7A7EA229 716BA6E9
+    B279710F 38FAA5FF AE574155 CE4EFB4F 743695E2 911B1D06
+    D5E290CB CD86F56D 0EDFCD21 6AE22427 055E6835 FD29EEF7
+    9E0D9077 1FEACEBE 12F20E95 B34F0F78 B737A961 8B26FA7D
+    BC9874F2 72C42BDB 563EAFA1 6B4FB68C 3BB1E78E AA81A002
+    43FAADD2 BF18E63D 389AE443 77DA18C5 76B50F00 96CF3419
+    5483B005 48C09862 36E3BC7C B8D6801C 0494CCD1 99E5C5BD
+    0D0EDC9E B8A0001E 15276754 FCC68566 054148E6 E764BEE7
+    C764DAAD 3FC45235 A6DAD428 FA20C170 E345003F 2F06EC81
+    05FEB25B 2281B63D 2733BE96 1C29951D 11DD2221 657A9F53
+    1DDA2A19 4DBB1264 48BDEEB2 58E07EA6 59C74619 A6380E1D
+    66D6832B FE67F638 CD8FAE1F 2723020F 9C40A3FD A67EDA3B
+    D29238FB D4D4B488 5C2A9917 6DB1A06C 50077849 1A8288F1
+    855F60FF FCF1D137 3FD94FC6 0C1811E1 AC3F1C6D 003BECDA
+    3B1F2725 CA595DE0 CA63328F 3BE57CC9 77556011 95140DFB
+    59D39CE0 91308B41 05746DAC 23D33E5F 7CE4848D A316A9C6
+    6B9581BA 3573BFAF 31149618 8AB15423 282EE416 DC2A19C5
+    724FA91A E4ADC88B C66796EA E5677A01 F64E8C08 63139582
+    2D9DB8FC EE35C06B 1FEEA547 4D6D8F34 B1534A93 6A18B0E0
+    D20EAB86 BC9C6D6A 5207194E 67FA3555 1B568026 7B00641C
+    0F212D18 ECA8D732 7ED91FE7 64A84EA1 B43FF5B4 F6E8E62F
+    05C661DE FB258877 C35B18A1 51D5C414 AAAD97BA 3E499332
+    E596078E 600DEB81 149C441C E95782F2 2A282563 C5BAC141
+    1423605D 1AE1AFAE 2C8B0660 237EC128 AA0FE346 4E435811
+    5DB84CC3 B523073A 28D45498 84B81FF7 0E10BF36 1C137296
+    28D5348F 07211E7E 4CF4F18B 286090BD B1240B66 D6CD4AFC
+    EADC00CA 446CE050 50FF183A D2BBF118 C1FC0EA5 1F97D22B
+    8F7E4670 5D4527F4 5B42AEFF 39585337 6F697DD5 FDF2C518
+    7D7D5F0E 2EB8D43F 17BA0F7C 60FF437F 535DFEF2 9833BF86
+    CBE88EA4 FBD4221E 84117283 54FA30A7 008F154A 41C7FC46
+    6B4645DB E2E32126 7FFFFFFF FFFFFFFF
+
+   The estimated symmetric-equivalent strength of this group is 192
+   bits.
+*/
+static const char dh_ffdhe8192_pem[] =
+"-----BEGIN DH PARAMETERS-----\n"
+"MIIECAKCBAEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz\n"
+"+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a\n"
+"87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7\n"
+"YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi\n"
+"7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD\n"
+"ssbzSibBsu/6iGtCOGEfz9zeNVs7ZRkDW7w09N75nAI4YbRvydbmyQd62R0mkff3\n"
+"7lmMsPrBhtkcrv4TCYUTknC0EwyTvEN5RPT9RFLi103TZPLiHnH1S/9croKrnJ32\n"
+"nuhtK8UiNjoNq8Uhl5sN6todv5pC1cRITgq80Gv6U93vPBsg7j/VnXwl5B0rZp4e\n"
+"8W5vUsMWTfT7eTDp5OWIV7asfV9C1p9tGHdjzx1VA0AEh/VbpX4xzHpxNciG77Qx\n"
+"iu1qHgEtnmgyqQdgCpGBMMRtx3j5ca0AOAkpmaMzy4t6Gh25PXFAADwqTs6p+Y0K\n"
+"zAqCkc3OyX3Pjsm1Wn+IpGtNtahR9EGC4caKAH5eDdkCC/1ktkUDbHpOZ30sOFMq\n"
+"OiO6RELK9T6mO7RUMpt2JMiRe91kscD9TLOOjDNMcBw6za0GV/zP7HGbH1w+TkYE\n"
+"HziBR/tM/bR3pSRx96mpaRC4VTIu22NA2KAO8JI1BRHjCr7B//njom5/sp+MGDAj\n"
+"w1h+ONoAd9m0dj5OS5Syu8GUxmUed8r5ku6qwCMqKBv2s6c5wSJhFoIK6NtYR6Z8\n"
+"vvnJCRtGLVOM1ysDdGrnf15iKSwxFWKoRlBdyC24VDOK5J9SNclbkReMzy3Vys70\n"
+"A+ydGBDGJysEWztx+dxrgNY/3UqOmtseaWKmlSbUMWHBpB1XDXk42tSkDjKcz/Rq\n"
+"qjatAEz2AMg4HkJaMdlRrmT9sj/OyVCdQ2h/62nt0cxeC4zDvfZLEO+GtjFCo6uI\n"
+"KVVbL3R8kyZlyywPHMAb1wIpOIg50q8F5FRQSseLdYKCKEbAujXDX1xZFgzARv2C\n"
+"UVQfxoychrAiu3CZh2pGDnRRqKkxCXA/7hwhfmw4JuUsUappHg5CPPyZ6eMWUMEh\n"
+"e2JIFs2tmpX51bgBlIjZwKCh/jB1pXfiMYP4HUo/L6RXHvyM4LqKT+i2hV3+crCm\n"
+"bt7S+6v75Yow+vq+HF1xqH4vdB74wf6G/qa7/eUwZ38Nl9EdSfeoRD0IIuUGqfRh\n"
+"TgEeKpSDj/iM1oyLt8XGQkz//////////wIBAg==\n"
+"-----END DH PARAMETERS-----\n";
+
+/* ========================================================================= */
+
+/*
+ * Generated by Phil as a non-standard option.
+ * openssl dhparam -2 2048
+ * No provenance to prove non-tampering available, beyond trusting that this
+ * developer generated this as stated above.
+ */
+
+/* MacOSX 10.10.5 invoking system OpenSSL 0.9.8zg */
+static const char dh_exim_20160529_1[] =
+"-----BEGIN DH PARAMETERS-----\n"
+"MIIBCAKCAQEA8ZMf89Gaye4bDEX1BXZ9+2edkXym9EK0GxmFilHEGpnhgLNmCk+H\n"
+"cCb+zn8Ed5bpCOmRuEv9N/VKPjSpno8jYiQbFgUL3vh8uKvQLJNTzDVDbpd3YO7E\n"
+"tiS0L0qWL57zIf8b3VZTMRsH4Orz2Rla61wVl6XpxE5WRfGqPS264Vvfew7xmCoi\n"
+"INaFzIU6zwk2WeD6K5asctYlQG/UtgY1nRFkQTebIOpm03a6/hw7F14l3yUZgXfv\n"
+"I3m4MFaWvxGcuZxddTijXw3VfjMdWvdH3Iz7IcqD32uEzK6Rgi/t4OVSw1kE2oDt\n"
+"cFThPUCWb7O4TVq9Xt2UZqZFNU6kUAkv2wIBAg==\n"
+"-----END DH PARAMETERS-----\n";
+
+/* MacOSX 10.10.5 invoking OpenSSL 1.0.2h installed from brew bottle */
+static const char dh_exim_20160529_2[] =
+"-----BEGIN DH PARAMETERS-----\n"
+"MIIBCAKCAQEAot84eqyfSb5l8GRCN2ioWP5T85Z/2lVX9A9r9JzwDfvliAAqm6Vp\n"
+"UcHdAfVt54kc8DsmLiHdDhxY1I/wo+DcBylfVx13cmkroAocowOD5dwQMYk6iXjV\n"
+"ys4heRJhYlAHgt8QZH8dA8c/HLs+rlAHhSUPnetsZmcoPE0LRsjigJsiVXasm+sl\n"
+"g/77u5FCkgSrFILcD9PLPto1ciIXp2y8cjXQDk+D9FH1HaSCXLCLkuHxhQXxjTYO\n"
+"C3Q53aNLkDJ4zpPt7Kc9NxQFBVlNc260IFDOHTWhgV2zpyG6oIzQoHSmmiLAAfcF\n"
+"HrG7I06uZBLjuNGGaM0eeuxHNhs2G2EduwIBAg==\n"
+"-----END DH PARAMETERS-----\n";
+
+/* Ubuntu 14.04.4 running on dual-core Atom D2500 with OneRNG entropy key */
+static const char dh_exim_20160529_3[] =
+"-----BEGIN DH PARAMETERS-----\n"
+"MIIBCAKCAQEAkbRYVoge2PtrmV1eKCKluSBFELgckuLSnkuH0TffqbmfoYM34lFu\n"
+"2vPM2LhnzKvEBQlIICOTzQD29kROacRfSKpsNINRXhXKUqI6sFXzUZu4Flk69XKG\n"
+"ZOSDYvWkI5pSn1amQ4Nnvn6s+uwn/f0ZDZDiKLW9TgntxJV4A2+yeymaeoGCbIXX\n"
+"5q8WgajFhAeut36RL93HBnXT1hT7Eja1Y81w9fOzQrwBuXhyfCkAdiMA/VCp0UD4\n"
+"0p7uf+okpckVnwD6WnUCHMij8nGlVblZELFYzNi0udtzIrSwlALbZXIeAqhbZXJO\n"
+"lCuYspJhzV0Vs0lDJwrxvNwtdg1ernVIowIBAg==\n"
+"-----END DH PARAMETERS-----\n";
 
 /* ========================================================================= */
 
@@ -470,7 +961,15 @@ struct dh_constant {
 /* KEEP SORTED ALPHABETICALLY;
  * duplicate PEM are okay, if we want aliases, but names must be alphabetical */
 static struct dh_constant dh_constants[] = {
-    { "default", dh_ike_23_pem },
+    { "default", dh_exim_20160529_3 },
+    { "exim.dev.20160529.1", dh_exim_20160529_1 },
+    { "exim.dev.20160529.2", dh_exim_20160529_2 },
+    { "exim.dev.20160529.3", dh_exim_20160529_3 },
+    { "ffdhe2048", dh_ffdhe2048_pem },
+    { "ffdhe3072", dh_ffdhe3072_pem },
+    { "ffdhe4096", dh_ffdhe4096_pem },
+    { "ffdhe6144", dh_ffdhe6144_pem },
+    { "ffdhe8192", dh_ffdhe8192_pem },
     { "ike1", dh_ike_1_pem },
     { "ike14", dh_ike_14_pem },
     { "ike15", dh_ike_15_pem },
index 790f79d..8628954 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Exim gets and frees all its store through these functions. In the original
@@ -144,40 +144,40 @@ if (size > yield_length[store_pool])
   {
   int length = (size <= STORE_BLOCK_SIZE)? STORE_BLOCK_SIZE : size;
   int mlength = length + ALIGNED_SIZEOF_STOREBLOCK;
-  storeblock *newblock = NULL;
+  storeblock * newblock = NULL;
 
   /* Sometimes store_reset() may leave a block for us; check if we can use it */
 
-  if (current_block[store_pool] != NULL &&
-      current_block[store_pool]->next != NULL)
+  if (  (newblock = current_block[store_pool])
+     && (newblock = newblock->next)
+     && newblock->length < length
+     )
     {
-    newblock = current_block[store_pool]->next;
-    if (newblock->length < length)
-      {
-      /* Give up on this block, because it's too small */
-      store_free(newblock);
-      newblock = NULL;
-      }
+    /* Give up on this block, because it's too small */
+    store_free(newblock);
+    newblock = NULL;
     }
 
   /* If there was no free block, get a new one */
 
-  if (newblock == NULL)
+  if (!newblock)
     {
     pool_malloc += mlength;           /* Used in pools */
     nonpool_malloc -= mlength;        /* Exclude from overall total */
     newblock = store_malloc(mlength);
     newblock->next = NULL;
     newblock->length = length;
-    if (chainbase[store_pool] == NULL) chainbase[store_pool] = newblock;
-      else current_block[store_pool]->next = newblock;
+    if (!chainbase[store_pool])
+      chainbase[store_pool] = newblock;
+    else
+      current_block[store_pool]->next = newblock;
     }
 
   current_block[store_pool] = newblock;
   yield_length[store_pool] = newblock->length;
   next_yield[store_pool] =
-    (void *)((char *)current_block[store_pool] + ALIGNED_SIZEOF_STOREBLOCK);
-  VALGRIND_MAKE_MEM_NOACCESS(next_yield[store_pool], yield_length[store_pool]);
+    (void *)(CS current_block[store_pool] + ALIGNED_SIZEOF_STOREBLOCK);
+  (void) VALGRIND_MAKE_MEM_NOACCESS(next_yield[store_pool], yield_length[store_pool]);
   }
 
 /* There's (now) enough room in the current block; the yield is the next
@@ -202,7 +202,7 @@ DEBUG(D_memory)
   }
 #endif  /* COMPILE_UTILITY */
 
-VALGRIND_MAKE_MEM_UNDEFINED(store_last_get[store_pool], size);
+(void) VALGRIND_MAKE_MEM_UNDEFINED(store_last_get[store_pool], size);
 /* Update next pointer and number of bytes left in the current block. */
 
 next_yield[store_pool] = (void *)((char *)next_yield[store_pool] + size);
@@ -297,7 +297,7 @@ DEBUG(D_memory)
 if (newsize % alignment != 0) newsize += alignment - (newsize % alignment);
 next_yield[store_pool] = (char *)ptr + newsize;
 yield_length[store_pool] -= newsize - rounded_oldsize;
-VALGRIND_MAKE_MEM_UNDEFINED(ptr + oldsize, inc);
+(void) VALGRIND_MAKE_MEM_UNDEFINED(ptr + oldsize, inc);
 return TRUE;
 }
 
@@ -325,9 +325,9 @@ Returns:      nothing
 void
 store_reset_3(void *ptr, const char *filename, int linenumber)
 {
-storeblock *bb;
-storeblock *b = current_block[store_pool];
-char *bc = (char *)b + ALIGNED_SIZEOF_STOREBLOCK;
+storeblock * bb;
+storeblock * b = current_block[store_pool];
+char * bc = CS b + ALIGNED_SIZEOF_STOREBLOCK;
 int newlength;
 
 /* Last store operation was not a get */
@@ -337,14 +337,14 @@ store_last_get[store_pool] = NULL;
 /* See if the place is in the current block - as it often will be. Otherwise,
 search for the block in which it lies. */
 
-if ((char *)ptr < bc || (char *)ptr > bc + b->length)
+if (CS ptr < bc || CS ptr > bc + b->length)
   {
-  for (b = chainbase[store_pool]; b != NULL; b = b->next)
+  for (b = chainbase[store_pool]; b; b = b->next)
     {
-    bc = (char *)b + ALIGNED_SIZEOF_STOREBLOCK;
-    if ((char *)ptr >= bc && (char *)ptr <= bc + b->length) break;
+    bc = CS b + ALIGNED_SIZEOF_STOREBLOCK;
+    if (CS ptr >= bc && CS ptr <= bc + b->length) break;
     }
-  if (b == NULL)
+  if (!b)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "internal error: store_reset(%p) "
       "failed: pool=%d %-14s %4d", ptr, store_pool, filename, linenumber);
   }
@@ -352,13 +352,21 @@ if ((char *)ptr < bc || (char *)ptr > bc + b->length)
 /* Back up, rounding to the alignment if necessary. When testing, flatten
 the released memory. */
 
-newlength = bc + b->length - (char *)ptr;
+newlength = bc + b->length - CS ptr;
 #ifndef COMPILE_UTILITY
-if (running_in_test_harness) memset(ptr, 0xF0, newlength);
+if (running_in_test_harness || debug_store)
+  {
+  assert_no_variables(ptr, newlength, filename, linenumber);
+  if (running_in_test_harness)
+    {
+    (void) VALGRIND_MAKE_MEM_DEFINED(ptr, newlength);
+    memset(ptr, 0xF0, newlength);
+    }
+  }
 #endif
-VALGRIND_MAKE_MEM_NOACCESS(ptr, newlength);
+(void) VALGRIND_MAKE_MEM_NOACCESS(ptr, newlength);
 yield_length[store_pool] = newlength - (newlength % alignment);
-next_yield[store_pool] = (char *)ptr + (newlength % alignment);
+next_yield[store_pool] = CS ptr + (newlength % alignment);
 current_block[store_pool] = b;
 
 /* Free any subsequent block. Do NOT free the first successor, if our
@@ -366,19 +374,29 @@ current block has less than 256 bytes left. This should prevent us from
 flapping memory. However, keep this block only when it has the default size. */
 
 if (yield_length[store_pool] < STOREPOOL_MIN_SIZE &&
-    b->next != NULL &&
+    b->next &&
     b->next->length == STORE_BLOCK_SIZE)
   {
   b = b->next;
-  VALGRIND_MAKE_MEM_NOACCESS((char *)b + ALIGNED_SIZEOF_STOREBLOCK, b->length - ALIGNED_SIZEOF_STOREBLOCK);
+#ifndef COMPILE_UTILITY
+  if (running_in_test_harness || debug_store)
+    assert_no_variables(b, b->length + ALIGNED_SIZEOF_STOREBLOCK,
+                       filename, linenumber);
+#endif
+  (void) VALGRIND_MAKE_MEM_NOACCESS(CS b + ALIGNED_SIZEOF_STOREBLOCK,
+               b->length - ALIGNED_SIZEOF_STOREBLOCK);
   }
 
 bb = b->next;
 b->next = NULL;
 
-while (bb != NULL)
+while ((b = bb))
   {
-  b = bb;
+#ifndef COMPILE_UTILITY
+  if (running_in_test_harness || debug_store)
+    assert_no_variables(b, b->length + ALIGNED_SIZEOF_STOREBLOCK,
+                       filename, linenumber);
+#endif
   bb = bb->next;
   pool_malloc -= b->length + ALIGNED_SIZEOF_STOREBLOCK;
   store_free_3(b, filename, linenumber);
@@ -492,9 +510,8 @@ store_malloc_3(int size, const char *filename, int linenumber)
 void *yield;
 
 if (size < 16) size = 16;
-yield = malloc((size_t)size);
 
-if (yield == NULL)
+if (!(yield = malloc((size_t)size)))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to malloc %d bytes of memory: "
     "called from line %d of %s", size, linenumber, filename);
 
index 0f657dc..cec5950 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Miscellaneous string-handling functions. Some are not required for
@@ -165,7 +165,7 @@ Returns:      pointer to the buffer
 uschar *
 string_format_size(int size, uschar *buffer)
 {
-if (size == 0) Ustrcpy(CS buffer, "     ");
+if (size == 0) Ustrcpy(buffer, "     ");
 else if (size < 1024) sprintf(CS buffer, "%5d", size);
 else if (size < 10*1024)
   sprintf(CS buffer, "%4.1fK", (double)size / 1024.0);
@@ -224,13 +224,13 @@ Returns:   the value of the character escape
 */
 
 int
-string_interpret_escape(uschar **pp)
+string_interpret_escape(const uschar **pp)
 {
 #ifdef COMPILE_UTILITY
 const uschar *hex_digits= CUS"0123456789abcdef";
 #endif
 int ch;
-uschar *p = *pp;
+const uschar *p = *pp;
 ch = *(++p);
 if (isdigit(ch) && ch != '8' && ch != '9')
   {
@@ -284,12 +284,12 @@ Arguments:
 Returns:        string with non-printers encoded as printing sequences
 */
 
-uschar *
-string_printing2(uschar *s, BOOL allow_tab)
+const uschar *
+string_printing2(const uschar *s, BOOL allow_tab)
 {
 int nonprintcount = 0;
 int length = 0;
-uschar *t = s;
+const uschar *t = s;
 uschar *ss, *tt;
 
 while (*t != 0)
@@ -306,7 +306,7 @@ expanded string. */
 
 ss = store_get(length + nonprintcount * 3 + 1);
 
-/* Copy everying, escaping non printers. */
+/* Copy everything, escaping non printers. */
 
 t = s;
 tt = ss;
@@ -374,7 +374,7 @@ while (*p)
   {
   if (*p == '\\')
     {
-    *q++ = string_interpret_escape(&p);
+    *q++ = string_interpret_escape((const uschar **)&p);
     p++;
     }
   else
@@ -437,7 +437,7 @@ Returns:  copy of string in new store
 */
 
 uschar *
-string_copy_malloc(uschar *s)
+string_copy_malloc(const uschar *s)
 {
 int len = Ustrlen(s) + 1;
 uschar *ss = store_malloc(len);
@@ -457,7 +457,7 @@ Returns:  copy of string in new store, with letters lowercased
 */
 
 uschar *
-string_copylc(uschar *s)
+string_copylc(const uschar *s)
 {
 uschar *ss = store_get(Ustrlen(s) + 1);
 uschar *p = ss;
@@ -483,7 +483,7 @@ Returns:    copy of string in new store
 */
 
 uschar *
-string_copyn(uschar *s, int n)
+string_copyn(const uschar *s, int n)
 {
 uschar *ss = store_get(n + 1);
 Ustrncpy(ss, s, n);
@@ -639,9 +639,9 @@ Returns:   the new string
 */
 
 uschar *
-string_dequote(uschar **sptr)
+string_dequote(const uschar **sptr)
 {
-uschar *s = *sptr;
+const uschar *s = *sptr;
 uschar *t, *yield;
 
 /* First find the end of the string */
@@ -717,8 +717,9 @@ uschar buffer[STRING_SPRINTF_BUFFER_SIZE];
 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 " (%s)",
-    sizeof(buffer), format);
+    "string_sprintf expansion was longer than " SIZE_T_FMT
+    "; format string was (%s)\nexpansion started '%.32s'",
+    sizeof(buffer), format, buffer);
 va_end(ap);
 return string_copy(buffer);
 }
@@ -868,10 +869,10 @@ Returns:     pointer to buffer, containing the next substring,
 */
 
 uschar *
-string_nextinlist(uschar **listptr, int *separator, uschar *buffer, int buflen)
+string_nextinlist(const uschar **listptr, int *separator, uschar *buffer, int buflen)
 {
-register int sep = *separator;
-register uschar *s = *listptr;
+int sep = *separator;
+const uschar *s = *listptr;
 BOOL sep_is_special;
 
 if (s == NULL) return NULL;
@@ -912,7 +913,7 @@ sep_is_special = iscntrl(sep);
 
 if (buffer != NULL)
   {
-  register int p = 0;
+  int p = 0;
   for (; *s != 0; s++)
     {
     if (*s == sep && (*(++s) != sep || sep_is_special)) break;
@@ -928,7 +929,7 @@ else
   {
   int size = 0;
   int ptr = 0;
-  uschar *ss;
+  const uschar *ss;
 
   /* We know that *s != 0 at this point. However, it might be pointing to a
   separator, which could indicate an empty string, or (if an ispunct()
@@ -951,7 +952,7 @@ else
   for (;;)
     {
     for (ss = s + 1; *ss != 0 && *ss != sep; ss++);
-    buffer = string_cat(buffer, &size, &ptr, s, ss-s);
+    buffer = string_catn(buffer, &size, &ptr, s, ss-s);
     s = ss;
     if (*s == 0 || *(++s) != sep || sep_is_special) break;
     }
@@ -969,18 +970,18 @@ return buffer;
 
 #ifndef COMPILE_UTILITY
 /************************************************
-*      Add element to seperated list           *
+*      Add element to separated list           *
 ************************************************/
 /* This function is used to build a list, returning
 an allocated null-terminated growable string. The
-given element has any embedded seperator characters
+given element has any embedded separator 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
+  sep  list separator character
+  ele  new element to be appended to the list
 
 Returns:  pointer to the start of the list, changed if copied for expansion.
 */
@@ -994,17 +995,62 @@ uschar * sp;
 
 if (list)
   {
-  new = string_cat(new, &sz, &off, list, Ustrlen(list));
-  new = string_cat(new, &sz, &off, &sep, 1);
+  new = string_cat (new, &sz, &off, list);
+  new = string_catn(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);
+  new = string_catn(new, &sz, &off, ele, sp-ele+1);
+  new = string_catn(new, &sz, &off, &sep, 1);
+  ele = sp+1;
+  }
+new = string_cat(new, &sz, &off, ele);
+new[off] = '\0';
+return new;
+}
+
+
+static const uschar *
+Ustrnchr(const uschar * s, int c, unsigned * len)
+{
+unsigned siz = *len;
+while (siz)
+  {
+  if (!*s) return NULL;
+  if (*s == c)
+    {
+    *len = siz;
+    return s;
+    }
+  s++;
+  siz--;
+  }
+return NULL;
+}
+
+uschar *
+string_append_listele_n(uschar * list, uschar sep, const uschar * ele,
+  unsigned len)
+{
+uschar * new = NULL;
+int sz = 0, off = 0;
+const uschar * sp;
+
+if (list)
+  {
+  new = string_cat (new, &sz, &off, list);
+  new = string_catn(new, &sz, &off, &sep, 1);
+  }
+
+while((sp = Ustrnchr(ele, sep, &len)))
+  {
+  new = string_catn(new, &sz, &off, ele, sp-ele+1);
+  new = string_catn(new, &sz, &off, &sep, 1);
   ele = sp+1;
+  len--;
   }
-new = string_cat(new, &sz, &off, ele, Ustrlen(ele));
+new = string_catn(new, &sz, &off, ele, len);
 new[off] = '\0';
 return new;
 }
@@ -1032,7 +1078,7 @@ Arguments:
              characters, updated to the new offset
   s        points to characters to add
   count    count of characters to add; must not exceed the length of s, if s
-             is a C string
+             is a C string.  If -1 given, strlen(s) is used.
 
 If string is given as NULL, *size and *ptr should both be zero.
 
@@ -1040,10 +1086,12 @@ Returns:   pointer to the start of the string, changed if copied for expansion.
            Note that a NUL is not added, though space is left for one. This is
            because string_cat() is often called multiple times to build up a
            string - there's no point adding the NUL till the end.
+
 */
+/* coverity[+alloc] */
 
 uschar *
-string_cat(uschar *string, int *size, int *ptr, const uschar *s, int count)
+string_catn(uschar *string, int *size, int *ptr, const uschar *s, int count)
 {
 int p = *ptr;
 
@@ -1086,12 +1134,25 @@ if (p + count >= *size)
 
 /* Because we always specify the exact number of characters to copy, we can
 use memcpy(), which is likely to be more efficient than strncopy() because the
-latter has to check for zero bytes. */
+latter has to check for zero bytes.
 
+The Coverity annotation deals with the lack of correlated variable tracking;
+common use is a null string and zero size and pointer, on first use for a
+string being built. The "if" above then allocates, but Coverity assume that
+the "if" might not happen and whines for a null-deref done by the memcpy(). */
+
+/* coverity[deref_parm_field_in_call] : FALSE */
 memcpy(string + p, s, count);
 *ptr = p + count;
 return string;
 }
+
+
+uschar *
+string_cat(uschar *string, int *size, int *ptr, const uschar *s)
+{
+return string_catn(string, size, ptr, s, Ustrlen(s));
+}
 #endif  /* COMPILE_UTILITY */
 
 
@@ -1129,7 +1190,7 @@ va_start(ap, count);
 for (i = 0; i < count; i++)
   {
   uschar *t = va_arg(ap, uschar *);
-  string = string_cat(string, size, ptr, t, Ustrlen(t));
+  string = string_cat(string, size, ptr, t);
   }
 va_end(ap);
 
@@ -1151,10 +1212,10 @@ on whether the variable length list of data arguments are given explicitly or
 as a va_list item.
 
 The formats are the usual printf() ones, with some omissions (never used) and
-two additions for strings: %S forces lower case, and %#s or %#S prints nothing
-for a NULL string. Without the # "NULL" is printed (useful in debugging). There
-is also the addition of %D and %M, which insert the date in the form used for
-datestamped log files.
+three additions for strings: %S forces lower case, %T forces upper case, and
+%#s or %#S prints nothing for a NULL string. Without thr # "NULL" is printed
+(useful in debugging). There is also the addition of %D and %M, which insert
+the date in the form used for datestamped log files.
 
 Arguments:
   buffer       a buffer in which to put the formatted string
@@ -1367,6 +1428,7 @@ while (*fp != 0)
 
     case 's':
     case 'S':                   /* Forces *lower* case */
+    case 'T':                   /* Forces *upper* case */
     s = va_arg(ap, char *);
 
     if (s == NULL) s = null;
@@ -1414,6 +1476,8 @@ while (*fp != 0)
     sprintf(CS p, "%*.*s", width, precision, s);
     if (fp[-1] == 'S')
       while (*p) { *p = tolower(*p); p++; }
+    else if (fp[-1] == 'T')
+      while (*p) { *p = toupper(*p); p++; }
     else
       while (*p) p++;
     if (!yield) goto END_FORMAT;
@@ -1472,6 +1536,7 @@ specified messages. If it does, the message just gets truncated, and there
 doesn't seem much we can do about that. */
 
 (void)string_vformat(buffer+15, sizeof(buffer) - 15, format, ap);
+va_end(ap);
 
 return (eno == EACCES)?
   string_sprintf("%s: %s (euid=%ld egid=%ld)", buffer, strerror(eno),
@@ -1482,159 +1547,19 @@ return (eno == EACCES)?
 
 
 
-#ifndef COMPILE_UTILITY
-/*************************************************
-*        Generate local prt for logging          *
-*************************************************/
 
-/* This function is a subroutine for use in string_log_address() below.
 
-Arguments:
-  addr        the address being logged
-  yield       the current dynamic buffer pointer
-  sizeptr     points to current size
-  ptrptr      points to current insert pointer
-
-Returns:      the new value of the buffer pointer
-*/
-
-static uschar *
-string_get_localpart(address_item *addr, uschar *yield, int *sizeptr,
-  int *ptrptr)
-{
-if (testflag(addr, af_include_affixes) && addr->prefix != NULL)
-  yield = string_cat(yield, sizeptr, ptrptr, addr->prefix,
-    Ustrlen(addr->prefix));
-yield = string_cat(yield, sizeptr, ptrptr, addr->local_part,
-  Ustrlen(addr->local_part));
-if (testflag(addr, af_include_affixes) && addr->suffix != NULL)
-  yield = string_cat(yield, sizeptr, ptrptr, addr->suffix,
-    Ustrlen(addr->suffix));
-return yield;
-}
-
-
-/*************************************************
-*          Generate log address list             *
-*************************************************/
-
-/* This function generates a list consisting of an address and its parents, for
-use in logging lines. For saved onetime aliased addresses, the onetime parent
-field is used. If the address was delivered by a transport with rcpt_include_
-affixes set, the af_include_affixes bit will be set in the address. In that
-case, we include the affixes here too.
-
-Arguments:
-  addr          bottom (ultimate) address
-  all_parents   if TRUE, include all parents
-  success       TRUE for successful delivery
-
-Returns:        a string in dynamic store
-*/
+#ifndef COMPILE_UTILITY
+/* qsort(3), currently used to sort the environment variables
+for -bP environment output, needs a function to compare two pointers to string
+pointers. Here it is. */
 
-uschar *
-string_log_address(address_item *addr, BOOL all_parents, BOOL success)
+int
+string_compare_by_pointer(const void *a, const void *b)
 {
-int size = 64;
-int ptr = 0;
-BOOL add_topaddr = TRUE;
-uschar *yield = store_get(size);
-address_item *topaddr;
-
-/* Find the ultimate parent */
-
-for (topaddr = addr; topaddr->parent != NULL; topaddr = topaddr->parent);
-
-/* We start with just the local part for pipe, file, and reply deliveries, and
-for successful local deliveries from routers that have the log_as_local flag
-set. File deliveries from filters can be specified as non-absolute paths in
-cases where the transport is goin to complete the path. If there is an error
-before this happens (expansion failure) the local part will not be updated, and
-so won't necessarily look like a path. Add extra text for this case. */
-
-if (testflag(addr, af_pfr) ||
-      (success &&
-       addr->router != NULL && addr->router->log_as_local &&
-       addr->transport != NULL && addr->transport->info->local))
-  {
-  if (testflag(addr, af_file) && addr->local_part[0] != '/')
-    yield = string_cat(yield, &size, &ptr, CUS"save ", 5);
-  yield = string_get_localpart(addr, yield, &size, &ptr);
-  }
-
-/* Other deliveries start with the full address. It we have split it into local
-part and domain, use those fields. Some early failures can happen before the
-splitting is done; in those cases use the original field. */
-
-else
-  {
-  if (addr->local_part != NULL)
-    {
-    yield = string_get_localpart(addr, yield, &size, &ptr);
-    yield = string_cat(yield, &size, &ptr, US"@", 1);
-    yield = string_cat(yield, &size, &ptr, addr->domain,
-      Ustrlen(addr->domain) );
-    }
-  else
-    {
-    yield = string_cat(yield, &size, &ptr, addr->address, Ustrlen(addr->address));
-    }
-  yield[ptr] = 0;
-
-  /* If the address we are going to print is the same as the top address,
-  and all parents are not being included, don't add on the top address. First
-  of all, do a caseless comparison; if this succeeds, do a caseful comparison
-  on the local parts. */
-
-  if (strcmpic(yield, topaddr->address) == 0 &&
-      Ustrncmp(yield, topaddr->address, Ustrchr(yield, '@') - yield) == 0 &&
-      addr->onetime_parent == NULL &&
-      (!all_parents || addr->parent == NULL || addr->parent == topaddr))
-    add_topaddr = FALSE;
-  }
-
-/* If all parents are requested, or this is a local pipe/file/reply, and
-there is at least one intermediate parent, show it in brackets, and continue
-with all of them if all are wanted. */
-
-if ((all_parents || testflag(addr, af_pfr)) &&
-    addr->parent != NULL &&
-    addr->parent != topaddr)
-  {
-  uschar *s = US" (";
-  address_item *addr2;
-  for (addr2 = addr->parent; addr2 != topaddr; addr2 = addr2->parent)
-    {
-    yield = string_cat(yield, &size, &ptr, s, 2);
-    yield = string_cat(yield, &size, &ptr, addr2->address, Ustrlen(addr2->address));
-    if (!all_parents) break;
-    s = US", ";
-    }
-  yield = string_cat(yield, &size, &ptr, US")", 1);
-  }
-
-/* Add the top address if it is required */
-
-if (add_topaddr)
-  {
-  yield = string_cat(yield, &size, &ptr, US" <", 2);
-
-  if (addr->onetime_parent == NULL)
-    yield = string_cat(yield, &size, &ptr, topaddr->address,
-      Ustrlen(topaddr->address));
-  else
-    yield = string_cat(yield, &size, &ptr, addr->onetime_parent,
-      Ustrlen(addr->onetime_parent));
-
-  yield = string_cat(yield, &size, &ptr, US">", 1);
-  }
-
-yield[ptr] = 0;  /* string_cat() leaves space */
-return yield;
+return Ustrcmp(* CUSS a, * CUSS b);
 }
-#endif  /* COMPILE_UTILITY */
-
-
+#endif /* COMPILE_UTILITY */
 
 
 
index 71ac5d8..38b095f 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -28,17 +28,18 @@ struct router_info;
 /* Structure for remembering macros for the configuration file */
 
 typedef struct macro_item {
-  struct  macro_item *next;
-  BOOL    command_line;
-  uschar *replacement;
-  uschar  name[1];
+  struct   macro_item *next;
+  BOOL     command_line;
+  unsigned namelen;
+  uschar * replacement;
+  uschar   name[1];
 } macro_item;
 
 /* Structure for bit tables for debugging and logging */
 
 typedef struct bit_table {
   uschar *name;
-  unsigned int bit;
+  int bit;
 } bit_table;
 
 /* Block for holding a uid and gid, possibly unset, and an initgroups flag. */
@@ -51,6 +52,11 @@ typedef struct ugid_block {
   BOOL    initgroups;
 } ugid_block;
 
+typedef enum { CHUNKING_NOT_OFFERED = -1,
+               CHUNKING_OFFERED,
+               CHUNKING_ACTIVE,
+               CHUNKING_LAST} chunking_state_t;
+
 /* Structure for holding information about a host for use mainly by routers,
 but also used when checking lists of hosts and when transporting. Looking up
 host addresses is done using this structure. */
@@ -59,8 +65,8 @@ typedef enum {DS_UNK=-1, DS_NO, DS_YES} dnssec_status_t;
 
 typedef struct host_item {
   struct host_item *next;
-  uschar *name;                   /* Host name */
-  uschar *address;                /* IP address in text form */
+  const uschar *name;             /* Host name */
+  const uschar *address;          /* IP address in text form */
   int     port;                   /* port value in host order (if SRV lookup) */
   int     mx;                     /* MX value if found via MX records */
   int     sort_key;               /* MX*1000 plus random "fraction" */
@@ -148,6 +154,7 @@ typedef struct transport_instance {
   uschar *home_dir;               /* ) Used only for local transports   */
   uschar *current_dir;            /* )                                  */
                                   /**************************************/
+  uschar *expand_multi_domain;    /* )                                  */
   BOOL    multi_domain;           /* )                                  */
   BOOL    overrides_hosts;        /* ) Used only for remote transports  */
   int     max_addresses;          /* )                                  */
@@ -170,6 +177,7 @@ typedef struct transport_instance {
   uschar *remove_headers;         /* Remove these headers */
   uschar *return_path;            /* Overriding (rewriting) return path */
   uschar *debug_string;           /* Debugging output */
+  uschar *max_parallel;           /* Number of concurrent instances */
   uschar *message_size_limit;     /* Biggest message this transport handles */
   uschar *headers_rewrite;        /* Rules for rewriting headers */
   rewrite_rule *rewrite_rules;    /* Parsed rewriting rules */
@@ -187,8 +195,8 @@ typedef struct transport_instance {
   BOOL    log_fail_output;
   BOOL    log_defer_output;
   BOOL    retry_use_local_part;   /* Defaults true for local, false for remote */
-#ifdef EXPERIMENTAL_TPDA
-  uschar  *tpda_delivery_action;  /* String to expand on success */
+#ifndef DISABLE_EVENT
+  uschar  *event_action;          /* String to expand on notable events */
 #endif
 } transport_instance;
 
@@ -216,6 +224,35 @@ typedef struct transport_info {
 } transport_info;
 
 
+/* smtp transport datachunk callback */
+
+#define tc_reap_prev   BIT(0)  /* Flags: reap previous SMTP cmd responses */
+#define tc_chunk_last  BIT(1)  /* annotate chunk SMTP cmd as LAST */
+
+struct transport_context;
+typedef int (*tpt_chunk_cmd_cb)(int fd, struct transport_context * tctx,
+                               unsigned len, unsigned flags);
+
+/* Structure for information about a delivery-in-progress */
+
+typedef struct transport_context {
+  transport_instance   * tblock;               /* transport */
+  struct address_item  * addr;
+  uschar               * check_string;         /* string replacement */
+  uschar               * escape_string;
+  int                    options;              /* output processing topt_* */
+
+  /* items below only used with option topt_use_bdat */
+  tpt_chunk_cmd_cb       chunk_cb;             /* per-datachunk callback */
+  void                 * smtp_context;
+} transport_ctx;
+
+
+
+typedef struct {
+  uschar *request;
+  uschar *require;
+} dnssec_domains;
 
 /* Structure for holding information about the configured routers. */
 
@@ -285,9 +322,7 @@ typedef struct router_instance {
   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 */
@@ -297,6 +332,8 @@ typedef struct router_instance {
   transport_instance *transport;  /* Transport block (when found) */
   struct router_instance *pass_router; /* Actual router for passed address */
   struct router_instance *redirect_router; /* Actual router for generated address */
+
+  dnssec_domains dnssec;
 } router_instance;
 
 
@@ -460,6 +497,11 @@ typedef struct address_item_propagated {
   #ifdef EXPERIMENTAL_SRS
   uschar *srs_sender;             /* Change return path when delivering */
   #endif
+  #ifdef SUPPORT_I18N
+  BOOL    utf8_msg:1;            /* requires SMTPUTF8 processing */
+  BOOL   utf8_downcvt:1;         /* mandatory downconvert on delivery */
+  BOOL   utf8_downcvt_maybe:1;   /* optional downconvert on delivery */
+  #endif
 } address_item_propagated;
 
 /* Bits for the flags field below */
@@ -494,7 +536,14 @@ typedef struct address_item_propagated {
 #ifndef DISABLE_PRDR
 # define af_prdr_used          0x08000000 /* delivery used SMTP PRDR */
 #endif
-#define af_force_command       0x10000000 /* force_command in pipe transport */
+#define af_chunking_used       0x10000000 /* delivery used SMTP CHUNKING */
+#define af_force_command       0x20000000 /* force_command in pipe transport */
+#ifdef EXPERIMENTAL_DANE
+# define af_dane_verified      0x40000000 /* TLS cert verify done with DANE */
+#endif
+#ifdef SUPPORT_I18N
+# define af_utf8_downcvt       0x80000000 /* downconvert was done for delivery */
+#endif
 
 /* These flags must be propagated when a child is created */
 
@@ -528,7 +577,7 @@ typedef struct address_item {
   uschar *local_part;             /* points to cc or lc version */
   uschar *prefix;                 /* stripped prefix of local part */
   uschar *suffix;                 /* stripped suffix of local part */
-  uschar *domain;                 /* working domain (lower cased) */
+  const uschar *domain;           /* working domain (lower cased) */
 
   uschar *address_retry_key;      /* retry key including full address */
   uschar *domain_retry_key;       /* retry key for domain only */
@@ -544,23 +593,26 @@ typedef struct address_item {
   uschar *self_hostname;          /* after self=pass */
   uschar *shadow_message;         /* info about shadow transporting */
 
-  #ifdef SUPPORT_TLS
+#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
+#endif
+
+#ifdef EXPERIMENTAL_DSN_INFO
+  const uschar *smtp_greeting;   /* peer self-identification */
+  const uschar *helo_response;   /* peer message */
+#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 */
@@ -573,7 +625,7 @@ typedef struct address_item {
                                   /* (may need to hold a timestamp) */
 
   short int basic_errno;          /* status after failure */
-  short int child_count;          /* number of child addresses */
+  unsigned short child_count;     /* number of child addresses */
   short int return_file;          /* fileno of return data file */
   short int special_action;       /* ( used when when deferred or failed */
                                   /* (  also  */
@@ -581,7 +633,7 @@ typedef struct address_item {
                                   /* (  also  */
                                   /* ( contains verify rc in sender verify cache */
   short int transport_return;     /* result of delivery attempt */
-  address_item_propagated p;      /* fields that are propagated to children */
+  address_item_propagated prop;   /* fields that are propagated to children */
 } address_item;
 
 /* The table of header names consists of items of this type */
@@ -597,7 +649,7 @@ typedef struct {
 
 typedef struct error_block {
   struct error_block *next;
-  uschar *text1;
+  const uschar *text1;
   uschar *text2;
 } error_block;
 
@@ -642,6 +694,16 @@ typedef struct tree_node {
   uschar  name[1];                /* node name - variable length */
 } tree_node;
 
+/* Structure for holding time-limited data such as DNS returns.
+We use this rather than extending tree_node to avoid wasting
+space for most tree use (variables...) at the cost of complexity
+for the lookups cache */
+
+typedef struct expiring_data {
+  time_t expiry;                 /* if nonzero, data invalid after this time */
+  void   *ptr;                   /* pointer to data */
+} expiring_data;
+
 /* Structure for holding the handle and the cached last lookup for searches.
 This block is pointed to by the tree entry for the file. The file can get
 closed if too many are opened at once. There is a LRU chain for deciding which
@@ -659,10 +721,11 @@ typedef struct search_cache {
 uncompressed, but the data pointer is into the raw data. */
 
 typedef struct {
-  uschar  name[DNS_MAXNAME];      /* domain name */
-  int     type;                   /* record type */
-  int     size;                   /* size of data */
-  uschar *data;                   /* pointer to data */
+  uschar        name[DNS_MAXNAME];      /* domain name */
+  int           type;                   /* record type */
+  unsigned short ttl;                  /* time-to-live, seconds */
+  int           size;                   /* size of data */
+  const uschar *data;                   /* pointer to data */
 } dns_record;
 
 /* Structure for holding the result of a DNS query. */
@@ -676,9 +739,9 @@ typedef struct {
 block. */
 
 typedef struct {
-  int     rrcount;                /* count of RRs in the answer */
-  uschar *aptr;                   /* pointer in the answer while scanning */
-  dns_record srr;                 /* data from current record in scan */
+  int            rrcount;         /* count of RRs in the answer */
+  const uschar *aptr;             /* pointer in the answer while scanning */
+  dns_record     srr;             /* data from current record in scan */
 } dns_scan;
 
 /* Structure for holding a chain of IP addresses that are extracted from
@@ -750,9 +813,9 @@ typedef struct redirect_block {
 /* Structure for passing arguments to check_host() */
 
 typedef struct check_host_block {
-  uschar *host_name;
-  uschar *host_address;
-  uschar *host_ipv4;
+  const uschar *host_name;
+  const uschar *host_address;
+  const uschar *host_ipv4;
   BOOL   negative;
 } check_host_block;
 
@@ -768,7 +831,7 @@ typedef struct namedlist_cacheblock {
 /* Structure for holding data for an entry in a named list */
 
 typedef struct namedlist_block {
-  uschar *string;                    /* the list string */
+  const uschar *string;              /* the list string */
   namedlist_cacheblock *cache_data;  /* cached domain_data or localpart_data */
   int number;                        /* the number of the list for caching */
 } namedlist_block;
@@ -791,4 +854,18 @@ typedef struct acl_block {
   int verb;
 } acl_block;
 
+/* smtp transport calc outbound_ip */
+typedef BOOL (*oicf) (uschar *message_id, void *data);
+
+/* DKIM information for transport */
+struct ob_dkim {
+  uschar *dkim_domain;
+  uschar *dkim_private_key;
+  uschar *dkim_selector;
+  uschar *dkim_canon;
+  uschar *dkim_sign_headers;
+  uschar *dkim_strict;
+  BOOL    dot_stuffed;
+};
+
 /* End of structs.h */
index 266ab89..7b16fc8 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Copyright (c) Phil Pennock 2012 */
@@ -13,7 +13,7 @@ tls.c when USE_GNUTLS has been set.
 
 The code herein is a revamp of GnuTLS integration using the current APIs; the
 original tls-gnu.c was based on a patch which was contributed by Nikos
-Mavroyanopoulos.  The revamp is partially a rewrite, partially cut&paste as
+Mavrogiannopoulos.  The revamp is partially a rewrite, partially cut&paste as
 appropriate.
 
 APIs current as of GnuTLS 2.12.18; note that the GnuTLS manual is for GnuTLS 3,
@@ -42,11 +42,24 @@ require current GnuTLS, then we'll drop support for the ancient libraries).
 /* needed to disable PKCS11 autoload unless requested */
 #if GNUTLS_VERSION_NUMBER >= 0x020c00
 # include <gnutls/pkcs11.h>
+# define SUPPORT_PARAM_TO_PK_BITS
 #endif
 #if GNUTLS_VERSION_NUMBER < 0x030103 && !defined(DISABLE_OCSP)
 # warning "GnuTLS library version too old; define DISABLE_OCSP in Makefile"
 # define DISABLE_OCSP
 #endif
+#if GNUTLS_VERSION_NUMBER < 0x020a00 && !defined(DISABLE_EVENT)
+# warning "GnuTLS library version too old; tls:cert event unsupported"
+# define DISABLE_EVENT
+#endif
+#if GNUTLS_VERSION_NUMBER >= 0x030306
+# define SUPPORT_CA_DIR
+#else
+# undef  SUPPORT_CA_DIR
+#endif
+#if GNUTLS_VERSION_NUMBER >= 0x030014
+# define SUPPORT_SYSDEFAULT_CABUNDLE
+#endif
 
 #ifndef DISABLE_OCSP
 # include <gnutls/ocsp.h>
@@ -66,11 +79,7 @@ Changes:
 /* Values for verify_requirement */
 
 enum peer_verify_requirement
-  { VERIFY_NONE, VERIFY_OPTIONAL, VERIFY_REQUIRED
-#ifdef EXPERIMENTAL_CERTNAMES
-    ,VERIFY_WITHHOST
-#endif
-  };
+  { VERIFY_NONE, VERIFY_OPTIONAL, VERIFY_REQUIRED };
 
 /* 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
@@ -112,8 +121,9 @@ typedef struct exim_gnutls_state {
   uschar *exp_tls_crl;
   uschar *exp_tls_require_ciphers;
   uschar *exp_tls_ocsp_file;
-#ifdef EXPERIMENTAL_CERTNAMES
-  uschar *exp_tls_verify_cert_hostnames;
+  const uschar *exp_tls_verify_cert_hostnames;
+#ifndef DISABLE_EVENT
+  uschar *event_action;
 #endif
 
   tls_support *tlsp;   /* set in tls_init() */
@@ -130,7 +140,8 @@ static const exim_gnutls_state_st exim_gnutls_state_init = {
   NULL, NULL, NULL, NULL,
   NULL, NULL, NULL, NULL, NULL, NULL,
   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-#ifdef EXPERIMENTAL_CERTNAMES
+  NULL,
+#ifndef DISABLE_EVENT
                                             NULL,
 #endif
   NULL,
@@ -144,7 +155,9 @@ context we're currently dealing with" pointer and rely upon being
 single-threaded to keep from processing data on an inbound TLS connection while
 talking to another TLS connection for an outbound check.  This does mean that
 there's no way for heart-beats to be responded to, for the duration of the
-second connection. */
+second connection.
+XXX But see gnutls_session_get_ptr()
+*/
 
 static exim_gnutls_state_st state_server, state_client;
 
@@ -164,6 +177,10 @@ static const char * const exim_default_gnutls_priority = "NORMAL";
 
 static BOOL exim_gnutls_base_init_done = FALSE;
 
+#ifndef DISABLE_OCSP
+static BOOL gnutls_buggy_ocsp = FALSE;
+#endif
+
 
 /* ------------------------------------------------------------------------ */
 /* macros */
@@ -174,18 +191,18 @@ static BOOL exim_gnutls_base_init_done = FALSE;
 the library logging; a value less than 0 disables the calls to set up logging
 callbacks. */
 #ifndef EXIM_GNUTLS_LIBRARY_LOG_LEVEL
-#define EXIM_GNUTLS_LIBRARY_LOG_LEVEL -1
+# define EXIM_GNUTLS_LIBRARY_LOG_LEVEL -1
 #endif
 
 #ifndef EXIM_CLIENT_DH_MIN_BITS
-#define EXIM_CLIENT_DH_MIN_BITS 1024
+# define EXIM_CLIENT_DH_MIN_BITS 1024
 #endif
 
 /* With GnuTLS 2.12.x+ we have gnutls_sec_param_to_pk_bits() with which we
 can ask for a bit-strength.  Without that, we stick to the constant we had
 before, for now. */
 #ifndef EXIM_SERVER_DH_BITS_PRE2_12
-#define EXIM_SERVER_DH_BITS_PRE2_12 1024
+# define EXIM_SERVER_DH_BITS_PRE2_12 1024
 #endif
 
 #define exim_gnutls_err_check(Label) do { \
@@ -256,7 +273,7 @@ tls_error(const uschar *prefix, const char *msg, const host_item *host)
 {
 if (host)
   {
-  log_write(0, LOG_MAIN, "TLS error on connection to %s [%s] (%s)%s%s",
+  log_write(0, LOG_MAIN, "H=%s [%s] TLS error on connection (%s)%s%s",
       host->name, host->address, prefix, msg ? ": " : "", msg ? msg : "");
   return FAIL;
   }
@@ -265,6 +282,7 @@ else
   uschar *conn_info = smtp_get_connection_info();
   if (Ustrncmp(conn_info, US"SMTP ", 5) == 0)
     conn_info += 5;
+  /* I'd like to get separated H= here, but too hard for now */
   log_write(0, LOG_MAIN, "TLS error on %s (%s)%s%s",
       conn_info, prefix, msg ? ": " : "", msg ? msg : "");
   return DEFER;
@@ -322,7 +340,7 @@ tls_error(when, msg, state->host);
     } while (0)
 
 static int
-import_cert(const gnutls_datum * cert, gnutls_x509_crt_t * crtp)
+import_cert(const gnutls_datum_t * cert, gnutls_x509_crt_t * crtp)
 {
 int rc;
 
@@ -396,7 +414,7 @@ if (rc) {
 } else {
   old_pool = store_pool;
   store_pool = POOL_PERM;
-  tls_channelbinding_b64 = auth_b64encode(channel.data, (int)channel.size);
+  tls_channelbinding_b64 = b64encode(channel.data, (int)channel.size);
   store_pool = old_pool;
   DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage.\n");
 }
@@ -408,7 +426,7 @@ tlsp->sni =    state->received_sni;
 
 /* record our certificate */
   {
-  const gnutls_datum * cert = gnutls_certificate_get_ours(state->session);
+  const gnutls_datum_t * cert = gnutls_certificate_get_ours(state->session);
   gnutls_x509_crt_t crt;
 
   tlsp->ourcert = cert && import_cert(cert, &crt)==0 ? crt : NULL;
@@ -440,7 +458,7 @@ init_server_dh(void)
 {
 int fd, rc;
 unsigned int dh_bits;
-gnutls_datum m;
+gnutls_datum_t m;
 uschar filename_buf[PATH_MAX];
 uschar *filename = NULL;
 size_t sz;
@@ -475,8 +493,7 @@ else if (Ustrcmp(exp_tls_dhparam, "none") == 0)
   }
 else if (exp_tls_dhparam[0] != '/')
   {
-  m.data = US std_dh_prime_named(exp_tls_dhparam);
-  if (m.data == NULL)
+  if (!(m.data = US std_dh_prime_named(exp_tls_dhparam)))
     return tls_error(US"No standard prime named", CS exp_tls_dhparam, NULL);
   m.size = Ustrlen(m.data);
   }
@@ -530,8 +547,7 @@ if (use_file_in_spool)
 /* Open the cache file for reading and if successful, read it and set up the
 parameters. */
 
-fd = Uopen(filename, O_RDONLY, 0);
-if (fd >= 0)
+if ((fd = Uopen(filename, O_RDONLY, 0)) >= 0)
   {
   struct stat statbuf;
   FILE *fp;
@@ -548,8 +564,7 @@ if (fd >= 0)
     (void)close(fd);
     return tls_error(US"TLS cache not a file", NULL, NULL);
     }
-  fp = fdopen(fd, "rb");
-  if (!fp)
+  if (!(fp = fdopen(fd, "rb")))
     {
     saved_errno = errno;
     (void)close(fd);
@@ -558,14 +573,12 @@ if (fd >= 0)
     }
 
   m.size = statbuf.st_size;
-  m.data = malloc(m.size);
-  if (m.data == NULL)
+  if (!(m.data = malloc(m.size)))
     {
     fclose(fp);
     return tls_error(US"malloc failed", strerror(errno), NULL);
     }
-  sz = fread(m.data, m.size, 1, fp);
-  if (!sz)
+  if (!(sz = fread(m.data, m.size, 1, fp)))
     {
     saved_errno = errno;
     fclose(fp);
@@ -609,8 +622,7 @@ if (rc < 0)
         CS filename, NULL);
 
   temp_fn = string_copy(US "%s.XXXXXXX");
-  fd = mkstemp(CS temp_fn); /* modifies temp_fn */
-  if (fd < 0)
+  if ((fd = mkstemp(CS temp_fn)) < 0)  /* modifies temp_fn */
     return tls_error(US"Unable to open temp file", strerror(errno), NULL);
   (void)fchown(fd, exim_uid, exim_gid);   /* Probably not necessary */
 
@@ -647,9 +659,9 @@ if (rc < 0)
   if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
     exim_gnutls_err_check(US"gnutls_dh_params_export_pkcs3(NULL) sizing");
   m.size = sz;
-  m.data = malloc(m.size);
-  if (m.data == NULL)
+  if (!(m.data = malloc(m.size)))
     return tls_error(US"memory allocation failed", strerror(errno), NULL);
+
   /* this will return a size 1 less than the allocation size above */
   rc = gnutls_dh_params_export_pkcs3(dh_server_params, GNUTLS_X509_FMT_PEM,
       m.data, &sz);
@@ -660,23 +672,19 @@ if (rc < 0)
     }
   m.size = sz; /* shrink by 1, probably */
 
-  sz = write_to_fd_buf(fd, m.data, (size_t) m.size);
-  if (sz != m.size)
+  if ((sz = write_to_fd_buf(fd, m.data, (size_t) m.size)) != m.size)
     {
     free(m.data);
     return tls_error(US"TLS cache write D-H params failed",
         strerror(errno), NULL);
     }
   free(m.data);
-  sz = write_to_fd_buf(fd, US"\n", 1);
-  if (sz != 1)
+  if ((sz = write_to_fd_buf(fd, US"\n", 1)) != 1)
     return tls_error(US"TLS cache write D-H params final newline failed",
         strerror(errno), NULL);
 
-  rc = close(fd);
-  if (rc)
-    return tls_error(US"TLS cache write close() failed",
-        strerror(errno), NULL);
+  if ((rc = close(fd)))
+    return tls_error(US"TLS cache write close() failed", strerror(errno), NULL);
 
   if (Urename(temp_fn, filename) < 0)
     return tls_error(string_sprintf("failed to rename \"%s\" as \"%s\"",
@@ -692,6 +700,74 @@ return OK;
 
 
 
+/* Create and install a selfsigned certificate, for use in server mode */
+
+static int
+tls_install_selfsign(exim_gnutls_state_st * state)
+{
+gnutls_x509_crt_t cert = NULL;
+time_t now;
+gnutls_x509_privkey_t pkey = NULL;
+const uschar * where;
+int rc;
+
+where = US"initialising pkey";
+if ((rc = gnutls_x509_privkey_init(&pkey))) goto err;
+
+where = US"initialising cert";
+if ((rc = gnutls_x509_crt_init(&cert))) goto err;
+
+where = US"generating pkey";
+if ((rc = gnutls_x509_privkey_generate(pkey, GNUTLS_PK_RSA,
+#ifdef SUPPORT_PARAM_TO_PK_BITS
+           gnutls_sec_param_to_pk_bits(GNUTLS_PK_RSA, GNUTLS_SEC_PARAM_LOW),
+#else
+           1024,
+#endif
+           0)))
+  goto err;
+
+where = US"configuring cert";
+now = 0;
+if (  (rc = gnutls_x509_crt_set_version(cert, 3))
+   || (rc = gnutls_x509_crt_set_serial(cert, &now, sizeof(now)))
+   || (rc = gnutls_x509_crt_set_activation_time(cert, now = time(NULL)))
+   || (rc = gnutls_x509_crt_set_expiration_time(cert, now + 60 * 60)) /* 1 hr */
+   || (rc = gnutls_x509_crt_set_key(cert, pkey))
+
+   || (rc = gnutls_x509_crt_set_dn_by_oid(cert,
+             GNUTLS_OID_X520_COUNTRY_NAME, 0, "UK", 2))
+   || (rc = gnutls_x509_crt_set_dn_by_oid(cert,
+             GNUTLS_OID_X520_ORGANIZATION_NAME, 0, "Exim Developers", 15))
+   || (rc = gnutls_x509_crt_set_dn_by_oid(cert,
+             GNUTLS_OID_X520_COMMON_NAME, 0,
+             smtp_active_hostname, Ustrlen(smtp_active_hostname)))
+   )
+  goto err;
+
+where = US"signing cert";
+if ((rc = gnutls_x509_crt_sign(cert, cert, pkey))) goto err;
+
+where = US"installing selfsign cert";
+                                       /* Since: 2.4.0 */
+if ((rc = gnutls_certificate_set_x509_key(state->x509_cred, &cert, 1, pkey)))
+  goto err;
+
+rc = OK;
+
+out:
+  if (cert) gnutls_x509_crt_deinit(cert);
+  if (pkey) gnutls_x509_privkey_deinit(pkey);
+  return rc;
+
+err:
+  rc = tls_error(where, gnutls_strerror(rc), NULL);
+  goto out;
+}
+
+
+
+
 /*************************************************
 *       Variables re-expanded post-SNI           *
 *************************************************/
@@ -724,7 +800,6 @@ int cert_count;
 
 /* We check for tls_sni *before* expansion. */
 if (!host)     /* server */
-  {
   if (!state->received_sni)
     {
     if (state->tls_certificate &&
@@ -745,7 +820,6 @@ if (!host)  /* server */
     saved_tls_verify_certificates = state->exp_tls_verify_certificates;
     saved_tls_crl = state->exp_tls_crl;
     }
-  }
 
 rc = gnutls_certificate_allocate_credentials(&state->x509_cred);
 exim_gnutls_err_check(US"gnutls_certificate_allocate_credentials");
@@ -762,14 +836,13 @@ if (!expand_check_tlsvar(tls_certificate))
 
 /* certificate is mandatory in server, optional in client */
 
-if ((state->exp_tls_certificate == NULL) ||
-    (*state->exp_tls_certificate == '\0'))
-  {
+if (  !state->exp_tls_certificate
+   || !*state->exp_tls_certificate
+   )
   if (!host)
-    return tls_error(US"no TLS server certificate is specified", NULL, NULL);
+    return tls_install_selfsign(state);
   else
     DEBUG(D_tls) debug_printf("TLS: no client certificate specified; okay\n");
-  }
 
 if (state->tls_privatekey && !expand_check_tlsvar(tls_privatekey))
   return DEFER;
@@ -789,9 +862,9 @@ if (state->exp_tls_certificate && *state->exp_tls_certificate)
       state->exp_tls_certificate, state->exp_tls_privatekey);
 
   if (state->received_sni)
-    {
-    if ((Ustrcmp(state->exp_tls_certificate, saved_tls_certificate) == 0) &&
-        (Ustrcmp(state->exp_tls_privatekey, saved_tls_privatekey) == 0))
+    if (  Ustrcmp(state->exp_tls_certificate, saved_tls_certificate) == 0
+       && Ustrcmp(state->exp_tls_privatekey,  saved_tls_privatekey)  == 0
+       )
       {
       DEBUG(D_tls) debug_printf("TLS SNI: cert and key unchanged\n");
       }
@@ -799,7 +872,6 @@ if (state->exp_tls_certificate && *state->exp_tls_certificate)
       {
       DEBUG(D_tls) debug_printf("TLS SNI: have a changed cert/key pair.\n");
       }
-    }
 
   rc = gnutls_certificate_set_x509_key_file(state->x509_cred,
       CS state->exp_tls_certificate, CS state->exp_tls_privatekey,
@@ -818,18 +890,25 @@ if (  !host       /* server */
    && tls_ocsp_file
    )
   {
-  if (!expand_check(tls_ocsp_file, US"tls_ocsp_file",
-       &state->exp_tls_ocsp_file))
-    return DEFER;
+  if (gnutls_buggy_ocsp)
+    {
+    DEBUG(D_tls) debug_printf("GnuTLS library is buggy for OCSP; avoiding\n");
+    }
+  else
+    {
+    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.  */
+    /* 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);
+    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);
+    DEBUG(D_tls) debug_printf("OCSP response file = %s\n", state->exp_tls_ocsp_file);
+    }
   }
 #endif
 
@@ -844,6 +923,10 @@ if (state->tls_verify_certificates && *state->tls_verify_certificates)
   {
   if (!expand_check_tlsvar(tls_verify_certificates))
     return DEFER;
+#ifndef SUPPORT_SYSDEFAULT_CABUNDLE
+  if (Ustrcmp(state->exp_tls_verify_certificates, "system") == 0)
+    state->exp_tls_verify_certificates = NULL;
+#endif
   if (state->tls_crl && *state->tls_crl)
     if (!expand_check_tlsvar(tls_crl))
       return DEFER;
@@ -864,46 +947,65 @@ else
   return OK;
   }
 
-if (Ustat(state->exp_tls_verify_certificates, &statbuf) < 0)
+#ifdef SUPPORT_SYSDEFAULT_CABUNDLE
+if (Ustrcmp(state->exp_tls_verify_certificates, "system") == 0)
+  cert_count = gnutls_certificate_set_x509_system_trust(state->x509_cred);
+else
+#endif
   {
-  log_write(0, LOG_MAIN|LOG_PANIC, "could not stat %s "
-      "(tls_verify_certificates): %s", state->exp_tls_verify_certificates,
-      strerror(errno));
-  return DEFER;
-  }
+  if (Ustat(state->exp_tls_verify_certificates, &statbuf) < 0)
+    {
+    log_write(0, LOG_MAIN|LOG_PANIC, "could not stat %s "
+       "(tls_verify_certificates): %s", state->exp_tls_verify_certificates,
+       strerror(errno));
+    return DEFER;
+    }
 
-/* The test suite passes in /dev/null; we could check for that path explicitly,
-but who knows if someone has some weird FIFO which always dumps some certs, or
-other weirdness.  The thing we really want to check is that it's not a
-directory, since while OpenSSL supports that, GnuTLS does not.
-So s/!S_ISREG/S_ISDIR/ and change some messsaging ... */
-if (S_ISDIR(statbuf.st_mode))
-  {
-  DEBUG(D_tls)
-    debug_printf("verify certificates path is a dir: \"%s\"\n",
-        state->exp_tls_verify_certificates);
-  log_write(0, LOG_MAIN|LOG_PANIC,
-      "tls_verify_certificates \"%s\" is a directory",
-      state->exp_tls_verify_certificates);
-  return DEFER;
-  }
+#ifndef SUPPORT_CA_DIR
+  /* The test suite passes in /dev/null; we could check for that path explicitly,
+  but who knows if someone has some weird FIFO which always dumps some certs, or
+  other weirdness.  The thing we really want to check is that it's not a
+  directory, since while OpenSSL supports that, GnuTLS does not.
+  So s/!S_ISREG/S_ISDIR/ and change some messaging ... */
+  if (S_ISDIR(statbuf.st_mode))
+    {
+    DEBUG(D_tls)
+      debug_printf("verify certificates path is a dir: \"%s\"\n",
+         state->exp_tls_verify_certificates);
+    log_write(0, LOG_MAIN|LOG_PANIC,
+       "tls_verify_certificates \"%s\" is a directory",
+       state->exp_tls_verify_certificates);
+    return DEFER;
+    }
+#endif
 
-DEBUG(D_tls) debug_printf("verify certificates = %s size=" OFF_T_FMT "\n",
-        state->exp_tls_verify_certificates, statbuf.st_size);
+  DEBUG(D_tls) debug_printf("verify certificates = %s size=" OFF_T_FMT "\n",
+         state->exp_tls_verify_certificates, statbuf.st_size);
 
-if (statbuf.st_size == 0)
-  {
-  DEBUG(D_tls)
-    debug_printf("cert file empty, no certs, no verification, ignoring any CRL\n");
-  return OK;
+  if (statbuf.st_size == 0)
+    {
+    DEBUG(D_tls)
+      debug_printf("cert file empty, no certs, no verification, ignoring any CRL\n");
+    return OK;
+    }
+
+  cert_count =
+
+#ifdef SUPPORT_CA_DIR
+    (statbuf.st_mode & S_IFMT) == S_IFDIR
+    ?
+    gnutls_certificate_set_x509_trust_dir(state->x509_cred,
+      CS state->exp_tls_verify_certificates, GNUTLS_X509_FMT_PEM)
+    :
+#endif
+    gnutls_certificate_set_x509_trust_file(state->x509_cred,
+      CS state->exp_tls_verify_certificates, GNUTLS_X509_FMT_PEM);
   }
 
-cert_count = gnutls_certificate_set_x509_trust_file(state->x509_cred,
-    CS state->exp_tls_verify_certificates, GNUTLS_X509_FMT_PEM);
 if (cert_count < 0)
   {
   rc = cert_count;
-  exim_gnutls_err_check(US"gnutls_certificate_set_x509_trust_file");
+  exim_gnutls_err_check(US"setting certificate trust");
   }
 DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n", cert_count);
 
@@ -975,6 +1077,38 @@ return OK;
 *            Initialize for GnuTLS               *
 *************************************************/
 
+
+#ifndef DISABLE_OCSP
+
+static BOOL
+tls_is_buggy_ocsp(void)
+{
+const uschar * s;
+uschar maj, mid, mic;
+
+s = CUS gnutls_check_version(NULL);
+maj = atoi(CCS s);
+if (maj == 3)
+  {
+  while (*s && *s != '.') s++;
+  mid = atoi(CCS ++s);
+  if (mid <= 2)
+    return TRUE;
+  else if (mid >= 5)
+    return FALSE;
+  else
+    {
+    while (*s && *s != '.') s++;
+    mic = atoi(CCS ++s);
+    return mic <= (mid == 3 ? 16 : 3);
+    }
+  }
+return FALSE;
+}
+
+#endif
+
+
 /* Called from both server and client code. In the case of a server, errors
 before actual TLS negotiation return DEFER.
 
@@ -1038,6 +1172,11 @@ if (!exim_gnutls_base_init_done)
     }
 #endif
 
+#ifndef DISABLE_OCSP
+  if (tls_ocsp_file && (gnutls_buggy_ocsp = tls_is_buggy_ocsp()))
+    log_write(0, LOG_MAIN, "OCSP unusable with this GnuTLS library version");
+#endif
+
   exim_gnutls_base_init_done = TRUE;
   }
 
@@ -1193,7 +1332,7 @@ static int
 peer_status(exim_gnutls_state_st *state)
 {
 uschar cipherbuf[256];
-const gnutls_datum *cert_list;
+const gnutls_datum_t *cert_list;
 int old_pool, rc;
 unsigned int cert_list_size = 0;
 gnutls_protocol_t protocol;
@@ -1359,25 +1498,27 @@ if (rc < 0 ||
 
 else
   {
-#ifdef EXPERIMENTAL_CERTNAMES
-  if (state->verify_requirement == VERIFY_WITHHOST)
+  if (state->exp_tls_verify_cert_hostnames)
     {
     int sep = 0;
-    uschar * list = state->exp_tls_verify_cert_hostnames;
+    const uschar * list = state->exp_tls_verify_cert_hostnames;
     uschar * name;
-    while (name = string_nextinlist(&list, &sep, NULL, 0))
+    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;
+      if (state->verify_requirement >= VERIFY_REQUIRED)
+       {
+       gnutls_alert_send(state->session,
+         GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE);
+       return FALSE;
+       }
+      return TRUE;
       }
     }
-#endif
   state->peer_cert_verified = TRUE;
   DEBUG(D_tls) debug_printf("TLS certificate verified: peerdn=\"%s\"\n",
       state->peerdn ? state->peerdn : US"<unset>");
@@ -1512,6 +1653,54 @@ return 0;
 #endif
 
 
+#ifndef DISABLE_EVENT
+/*
+We use this callback to get observability and detail-level control
+for an exim TLS connection (either direction), raising a tls:cert event
+for each cert in the chain presented by the peer.  Any event
+can deny verification.
+
+Return 0 for the handshake to continue or non-zero to terminate.
+*/
+
+static int
+verify_cb(gnutls_session_t session)
+{
+const gnutls_datum_t * cert_list;
+unsigned int cert_list_size = 0;
+gnutls_x509_crt_t crt;
+int rc;
+uschar * yield;
+exim_gnutls_state_st * state = gnutls_session_get_ptr(session);
+
+cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
+if (cert_list)
+  while (cert_list_size--)
+  {
+  rc = import_cert(&cert_list[cert_list_size], &crt);
+  if (rc != GNUTLS_E_SUCCESS)
+    {
+    DEBUG(D_tls) debug_printf("TLS: peer cert problem: depth %d: %s\n",
+      cert_list_size, gnutls_strerror(rc));
+    break;
+    }
+
+  state->tlsp->peercert = crt;
+  if ((yield = event_raise(state->event_action,
+             US"tls:cert", string_sprintf("%d", cert_list_size))))
+    {
+    log_write(0, LOG_MAIN,
+             "SSL verify denied by event-action: depth=%d: %s",
+             cert_list_size, yield);
+    return 1;                     /* reject */
+    }
+  state->tlsp->peercert = NULL;
+  }
+
+return 0;
+}
+
+#endif
 
 
 
@@ -1534,7 +1723,7 @@ Arguments:
 
 Returns:           OK on success
                    DEFER for errors before the start of the negotiation
-                   FAIL for errors during the negotation; the server can't
+                   FAIL for errors during the negotiation; the server can't
                      continue running.
 */
 
@@ -1588,6 +1777,15 @@ else
   gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_IGNORE);
   }
 
+#ifndef DISABLE_EVENT
+if (event_action)
+  {
+  state->event_action = event_action;
+  gnutls_session_set_ptr(state->session, state);
+  gnutls_certificate_set_verify_function(state->x509_cred, verify_cb);
+  }
+#endif
+
 /* Register SNI handling; always, even if not in tls_certificate, so that the
 expansion variable $tls_sni is always available. */
 
@@ -1610,32 +1808,41 @@ if (!state->tlsp->on_connect)
 that the GnuTLS library doesn't. */
 
 gnutls_transport_set_ptr2(state->session,
-    (gnutls_transport_ptr)(long) fileno(smtp_in),
-    (gnutls_transport_ptr)(long) fileno(smtp_out));
+    (gnutls_transport_ptr_t)(long) fileno(smtp_in),
+    (gnutls_transport_ptr_t)(long) fileno(smtp_out));
 state->fd_in = fileno(smtp_in);
 state->fd_out = fileno(smtp_out);
 
 sigalrm_seen = FALSE;
 if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
 do
-  {
   rc = gnutls_handshake(state->session);
-  } while ((rc == GNUTLS_E_AGAIN) ||
-      (rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen));
+while (rc == GNUTLS_E_AGAIN ||  rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen);
 alarm(0);
 
 if (rc != GNUTLS_E_SUCCESS)
   {
-  tls_error(US"gnutls_handshake",
-      sigalrm_seen ? "timed out" : gnutls_strerror(rc), NULL);
   /* It seems that, except in the case of a timeout, we have to close the
   connection right here; otherwise if the other end is running OpenSSL it hangs
   until the server times out. */
 
-  if (!sigalrm_seen)
+  if (sigalrm_seen)
     {
+    tls_error(US"gnutls_handshake", "timed out", NULL);
+    gnutls_db_remove_session(state->session);
+    }
+  else
+    {
+    tls_error(US"gnutls_handshake", gnutls_strerror(rc), NULL);
+    (void) gnutls_alert_send_appropriate(state->session, rc);
+    gnutls_deinit(state->session);
+    gnutls_certificate_free_credentials(state->x509_cred);
+    millisleep(500);
+    shutdown(state->fd_out, SHUT_WR);
+    for (rc = 1024; fgetc(smtp_in) != EOF && rc > 0; ) rc--;   /* drain skt */
     (void)fclose(smtp_out);
     (void)fclose(smtp_in);
+    smtp_out = smtp_in = NULL;
     }
 
   return FAIL;
@@ -1660,8 +1867,7 @@ if (  state->verify_requirement != VERIFY_NONE
 
 /* 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; always safe within server */
 
@@ -1673,6 +1879,7 @@ and initialize appropriately. */
 state->xfer_buffer = store_malloc(ssl_xfer_buffer_size);
 
 receive_getc = tls_getc;
+receive_get_cache = tls_get_cache;
 receive_ungetc = tls_ungetc;
 receive_feof = tls_feof;
 receive_ferror = tls_ferror;
@@ -1684,6 +1891,25 @@ return OK;
 
 
 
+static void
+tls_client_setup_hostname_checks(host_item * host, exim_gnutls_state_st * state,
+  smtp_transport_options_block * ob)
+{
+if (verify_check_given_host(&ob->tls_verify_cert_hostnames, host) == OK)
+  {
+  state->exp_tls_verify_cert_hostnames =
+#ifdef SUPPORT_I18N
+    string_domain_utf8_to_alabel(host->name, NULL);
+#else
+    host->name;
+#endif
+  DEBUG(D_tls)
+    debug_printf("TLS: server cert verification includes hostname: \"%s\".\n",
+                   state->exp_tls_verify_cert_hostnames);
+  }
+}
+
+
 /*************************************************
 *    Start a TLS session in a client             *
 *************************************************/
@@ -1694,7 +1920,7 @@ Arguments:
   fd                the fd of the connection
   host              connected host (for messages)
   addr              the first address (not used)
-  ob                smtp transport options
+  tb                transport (always smtp)
 
 Returns:            OK/DEFER/FAIL (because using common functions),
                     but for a client, DEFER and FAIL have the same meaning
@@ -1703,18 +1929,22 @@ Returns:            OK/DEFER/FAIL (because using common functions),
 int
 tls_client_start(int fd, host_item *host,
     address_item *addr ARG_UNUSED,
-    void *v_ob)
+    transport_instance *tb
+#ifdef EXPERIMENTAL_DANE
+    , dns_answer * unused_tlsa_dnsa
+#endif
+    )
 {
-smtp_transport_options_block *ob = v_ob;
+smtp_transport_options_block *ob =
+  (smtp_transport_options_block *)tb->options_block;
 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 require_ocsp =
+  verify_check_given_host(&ob->hosts_require_ocsp, host) == OK;
 BOOL request_ocsp = require_ocsp ? TRUE
-  : verify_check_this_host(&ob->hosts_request_ocsp,
-      NULL, host->name, host->address, NULL) == OK;
+  : verify_check_given_host(&ob->hosts_request_ocsp, host) == OK;
 #endif
 
 DEBUG(D_tls) debug_printf("initialising GnuTLS as a client on fd %d\n", fd);
@@ -1741,43 +1971,26 @@ if ((rc = tls_init(host, ob->tls_certificate, ob->tls_privatekey,
   gnutls_dh_set_prime_bits(state->session, dh_min_bits);
   }
 
-/* Stick to the old behaviour for compatibility if tls_verify_certificates is 
+/* 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
+if (  (  state->exp_tls_verify_certificates
+      && !ob->tls_verify_hosts
+      && (!ob->tls_try_verify_hosts || !*ob->tls_try_verify_hosts)
+      )
+    || verify_check_given_host(&ob->tls_verify_hosts, host) == 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;
-    }
+  tls_client_setup_hostname_checks(host, state, ob);
+  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);
   }
-else if (verify_check_host(&ob->tls_try_verify_hosts) == OK)
+else if (verify_check_given_host(&ob->tls_try_verify_hosts, host) == OK)
   {
+  tls_client_setup_hostname_checks(host, state, ob);
   DEBUG(D_tls)
     debug_printf("TLS: server certificate verification optional.\n");
   state->verify_requirement = VERIFY_OPTIONAL;
@@ -1804,7 +2017,16 @@ if (request_ocsp)
   }
 #endif
 
-gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr)(long) fd);
+#ifndef DISABLE_EVENT
+if (tb->event_action)
+  {
+  state->event_action = tb->event_action;
+  gnutls_session_set_ptr(state->session, state);
+  gnutls_certificate_set_verify_function(state->x509_cred, verify_cb);
+  }
+#endif
+
+gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr_t)(long) fd);
 state->fd_in = fd;
 state->fd_out = fd;
 
@@ -1821,8 +2043,13 @@ do
 alarm(0);
 
 if (rc != GNUTLS_E_SUCCESS)
-  return tls_error(US"gnutls_handshake",
-      sigalrm_seen ? "timed out" : gnutls_strerror(rc), state->host);
+  if (sigalrm_seen)
+    {
+    gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_USER_CANCELED);
+    return tls_error(US"gnutls_handshake", "timed out", state->host);
+    }
+  else
+    return tls_error(US"gnutls_handshake", gnutls_strerror(rc), state->host);
 
 DEBUG(D_tls) debug_printf("gnutls_handshake was successful\n");
 
@@ -1904,6 +2131,8 @@ if (shutdown)
   }
 
 gnutls_deinit(state->session);
+gnutls_certificate_free_credentials(state->x509_cred);
+
 
 state->tlsp->active = -1;
 memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
@@ -1929,12 +2158,12 @@ Only used by the server-side TLS.
 
 This feeds DKIM and should be used for all message-body reads.
 
-Arguments:  none
+Arguments:  lim                Maximum amount to read/bufffer
 Returns:    the next character or EOF
 */
 
 int
-tls_getc(void)
+tls_getc(unsigned lim)
 {
 exim_gnutls_state_st *state = &state_server;
 if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm)
@@ -1946,24 +2175,35 @@ if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm)
 
   if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
   inbytes = gnutls_record_recv(state->session, state->xfer_buffer,
-    ssl_xfer_buffer_size);
+    MIN(ssl_xfer_buffer_size, lim));
   alarm(0);
 
-  /* A zero-byte return appears to mean that the TLS session has been
+  /* Timeouts do not get this far; see command_timeout_handler().
+     A zero-byte return appears to mean that the TLS session has been
      closed down, not that the socket itself has been closed down. Revert to
      non-TLS handling. */
 
-  if (inbytes == 0)
+  if (sigalrm_seen)
+    {
+    DEBUG(D_tls) debug_printf("Got tls read timeout\n");
+    state->xfer_error = 1;
+    return EOF;
+    }
+
+  else if (inbytes == 0)
     {
     DEBUG(D_tls) debug_printf("Got TLS_EOF\n");
 
     receive_getc = smtp_getc;
+    receive_get_cache = smtp_get_cache;
     receive_ungetc = smtp_ungetc;
     receive_feof = smtp_feof;
     receive_ferror = smtp_ferror;
     receive_smtp_buffered = smtp_buffered;
 
     gnutls_deinit(state->session);
+    gnutls_certificate_free_credentials(state->x509_cred);
+
     state->session = NULL;
     state->tlsp->active = -1;
     state->tlsp->bits = 0;
@@ -1973,7 +2213,7 @@ if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm)
     state->tlsp->peercert = NULL;
     state->tlsp->peerdn = NULL;
 
-    return smtp_getc();
+    return smtp_getc(lim);
     }
 
   /* Handle genuine errors */
@@ -1996,6 +2236,17 @@ if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm)
 return state->xfer_buffer[state->xfer_buffer_lwm++];
 }
 
+void
+tls_get_cache()
+{
+#ifndef DISABLE_DKIM
+exim_gnutls_state_st * state = &state_server;
+int n = state->xfer_buffer_hwm - state->xfer_buffer_lwm;
+if (n > 0)
+  dkim_exim_verify_feed(state->xfer_buffer+state->xfer_buffer_lwm, n);
+#endif
+}
+
 
 
 
index 18994ea..392d59d 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2017 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Portions Copyright (c) The OpenSSL Project 1999 */
@@ -22,9 +22,16 @@ functions from the OpenSSL library. */
 #include <openssl/ssl.h>
 #include <openssl/err.h>
 #include <openssl/rand.h>
+#ifndef OPENSSL_NO_ECDH
+# include <openssl/ec.h>
+#endif
 #ifndef DISABLE_OCSP
 # include <openssl/ocsp.h>
 #endif
+#ifdef EXPERIMENTAL_DANE
+# include <danessl.h>
+#endif
+
 
 #ifndef DISABLE_OCSP
 # define EXIM_OCSP_SKEW_SECONDS (300L)
@@ -34,6 +41,52 @@ functions from the OpenSSL library. */
 #if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT)
 # define EXIM_HAVE_OPENSSL_TLSEXT
 #endif
+#if OPENSSL_VERSION_NUMBER >= 0x00908000L
+# define EXIM_HAVE_RSA_GENKEY_EX
+#endif
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+# define EXIM_HAVE_OCSP_RESP_COUNT
+#else
+# define EXIM_HAVE_EPHEM_RSA_KEX
+# define EXIM_HAVE_RAND_PSEUDO
+#endif
+#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_SHA256)
+# define EXIM_HAVE_SHA256
+#endif
+
+/*
+ * X509_check_host provides sane certificate hostname checking, but was added
+ * to OpenSSL late, after other projects forked off the code-base.  So in
+ * addition to guarding against the base version number, beware that LibreSSL
+ * does not (at this time) support this function.
+ *
+ * If LibreSSL gains a different API, perhaps via libtls, then we'll probably
+ * opt to disentangle and ask a LibreSSL user to provide glue for a third
+ * crypto provider for libtls instead of continuing to tie the OpenSSL glue
+ * into even twistier knots.  If LibreSSL gains the same API, we can just
+ * change this guard and punt the issue for a while longer.
+ */
+#ifndef LIBRESSL_VERSION_NUMBER
+# if OPENSSL_VERSION_NUMBER >= 0x010100000L
+#  define EXIM_HAVE_OPENSSL_CHECKHOST
+# endif
+# if OPENSSL_VERSION_NUMBER >= 0x010000000L \
+    && (OPENSSL_VERSION_NUMBER & 0x0000ff000L) >= 0x000002000L
+#  define EXIM_HAVE_OPENSSL_CHECKHOST
+# endif
+#endif
+
+#if !defined(LIBRESSL_VERSION_NUMBER) \
+    || LIBRESSL_VERSION_NUMBER >= 0x20010000L
+# if !defined(OPENSSL_NO_ECDH)
+#  if OPENSSL_VERSION_NUMBER >= 0x0090800fL
+#   define EXIM_HAVE_ECDH
+#  endif
+#  if OPENSSL_VERSION_NUMBER >= 0x10002000L
+#   define EXIM_HAVE_OPENSSL_EC_NIST2NID
+#  endif
+# endif
+#endif
 
 #if !defined(EXIM_HAVE_OPENSSL_TLSEXT) && !defined(DISABLE_OCSP)
 # warning "OpenSSL library version too old; define DISABLE_OCSP in Makefile"
@@ -95,6 +148,7 @@ typedef struct tls_ext_ctx_cb {
   uschar *privatekey;
 #ifndef DISABLE_OCSP
   BOOL is_server;
+  STACK_OF(X509) *verify_stack;                /* chain for verifying the proof */
   union {
     struct {
       uschar        *file;
@@ -112,9 +166,9 @@ typedef struct tls_ext_ctx_cb {
   uschar *server_cipher_list;
   /* only passed down to tls_error: */
   host_item *host;
-
-#ifdef EXPERIMENTAL_CERTNAMES
-  uschar * verify_cert_hostnames;
+  const uschar * verify_cert_hostnames;
+#ifndef DISABLE_EVENT
+  uschar * event_action;
 #endif
 } tls_ext_ctx_cb;
 
@@ -158,33 +212,35 @@ Returns:    OK/DEFER/FAIL
 */
 
 static int
-tls_error(uschar *prefix, host_item *host, uschar *msg)
+tls_error(uschar * prefix, const host_item * host, uschar * msg)
 {
-if (msg == NULL)
+if (!msg)
   {
   ERR_error_string(ERR_get_error(), ssl_errstring);
   msg = (uschar *)ssl_errstring;
   }
 
-if (host == NULL)
+if (host)
+  {
+  log_write(0, LOG_MAIN, "H=%s [%s] TLS error on connection (%s): %s",
+    host->name, host->address, prefix, msg);
+  return FAIL;
+  }
+else
   {
   uschar *conn_info = smtp_get_connection_info();
   if (Ustrncmp(conn_info, US"SMTP ", 5) == 0)
     conn_info += 5;
+  /* I'd like to get separated H= here, but too hard for now */
   log_write(0, LOG_MAIN, "TLS error on %s (%s): %s",
     conn_info, prefix, msg);
   return DEFER;
   }
-else
-  {
-  log_write(0, LOG_MAIN, "TLS error on connection to %s [%s] (%s): %s",
-    host->name, host->address, prefix, msg);
-  return FAIL;
-  }
 }
 
 
 
+#ifdef EXIM_HAVE_EPHEM_RSA_KEX
 /*************************************************
 *        Callback to generate RSA key            *
 *************************************************/
@@ -202,10 +258,22 @@ static RSA *
 rsa_callback(SSL *s, int export, int keylength)
 {
 RSA *rsa_key;
+#ifdef EXIM_HAVE_RSA_GENKEY_EX
+BIGNUM *bn = BN_new();
+#endif
+
 export = export;     /* Shut picky compilers up */
 DEBUG(D_tls) debug_printf("Generating %d bit RSA key...\n", keylength);
-rsa_key = RSA_generate_key(keylength, RSA_F4, NULL, NULL);
-if (rsa_key == NULL)
+
+#ifdef EXIM_HAVE_RSA_GENKEY_EX
+if (  !BN_set_word(bn, (unsigned long)RSA_F4)
+   || !(rsa_key = RSA_new())
+   || !RSA_generate_key_ex(rsa_key, keylength, bn, NULL)
+   )
+#else
+if (!(rsa_key = RSA_generate_key(keylength, RSA_F4, NULL, NULL)))
+#endif
+
   {
   ERR_error_string(ERR_get_error(), ssl_errstring);
   log_write(0, LOG_MAIN|LOG_PANIC, "TLS error (RSA_generate_key): %s",
@@ -214,6 +282,7 @@ if (rsa_key == NULL)
   }
 return rsa_key;
 }
+#endif
 
 
 
@@ -233,6 +302,7 @@ for(i= 0; i<sk_X509_OBJECT_num(roots); i++)
     {
     X509 * current_cert= tmp_obj->data.x509;
     X509_NAME_oneline(X509_get_subject_name(current_cert), CS name, sizeof(name));
+       name[sizeof(name)-1] = '\0';
     debug_printf(" %s\n", name);
     }
   }
@@ -241,149 +311,253 @@ for(i= 0; i<sk_X509_OBJECT_num(roots); i++)
 */
 
 
+#ifndef DISABLE_EVENT
+static int
+verify_event(tls_support * tlsp, X509 * cert, int depth, const uschar * dn,
+  BOOL *calledp, const BOOL *optionalp, const uschar * what)
+{
+uschar * ev;
+uschar * yield;
+X509 * old_cert;
+
+ev = tlsp == &tls_out ? client_static_cbinfo->event_action : event_action;
+if (ev)
+  {
+  DEBUG(D_tls) debug_printf("verify_event: %s %d\n", what, depth);
+  old_cert = tlsp->peercert;
+  tlsp->peercert = X509_dup(cert);
+  /* NB we do not bother setting peerdn */
+  if ((yield = event_raise(ev, US"tls:cert", string_sprintf("%d", depth))))
+    {
+    log_write(0, LOG_MAIN, "[%s] %s verify denied by event-action: "
+               "depth=%d cert=%s: %s",
+             tlsp == &tls_out ? deliver_host_address : sender_host_address,
+             what, depth, dn, yield);
+    *calledp = TRUE;
+    if (!*optionalp)
+      {
+      if (old_cert) tlsp->peercert = old_cert; /* restore 1st failing cert */
+      return 1;                            /* reject (leaving peercert set) */
+      }
+    DEBUG(D_tls) debug_printf("Event-action verify failure overridden "
+      "(host in tls_try_verify_hosts)\n");
+    }
+  X509_free(tlsp->peercert);
+  tlsp->peercert = old_cert;
+  }
+return 0;
+}
+#endif
+
 /*************************************************
 *        Callback for verification               *
 *************************************************/
 
 /* The SSL library does certificate verification if set up to do so. This
 callback has the current yes/no state is in "state". If verification succeeded,
-we set up the tls_peerdn string. If verification failed, what happens depends
-on whether the client is required to present a verifiable certificate or not.
+we set the certificate-verified flag. If verification failed, what happens
+depends on whether the client is required to present a verifiable certificate
+or not.
 
 If verification is optional, we change the state to yes, but still log the
 verification error. For some reason (it really would help to have proper
 documentation of OpenSSL), this callback function then gets called again, this
-time with state = 1. In fact, that's useful, because we can set up the peerdn
-value, but we must take care not to set the private verified flag on the second
-time through.
+time with state = 1.  We must take care not to set the private verified flag on
+the second time through.
 
 Note: this function is not called if the client fails to present a certificate
 when asked. We get here only if a certificate has been received. Handling of
 optional verification for this case is done when requesting SSL to verify, by
 setting SSL_VERIFY_FAIL_IF_NO_PEER_CERT in the non-optional case.
 
+May be called multiple times for different issues with a certificate, even
+for a given "depth" in the certificate chain.
+
 Arguments:
-  state      current yes/no state as 1/0
-  x509ctx    certificate information.
-  client     TRUE for client startup, FALSE for server startup
+  preverify_ok current yes/no state as 1/0
+  x509ctx      certificate information.
+  tlsp         per-direction (client vs. server) support data
+  calledp      has-been-called flag
+  optionalp    verification-is-optional flag
 
-Returns:     1 if verified, 0 if not
+Returns:     0 if verification should fail, otherwise 1
 */
 
 static int
-verify_callback(int state, X509_STORE_CTX *x509ctx,
+verify_callback(int preverify_ok, X509_STORE_CTX *x509ctx,
   tls_support *tlsp, BOOL *calledp, BOOL *optionalp)
 {
 X509 * cert = X509_STORE_CTX_get_current_cert(x509ctx);
-static uschar txt[256];
+int depth = X509_STORE_CTX_get_error_depth(x509ctx);
+uschar dn[256];
 
-X509_NAME_oneline(X509_get_subject_name(cert), CS txt, sizeof(txt));
+X509_NAME_oneline(X509_get_subject_name(cert), CS dn, sizeof(dn));
+dn[sizeof(dn)-1] = '\0';
 
-if (state == 0)
+if (preverify_ok == 0)
   {
-  log_write(0, LOG_MAIN, "SSL verify error: depth=%d error=%s cert=%s",
-    X509_STORE_CTX_get_error_depth(x509ctx),
+  log_write(0, LOG_MAIN, "[%s] SSL verify error: depth=%d error=%s cert=%s",
+       tlsp == &tls_out ? deliver_host_address : sender_host_address,
+    depth,
     X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509ctx)),
-    txt);
-  tlsp->certificate_verified = FALSE;
+    dn);
   *calledp = TRUE;
   if (!*optionalp)
     {
-    tlsp->peercert = X509_dup(cert);
-    return 0;                      /* reject */
+    if (!tlsp->peercert)
+      tlsp->peercert = X509_dup(cert); /* record failing cert */
+    return 0;                          /* reject */
     }
   DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in "
     "tls_try_verify_hosts)\n");
   }
 
-else if (X509_STORE_CTX_get_error_depth(x509ctx) != 0)
+else if (depth != 0)
   {
-  DEBUG(D_tls) debug_printf("SSL verify ok: depth=%d SN=%s\n",
-     X509_STORE_CTX_get_error_depth(x509ctx), txt);
+  DEBUG(D_tls) debug_printf("SSL verify ok: depth=%d SN=%s\n", depth, dn);
 #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,
                              cert))
       ERR_clear_error();
+    sk_X509_push(client_static_cbinfo->verify_stack, cert);
     }
+#endif
+#ifndef DISABLE_EVENT
+    if (verify_event(tlsp, cert, depth, dn, calledp, optionalp, US"SSL"))
+      return 0;                                /* reject, with peercert set */
 #endif
   }
 else
   {
-#ifdef EXPERIMENTAL_CERTNAMES
-  uschar * verify_cert_hostnames;
-#endif
-
-  tlsp->peerdn = txt;
-  tlsp->peercert = X509_dup(cert);
+  const uschar * verify_cert_hostnames;
 
-#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
     {
+
+#ifdef EXIM_HAVE_OPENSSL_CHECKHOST
+# ifndef X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS
+#  define X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS 0
+# endif
+# ifndef X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS
+#  define X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS 0
+# endif
     int sep = 0;
-    uschar * list = verify_cert_hostnames;
+    const 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 = X509_check_host(cert, CCS name, 0,
+                 X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS
+                 | X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS,
+                 NULL)))
        {
        if (rc < 0)
          {
-         log_write(0, LOG_MAIN, "SSL verify error: internal error\n");
+         log_write(0, LOG_MAIN, "[%s] SSL verify error: internal error",
+               deliver_host_address);
          name = NULL;
          }
        break;
        }
     if (!name)
-      {
-      log_write(0, LOG_MAIN,
-       "SSL verify error: certificate name mismatch: \"%s\"\n", txt);
-      return 0;                                /* reject */
-      }
-    }
-# else
+#else
     if (!tls_is_name_for_cert(verify_cert_hostnames, cert))
+#endif
       {
       log_write(0, LOG_MAIN,
-       "SSL verify error: certificate name mismatch: \"%s\"\n", txt);
-      return 0;                                /* reject */
+               "[%s] SSL verify error: certificate name mismatch: "
+               "DN=\"%s\" H=\"%s\"",
+               deliver_host_address, dn, verify_cert_hostnames);
+      *calledp = TRUE;
+      if (!*optionalp)
+       {
+       if (!tlsp->peercert)
+         tlsp->peercert = X509_dup(cert);      /* record failing cert */
+       return 0;                               /* reject */
+       }
+      DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in "
+       "tls_try_verify_hosts)\n");
       }
-# endif
+    }
+
+#ifndef DISABLE_EVENT
+  if (verify_event(tlsp, cert, depth, dn, calledp, optionalp, US"SSL"))
+    return 0;                          /* reject, with peercert set */
 #endif
 
   DEBUG(D_tls) debug_printf("SSL%s verify ok: depth=0 SN=%s\n",
-    *calledp ? "" : " authenticated", txt);
+    *calledp ? "" : " authenticated", dn);
   if (!*calledp) tlsp->certificate_verified = TRUE;
   *calledp = TRUE;
   }
 
-return 1;   /* accept */
+return 1;   /* accept, at least for this level */
 }
 
 static int
-verify_callback_client(int state, X509_STORE_CTX *x509ctx)
+verify_callback_client(int preverify_ok, X509_STORE_CTX *x509ctx)
 {
-return verify_callback(state, x509ctx, &tls_out, &client_verify_callback_called, &client_verify_optional);
+return verify_callback(preverify_ok, x509ctx, &tls_out,
+  &client_verify_callback_called, &client_verify_optional);
 }
 
 static int
-verify_callback_server(int state, X509_STORE_CTX *x509ctx)
+verify_callback_server(int preverify_ok, X509_STORE_CTX *x509ctx)
 {
-return verify_callback(state, x509ctx, &tls_in, &server_verify_callback_called, &server_verify_optional);
+return verify_callback(preverify_ok, x509ctx, &tls_in,
+  &server_verify_callback_called, &server_verify_optional);
+}
+
+
+#ifdef EXPERIMENTAL_DANE
+
+/* This gets called *by* the dane library verify callback, which interposes
+itself.
+*/
+static int
+verify_callback_client_dane(int preverify_ok, X509_STORE_CTX * x509ctx)
+{
+X509 * cert = X509_STORE_CTX_get_current_cert(x509ctx);
+uschar dn[256];
+int depth = X509_STORE_CTX_get_error_depth(x509ctx);
+#ifndef DISABLE_EVENT
+BOOL dummy_called, optional = FALSE;
+#endif
+
+X509_NAME_oneline(X509_get_subject_name(cert), CS dn, sizeof(dn));
+dn[sizeof(dn)-1] = '\0';
+
+DEBUG(D_tls) debug_printf("verify_callback_client_dane: %s depth %d %s\n",
+  preverify_ok ? "ok":"BAD", depth, dn);
+
+#ifndef DISABLE_EVENT
+  if (verify_event(&tls_out, cert, depth, dn,
+         &dummy_called, &optional, US"DANE"))
+    return 0;                          /* reject, with peercert set */
+#endif
+
+if (preverify_ok == 1)
+  tls_out.dane_verified =
+  tls_out.certificate_verified = TRUE;
+else
+  {
+  int err = X509_STORE_CTX_get_error(x509ctx);
+  DEBUG(D_tls)
+    debug_printf(" - err %d '%s'\n", err, X509_verify_cert_error_string(err));
+  if (err == X509_V_ERR_APPLICATION_VERIFICATION)
+    preverify_ok = 1;
+  }
+return preverify_ok;
 }
 
+#endif /*EXPERIMENTAL_DANE*/
 
 
 /*************************************************
@@ -419,6 +593,7 @@ DEBUG(D_tls) debug_printf("SSL info: %s\n", SSL_state_string_long(s));
 /* If dhparam is set, expand it, and load up the parameters for DH encryption.
 
 Arguments:
+  sctx      The current SSL CTX (inbound or outbound)
   dhparam   DH parameter file or fixed parameter identity string
   host      connected host, if client; NULL if server
 
@@ -426,7 +601,7 @@ Returns:    TRUE if OK (nothing to set up, or setup worked)
 */
 
 static BOOL
-init_dh(SSL_CTX *sctx, uschar *dhparam, host_item *host)
+init_dh(SSL_CTX *sctx, uschar *dhparam, const host_item *host)
 {
 BIO *bio;
 DH *dh;
@@ -498,11 +673,127 @@ return TRUE;
 
 
 
+/*************************************************
+*               Initialize for ECDH              *
+*************************************************/
+
+/* Load parameters for ECDH encryption.
+
+For now, we stick to NIST P-256 because: it's simple and easy to configure;
+it avoids any patent issues that might bite redistributors; despite events in
+the news and concerns over curve choices, we're not cryptographers, we're not
+pretending to be, and this is "good enough" to be better than no support,
+protecting against most adversaries.  Given another year or two, there might
+be sufficient clarity about a "right" way forward to let us make an informed
+decision, instead of a knee-jerk reaction.
+
+Longer-term, we should look at supporting both various named curves and
+external files generated with "openssl ecparam", much as we do for init_dh().
+We should also support "none" as a value, to explicitly avoid initialisation.
+
+Patches welcome.
+
+Arguments:
+  sctx      The current SSL CTX (inbound or outbound)
+  host      connected host, if client; NULL if server
+
+Returns:    TRUE if OK (nothing to set up, or setup worked)
+*/
+
+static BOOL
+init_ecdh(SSL_CTX * sctx, host_item * host)
+{
+#ifdef OPENSSL_NO_ECDH
+return TRUE;
+#else
+
+EC_KEY * ecdh;
+uschar * exp_curve;
+int nid;
+BOOL rv;
+
+if (host)      /* No ECDH setup for clients, only for servers */
+  return TRUE;
+
+# ifndef EXIM_HAVE_ECDH
+DEBUG(D_tls)
+  debug_printf("No OpenSSL API to define ECDH parameters, skipping\n");
+return TRUE;
+# else
+
+if (!expand_check(tls_eccurve, US"tls_eccurve", &exp_curve))
+  return FALSE;
+if (!exp_curve || !*exp_curve)
+  return TRUE;
+
+/* "auto" needs to be handled carefully.
+ * OpenSSL <  1.0.2: we do not select anything, but fallback to prime256v1
+ * OpenSSL <  1.1.0: we have to call SSL_CTX_set_ecdh_auto
+ *                   (openssl/ssl.h defines SSL_CTRL_SET_ECDH_AUTO)
+ * OpenSSL >= 1.1.0: we do not set anything, the libray does autoselection
+ *                   https://github.com/openssl/openssl/commit/fe6ef2472db933f01b59cad82aa925736935984b
+ */
+if (Ustrcmp(exp_curve, "auto") == 0)
+  {
+#if OPENSSL_VERSION_NUMBER < 0x10002000L
+  DEBUG(D_tls) debug_printf(
+    "ECDH OpenSSL < 1.0.2: temp key parameter settings: overriding \"auto\" with \"prime256v1\"\n");
+  exp_curve = "prime256v1";
+#else
+# if defined SSL_CTRL_SET_ECDH_AUTO
+  DEBUG(D_tls) debug_printf(
+    "ECDH OpenSSL 1.0.2+ temp key parameter settings: autoselection\n");
+  SSL_CTX_set_ecdh_auto(sctx, 1);
+  return TRUE;
+# else
+  DEBUG(D_tls) debug_printf(
+    "ECDH OpenSSL 1.1.0+ temp key parameter settings: default selection\n");
+  return TRUE;
+# endif
+#endif
+  }
+
+DEBUG(D_tls) debug_printf("ECDH: curve '%s'\n", exp_curve);
+if (  (nid = OBJ_sn2nid       (CCS exp_curve)) == NID_undef
+#   ifdef EXIM_HAVE_OPENSSL_EC_NIST2NID
+   && (nid = EC_curve_nist2nid(CCS exp_curve)) == NID_undef
+#   endif
+   )
+  {
+  tls_error(string_sprintf("Unknown curve name tls_eccurve '%s'",
+      exp_curve),
+    host, NULL);
+  return FALSE;
+  }
+
+if (!(ecdh = EC_KEY_new_by_curve_name(nid)))
+  {
+  tls_error(US"Unable to create ec curve", host, NULL);
+  return FALSE;
+  }
+
+/* The "tmp" in the name here refers to setting a temporary key
+not to the stability of the interface. */
+
+if ((rv = SSL_CTX_set_tmp_ecdh(sctx, ecdh) == 0))
+  tls_error(string_sprintf("Error enabling '%s' curve", exp_curve), host, NULL);
+else
+  DEBUG(D_tls) debug_printf("ECDH: enabled '%s' curve\n", exp_curve);
+
+EC_KEY_free(ecdh);
+return !rv;
+
+# endif        /*EXIM_HAVE_ECDH*/
+#endif /*OPENSSL_NO_ECDH*/
+}
+
+
+
+
 #ifndef DISABLE_OCSP
 /*************************************************
 *       Load OCSP information into state         *
 *************************************************/
-
 /* Called to load the server OCSP response from the given file into memory, once
 caller has determined this is needed.  Checks validity.  Debugs a message
 if invalid.
@@ -519,12 +810,12 @@ Arguments:
 static void
 ocsp_load_response(SSL_CTX *sctx, tls_ext_ctx_cb *cbinfo, const uschar *expanded)
 {
-BIO *bio;
-OCSP_RESPONSE *resp;
-OCSP_BASICRESP *basic_response;
-OCSP_SINGLERESP *single_response;
-ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd;
-X509_STORE *store;
+BIO * bio;
+OCSP_RESPONSE * resp;
+OCSP_BASICRESP * basic_response;
+OCSP_SINGLERESP * single_response;
+ASN1_GENERALIZEDTIME * rev, * thisupd, * nextupd;
+STACK_OF(X509) * sk;
 unsigned long verify_flags;
 int status, reason, i;
 
@@ -535,8 +826,7 @@ if (cbinfo->u_ocsp.server.response)
   cbinfo->u_ocsp.server.response = NULL;
   }
 
-bio = BIO_new_file(CS cbinfo->u_ocsp.server.file_expanded, "rb");
-if (!bio)
+if (!(bio = BIO_new_file(CS cbinfo->u_ocsp.server.file_expanded, "rb")))
   {
   DEBUG(D_tls) debug_printf("Failed to open OCSP response file \"%s\"\n",
       cbinfo->u_ocsp.server.file_expanded);
@@ -551,33 +841,54 @@ if (!resp)
   return;
   }
 
-status = OCSP_response_status(resp);
-if (status != OCSP_RESPONSE_STATUS_SUCCESSFUL)
+if ((status = OCSP_response_status(resp)) != OCSP_RESPONSE_STATUS_SUCCESSFUL)
   {
   DEBUG(D_tls) debug_printf("OCSP response not valid: %s (%d)\n",
       OCSP_response_status_str(status), status);
   goto bad;
   }
 
-basic_response = OCSP_response_get1_basic(resp);
-if (!basic_response)
+if (!(basic_response = OCSP_response_get1_basic(resp)))
   {
   DEBUG(D_tls)
     debug_printf("OCSP response parse error: unable to extract basic response.\n");
   goto bad;
   }
 
-store = SSL_CTX_get_cert_store(sctx);
+sk = cbinfo->verify_stack;
 verify_flags = OCSP_NOVERIFY; /* check sigs, but not purpose */
 
 /* May need to expose ability to adjust those flags?
 OCSP_NOSIGS OCSP_NOVERIFY OCSP_NOCHAIN OCSP_NOCHECKS OCSP_NOEXPLICIT
 OCSP_TRUSTOTHER OCSP_NOINTERN */
 
-i = OCSP_basic_verify(basic_response, NULL, store, verify_flags);
-if (i <= 0)
+/* This does a full verify on the OCSP proof before we load it for serving
+up; possibly overkill - just date-checks might be nice enough.
+
+OCSP_basic_verify takes a "store" arg, but does not
+use it for the chain verification, which is all we do
+when OCSP_NOVERIFY is set.  The content from the wire
+"basic_response" and a cert-stack "sk" are all that is used.
+
+We have a stack, loaded in setup_certs() if tls_verify_certificates
+was a file (not a directory, or "system").  It is unfortunate we
+cannot used the connection context store, as that would neatly
+handle the "system" case too, but there seems to be no library
+function for getting a stack from a store.
+[ In OpenSSL 1.1 - ?  X509_STORE_CTX_get0_chain(ctx) ? ]
+We do not free the stack since it could be needed a second time for
+SNI handling.
+
+Separately we might try to replace using OCSP_basic_verify() - which seems to not
+be a public interface into the OpenSSL library (there's no manual entry) - 
+But what with?  We also use OCSP_basic_verify in the client stapling callback.
+And there we NEED it; we must verify that status... unless the
+library does it for us anyway?  */
+
+if ((i = OCSP_basic_verify(basic_response, sk, NULL, verify_flags)) < 0)
   {
-  DEBUG(D_tls) {
+  DEBUG(D_tls)
+    {
     ERR_error_string(ERR_get_error(), ssl_errstring);
     debug_printf("OCSP response verify failure: %s\n", US ssl_errstring);
     }
@@ -591,8 +902,8 @@ proves false, we need to extract a cert id from our issued cert
 right cert in the stack and then calls OCSP_single_get0_status()).
 
 I'm hoping to avoid reworking a bunch more of how we handle state here. */
-single_response = OCSP_resp_get0(basic_response, 0);
-if (!single_response)
+
+if (!(single_response = OCSP_resp_get0(basic_response, 0)))
   {
   DEBUG(D_tls)
     debug_printf("Unable to get first response from OCSP basic response.\n");
@@ -623,7 +934,7 @@ bad:
     {
     extern char ** environ;
     uschar ** p;
-    for (p = USS environ; *p != NULL; p++)
+    if (environ) 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");
@@ -637,6 +948,73 @@ return;
 
 
 
+/* Create and install a selfsigned certificate, for use in server mode */
+
+static int
+tls_install_selfsign(SSL_CTX * sctx)
+{
+X509 * x509 = NULL;
+EVP_PKEY * pkey;
+RSA * rsa;
+X509_NAME * name;
+uschar * where;
+
+where = US"allocating pkey";
+if (!(pkey = EVP_PKEY_new()))
+  goto err;
+
+where = US"allocating cert";
+if (!(x509 = X509_new()))
+  goto err;
+
+where = US"generating pkey";
+               /* deprecated, use RSA_generate_key_ex() */
+if (!(rsa = RSA_generate_key(1024, RSA_F4, NULL, NULL)))
+  goto err;
+
+where = US"assigning pkey";
+if (!EVP_PKEY_assign_RSA(pkey, rsa))
+  goto err;
+
+X509_set_version(x509, 2);                             /* N+1 - version 3 */
+ASN1_INTEGER_set(X509_get_serialNumber(x509), 0);
+X509_gmtime_adj(X509_get_notBefore(x509), 0);
+X509_gmtime_adj(X509_get_notAfter(x509), (long)60 * 60);       /* 1 hour */
+X509_set_pubkey(x509, pkey);
+
+name = X509_get_subject_name(x509);
+X509_NAME_add_entry_by_txt(name, "C",
+                         MBSTRING_ASC, CUS "UK", -1, -1, 0);
+X509_NAME_add_entry_by_txt(name, "O",
+                         MBSTRING_ASC, CUS "Exim Developers", -1, -1, 0);
+X509_NAME_add_entry_by_txt(name, "CN",
+                         MBSTRING_ASC, CUS smtp_active_hostname, -1, -1, 0);
+X509_set_issuer_name(x509, name);
+
+where = US"signing cert";
+if (!X509_sign(x509, pkey, EVP_md5()))
+  goto err;
+
+where = US"installing selfsign cert";
+if (!SSL_CTX_use_certificate(sctx, x509))
+  goto err;
+
+where = US"installing selfsign key";
+if (!SSL_CTX_use_PrivateKey(sctx, pkey))
+  goto err;
+
+return OK;
+
+err:
+  (void) tls_error(where, NULL, NULL);
+  if (x509) X509_free(x509);
+  if (pkey) EVP_PKEY_free(pkey);
+  return DEFER;
+}
+
+
+
+
 /*************************************************
 *        Expand key and cert file specs          *
 *************************************************/
@@ -657,60 +1035,67 @@ tls_expand_session_files(SSL_CTX *sctx, tls_ext_ctx_cb *cbinfo)
 {
 uschar *expanded;
 
-if (cbinfo->certificate == NULL)
-  return OK;
-
-if (Ustrstr(cbinfo->certificate, US"tls_sni") ||
-    Ustrstr(cbinfo->certificate, US"tls_in_sni") ||
-    Ustrstr(cbinfo->certificate, US"tls_out_sni")
-   )
-  reexpand_tls_files_for_sni = TRUE;
-
-if (!expand_check(cbinfo->certificate, US"tls_certificate", &expanded))
-  return DEFER;
-
-if (expanded != NULL)
+if (!cbinfo->certificate)
   {
-  DEBUG(D_tls) debug_printf("tls_certificate file %s\n", expanded);
-  if (!SSL_CTX_use_certificate_chain_file(sctx, CS expanded))
-    return tls_error(string_sprintf(
-      "SSL_CTX_use_certificate_chain_file file=%s", expanded),
-        cbinfo->host, NULL);
+  if (cbinfo->host)                    /* client */
+    return OK;
+                                       /* server */
+  if (tls_install_selfsign(sctx) != OK)
+    return DEFER;
   }
+else
+  {
+  if (Ustrstr(cbinfo->certificate, US"tls_sni") ||
+      Ustrstr(cbinfo->certificate, US"tls_in_sni") ||
+      Ustrstr(cbinfo->certificate, US"tls_out_sni")
+     )
+    reexpand_tls_files_for_sni = TRUE;
 
-if (cbinfo->privatekey != NULL &&
-    !expand_check(cbinfo->privatekey, US"tls_privatekey", &expanded))
-  return DEFER;
+  if (!expand_check(cbinfo->certificate, US"tls_certificate", &expanded))
+    return DEFER;
 
-/* If expansion was forced to fail, key_expanded will be NULL. If the result
-of the expansion is an empty string, ignore it also, and assume the private
-key is in the same file as the certificate. */
+  if (expanded != NULL)
+    {
+    DEBUG(D_tls) debug_printf("tls_certificate file %s\n", expanded);
+    if (!SSL_CTX_use_certificate_chain_file(sctx, CS expanded))
+      return tls_error(string_sprintf(
+       "SSL_CTX_use_certificate_chain_file file=%s", expanded),
+         cbinfo->host, NULL);
+    }
 
-if (expanded != NULL && *expanded != 0)
-  {
-  DEBUG(D_tls) debug_printf("tls_privatekey file %s\n", expanded);
-  if (!SSL_CTX_use_PrivateKey_file(sctx, CS expanded, SSL_FILETYPE_PEM))
-    return tls_error(string_sprintf(
-      "SSL_CTX_use_PrivateKey_file file=%s", expanded), cbinfo->host, NULL);
+  if (cbinfo->privatekey != NULL &&
+      !expand_check(cbinfo->privatekey, US"tls_privatekey", &expanded))
+    return DEFER;
+
+  /* If expansion was forced to fail, key_expanded will be NULL. If the result
+  of the expansion is an empty string, ignore it also, and assume the private
+  key is in the same file as the certificate. */
+
+  if (expanded && *expanded)
+    {
+    DEBUG(D_tls) debug_printf("tls_privatekey file %s\n", expanded);
+    if (!SSL_CTX_use_PrivateKey_file(sctx, CS expanded, SSL_FILETYPE_PEM))
+      return tls_error(string_sprintf(
+       "SSL_CTX_use_PrivateKey_file file=%s", expanded), cbinfo->host, NULL);
+    }
   }
 
 #ifndef DISABLE_OCSP
-if (cbinfo->is_server &&  cbinfo->u_ocsp.server.file != NULL)
+if (cbinfo->is_server && cbinfo->u_ocsp.server.file)
   {
   if (!expand_check(cbinfo->u_ocsp.server.file, US"tls_ocsp_file", &expanded))
     return DEFER;
 
-  if (expanded != NULL && *expanded != 0)
+  if (expanded && *expanded)
     {
     DEBUG(D_tls) debug_printf("tls_ocsp_file %s\n", expanded);
-    if (cbinfo->u_ocsp.server.file_expanded &&
-        (Ustrcmp(expanded, cbinfo->u_ocsp.server.file_expanded) == 0))
+    if (  cbinfo->u_ocsp.server.file_expanded
+       && (Ustrcmp(expanded, cbinfo->u_ocsp.server.file_expanded) == 0))
       {
-      DEBUG(D_tls)
-        debug_printf("tls_ocsp_file value unchanged, using existing values.\n");
-      } else {
-        ocsp_load_response(sctx, cbinfo, expanded);
+      DEBUG(D_tls) debug_printf(" - value unchanged, using existing values\n");
       }
+    else
+      ocsp_load_response(sctx, cbinfo, expanded);
     }
   }
 #endif
@@ -781,6 +1166,12 @@ SSL_CTX_set_options(server_sni, SSL_CTX_get_options(server_ctx));
 SSL_CTX_set_timeout(server_sni, SSL_CTX_get_timeout(server_ctx));
 SSL_CTX_set_tlsext_servername_callback(server_sni, tls_servername_cb);
 SSL_CTX_set_tlsext_servername_arg(server_sni, cbinfo);
+
+if (  !init_dh(server_sni, cbinfo->dhparam, NULL)
+   || !init_ecdh(server_sni, NULL)
+   )
+  return SSL_TLSEXT_ERR_NOACK;
+
 if (cbinfo->server_cipher_list)
   SSL_CTX_set_cipher_list(server_sni, CS cbinfo->server_cipher_list);
 #ifndef DISABLE_OCSP
@@ -791,15 +1182,13 @@ if (cbinfo->u_ocsp.server.file)
   }
 #endif
 
-rc = setup_certs(server_sni, tls_verify_certificates, tls_crl, NULL, FALSE, verify_callback_server);
-if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
+if ((rc = setup_certs(server_sni, tls_verify_certificates, tls_crl, NULL, FALSE,
+                     verify_callback_server)) != OK)
+  return SSL_TLSEXT_ERR_NOACK;
 
 /* do this after setup_certs, because this can require the certs for verifying
 OCSP information. */
-rc = tls_expand_session_files(server_sni, cbinfo);
-if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
-
-if (!init_dh(server_sni, cbinfo->dhparam, NULL))
+if ((rc = tls_expand_session_files(server_sni, cbinfo)) != OK)
   return SSL_TLSEXT_ERR_NOACK;
 
 DEBUG(D_tls) debug_printf("Switching SSL context.\n");
@@ -834,7 +1223,7 @@ uschar *response_der;
 int response_der_len;
 
 DEBUG(D_tls)
-  debug_printf("Received TLS status request (OCSP stapling); %s response.",
+  debug_printf("Received TLS status request (OCSP stapling); %s response\n",
     cbinfo->u_ocsp.server.response ? "have" : "lack");
 
 tls_in.ocsp = OCSP_NOT_RESP;
@@ -876,8 +1265,7 @@ len = SSL_get_tlsext_status_ocsp_resp(s, &p);
 if(!p)
  {
   /* Expect this when we requested ocsp but got none */
-  if (  cbinfo->u_ocsp.client.verify_required
-     && log_extra_selector & LX_tls_cipher)
+  if (cbinfo->u_ocsp.client.verify_required && LOGGING(tls_cipher))
     log_write(0, LOG_MAIN, "Received TLS status callback, null content");
   else
     DEBUG(D_tls) debug_printf(" null\n");
@@ -887,8 +1275,8 @@ if(!p)
 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");
+  if (LOGGING(tls_cipher))
+    log_write(0, LOG_MAIN, "Received TLS cert status response, parse error");
   else
     DEBUG(D_tls) debug_printf(" parse error\n");
   return 0;
@@ -897,8 +1285,8 @@ if(!(rsp = d2i_OCSP_RESPONSE(NULL, &p, len)))
 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");
+  if (LOGGING(tls_cipher))
+    log_write(0, LOG_MAIN, "Received TLS cert status response, error parsing response");
   else
     DEBUG(D_tls) debug_printf(" error parsing response\n");
   OCSP_RESPONSE_free(rsp);
@@ -924,29 +1312,41 @@ if(!(bs = OCSP_response_get1_basic(rsp)))
     /* 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,
+    if ((i = OCSP_basic_verify(bs, cbinfo->verify_stack,
              cbinfo->u_ocsp.client.verify_store, 0)) <= 0)
       {
       tls_out.ocsp = OCSP_FAILED;
+      if (LOGGING(tls_cipher))
+       log_write(0, LOG_MAIN, "Received TLS cert status response, itself unverifiable");
       BIO_printf(bp, "OCSP response verify failure\n");
       ERR_print_errors(bp);
-      i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
-      goto out;
+      goto failed;
       }
 
     BIO_printf(bp, "OCSP response well-formed and signed OK\n");
 
+    /*XXX So we have a good stapled OCSP status.  How do we know
+    it is for the cert of interest?  OpenSSL 1.1.0 has a routine
+    OCSP_resp_find_status() which matches on a cert id, which presumably
+    we should use. Making an id needs OCSP_cert_id_new(), which takes
+    issuerName, issuerKey, serialNumber.  Are they all in the cert?
+
+    For now, carry on blindly accepting the resp. */
+
       {
-      STACK_OF(OCSP_SINGLERESP) * sresp = bs->tbsResponseData->responses;
       OCSP_SINGLERESP * single;
 
+#ifdef EXIM_HAVE_OCSP_RESP_COUNT
+      if (OCSP_resp_count(bs) != 1)
+#else
+      STACK_OF(OCSP_SINGLERESP) * sresp = bs->tbsResponseData->responses;
       if (sk_OCSP_SINGLERESP_num(sresp) != 1)
+#endif
         {
        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;
+        goto failed;
         }
       single = OCSP_resp_get0(bs, 0);
       status = OCSP_single_get0_status(single, &reason, &rev,
@@ -961,7 +1361,6 @@ if(!(bs = OCSP_response_get1_basic(rsp)))
       tls_out.ocsp = OCSP_FAILED;
       DEBUG(D_tls) ERR_print_errors(bp);
       log_write(0, LOG_MAIN, "Server OSCP dates invalid");
-      i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
       }
     else
       {
@@ -972,24 +1371,24 @@ if(!(bs = OCSP_response_get1_basic(rsp)))
        case V_OCSP_CERTSTATUS_GOOD:
          tls_out.ocsp = OCSP_VFIED;
          i = 1;
-         break;
+         goto good;
        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:
+  failed:
+    i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
+  good:
     BIO_free(bp);
   }
 
@@ -999,7 +1398,6 @@ return i;
 #endif /*!DISABLE_OCSP*/
 
 
-
 /*************************************************
 *            Initialize for TLS                  *
 *************************************************/
@@ -1008,13 +1406,14 @@ return i;
 of the library.  We allocate and return a context structure.
 
 Arguments:
+  ctxp            returned SSL context
   host            connected host, if client; NULL if server
   dhparam         DH parameter file
   certificate     certificate file
   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
+  cbp             place to put allocated callback context
 
 Returns:          OK/DEFER/FAIL
 */
@@ -1030,12 +1429,13 @@ tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate,
 long init_options;
 int rc;
 BOOL okay;
-tls_ext_ctx_cb *cbinfo;
+tls_ext_ctx_cb * cbinfo;
 
 cbinfo = store_malloc(sizeof(tls_ext_ctx_cb));
 cbinfo->certificate = certificate;
 cbinfo->privatekey = privatekey;
 #ifndef DISABLE_OCSP
+cbinfo->verify_stack = NULL;
 if ((cbinfo->is_server = host==NULL))
   {
   cbinfo->u_ocsp.server.file = ocsp_file;
@@ -1048,11 +1448,14 @@ else
 cbinfo->dhparam = dhparam;
 cbinfo->server_cipher_list = NULL;
 cbinfo->host = host;
+#ifndef DISABLE_EVENT
+cbinfo->event_action = NULL;
+#endif
 
 SSL_load_error_strings();          /* basic set up */
 OpenSSL_add_ssl_algorithms();
 
-#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_SHA256)
+#ifdef EXIM_HAVE_SHA256
 /* SHA256 is becoming ever more popular. This makes sure it gets added to the
 list of available digests. */
 EVP_add_digest(EVP_sha256());
@@ -1066,10 +1469,9 @@ when OpenSSL is built without SSLv2 support.
 By disabling with openssl_options, we can let admins re-enable with the
 existing knob. */
 
-*ctxp = SSL_CTX_new((host == NULL)?
-  SSLv23_server_method() : SSLv23_client_method());
+*ctxp = SSL_CTX_new(host ? SSLv23_client_method() : SSLv23_server_method());
 
-if (*ctxp == NULL) return tls_error(US"SSL_CTX_new", host, NULL);
+if (!*ctxp) return tls_error(US"SSL_CTX_new", host, NULL);
 
 /* It turns out that we need to seed the random number generator this early in
 order to get the full complement of ciphers to work. It took me roughly a day
@@ -1097,7 +1499,7 @@ if (!RAND_status())
 /* Set up the information callback, which outputs if debugging is at a suitable
 level. */
 
-SSL_CTX_set_info_callback(*ctxp, (void (*)())info_callback);
+DEBUG(D_tls) SSL_CTX_set_info_callback(*ctxp, (void (*)())info_callback);
 
 /* Automatically re-try reads/writes after renegotiation. */
 (void) SSL_CTX_set_mode(*ctxp, SSL_MODE_AUTO_RETRY);
@@ -1126,16 +1528,29 @@ else
   DEBUG(D_tls) debug_printf("no SSL CTX options to set\n");
 
 /* Initialize with DH parameters if supplied */
+/* Initialize ECDH temp key parameter selection */
 
-if (!init_dh(*ctxp, dhparam, host)) return DEFER;
+if (  !init_dh(*ctxp, dhparam, host)
+   || !init_ecdh(*ctxp, host)
+   )
+  return DEFER;
 
 /* Set up certificate and key (and perhaps OCSP info) */
 
-rc = tls_expand_session_files(*ctxp, cbinfo);
-if (rc != OK) return rc;
+if ((rc = tls_expand_session_files(*ctxp, cbinfo)) != OK)
+  return rc;
+
+/* If we need to handle SNI or OCSP, do so */
 
-/* If we need to handle SNI, do so */
 #ifdef EXIM_HAVE_OPENSSL_TLSEXT
+# ifndef DISABLE_OCSP
+  if (!(cbinfo->verify_stack = sk_X509_new_null()))
+    {
+    DEBUG(D_tls) debug_printf("failed to create stack for stapling verify\n");
+    return FAIL;
+    }
+# endif
+
 if (host == NULL)              /* server */
   {
 # ifndef DISABLE_OCSP
@@ -1169,13 +1584,12 @@ else                    /* client */
 # endif
 #endif
 
-#ifdef EXPERIMENTAL_CERTNAMES
 cbinfo->verify_cert_hostnames = NULL;
-#endif
 
+#ifdef EXIM_HAVE_EPHEM_RSA_KEX
 /* Set up the RSA callback */
-
 SSL_CTX_set_tmp_rsa_callback(*ctxp, rsa_callback);
+#endif
 
 /* Finally, set the timeout, and we are done */
 
@@ -1223,6 +1637,29 @@ DEBUG(D_tls) debug_printf("Cipher: %s\n", cipherbuf);
 }
 
 
+static void
+peer_cert(SSL * ssl, tls_support * tlsp, uschar * peerdn, unsigned bsize)
+{
+/*XXX we might consider a list-of-certs variable for the cert chain.
+SSL_get_peer_cert_chain(SSL*).  We'd need a new variable type and support
+in list-handling functions, also consider the difference between the entire
+chain and the elements sent by the peer. */
+
+/* Will have already noted peercert on a verify fail; possibly not the leaf */
+if (!tlsp->peercert)
+  tlsp->peercert = SSL_get_peer_certificate(ssl);
+/* Beware anonymous ciphers which lead to server_cert being NULL */
+if (tlsp->peercert)
+  {
+  X509_NAME_oneline(X509_get_subject_name(tlsp->peercert), CS peerdn, bsize);
+  peerdn[bsize-1] = '\0';
+  tlsp->peerdn = peerdn;               /*XXX a static buffer... */
+  }
+else
+  tlsp->peerdn = NULL;
+}
+
+
 
 
 
@@ -1230,6 +1667,23 @@ DEBUG(D_tls) debug_printf("Cipher: %s\n", cipherbuf);
 *        Set up for verifying certificates       *
 *************************************************/
 
+/* Load certs from file, return TRUE on success */
+
+static BOOL
+chain_from_pem_file(const uschar * file, STACK_OF(X509) * verify_stack)
+{
+BIO * bp;
+X509 * x;
+
+if (!(bp = BIO_new_file(CS file, "r"))) return FALSE;
+while ((x = PEM_read_bio_X509(bp, NULL, 0, NULL)))
+  sk_X509_push(verify_stack, x);
+BIO_free(bp);
+return TRUE;
+}
+
+
+
 /* Called by both client and server startup
 
 Arguments:
@@ -1253,57 +1707,96 @@ uschar *expcerts, *expcrl;
 if (!expand_check(certs, US"tls_verify_certificates", &expcerts))
   return DEFER;
 
-if (expcerts != NULL && *expcerts != '\0')
+if (expcerts && *expcerts)
   {
-  struct stat statbuf;
+  /* Tell the library to use its compiled-in location for the system default
+  CA bundle. Then add the ones specified in the config, if any. */
+
   if (!SSL_CTX_set_default_verify_paths(sctx))
     return tls_error(US"SSL_CTX_set_default_verify_paths", host, NULL);
 
-  if (Ustat(expcerts, &statbuf) < 0)
-    {
-    log_write(0, LOG_MAIN|LOG_PANIC,
-      "failed to stat %s for certificates", expcerts);
-    return DEFER;
-    }
-  else
+  if (Ustrcmp(expcerts, "system") != 0)
     {
-    uschar *file, *dir;
-    if ((statbuf.st_mode & S_IFMT) == S_IFDIR)
-      { file = NULL; dir = expcerts; }
-    else
-      { file = expcerts; dir = NULL; }
+    struct stat statbuf;
 
-    /* If a certificate file is empty, the next function fails with an
-    unhelpful error message. If we skip it, we get the correct behaviour (no
-    certificates are recognized, but the error message is still misleading (it
-    says no certificate was supplied.) But this is better. */
+    if (Ustat(expcerts, &statbuf) < 0)
+      {
+      log_write(0, LOG_MAIN|LOG_PANIC,
+       "failed to stat %s for certificates", expcerts);
+      return DEFER;
+      }
+    else
+      {
+      uschar *file, *dir;
+      if ((statbuf.st_mode & S_IFMT) == S_IFDIR)
+       { file = NULL; dir = expcerts; }
+      else
+       {
+       file = expcerts; dir = NULL;
+#ifndef DISABLE_OCSP
+       /* In the server if we will be offering an OCSP proof, load chain from
+       file for verifying the OCSP proof at load time. */
+
+       if (  !host
+          && statbuf.st_size > 0
+          && server_static_cbinfo->u_ocsp.server.file
+          && !chain_from_pem_file(file, server_static_cbinfo->verify_stack)
+          )
+         {
+         log_write(0, LOG_MAIN|LOG_PANIC,
+           "failed to load cert hain from %s", file);
+         return DEFER;
+         }
+#endif
+       }
 
-    if ((file == NULL || statbuf.st_size > 0) &&
-          !SSL_CTX_load_verify_locations(sctx, CS file, CS dir))
-      return tls_error(US"SSL_CTX_load_verify_locations", host, NULL);
+      /* If a certificate file is empty, the next function fails with an
+      unhelpful error message. If we skip it, we get the correct behaviour (no
+      certificates are recognized, but the error message is still misleading (it
+      says no certificate was supplied).  But this is better. */
+
+      if (  (!file || statbuf.st_size > 0)
+         && !SSL_CTX_load_verify_locations(sctx, CS file, CS dir))
+       return tls_error(US"SSL_CTX_load_verify_locations", host, NULL);
+
+      /* Load the list of CAs for which we will accept certs, for sending
+      to the client.  This is only for the one-file tls_verify_certificates
+      variant.
+      If a list isn't loaded into the server, but
+      some verify locations are set, the server end appears to make
+      a wildcard request for client certs.
+      Meanwhile, the client library as default behaviour *ignores* the list
+      we send over the wire - see man SSL_CTX_set_client_cert_cb.
+      Because of this, and that the dir variant is likely only used for
+      the public-CA bundle (not for a private CA), not worth fixing.
+      */
+      if (file)
+       {
+       STACK_OF(X509_NAME) * names = SSL_load_client_CA_file(CS file);
 
-    if (file != NULL)
-      {
-      SSL_CTX_set_client_CA_list(sctx, SSL_load_client_CA_file(CS file));
+       DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n",
+                                   sk_X509_NAME_num(names));
+       SSL_CTX_set_client_CA_list(sctx, names);
+       }
       }
     }
 
   /* Handle a certificate revocation list. */
 
-  #if OPENSSL_VERSION_NUMBER > 0x00907000L
+#if OPENSSL_VERSION_NUMBER > 0x00907000L
 
   /* This bit of code is now the version supplied by Lars Mainka. (I have
-   * merely reformatted it into the Exim code style.)
+  merely reformatted it into the Exim code style.)
 
-   * "From here I changed the code to add support for multiple crl's
-   * in pem format in one file or to support hashed directory entries in
-   * pem format instead of a file. This method now uses the library function
-   * X509_STORE_load_locations to add the CRL location to the SSL context.
-   * OpenSSL will then handle the verify against CA certs and CRLs by
-   * itself in the verify callback." */
+  "From here I changed the code to add support for multiple crl's
+  in pem format in one file or to support hashed directory entries in
+  pem format instead of a file. This method now uses the library function
+  X509_STORE_load_locations to add the CRL location to the SSL context.
+  OpenSSL will then handle the verify against CA certs and CRLs by
+  itself in the verify callback." */
 
   if (!expand_check(crl, US"tls_crl", &expcrl)) return DEFER;
-  if (expcrl != NULL && *expcrl != 0)
+  if (expcrl && *expcrl)
     {
     struct stat statbufcrl;
     if (Ustat(expcrl, &statbufcrl) < 0)
@@ -1339,7 +1832,7 @@ if (expcerts != NULL && *expcerts != '\0')
       }
     }
 
-  #endif  /* OPENSSL_VERSION_NUMBER > 0x00907000L */
+#endif  /* OPENSSL_VERSION_NUMBER > 0x00907000L */
 
   /* If verification is optional, don't fail if no certificate */
 
@@ -1366,7 +1859,7 @@ Arguments:
 
 Returns:            OK on success
                     DEFER for errors before the start of the negotiation
-                    FAIL for errors during the negotation; the server can't
+                    FAIL for errors during the negotiation; the server can't
                       continue running.
 */
 
@@ -1376,6 +1869,7 @@ tls_server_start(const uschar *require_ciphers)
 int rc;
 uschar *expciphers;
 tls_ext_ctx_cb *cbinfo;
+static uschar peerdn[256];
 static uschar cipherbuf[256];
 
 /* Check for previous activation */
@@ -1406,9 +1900,9 @@ were historically separated by underscores. So that I can use either form in my
 tests, and also for general convenience, we turn underscores into hyphens here.
 */
 
-if (expciphers != NULL)
+if (expciphers)
   {
-  uschar *s = expciphers;
+  uschar * s = expciphers;
   while (*s != 0) { if (*s == '_') *s = '-'; s++; }
   DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers);
   if (!SSL_CTX_set_cipher_list(server_ctx, CS expciphers))
@@ -1420,6 +1914,9 @@ if (expciphers != NULL)
 optional, set up appropriately. */
 
 tls_in.certificate_verified = FALSE;
+#ifdef EXPERIMENTAL_DANE
+tls_in.dane_verified = FALSE;
+#endif
 server_verify_callback_called = FALSE;
 
 if (verify_check_host(&tls_verify_hosts) == OK)
@@ -1439,7 +1936,7 @@ else if (verify_check_host(&tls_try_verify_hosts) == OK)
 
 /* Prepare for new connection */
 
-if ((server_ssl = SSL_new(server_ctx)) == NULL) return tls_error(US"SSL_new", NULL, NULL);
+if (!(server_ssl = SSL_new(server_ctx))) return tls_error(US"SSL_new", NULL, NULL);
 
 /* Warning: we used to SSL_clear(ssl) here, it was removed.
  *
@@ -1495,6 +1992,8 @@ DEBUG(D_tls) debug_printf("SSL_accept was successful\n");
 /* TLS has been set up. Adjust the input functions to read via TLS,
 and initialize things. */
 
+peer_cert(server_ssl, &tls_in, peerdn, sizeof(peerdn));
+
 construct_cipher_name(server_ssl, cipherbuf, sizeof(cipherbuf), &tls_in.bits);
 tls_in.cipher = cipherbuf;
 
@@ -1521,6 +2020,7 @@ ssl_xfer_buffer_lwm = ssl_xfer_buffer_hwm = 0;
 ssl_xfer_eof = ssl_xfer_error = 0;
 
 receive_getc = tls_getc;
+receive_get_cache = tls_get_cache;
 receive_ungetc = tls_ungetc;
 receive_feof = tls_feof;
 receive_ferror = tls_ferror;
@@ -1533,6 +2033,104 @@ return OK;
 
 
 
+static int
+tls_client_basic_ctx_init(SSL_CTX * ctx,
+    host_item * host, smtp_transport_options_block * ob, tls_ext_ctx_cb * cbinfo
+                         )
+{
+int 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 || !*ob->tls_try_verify_hosts)
+      )
+   || (verify_check_given_host(&ob->tls_verify_hosts, host) == OK)
+   )
+  client_verify_optional = FALSE;
+else if (verify_check_given_host(&ob->tls_try_verify_hosts, host) == OK)
+  client_verify_optional = TRUE;
+else
+  return OK;
+
+if ((rc = setup_certs(ctx, ob->tls_verify_certificates,
+      ob->tls_crl, host, client_verify_optional, verify_callback_client)) != OK)
+  return rc;
+
+if (verify_check_given_host(&ob->tls_verify_cert_hostnames, host) == OK)
+  {
+  cbinfo->verify_cert_hostnames =
+#ifdef SUPPORT_I18N
+    string_domain_utf8_to_alabel(host->name, NULL);
+#else
+    host->name;
+#endif
+  DEBUG(D_tls) debug_printf("Cert hostname to check: \"%s\"\n",
+                   cbinfo->verify_cert_hostnames);
+  }
+return OK;
+}
+
+
+#ifdef EXPERIMENTAL_DANE
+static int
+dane_tlsa_load(SSL * ssl, host_item * host, dns_answer * dnsa)
+{
+dns_record * rr;
+dns_scan dnss;
+const char * hostnames[2] = { CS host->name, NULL };
+int found = 0;
+
+if (DANESSL_init(ssl, NULL, hostnames) != 1)
+  return tls_error(US"hostnames load", host, NULL);
+
+for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
+     rr;
+     rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
+    ) if (rr->type == T_TLSA)
+  {
+  const uschar * p = rr->data;
+  uint8_t usage, selector, mtype;
+  const char * mdname;
+
+  usage = *p++;
+
+  /* Only DANE-TA(2) and DANE-EE(3) are supported */
+  if (usage != 2 && usage != 3) continue;
+
+  selector = *p++;
+  mtype = *p++;
+
+  switch (mtype)
+    {
+    default: continue; /* Only match-types 0, 1, 2 are supported */
+    case 0:  mdname = NULL; break;
+    case 1:  mdname = "sha256"; break;
+    case 2:  mdname = "sha512"; break;
+    }
+
+  found++;
+  switch (DANESSL_add_tlsa(ssl, usage, selector, mdname, p, rr->size - 3))
+    {
+    default:
+      return tls_error(US"tlsa load", host, NULL);
+    case 0:    /* action not taken */
+    case 1:    break;
+    }
+
+  tls_out.tlsa_usage |= 1<<usage;
+  }
+
+if (found)
+  return OK;
+
+log_write(0, LOG_MAIN, "DANE error: No usable TLSA records");
+return DEFER;
+}
+#endif /*EXPERIMENTAL_DANE*/
+
+
 
 /*************************************************
 *    Start a TLS session in a client             *
@@ -1544,7 +2142,8 @@ Argument:
   fd               the fd of the connection
   host             connected host (for messages)
   addr             the first address
-  ob               smtp transport options
+  tb               transport (always smtp)
+  tlsa_dnsa        tlsa lookup, if DANE, else null
 
 Returns:           OK on success
                    FAIL otherwise - note that tls_error() will not give DEFER
@@ -1553,20 +2152,54 @@ Returns:           OK on success
 
 int
 tls_client_start(int fd, host_item *host, address_item *addr,
-  void *v_ob)
+  transport_instance *tb
+#ifdef EXPERIMENTAL_DANE
+  , dns_answer * tlsa_dnsa
+#endif
+  )
 {
-smtp_transport_options_block * ob = v_ob;
-static uschar txt[256];
-uschar *expciphers;
-X509* server_cert;
+smtp_transport_options_block * ob =
+  (smtp_transport_options_block *)tb->options_block;
+static uschar peerdn[256];
+uschar * expciphers;
 int rc;
 static uschar cipherbuf[256];
+
 #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;
+BOOL request_ocsp = FALSE;
+BOOL require_ocsp = FALSE;
+#endif
+
+#ifdef EXPERIMENTAL_DANE
+tls_out.tlsa_usage = 0;
+#endif
+
+#ifndef DISABLE_OCSP
+  {
+# ifdef EXPERIMENTAL_DANE
+  if (  tlsa_dnsa
+     && ob->hosts_request_ocsp[0] == '*'
+     && ob->hosts_request_ocsp[1] == '\0'
+     )
+    {
+    /* Unchanged from default.  Use a safer one under DANE */
+    request_ocsp = TRUE;
+    ob->hosts_request_ocsp = US"${if or { {= {0}{$tls_out_tlsa_usage}} "
+                                     "   {= {4}{$tls_out_tlsa_usage}} } "
+                                " {*}{}}";
+    }
+# endif
+
+  if ((require_ocsp =
+       verify_check_given_host(&ob->hosts_require_ocsp, host) == OK))
+    request_ocsp = TRUE;
+  else
+# ifdef EXPERIMENTAL_DANE
+    if (!request_ocsp)
+# endif
+      request_ocsp =
+       verify_check_given_host(&ob->hosts_request_ocsp, host) == OK;
+  }
 #endif
 
 rc = tls_init(&client_ctx, host, NULL,
@@ -1597,38 +2230,25 @@ if (expciphers != NULL)
     return tls_error(US"SSL_CTX_set_cipher_list", host, NULL);
   }
 
-/* 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))
+#ifdef EXPERIMENTAL_DANE
+if (tlsa_dnsa)
   {
-  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;
+  SSL_CTX_set_verify(client_ctx,
+    SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
+    verify_callback_client_dane);
+
+  if (!DANESSL_library_init())
+    return tls_error(US"library init", host, NULL);
+  if (DANESSL_CTX_init(client_ctx) <= 0)
+    return tls_error(US"context init", host, NULL);
+  }
+else
 
-#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)
+
+  if ((rc = tls_client_basic_ctx_init(client_ctx, host, ob, client_static_cbinfo))
+      != OK)
     return rc;
-  client_verify_optional = TRUE;
-  }
 
 if ((client_ssl = SSL_new(client_ctx)) == NULL)
   return tls_error(US"SSL_new", host, NULL);
@@ -1652,16 +2272,38 @@ if (ob->tls_sni)
     DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tls_out.sni);
     SSL_set_tlsext_host_name(client_ssl, tls_out.sni);
 #else
-    DEBUG(D_tls)
-      debug_printf("OpenSSL at build-time lacked SNI support, ignoring \"%s\"\n",
+    log_write(0, LOG_MAIN, "SNI unusable with this OpenSSL library version; ignoring \"%s\"\n",
           tls_out.sni);
 #endif
     }
   }
 
+#ifdef EXPERIMENTAL_DANE
+if (tlsa_dnsa)
+  if ((rc = dane_tlsa_load(client_ssl, host, tlsa_dnsa)) != OK)
+    return rc;
+#endif
+
 #ifndef DISABLE_OCSP
 /* Request certificate status at connection-time.  If the server
 does OCSP stapling we will get the callback (set in tls_init()) */
+# ifdef EXPERIMENTAL_DANE
+if (request_ocsp)
+  {
+  const uschar * s;
+  if (  ((s = ob->hosts_require_ocsp) && Ustrstr(s, US"tls_out_tlsa_usage"))
+     || ((s = ob->hosts_request_ocsp) && Ustrstr(s, US"tls_out_tlsa_usage"))
+     )
+    {  /* Re-eval now $tls_out_tlsa_usage is populated.  If
+       this means we avoid the OCSP request, we wasted the setup
+       cost in tls_init(). */
+    require_ocsp = verify_check_given_host(&ob->hosts_require_ocsp, host) == OK;
+    request_ocsp = require_ocsp
+      || verify_check_given_host(&ob->hosts_request_ocsp, host) == OK;
+    }
+  }
+# endif
+
 if (request_ocsp)
   {
   SSL_set_tlsext_status_type(client_ssl, TLSEXT_STATUSTYPE_ocsp);
@@ -1670,6 +2312,10 @@ if (request_ocsp)
   }
 #endif
 
+#ifndef DISABLE_EVENT
+client_static_cbinfo->event_action = tb->event_action;
+#endif
+
 /* There doesn't seem to be a built-in timeout on connection. */
 
 DEBUG(D_tls) debug_printf("Calling SSL_connect\n");
@@ -1678,22 +2324,17 @@ alarm(ob->command_timeout);
 rc = SSL_connect(client_ssl);
 alarm(0);
 
+#ifdef EXPERIMENTAL_DANE
+if (tlsa_dnsa)
+  DANESSL_cleanup(client_ssl);
+#endif
+
 if (rc <= 0)
   return tls_error(US"SSL_connect", host, sigalrm_seen ? US"timed out" : NULL);
 
 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;                /*XXX a static buffer... */
-  }
-else
-  tls_out.peerdn = NULL;
+peer_cert(client_ssl, &tls_out, peerdn, sizeof(peerdn));
 
 construct_cipher_name(client_ssl, cipherbuf, sizeof(cipherbuf), &tls_out.bits);
 tls_out.cipher = cipherbuf;
@@ -1719,14 +2360,14 @@ return OK;
 /* This gets the next byte from the TLS input buffer. If the buffer is empty,
 it refills the buffer via the SSL reading function.
 
-Arguments:  none
+Arguments:  lim                Maximum amount to read/buffer
 Returns:    the next character or EOF
 
 Only used by the server-side TLS.
 */
 
 int
-tls_getc(void)
+tls_getc(unsigned lim)
 {
 if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
   {
@@ -1737,7 +2378,8 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
     ssl_xfer_buffer, ssl_xfer_buffer_size);
 
   if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
-  inbytes = SSL_read(server_ssl, CS ssl_xfer_buffer, ssl_xfer_buffer_size);
+  inbytes = SSL_read(server_ssl, CS ssl_xfer_buffer,
+                   MIN(ssl_xfer_buffer_size, lim));
   error = SSL_get_error(server_ssl, inbytes);
   alarm(0);
 
@@ -1750,6 +2392,7 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
     DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n");
 
     receive_getc = smtp_getc;
+    receive_get_cache = smtp_get_cache;
     receive_ungetc = smtp_ungetc;
     receive_feof = smtp_feof;
     receive_ferror = smtp_ferror;
@@ -1763,7 +2406,7 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
     tls_in.peerdn = NULL;
     tls_in.sni = NULL;
 
-    return smtp_getc();
+    return smtp_getc(lim);
     }
 
   /* Handle genuine errors */
@@ -1795,6 +2438,16 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
 return ssl_xfer_buffer[ssl_xfer_buffer_lwm++];
 }
 
+void
+tls_get_cache()
+{
+#ifndef DISABLE_DKIM
+int n = ssl_xfer_buffer_hwm - ssl_xfer_buffer_lwm;
+if (n > 0)
+  dkim_exim_verify_feed(ssl_xfer_buffer+ssl_xfer_buffer_lwm, n);
+#endif
+}
+
 
 
 /*************************************************
@@ -1876,27 +2529,28 @@ while (left > 0)
   switch (error)
     {
     case SSL_ERROR_SSL:
-    ERR_error_string(ERR_get_error(), ssl_errstring);
-    log_write(0, LOG_MAIN, "TLS error (SSL_write): %s", ssl_errstring);
-    return -1;
+      ERR_error_string(ERR_get_error(), ssl_errstring);
+      log_write(0, LOG_MAIN, "TLS error (SSL_write): %s", ssl_errstring);
+      return -1;
 
     case SSL_ERROR_NONE:
-    left -= outbytes;
-    buff += outbytes;
-    break;
+      left -= outbytes;
+      buff += outbytes;
+      break;
 
     case SSL_ERROR_ZERO_RETURN:
-    log_write(0, LOG_MAIN, "SSL channel closed on write");
-    return -1;
+      log_write(0, LOG_MAIN, "SSL channel closed on write");
+      return -1;
 
     case SSL_ERROR_SYSCALL:
-    log_write(0, LOG_MAIN, "SSL_write: (from %s) syscall: %s",
-      sender_fullhost ? sender_fullhost : US"<unknown>",
-      strerror(errno));
+      log_write(0, LOG_MAIN, "SSL_write: (from %s) syscall: %s",
+       sender_fullhost ? sender_fullhost : US"<unknown>",
+       strerror(errno));
+      return -1;
 
     default:
-    log_write(0, LOG_MAIN, "SSL_write error %d", error);
-    return -1;
+      log_write(0, LOG_MAIN, "SSL_write error %d", error);
+      return -1;
     }
   }
 return len;
@@ -2107,8 +2761,13 @@ i = (i + 7) / 8;
 if (i < needed_len)
   needed_len = i;
 
+#ifdef EXIM_HAVE_RAND_PSEUDO
 /* We do not care if crypto-strong */
 i = RAND_pseudo_bytes(smallbuf, needed_len);
+#else
+i = RAND_bytes(smallbuf, needed_len);
+#endif
+
 if (i < 0)
   {
   DEBUG(D_all)
@@ -2302,6 +2961,9 @@ result = 0L;
 #ifdef SSL_OP_NO_SSLv2
 result |= SSL_OP_NO_SSLv2;
 #endif
+#ifdef SSL_OP_SINGLE_DH_USE
+result |= SSL_OP_SINGLE_DH_USE;
+#endif
 
 if (option_spec == NULL)
   {
@@ -2325,6 +2987,7 @@ for (s=option_spec; *s != '\0'; /**/)
   keep_c = *end;
   *end = '\0';
   item_parsed = tls_openssl_one_option_parse(s, &item);
+  *end = keep_c;
   if (!item_parsed)
     {
     DEBUG(D_tls) debug_printf("openssl option setting unrecognised: \"%s\"\n", s);
@@ -2336,7 +2999,6 @@ for (s=option_spec; *s != '\0'; /**/)
     result |= item;
   else
     result &= ~item;
-  *end = keep_c;
   s = end;
   }
 
index f2ab567..f34cf63 100644 (file)
--- a/src/tls.c
+++ b/src/tls.c
@@ -2,13 +2,13 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* This module provides TLS (aka SSL) support for Exim. The code for OpenSSL is
 based on a patch that was originally contributed by Steve Haslam. It was
 adapted from stunnel, a GPL program by Michal Trojnara. The code for GNU TLS is
-based on a patch contributed by Nikos Mavroyanopoulos. Because these packages
+based on a patch contributed by Nikos Mavrogiannopoulos. Because these packages
 are so very different, the functions for each are kept in separate files. The
 relevant file is #included as required, after any any common functions.
 
@@ -80,23 +80,46 @@ return TRUE;
 }
 
 
+/*************************************************
+*        Timezone environment flipping           *
+*************************************************/
+
+static uschar *
+to_tz(uschar * tz)
+{
+uschar * old = US getenv("TZ");
+(void) setenv("TZ", CCS tz, 1);
+tzset();
+return old;
+}
+
+static void
+restore_tz(uschar * tz)
+{
+if (tz)
+  (void) setenv("TZ", CCS tz, 1);
+else
+  (void) os_unsetenv(US"TZ");
+tzset();
+}
+
 /*************************************************
 *        Many functions are package-specific     *
 *************************************************/
 
 #ifdef USE_GNUTLS
-#include "tls-gnu.c"
-#include "tlscert-gnu.c"
+# 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)
-#define ssl_xfer_buffer_hwm (state_server.xfer_buffer_hwm)
-#define ssl_xfer_eof (state_server.xfer_eof)
-#define ssl_xfer_error (state_server.xfer_error)
+# define ssl_xfer_buffer (state_server.xfer_buffer)
+# define ssl_xfer_buffer_lwm (state_server.xfer_buffer_lwm)
+# define ssl_xfer_buffer_hwm (state_server.xfer_buffer_hwm)
+# define ssl_xfer_eof (state_server.xfer_eof)
+# define ssl_xfer_error (state_server.xfer_error)
 
 #else
-#include "tls-openssl.c"
-#include "tlscert-openssl.c"
+# include "tls-openssl.c"
+# include "tlscert-openssl.c"
 #endif
 
 
@@ -224,7 +247,7 @@ NOTE: We modify the supplied dn string during operation.
 
 Arguments:
        dn      Distinguished Name string
-       mod     string containing optional list-sep and
+       mod     list containing optional output list-sep and
                field selector match, comma-separated
 Return:
        allocated string with list of matching fields,
@@ -232,7 +255,7 @@ Return:
 */
 
 uschar *
-tls_field_from_dn(uschar * dn, uschar * mod)
+tls_field_from_dn(uschar * dn, const uschar * mod)
 {
 int insep = ',';
 uschar outsep = '\n';
@@ -245,19 +268,20 @@ 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 */
+    outsep = ele[1];   /* nondefault output 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] == '=')
+len = match ? Ustrlen(match) : -1;
+while ((ele = string_nextinlist(CUSS &dn, &insep, NULL, 0)))
+  if (  !match
+     || 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.
@@ -290,7 +314,7 @@ Returns:
 */
 
 BOOL
-tls_is_name_for_cert(uschar * namelist, void * cert)
+tls_is_name_for_cert(const uschar * namelist, void * cert)
 {
 uschar * altnames = tls_cert_subject_altname(cert, US"dns");
 uschar * subjdn;
@@ -303,7 +327,7 @@ 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;
+    const uschar * an = altnames;
     while ((certname = string_nextinlist(&an, &alt_sep, NULL, 0)))
       if (is_name_match(cmpname, certname))
        return TRUE;
@@ -317,7 +341,7 @@ else if ((subjdn = tls_cert_subject(cert, NULL)))
   dn_to_list(subjdn);
   while ((cmpname = string_nextinlist(&namelist, &cmp_sep, NULL, 0)))
     {
-    uschar * sn = subjdn;
+    const uschar * sn = subjdn;
     while ((certname = string_nextinlist(&sn, &sn_sep, NULL, 0)))
       if (  *certname++ == 'C'
         && *certname++ == 'N'
@@ -329,7 +353,6 @@ else if ((subjdn = tls_cert_subject(cert, NULL)))
   }
 return FALSE;
 }
-# endif        /*EXPERIMENTAL_CERTNAMES*/
 #endif /*SUPPORT_TLS*/
 
 /* vi: aw ai sw=2
index 3261c4e..296398a 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) Jeremy Harris 2014 */
+/* Copyright (c) Jeremy Harris 2014 - 2015 */
 
 /* This file provides TLS/SSL support for Exim using the GnuTLS library,
 one of the available supported implementations.  This file is #included into
@@ -27,7 +27,7 @@ tls_export_cert(uschar * buf, size_t buflen, void * cert)
 size_t sz = buflen;
 void * reset_point = store_get(0);
 int fail;
-uschar * cp;
+const uschar * cp;
 
 if ((fail = gnutls_x509_crt_export((gnutls_x509_crt_t)cert,
     GNUTLS_X509_FMT_PEM, buf, &sz)))
@@ -51,10 +51,14 @@ tls_import_cert(const uschar * buf, void ** cert)
 {
 void * reset_point = store_get(0);
 gnutls_datum_t datum;
-gnutls_x509_crt_t crt;
+gnutls_x509_crt_t crt = *(gnutls_x509_crt_t *)cert;
 int fail = 0;
 
-gnutls_global_init();
+if (crt)
+  gnutls_x509_crt_deinit(crt);
+else
+  gnutls_global_init();
+
 gnutls_x509_crt_init(&crt);
 
 datum.data = string_unprinting(US buf);
@@ -73,10 +77,15 @@ return fail;
 }
 
 void
-tls_free_cert(void * cert)
+tls_free_cert(void ** cert)
 {
-gnutls_x509_crt_deinit((gnutls_x509_crt_t) cert);
-gnutls_global_deinit();
+gnutls_x509_crt_t crt = *(gnutls_x509_crt_t *)cert;
+if (crt)
+  {
+  gnutls_x509_crt_deinit(crt);
+  gnutls_global_deinit();
+  *cert = NULL;
+  }
 }
 
 /*****************************************************
@@ -98,15 +107,20 @@ static uschar *
 time_copy(time_t t, uschar * mod)
 {
 uschar * cp;
-struct tm * tp;
-size_t len;
+size_t len = 32;
 
 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);
+cp = store_get(len);
+if (timestamps_utc)
+  {
+  uschar * tz = to_tz(US"GMT0");
+  len = strftime(CS cp, len, "%b %e %T %Y %Z", gmtime(&t));
+  restore_tz(tz);
+  }
+else
+  len = strftime(CS cp, len, "%b %e %T %Y %Z", localtime(&t));
 return len > 0 ? cp : NULL;
 }
 
@@ -128,12 +142,12 @@ uschar * cp = NULL;
 int ret;
 size_t siz = 0;
 
-if ((ret = gnutls_x509_crt_get_issuer_dn(cert, cp, &siz))
+if ((ret = gnutls_x509_crt_get_issuer_dn(cert, CS 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)
+if ((ret = gnutls_x509_crt_get_issuer_dn(cert, CS cp, &siz)) < 0)
   return g_err("gi1", __FUNCTION__, ret);
 
 return mod ? tls_field_from_dn(cp, mod) : cp;
@@ -169,7 +183,7 @@ if ((ret = gnutls_x509_crt_get_serial((gnutls_x509_crt_t)cert,
   return g_err("gs0", __FUNCTION__, ret);
 
 for(dp = txt, sp = bin; sz; dp += 2, sp++, sz--)
-  sprintf(dp, "%.2x", *sp);
+  sprintf(CS dp, "%.2x", *sp);
 for(sp = txt; sp[0]=='0' && sp[1]; ) sp++;     /* leading zeroes */
 return string_copy(sp);
 }
@@ -177,22 +191,22 @@ return string_copy(sp);
 uschar *
 tls_cert_signature(void * cert, uschar * mod)
 {
-uschar * cp1;
+uschar * cp1 = NULL;
 uschar * cp2;
 uschar * cp3;
 size_t len = 0;
 int ret;
 
-if ((ret = gnutls_x509_crt_get_signature((gnutls_x509_crt_t)cert, cp1, &len))
+if ((ret = gnutls_x509_crt_get_signature((gnutls_x509_crt_t)cert, CS 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)
+if (gnutls_x509_crt_get_signature((gnutls_x509_crt_t)cert, CS cp1, &len) != 0)
   return g_err("gs1", __FUNCTION__, ret);
 
 for(cp3 = cp2 = cp1+len; cp1 < cp2; cp3 += 3, cp1++)
-  sprintf(cp3, "%.2x ", *cp1);
+  sprintf(CS cp3, "%.2x ", *cp1);
 cp3[-1]= '\0';
 
 return cp2;
@@ -203,7 +217,7 @@ 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));
+return algo < 0 ? NULL : string_copy(US gnutls_sign_get_name(algo));
 }
 
 uschar *
@@ -213,12 +227,12 @@ uschar * cp = NULL;
 int ret;
 size_t siz = 0;
 
-if ((ret = gnutls_x509_crt_get_dn(cert, cp, &siz))
+if ((ret = gnutls_x509_crt_get_dn(cert, CS 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)
+if ((ret = gnutls_x509_crt_get_dn(cert, CS cp, &siz)) < 0)
   return g_err("gs1", __FUNCTION__, ret);
 
 return mod ? tls_field_from_dn(cp, mod) : cp;
@@ -241,14 +255,14 @@ unsigned int crit;
 int ret;
 
 ret = gnutls_x509_crt_get_extension_by_oid ((gnutls_x509_crt_t)cert,
-  oid, idx, cp1, &siz, &crit);
+  oid, idx, CS 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);
+  oid, idx, CS cp1, &siz, &crit);
 if (ret < 0)
   return g_err("ge1", __FUNCTION__, ret);
 
@@ -256,7 +270,7 @@ if (ret < 0)
 
 /* just dump for now */
 for(cp3 = cp2 = cp1+siz; cp1 < cp2; cp3 += 3, cp1++)
-  sprintf(cp3, "%.2x ", *cp1);
+  sprintf(CS cp3, "%.2x ", *cp1);
 cp3[-1]= '\0';
 
 return cp2;
@@ -314,11 +328,11 @@ for(index = 0;; index++)
   switch (ret)
     {
     case GNUTLS_SAN_DNSNAME:    tag = US"DNS";  break;
-    case GNUTLS_SAN_URI:        tag = US"URI";  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, 
+  list = string_append_listele(list, sep,
           match == -1 ? string_sprintf("%s=%s", tag, ele) : ele);
   }
 /*NOTREACHED*/
@@ -354,7 +368,7 @@ for(index = 0;; index++)
 
 #else
 
-expand_string_message = 
+expand_string_message =
   string_sprintf("%s: OCSP support with GnuTLS requires version 3.0.0\n",
     __FUNCTION__);
 return NULL;
@@ -404,6 +418,28 @@ for(index = 0;; index++)
 /*****************************************************
 *  Certificate operator routines
 *****************************************************/
+uschar *
+tls_cert_der_b64(void * cert)
+{
+size_t len = 0;
+uschar * cp = NULL;
+int fail;
+
+if (  (fail = gnutls_x509_crt_export((gnutls_x509_crt_t)cert,
+       GNUTLS_X509_FMT_DER, cp, &len)) != GNUTLS_E_SHORT_MEMORY_BUFFER
+   || !(cp = store_get((int)len))
+   || (fail = gnutls_x509_crt_export((gnutls_x509_crt_t)cert,
+        GNUTLS_X509_FMT_DER, cp, &len))
+   )
+  {
+  log_write(0, LOG_MAIN, "TLS error in certificate export: %s",
+    gnutls_strerror(fail));
+  return NULL;
+  }
+return b64encode(cp, (int)len);
+}
+
+
 static uschar *
 fingerprint(gnutls_x509_crt_t cert, gnutls_digest_algorithm_t algo)
 {
@@ -422,7 +458,7 @@ 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);
+  sprintf(CS cp3, "%02X",*cp);
 return cp2;
 }
 
index a57980d..690f950 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) Jeremy Harris 2014 */
+/* Copyright (c) Jeremy Harris 2014 - 2016 */
 
 /* 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.
@@ -17,6 +17,10 @@ library. It is #included into the tls.c file when that library is used.
 #include <openssl/rand.h>
 #include <openssl/x509v3.h>
 
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+# define EXIM_HAVE_ASN1_MACROS
+#endif
+
 
 /*****************************************************
 *  Export/import a certificate, binary/printable
@@ -55,9 +59,11 @@ 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;
+X509 * x = *(X509 **)cert;
 int fail = 0;
 
+if (x) X509_free(x);
+
 bp = BIO_new_mem_buf(US cp, -1);
 if (!(x = PEM_read_bio_X509(bp, NULL, 0, NULL)))
   {
@@ -73,9 +79,14 @@ return fail;
 }
 
 void
-tls_free_cert(void * cert)
+tls_free_cert(void ** cert)
 {
-X509_free((X509 *)cert);
+X509 * x = *(X509 **)cert;
+if (x)
+  {
+  X509_free(x);
+  *cert = NULL;
+  }
 }
 
 
@@ -103,30 +114,65 @@ 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)
+asn1_time_copy(const ASN1_TIME * asntime, uschar * mod)
 {
+uschar * s = NULL;
 BIO * bp = BIO_new(BIO_s_mem());
 int len;
 
-if (!bp) return badalloc();
+if (!bp)
+  return badalloc();
+len = ASN1_TIME_print(bp, asntime);
+len = len > 0 ? (int) BIO_get_mem_data(bp, CSS &s) : 0;
 
-len = ASN1_TIME_print(bp, time);
-return mod &&  Ustrcmp(mod, "int") == 0
-  ? bio_string_time_to_int(bp, len)
-  : bio_string_copy(bp, len);
+if (mod && Ustrcmp(mod, "raw") == 0)           /* native ASN */
+  s = string_copyn(s, len);
+else
+  {
+  struct tm tm;
+  struct tm * tm_p = &tm;
+  BOOL mod_tz = TRUE;
+  uschar * tz = to_tz(US"GMT0");    /* need to call strptime with baseline TZ */
+
+  /* Parse OpenSSL ASN1_TIME_print output.  A shame there seems to
+  be no other interface for the times.
+  */
+
+  /*XXX %Z might be glibc-specific?  Solaris has it, at least*/
+  /*XXX should we switch to POSIX locale for this? */
+  tm.tm_isdst = 0;
+  if (!len || !strptime(CCS s, "%b %e %T %Y %Z", &tm))
+    expand_string_message = US"failed time conversion";
+
+  else
+    {
+    time_t t = mktime(&tm);    /* make the tm self-consistent */
+
+    if (mod && Ustrcmp(mod, "int") == 0)       /* seconds since epoch */
+      s = string_sprintf("%u", t);
+
+    else
+      {
+      if (!timestamps_utc)     /* decoded string in local TZ */
+       {                               /* shift to local TZ */
+       restore_tz(tz);
+       mod_tz = FALSE;
+       tm_p = localtime(&t);
+       }
+      /* "utc" is default, and rfc5280 says cert times should be Zulu */
+
+      /* convert to string in our format */
+      len = 32;
+      s = store_get(len);
+      strftime(CS s, (size_t)len, "%b %e %T %Y %z", tm_p);
+      }
+    }
+
+  if (mod_tz)
+    restore_tz(tz);
+  }
+BIO_free(bp);
+return s;
 }
 
 static uschar *
@@ -199,9 +245,9 @@ 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_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)
   {
@@ -225,10 +271,10 @@ 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_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_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);
@@ -272,9 +318,13 @@ uschar * cp3;
 
 if (!bp) return badalloc();
 
+#ifdef EXIM_HAVE_ASN1_MACROS
+ASN1_STRING_print(bp, adata);
+#else
 M_ASN1_OCTET_STRING_print(bp, adata);
-/* binary data, DER encoded */
+#endif
 
+/* binary data, DER encoded */
 /* just dump for now */
 len = BIO_get_mem_data(bp, &cp1);
 cp3 = cp2 = store_get(len*3+1);
@@ -296,7 +346,7 @@ 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 osep = '\n';
 uschar * tag = US"";
 uschar * ele;
 int match = -1;
@@ -304,16 +354,15 @@ int len;
 
 if (!san) return NULL;
 
-while (mod)
+while (mod && *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 == '>' && *++mod) osep = *mod++;
+  else if (Ustrncmp(mod,"dns",3)==0) { match = GEN_DNS; mod += 3; }
+  else if (Ustrncmp(mod,"uri",3)==0) { match = GEN_URI; mod += 3; }
+  else if (Ustrncmp(mod,"mail",4)==0) { match = GEN_EMAIL; mod += 4; }
+  else mod++;
 
-  if (*mod++ != ',')
-    break;
+  if (*mod == ',') mod++;
   }
 
 while (sk_GENERAL_NAME_num(san) > 0)
@@ -344,8 +393,8 @@ while (sk_GENERAL_NAME_num(san) > 0)
   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,
+  if (Ustrlen(ele) == len)     /* ignore any with embedded nul */
+    list = string_append_listele(list, osep,
          match == -1 ? string_sprintf("%s=%s", tag, ele) : ele);
   }
 
@@ -371,9 +420,13 @@ 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));
+    {
+    uschar * ele = ASN1_STRING_data(ad->location->d.ia5);
+    int len =  ASN1_STRING_length(ad->location->d.ia5);
+    list = string_append_listele_n(list, sep, ele, len);
+    }
   }
+sk_ACCESS_DESCRIPTION_free(ads);
 return list;
 }
 
@@ -404,9 +457,13 @@ if (dps) for (i = 0; i < dpsnum; i++)
       if (  (np = sk_GENERAL_NAME_value(names, j))
         && np->type == GEN_URI
         )
-       list = string_append_listele(list, sep,
-               ASN1_STRING_data(np->d.uniformResourceIdentifier));
+       {
+       uschar * ele = ASN1_STRING_data(np->d.uniformResourceIdentifier);
+       int len =  ASN1_STRING_length(np->d.uniformResourceIdentifier);
+       list = string_append_listele_n(list, sep, ele, len);
+       }
     }
+sk_DIST_POINT_free(dps);
 return list;
 }
 
@@ -415,6 +472,26 @@ return list;
 /*****************************************************
 *  Certificate operator routines
 *****************************************************/
+uschar *
+tls_cert_der_b64(void * cert)
+{
+BIO * bp = BIO_new(BIO_s_mem());
+uschar * cp = NULL;
+
+if (!i2d_X509_bio(bp, (X509 *)cert))
+  log_write(0, LOG_MAIN, "TLS error in certificate export: %s",
+    ERR_error_string(ERR_get_error(), NULL));
+else
+  {
+  long len = BIO_get_mem_data(bp, &cp);
+  cp = b64encode(cp, (int)len);
+  }
+
+BIO_free(bp);
+return cp;
+}
+
+
 static uschar *
 fingerprint(X509 * cert, const EVP_MD * fdig)
 {
@@ -433,19 +510,19 @@ for (j = 0; j < (int)n; j++) sprintf(CS cp+2*j, "%02X", md[j]);
 return(cp);
 }
 
-uschar * 
+uschar *
 tls_cert_fprt_md5(void * cert)
 {
 return fingerprint((X509 *)cert, EVP_md5());
 }
 
-uschar * 
+uschar *
 tls_cert_fprt_sha1(void * cert)
 {
 return fingerprint((X509 *)cert, EVP_sha1());
 }
 
-uschar * 
+uschar *
 tls_cert_fprt_sha256(void * cert)
 {
 return fingerprint((X509 *)cert, EVP_sha256());
index 0297e37..5f451ba 100644 (file)
--- a/src/tod.c
+++ b/src/tod.c
@@ -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. */
 
 /* A function for returning the time of day in various formats */
@@ -60,7 +60,7 @@ if (type == tod_epoch_l)
   struct timeval tv;
   gettimeofday(&tv, NULL);
   /* Unix epoch/usec format */
-  (void) sprintf(CS timebuf, "%ld%06ld", tv.tv_sec, (long) tv.tv_usec );
+  (void) sprintf(CS timebuf, TIME_T_FMT "%06ld", tv.tv_sec, (long) tv.tv_usec );
   return timebuf;
   }
 
index 92a3b9d..3f250e6 100644 (file)
@@ -1,4 +1,4 @@
-#! PERL_COMMAND -w
+#! PERL_COMMAND
 
 # This is a Perl script to demonstrate the possibilities of on-the-fly
 # delivery filtering in Exim. It is presented with a message on its standard
@@ -11,6 +11,8 @@
 # Philip Hazel, May 1997
 #############################################################################
 
+use warnings;
+BEGIN { pop @INC if $INC[-1] eq '.' };
 
 # If the filter is called with any arguments, insert them into the message
 # as X-Arg headers, just to verify what they are.
index 3648bfc..b8a4636 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* General functions concerned with transportation, and generic options for all
@@ -66,6 +66,10 @@ optionlist optionlist_transports[] = {
                  (void *)offsetof(transport_instance, driver_name) },
   { "envelope_to_add",   opt_bool|opt_public,
                  (void *)(offsetof(transport_instance, envelope_to_add)) },
+#ifndef DISABLE_EVENT
+  { "event_action",     opt_stringptr | opt_public,
+                 (void *)offsetof(transport_instance, event_action) },
+#endif
   { "group",             opt_expand_gid|opt_public,
                  (void *)offsetof(transport_instance, gid) },
   { "headers_add",      opt_stringptr|opt_public|opt_rep_str,
@@ -80,6 +84,8 @@ optionlist optionlist_transports[] = {
                  (void *)offsetof(transport_instance, home_dir) },
   { "initgroups",       opt_bool|opt_public,
                  (void *)offsetof(transport_instance, initgroups) },
+  { "max_parallel",     opt_stringptr|opt_public,
+                 (void *)offsetof(transport_instance, max_parallel) },
   { "message_size_limit", opt_stringptr|opt_public,
                  (void *)offsetof(transport_instance, message_size_limit) },
   { "rcpt_include_affixes", opt_bool|opt_public,
@@ -94,10 +100,6 @@ optionlist optionlist_transports[] = {
                  (void *)offsetof(transport_instance, shadow_condition) },
   { "shadow_transport", opt_stringptr|opt_public,
                  (void *)offsetof(transport_instance, shadow) },
-#ifdef EXPERIMENTAL_TPDA
-  { "tpda_delivery_action",opt_stringptr | opt_public,
-                 (void *)offsetof(transport_instance, tpda_delivery_action) },
-#endif
   { "transport_filter", opt_stringptr|opt_public,
                  (void *)offsetof(transport_instance, filter_command) },
   { "transport_filter_timeout", opt_time|opt_public,
@@ -106,9 +108,22 @@ optionlist optionlist_transports[] = {
                  (void *)offsetof(transport_instance, uid) }
 };
 
-int optionlist_transports_size =
-  sizeof(optionlist_transports)/sizeof(optionlist);
+int optionlist_transports_size = nelem(optionlist_transports);
+
+
+void
+readconf_options_transports(void)
+{
+struct transport_info * ti;
+
+readconf_options_from_list(optionlist_transports, nelem(optionlist_transports), US"TRANSPORTS", NULL);
 
+for (ti = transports_available; ti->driver_name[0]; ti++)
+  {
+  macro_create(string_sprintf("_DRIVER_TRANSPORT_%T", ti->driver_name), US"y", FALSE, TRUE);
+  readconf_options_from_list(ti->options, (unsigned)*ti->options_count, US"TRANSPORT", ti->driver_name);
+  }
+}
 
 /*************************************************
 *             Initialize transport list           *
@@ -137,14 +152,11 @@ readconf_driver_init(US"transport",
 /* Now scan the configured transports and check inconsistencies. A shadow
 transport is permitted only for local transports. */
 
-for (t = transports; t != NULL; t = t->next)
+for (t = transports; t; t = t->next)
   {
-  if (!t->info->local)
-    {
-    if (t->shadow != NULL)
-      log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
-        "shadow transport not allowed on non-local transport %s", t->name);
-    }
+  if (!t->info->local && t->shadow)
+    log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
+      "shadow transport not allowed on non-local transport %s", t->name);
 
   if (t->body_only && t->headers_only)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
@@ -234,10 +246,12 @@ for (i = 0; i < 100; i++)
   else
     {
     alarm(local_timeout);
-    #ifdef SUPPORT_TLS
-    if (tls_out.active == fd) rc = tls_write(FALSE, block, len); else
-    #endif
-    rc = write(fd, block, len);
+#ifdef SUPPORT_TLS
+    if (tls_out.active == fd)
+      rc = tls_write(FALSE, block, len);
+    else
+#endif
+      rc = write(fd, block, len);
     save_errno = errno;
     local_timeout = alarm(0);
     if (sigalrm_seen)
@@ -355,7 +369,7 @@ Arguments:
   fd         file descript to write to
   chunk      pointer to data to write
   len        length of data to write
-  usr_crlf   TRUE if CR LF is wanted at the end of each line
+  tctx       transport context - processing to be done during output
 
 In addition, the static nl_xxx variables must be set as required.
 
@@ -363,11 +377,11 @@ Returns:     TRUE on success, FALSE on failure (with errno preserved)
 */
 
 static BOOL
-write_chunk(int fd, uschar *chunk, int len, BOOL use_crlf)
+write_chunk(int fd, transport_ctx * tctx, uschar *chunk, int len)
 {
 uschar *start = chunk;
 uschar *end = chunk + len;
-register uschar *ptr;
+uschar *ptr;
 int mlen = DELIVER_OUT_BUFFER_SIZE - nl_escape_length - 2;
 
 /* The assumption is made that the check string will never stretch over move
@@ -406,17 +420,30 @@ possible. */
 
 for (ptr = start; ptr < end; ptr++)
   {
-  register int ch;
+  int ch, len;
 
   /* Flush the buffer if it has reached the threshold - we want to leave enough
   room for the next uschar, plus a possible extra CR for an LF, plus the escape
   string. */
 
-  if (chunk_ptr - deliver_out_buffer > mlen)
+  if ((len = chunk_ptr - deliver_out_buffer) > mlen)
     {
-    if (!transport_write_block(fd, deliver_out_buffer,
-          chunk_ptr - deliver_out_buffer))
-      return FALSE;
+    DEBUG(D_transport) debug_printf("flushing headers buffer\n");
+
+    /* If CHUNKING, prefix with BDAT (size) NON-LAST.  Also, reap responses
+    from previous SMTP commands. */
+
+    if (tctx &&  tctx->options & topt_use_bdat  &&  tctx->chunk_cb)
+      {
+      if (  tctx->chunk_cb(fd, tctx, (unsigned)len, 0) != OK
+        || !transport_write_block(fd, deliver_out_buffer, len)
+        || tctx->chunk_cb(fd, tctx, 0, tc_reap_prev) != OK
+        )
+       return FALSE;
+      }
+    else
+      if (!transport_write_block(fd, deliver_out_buffer, len))
+       return FALSE;
     chunk_ptr = deliver_out_buffer;
     }
 
@@ -426,7 +453,7 @@ for (ptr = start; ptr < end; ptr++)
 
     /* Insert CR before NL if required */
 
-    if (use_crlf) *chunk_ptr++ = '\r';
+    if (tctx  &&  tctx->options & topt_use_crlf) *chunk_ptr++ = '\r';
     *chunk_ptr++ = '\n';
     transport_newlines++;
 
@@ -543,14 +570,14 @@ Arguments:
   pdlist    address of anchor of the list of processed addresses
   first     TRUE if this is the first address; set it FALSE afterwards
   fd        the file descriptor to write to
-  use_crlf  to be passed on to write_chunk()
+  tctx      transport context - processing to be done during output
 
 Returns:    FALSE if writing failed
 */
 
 static BOOL
 write_env_to(address_item *p, struct aci **pplist, struct aci **pdlist,
-  BOOL *first, int fd, BOOL use_crlf)
+  BOOL *first, int fd, transport_ctx * tctx)
 {
 address_item *pp;
 struct aci *ppp;
@@ -558,8 +585,7 @@ struct aci *ppp;
 /* Do nothing if we have already handled this address. If not, remember it
 so that we don't handle it again. */
 
-for (ppp = *pdlist; ppp != NULL; ppp = ppp->next)
-  { if (p == ppp->ptr) return TRUE; }
+for (ppp = *pdlist; ppp; ppp = ppp->next) if (p == ppp->ptr) return TRUE;
 
 ppp = store_get(sizeof(struct aci));
 ppp->next = *pdlist;
@@ -571,19 +597,17 @@ ppp->ptr = p;
 for (pp = p;; pp = pp->parent)
   {
   address_item *dup;
-  for (dup = addr_duplicate; dup != NULL; dup = dup->next)
-    {
-    if (dup->dupof != pp) continue;   /* Not a dup of our address */
-    if (!write_env_to(dup, pplist, pdlist, first, fd, use_crlf)) return FALSE;
-    }
-  if (pp->parent == NULL) break;
+  for (dup = addr_duplicate; dup; dup = dup->next)
+    if (dup->dupof == pp)   /* a dup of our address */
+      if (!write_env_to(dup, pplist, pdlist, first, fd, tctx))
+       return FALSE;
+  if (!pp->parent) break;
   }
 
 /* Check to see if we have already output the progenitor. */
 
-for (ppp = *pplist; ppp != NULL; ppp = ppp->next)
-  { if (pp == ppp->ptr) break; }
-if (ppp != NULL) return TRUE;
+for (ppp = *pplist; ppp; ppp = ppp->next) if (pp == ppp->ptr) break;
+if (ppp) return TRUE;
 
 /* Remember what we have output, and output it. */
 
@@ -592,15 +616,15 @@ ppp->next = *pplist;
 *pplist = ppp;
 ppp->ptr = pp;
 
-if (!(*first) && !write_chunk(fd, US",\n ", 3, use_crlf)) return FALSE;
+if (!*first && !write_chunk(fd, tctx, US",\n ", 3)) return FALSE;
 *first = FALSE;
-return write_chunk(fd, pp->address, Ustrlen(pp->address), use_crlf);
+return write_chunk(fd, tctx, pp->address, Ustrlen(pp->address));
 }
 
 
 
 
-/* Add/remove/rewwrite headers, and send them plus the empty-line sparator.
+/* Add/remove/rewrite headers, and send them plus the empty-line separator.
 
 Globals:
   header_list
@@ -609,42 +633,40 @@ 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
+  tctx                  transport context
+  sendfn               function for output (transport or verify)
 
 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)
+transport_headers_send(int fd, transport_ctx * tctx,
+  BOOL (*sendfn)(int fd, transport_ctx * tctx, uschar * s, int len))
 {
 header_line *h;
+const uschar *list;
+transport_instance * tblock = tctx ? tctx->tblock : NULL;
+address_item * addr = tctx ? tctx->addr : NULL;
 
 /* 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. */
+Then check addr->prop.remove_headers too, provided that addr is not NULL. */
 
-for (h = header_list; h != NULL; h = h->next) if (h->type != htype_old)
+for (h = header_list; h; 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 */
+  list = tblock ? tblock->remove_headers : NULL;
+  for (i = 0; i < 2; i++)    /* For remove_headers && addr->prop.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))))
+      while ((s = string_nextinlist(&list, &sep, NULL, 0)))
        {
        int len;
 
@@ -654,15 +676,15 @@ for (h = header_list; h != NULL; h = h->next) if (h->type != htype_old)
            errno = ERRNO_CHHEADER_FAIL;
            return FALSE;
            }
-       len = Ustrlen(s);
+       len = s ? Ustrlen(s) : 0;
        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 (s) { include_header = FALSE; break; }
       }
-    if (addr != NULL) list = addr->p.remove_headers;
+    if (addr) list = addr->prop.remove_headers;
     }
 
   /* If this header is to be output, try to rewrite it if there are rewriting
@@ -670,14 +692,15 @@ for (h = header_list; h != NULL; h = h->next) if (h->type != htype_old)
 
   if (include_header)
     {
-    if (rewrite_rules)
+    if (tblock && tblock->rewrite_rules)
       {
       void *reset_point = store_get(0);
       header_line *hh;
 
-      if ((hh = rewrite_header(h, NULL, NULL, rewrite_rules, rewrite_existflags, FALSE)))
+      if ((hh = rewrite_header(h, NULL, NULL, tblock->rewrite_rules,
+                 tblock->rewrite_existflags, FALSE)))
        {
-       if (!sendfn(fd, hh->text, hh->slen, use_crlf)) return FALSE;
+       if (!sendfn(fd, tctx, hh->text, hh->slen)) return FALSE;
        store_reset(reset_point);
        continue;     /* With the next header line */
        }
@@ -685,7 +708,7 @@ for (h = header_list; h != NULL; h = h->next) if (h->type != htype_old)
 
     /* Either no rewriting rules, or it didn't get rewritten */
 
-    if (!sendfn(fd, h->text, h->slen, use_crlf)) return FALSE;
+    if (!sendfn(fd, tctx, h->text, h->slen)) return FALSE;
     }
 
   /* Header removed */
@@ -710,23 +733,21 @@ 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 *hprev = addr->prop.extra_headers;
   header_line *hnext;
   for (i = 0; i < 2; i++)
-    {
-    for (h = hprev, hprev = NULL; h != NULL; h = hnext)
+    for (h = hprev, hprev = NULL; h; h = hnext)
       {
       hnext = h->next;
       h->next = hprev;
       hprev = h;
       if (i == 1)
        {
-       if (!sendfn(fd, h->text, h->slen, use_crlf)) return FALSE;
+       if (!sendfn(fd, tctx, h->text, h->slen)) 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
@@ -736,24 +757,19 @@ 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)
+if (tblock && (list = CUS tblock->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
+  while ((s = string_nextinlist(&list, &sep, NULL, 0)))
+    if ((s = expand_string(s)))
       {
       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))
+       if (!sendfn(fd, tctx, s, len)) return FALSE;
+       if (s[len-1] != '\n' && !sendfn(fd, tctx, US"\n", 1))
          return FALSE;
        DEBUG(D_transport)
          {
@@ -763,11 +779,13 @@ if (add_headers)
          }
        }
       }
+    else if (!expand_string_forcedfail)
+      { errno = ERRNO_CHHEADER_FAIL; return FALSE; }
   }
 
 /* Separate headers from body with a blank line */
 
-return sendfn(fd, US"\n", 1, use_crlf);
+return sendfn(fd, tctx, US"\n", 1);
 }
 
 
@@ -800,30 +818,32 @@ can include timeouts for certain transports, which are requested by setting
 transport_write_timeout non-zero.
 
 Arguments:
-  addr                  (chain of) addresses (for extra headers), or NULL;
-                          only the first address is used
   fd                    file descriptor to write the message to
-  options               bit-wise options:
-    add_return_path       if TRUE, add a "return-path" header
-    add_envelope_to       if TRUE, add a "envelope-to" header
-    add_delivery_date     if TRUE, add a "delivery-date" header
-    use_crlf              if TRUE, turn NL into CR LF
-    end_dot               if TRUE, send a terminating "." line at the end
-    no_headers            if TRUE, omit the headers
-    no_body               if TRUE, omit the body
-  size_limit            if > 0, this is a limit to the size of message written;
-                          it is used when returning messages to their senders,
-                          and is approximate rather than exact, owing to chunk
-                          buffering
-  add_headers           a string containing one or more headers to add; it is
-                          expanded, and must be in correct RFC 822 format as
-                          it is transmitted verbatim; NULL => no additions,
-                          and so does empty string or forced expansion fail
-  remove_headers        a colon-separated list of headers to remove, or NULL
-  check_string          a string to check for at the start of lines, or NULL
-  escape_string         a string to insert in front of any check string
-  rewrite_rules         chain of header rewriting rules
-  rewrite_existflags    flags for the rewriting rules
+  tctx
+    addr                (chain of) addresses (for extra headers), or NULL;
+                          only the first address is used
+    tblock             optional transport instance block (NULL signifies NULL/0):
+      add_headers           a string containing one or more headers to add; it is
+                            expanded, and must be in correct RFC 822 format as
+                            it is transmitted verbatim; NULL => no additions,
+                            and so does empty string or forced expansion fail
+      remove_headers        a colon-separated list of headers to remove, or NULL
+      rewrite_rules         chain of header rewriting rules
+      rewrite_existflags    flags for the rewriting rules
+    options               bit-wise options:
+      add_return_path       if TRUE, add a "return-path" header
+      add_envelope_to       if TRUE, add a "envelope-to" header
+      add_delivery_date     if TRUE, add a "delivery-date" header
+      use_crlf              if TRUE, turn NL into CR LF
+      end_dot               if TRUE, send a terminating "." line at the end
+      no_headers            if TRUE, omit the headers
+      no_body               if TRUE, omit the body
+    check_string          a string to check for at the start of lines, or NULL
+    escape_string         a string to insert in front of any check string
+  size_limit              if > 0, this is a limit to the size of message written;
+                            it is used when returning messages to their senders,
+                            and is approximate rather than exact, owing to chunk
+                            buffering
 
 Returns:                TRUE on success; FALSE (with errno) on failure.
                         In addition, the global variable transport_count
@@ -831,13 +851,9 @@ Returns:                TRUE on success; FALSE (with errno) on failure.
 */
 
 static BOOL
-internal_transport_write_message(address_item *addr, int fd, int options,
-  int size_limit, uschar *add_headers, uschar *remove_headers, uschar *check_string,
-  uschar *escape_string, rewrite_rule *rewrite_rules, int rewrite_existflags)
+internal_transport_write_message(int fd, transport_ctx * tctx, int size_limit)
 {
-int written = 0;
 int len;
-BOOL use_crlf  = (options & topt_use_crlf)  != 0;
 
 /* Initialize pointer in output buffer. */
 
@@ -846,39 +862,41 @@ chunk_ptr = deliver_out_buffer;
 /* Set up the data for start-of-line data checking and escaping */
 
 nl_partial_match = -1;
-if (check_string != NULL && escape_string != NULL)
+if (tctx->check_string && tctx->escape_string)
   {
-  nl_check = check_string;
+  nl_check = tctx->check_string;
   nl_check_length = Ustrlen(nl_check);
-  nl_escape = escape_string;
+  nl_escape = tctx->escape_string;
   nl_escape_length = Ustrlen(nl_escape);
   }
-else nl_check_length = nl_escape_length = 0;
+else
+  nl_check_length = nl_escape_length = 0;
 
 /* Whether the escaping mechanism is applied to headers or not is controlled by
 an option (set for SMTP, not otherwise). Negate the length if not wanted till
 after the headers. */
 
-if ((options & topt_escape_headers) == 0) nl_check_length = -nl_check_length;
+if (!(tctx->options & topt_escape_headers))
+  nl_check_length = -nl_check_length;
 
 /* Write the headers if required, including any that have to be added. If there
 are header rewriting rules, apply them. */
 
-if ((options & topt_no_headers) == 0)
+if (!(tctx->options & topt_no_headers))
   {
   /* Add return-path: if requested. */
 
-  if ((options & topt_add_return_path) != 0)
+  if (tctx->options & topt_add_return_path)
     {
     uschar buffer[ADDRESS_MAXLENGTH + 20];
-    sprintf(CS buffer, "Return-path: <%.*s>\n", ADDRESS_MAXLENGTH,
+    int n = sprintf(CS buffer, "Return-path: <%.*s>\n", ADDRESS_MAXLENGTH,
       return_path);
-    if (!write_chunk(fd, buffer, Ustrlen(buffer), use_crlf)) return FALSE;
+    if (!write_chunk(fd, tctx, buffer, n)) return FALSE;
     }
 
   /* Add envelope-to: if requested */
 
-  if ((options & topt_add_envelope_to) != 0)
+  if (tctx->options & topt_add_envelope_to)
     {
     BOOL first = TRUE;
     address_item *p;
@@ -886,40 +904,96 @@ if ((options & topt_no_headers) == 0)
     struct aci *dlist = NULL;
     void *reset_point = store_get(0);
 
-    if (!write_chunk(fd, US"Envelope-to: ", 13, use_crlf)) return FALSE;
+    if (!write_chunk(fd, tctx, US"Envelope-to: ", 13)) return FALSE;
 
     /* Pick up from all the addresses. The plist and dlist variables are
     anchors for lists of addresses already handled; they have to be defined at
-    this level becuase write_env_to() calls itself recursively. */
+    this level because write_env_to() calls itself recursively. */
 
-    for (p = addr; p != NULL; p = p->next)
-      {
-      if (!write_env_to(p, &plist, &dlist, &first, fd, use_crlf)) return FALSE;
-      }
+    for (p = tctx->addr; p; p = p->next)
+      if (!write_env_to(p, &plist, &dlist, &first, fd, tctx))
+       return FALSE;
 
     /* Add a final newline and reset the store used for tracking duplicates */
 
-    if (!write_chunk(fd, US"\n", 1, use_crlf)) return FALSE;
+    if (!write_chunk(fd, tctx, US"\n", 1)) return FALSE;
     store_reset(reset_point);
     }
 
   /* Add delivery-date: if requested. */
 
-  if ((options & topt_add_delivery_date) != 0)
+  if (tctx->options & topt_add_delivery_date)
     {
     uschar buffer[100];
-    sprintf(CS buffer, "Delivery-date: %s\n", tod_stamp(tod_full));
-    if (!write_chunk(fd, buffer, Ustrlen(buffer), use_crlf)) return FALSE;
+    int n = sprintf(CS buffer, "Delivery-date: %s\n", tod_stamp(tod_full));
+    if (!write_chunk(fd, tctx, buffer, n)) return FALSE;
     }
 
   /* 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. Then check addr->p.remove_headers too, provided that
+  match any entries therein. Then check addr->prop.remove_headers too, provided that
   addr is not NULL. */
-  if (!transport_headers_send(addr, fd, add_headers, remove_headers, &write_chunk,
-       use_crlf, rewrite_rules, rewrite_existflags))
+
+  if (!transport_headers_send(fd, tctx, &write_chunk))
+    return FALSE;
+  }
+
+/* When doing RFC3030 CHUNKING output, work out how much data would be in a
+last-BDAT, consisting of the current write_chunk() output buffer fill
+(optimally, all of the headers - but it does not matter if we already had to
+flush that buffer with non-last BDAT prependix) plus the amount of body data
+(as expanded for CRLF lines).  Then create and write BDAT(s), and ensure
+that further use of write_chunk() will not prepend BDATs.
+The first BDAT written will also first flush any outstanding MAIL and RCPT
+commands which were buffered thans to PIPELINING.
+Commands go out (using a send()) from a different buffer to data (using a
+write()).  They might not end up in the same TCP segment, which is
+suboptimal. */
+
+if (tctx->options & topt_use_bdat)
+  {
+  off_t fsize;
+  int hsize, size = 0;
+
+  if ((hsize = chunk_ptr - deliver_out_buffer) < 0)
+    hsize = 0;
+  if (!(tctx->options & topt_no_body))
+    {
+    if ((fsize = lseek(deliver_datafile, 0, SEEK_END)) < 0) return FALSE;
+    fsize -= SPOOL_DATA_START_OFFSET;
+    if (size_limit > 0  &&  fsize > size_limit)
+      fsize = size_limit;
+    size = hsize + fsize;
+    if (tctx->options & topt_use_crlf)
+      size += body_linecount;  /* account for CRLF-expansion */
+    }
+
+  /* If the message is large, emit first a non-LAST chunk with just the
+  headers, and reap the command responses.  This lets us error out early
+  on RCPT rejects rather than sending megabytes of data.  Include headers
+  on the assumption they are cheap enough and some clever implementations
+  might errorcheck them too, on-the-fly, and reject that chunk. */
+
+  if (size > DELIVER_OUT_BUFFER_SIZE && hsize > 0)
+    {
+    DEBUG(D_transport)
+      debug_printf("sending small initial BDAT; hsize=%d\n", hsize);
+    if (  tctx->chunk_cb(fd, tctx, hsize, 0) != OK
+       || !transport_write_block(fd, deliver_out_buffer, hsize)
+       || tctx->chunk_cb(fd, tctx, 0, tc_reap_prev) != OK
+       )
+      return FALSE;
+    chunk_ptr = deliver_out_buffer;
+    size -= hsize;
+    }
+
+  /* Emit a LAST datachunk command. */
+
+  if (tctx->chunk_cb(fd, tctx, size, tc_chunk_last) != OK)
     return FALSE;
+
+  tctx->options &= ~topt_use_bdat;
   }
 
 /* If the body is required, ensure that the data for check strings (formerly
@@ -928,24 +1002,20 @@ negative in cases where it isn't to apply to the headers). Then ensure the body
 is positioned at the start of its file (following the message id), then write
 it, applying the size limit if required. */
 
-if ((options & topt_no_body) == 0)
+if (!(tctx->options & topt_no_body))
   {
+  int size = size_limit;
+
   nl_check_length = abs(nl_check_length);
   nl_partial_match = 0;
-  lseek(deliver_datafile, SPOOL_DATA_START_OFFSET, SEEK_SET);
-  while ((len = read(deliver_datafile, deliver_in_buffer,
-           DELIVER_IN_BUFFER_SIZE)) > 0)
+  if (lseek(deliver_datafile, SPOOL_DATA_START_OFFSET, SEEK_SET) < 0)
+    return FALSE;
+  while (  (len = MAX(DELIVER_IN_BUFFER_SIZE, size)) > 0
+       && (len = read(deliver_datafile, deliver_in_buffer, len)) > 0)
     {
-    if (!write_chunk(fd, deliver_in_buffer, len, use_crlf)) return FALSE;
-    if (size_limit > 0)
-      {
-      written += len;
-      if (written > size_limit)
-        {
-        len = 0;    /* Pretend EOF */
-        break;
-        }
-      }
+    if (!write_chunk(fd, tctx, deliver_in_buffer, len))
+      return FALSE;
+    size -= len;
     }
 
   /* A read error on the body will have left len == -1 and errno set. */
@@ -959,7 +1029,7 @@ nl_check_length = nl_escape_length = 0;
 
 /* If requested, add a terminating "." line (SMTP output). */
 
-if ((options & topt_end_dot) != 0 && !write_chunk(fd, US".\n", 2, use_crlf))
+if (tctx->options & topt_end_dot && !write_chunk(fd, tctx, US".\n", 2))
   return FALSE;
 
 /* Write out any remaining data in the buffer before returning. */
@@ -984,50 +1054,34 @@ return (len = chunk_ptr - deliver_out_buffer) <= 0 ||
    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
+  as for internal_transport_write_message() above, with additional arguments
+  for DKIM.
 
 Returns:       TRUE on success; FALSE (with errno) for any failure
 */
 
 BOOL
-dkim_transport_write_message(address_item *addr, int fd, int options,
-  int size_limit, uschar *add_headers, uschar *remove_headers,
-  uschar *check_string, uschar *escape_string, rewrite_rule *rewrite_rules,
-  int rewrite_existflags, uschar *dkim_private_key, uschar *dkim_domain,
-  uschar *dkim_selector, uschar *dkim_canon, uschar *dkim_strict, uschar *dkim_sign_headers
-  )
+dkim_transport_write_message(int out_fd, transport_ctx * tctx,
+  struct ob_dkim * dkim)
 {
 int dkim_fd;
 int save_errno = 0;
 BOOL rc;
-uschar dkim_spool_name[256];
-char sbuf[2048];
+uschar * dkim_spool_name;
 int sread = 0;
 int wwritten = 0;
 uschar *dkim_signature = NULL;
-off_t size = 0;
+int siglen = 0;
+off_t k_file_size;
+int options;
 
 /* 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);
+if (!(dkim->dkim_private_key && dkim->dkim_domain && dkim->dkim_selector))
+  return transport_write_message(out_fd, tctx, 0);
 
-(void)string_format(dkim_spool_name, 256, "%s/input/%s/%s-%d-K",
-       spool_directory, message_subdir, message_id, (int)getpid());
+dkim_spool_name = spool_fname(US"input", message_subdir, message_id,
+                   string_sprintf("-%d-K", (int)getpid()));
 
 if ((dkim_fd = Uopen(dkim_spool_name, O_RDWR|O_CREAT|O_TRUNC, SPOOL_MODE)) < 0)
   {
@@ -1037,12 +1091,13 @@ if ((dkim_fd = Uopen(dkim_spool_name, O_RDWR|O_CREAT|O_TRUNC, SPOOL_MODE)) < 0)
   goto CLEANUP;
   }
 
-/* Call original function to write the -K file */
+/* Call original function to write the -K file; does the CRLF expansion
+(but, in the CHUNKING case, not dot-stuffing and dot-termination). */
 
-rc = transport_write_message(addr, dkim_fd, options,
-  size_limit, add_headers, remove_headers,
-  check_string, escape_string, rewrite_rules,
-  rewrite_existflags);
+options = tctx->options;
+tctx->options &= ~topt_use_bdat;
+rc = transport_write_message(dkim_fd, tctx, 0);
+tctx->options = options;
 
 /* Save error state. We must clean up before returning. */
 if (!rc)
@@ -1051,125 +1106,122 @@ if (!rc)
   goto CLEANUP;
   }
 
-if (dkim_private_key && dkim_domain && dkim_selector)
+/* Rewind file and feed it to the goats^W DKIM lib */
+dkim->dot_stuffed = !!(options & topt_end_dot);
+lseek(dkim_fd, 0, SEEK_SET);
+if ((dkim_signature = dkim_exim_sign(dkim_fd, dkim)))
+  siglen = Ustrlen(dkim_signature);
+else if (dkim->dkim_strict)
   {
-  /* 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->dkim_strict);
+  if (dkim_strict_result)
+    if ( (strcmpic(dkim->dkim_strict,US"1") == 0) ||
+        (strcmpic(dkim->dkim_strict,US"true") == 0) )
       {
-      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;
-         }
+      /* 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
-      wwritten = tls_out.active == fd
-       ? tls_write(FALSE, dkim_signature, siglen)
-       : write(fd, dkim_signature, siglen);
-#else
-      wwritten = write(fd, dkim_signature, siglen);
+  }
+
+#ifndef HAVE_LINUX_SENDFILE
+if (options & topt_use_bdat)
 #endif
-      if (wwritten == -1)
-        {
-       /* error, bail out */
-       save_errno = errno;
-       rc = FALSE;
-       goto CLEANUP;
-       }
-      siglen -= wwritten;
-      dkim_signature += wwritten;
-      }
+  k_file_size = lseek(dkim_fd, 0, SEEK_END); /* Fetch file size */
+
+if (options & topt_use_bdat)
+  {
+
+  /* On big messages output a precursor chunk to get any pipelined
+  MAIL & RCPT commands flushed, then reap the responses so we can
+  error out on RCPT rejects before sending megabytes. */
+
+  if (siglen + k_file_size > DELIVER_OUT_BUFFER_SIZE && siglen > 0)
+    {
+    if (  tctx->chunk_cb(out_fd, tctx, siglen, 0) != OK
+       || !transport_write_block(out_fd, dkim_signature, siglen)
+       || tctx->chunk_cb(out_fd, tctx, 0, tc_reap_prev) != OK
+       )
+      goto err;
+    siglen = 0;
     }
-  }
 
-/* Fetch file size */
-size = lseek(dkim_fd, 0, SEEK_END);
+  if (tctx->chunk_cb(out_fd, tctx, siglen + k_file_size, tc_chunk_last) != OK)
+    goto err;
+  }
 
-/* Rewind file */
-lseek(dkim_fd, 0, SEEK_SET);
+if(siglen > 0 && !transport_write_block(out_fd, dkim_signature, siglen))
+  goto err;
 
 #ifdef HAVE_LINUX_SENDFILE
 /* We can use sendfile() to shove the file contents
    to the socket. However only if we don't use TLS,
    as then there's another layer of indirection
    before the data finally hits the socket. */
-if (tls_out.active != fd)
+if (tls_out.active != out_fd)
   {
   ssize_t copied = 0;
   off_t offset = 0;
-  while(copied >= 0 && offset < size)
-    copied = sendfile(fd, dkim_fd, &offset, size - offset);
+
+  /* Rewind file */
+  lseek(dkim_fd, 0, SEEK_SET);
+
+  while(copied >= 0 && offset < k_file_size)
+    copied = sendfile(out_fd, dkim_fd, &offset, k_file_size - offset);
   if (copied < 0)
-    {
-    save_errno = errno;
-    rc = FALSE;
-    }
-  goto CLEANUP;
+    goto err;
   }
+else
+
 #endif
 
-/* Send file down the original fd */
-while((sread = read(dkim_fd, sbuf, 2048)) > 0)
   {
-  char *p = sbuf;
-  /* write the chunk */
+  /* Rewind file */
+  lseek(dkim_fd, 0, SEEK_SET);
 
-  while (sread)
+  /* Send file down the original fd */
+  while((sread = read(dkim_fd, deliver_out_buffer, DELIVER_OUT_BUFFER_SIZE)) >0)
     {
+    uschar * p = deliver_out_buffer;
+    /* write the chunk */
+
+    while (sread)
+      {
 #ifdef SUPPORT_TLS
-    wwritten = tls_out.active == fd
-      ? tls_write(FALSE, US p, sread)
-      : write(fd, p, sread);
+      wwritten = tls_out.active == out_fd
+       ? tls_write(FALSE, p, sread)
+       : write(out_fd, CS p, sread);
 #else
-    wwritten = write(fd, p, sread);
+      wwritten = write(out_fd, CS p, sread);
 #endif
-    if (wwritten == -1)
-      {
-      /* error, bail out */
-      save_errno = errno;
-      rc = FALSE;
-      goto CLEANUP;
+      if (wwritten == -1)
+       goto err;
+      p += wwritten;
+      sread -= wwritten;
       }
-    p += wwritten;
-    sread -= wwritten;
+    }
+
+  if (sread == -1)
+    {
+    save_errno = errno;
+    rc = FALSE;
     }
   }
 
-if (sread == -1)
-  {
+CLEANUP:
+  /* unlink -K file */
+  (void)close(dkim_fd);
+  Uunlink(dkim_spool_name);
+  errno = save_errno;
+  return rc;
+
+err:
   save_errno = errno;
   rc = FALSE;
   goto CLEANUP;
-  }
-
-CLEANUP:
-/* unlink -K file */
-(void)close(dkim_fd);
-Uunlink(dkim_spool_name);
-errno = save_errno;
-return rc;
 }
 
 #endif
@@ -1186,6 +1238,7 @@ set up a filtering process, fork another process to call the internal function
 to write to the filter, and in this process just suck from the filter and write
 down the given fd. At the end, tidy up the pipes and the processes.
 
+XXX
 Arguments:     as for internal_transport_write_message() above
 
 Returns:       TRUE on success; FALSE (with errno) for any failure
@@ -1193,39 +1246,38 @@ Returns:       TRUE on success; FALSE (with errno) for any failure
 */
 
 BOOL
-transport_write_message(address_item *addr, int fd, int options,
-  int size_limit, uschar *add_headers, uschar *remove_headers,
-  uschar *check_string, uschar *escape_string, rewrite_rule *rewrite_rules,
-  int rewrite_existflags)
+transport_write_message(int fd, transport_ctx * tctx, int size_limit)
 {
-BOOL use_crlf;
 BOOL last_filter_was_NL = TRUE;
 int rc, len, yield, fd_read, fd_write, save_errno;
-int pfd[2];
+int pfd[2] = {-1, -1};
 pid_t filter_pid, write_pid;
+static transport_ctx dummy_tctx = {0};
+
+if (!tctx) tctx = &dummy_tctx;
 
 transport_filter_timed_out = FALSE;
 
 /* If there is no filter command set up, call the internal function that does
 the actual work, passing it the incoming fd, and return its result. */
 
-if (transport_filter_argv == NULL)
-  return internal_transport_write_message(addr, fd, options, size_limit,
-    add_headers, remove_headers, check_string, escape_string,
-    rewrite_rules, rewrite_existflags);
+if (  !transport_filter_argv
+   || !*transport_filter_argv
+   || !**transport_filter_argv
+   )
+  return internal_transport_write_message(fd, tctx, size_limit);
 
 /* Otherwise the message must be written to a filter process and read back
 before being written to the incoming fd. First set up the special processing to
 be done during the copying. */
 
-use_crlf  = (options & topt_use_crlf) != 0;
 nl_partial_match = -1;
 
-if (check_string != NULL && escape_string != NULL)
+if (tctx->check_string && tctx->escape_string)
   {
-  nl_check = check_string;
+  nl_check = tctx->check_string;
   nl_check_length = Ustrlen(nl_check);
-  nl_escape = escape_string;
+  nl_escape = tctx->escape_string;
   nl_escape_length = Ustrlen(nl_escape);
   }
 else nl_check_length = nl_escape_length = 0;
@@ -1242,14 +1294,17 @@ save_errno = 0;
 yield = FALSE;
 write_pid = (pid_t)(-1);
 
-(void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
-filter_pid = child_open(transport_filter_argv, NULL, 077, &fd_write, &fd_read,
-  FALSE);
-(void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) & ~FD_CLOEXEC);
+  {
+  int bits = fcntl(fd, F_GETFD);
+  (void)fcntl(fd, F_SETFD, bits | FD_CLOEXEC);
+  filter_pid = child_open(USS transport_filter_argv, NULL, 077,
+   &fd_write, &fd_read, FALSE);
+  (void)fcntl(fd, F_SETFD, bits & ~FD_CLOEXEC);
+  }
 if (filter_pid < 0) goto TIDY_UP;      /* errno set */
 
 DEBUG(D_transport)
-  debug_printf("process %d running as transport filter: write=%d read=%d\n",
+  debug_printf("process %d running as transport filter: fd_write=%d fd_read=%d\n",
     (int)filter_pid, fd_write, fd_read);
 
 /* Fork subprocess to write the message to the filter, and return the result
@@ -1263,16 +1318,18 @@ if ((write_pid = fork()) == 0)
   (void)close(fd_read);
   (void)close(pfd[pipe_read]);
   nl_check_length = nl_escape_length = 0;
-  rc = internal_transport_write_message(addr, fd_write,
-    (options & ~(topt_use_crlf | topt_end_dot)),
-    size_limit, add_headers, remove_headers, NULL, NULL,
-    rewrite_rules, rewrite_existflags);
+
+  tctx->check_string = tctx->escape_string = NULL;
+  tctx->options &= ~(topt_use_crlf | topt_end_dot | topt_use_bdat);
+
+  rc = internal_transport_write_message(fd_write, tctx, size_limit);
+
   save_errno = errno;
   if (  write(pfd[pipe_write], (void *)&rc, sizeof(BOOL))
         != sizeof(BOOL)
      || write(pfd[pipe_write], (void *)&save_errno, sizeof(int))
         != sizeof(int)
-     || write(pfd[pipe_write], (void *)&(addr->more_errno), sizeof(int))
+     || write(pfd[pipe_write], (void *)&tctx->addr->more_errno, sizeof(int))
         != sizeof(int)
      )
     rc = FALSE;        /* compiler quietening */
@@ -1332,7 +1389,7 @@ for (;;)
 
   if (len > 0)
     {
-    if (!write_chunk(fd, deliver_in_buffer, len, use_crlf)) goto TIDY_UP;
+    if (!write_chunk(fd, tctx, deliver_in_buffer, len)) goto TIDY_UP;
     last_filter_was_NL = (deliver_in_buffer[len-1] == '\n');
     }
 
@@ -1369,7 +1426,7 @@ if (filter_pid > 0 && (rc = child_close(filter_pid, 30)) != 0 && yield)
   {
   yield = FALSE;
   save_errno = ERRNO_FILTER_FAIL;
-  addr->more_errno = rc;
+  tctx->addr->more_errno = rc;
   DEBUG(D_transport) debug_printf("filter process returned %d\n", rc);
   }
 
@@ -1382,15 +1439,20 @@ if (write_pid > 0)
   {
   rc = child_close(write_pid, 30);
   if (yield)
-    {
     if (rc == 0)
       {
       BOOL ok;
-      int dummy = read(pfd[pipe_read], (void *)&ok, sizeof(BOOL));
-      if (!ok)
+      if (read(pfd[pipe_read], (void *)&ok, sizeof(BOOL)) != sizeof(BOOL))
+       {
+       DEBUG(D_transport)
+         debug_printf("pipe read from writing process: %s\n", strerror(errno));
+       save_errno = ERRNO_FILTER_FAIL;
+        yield = FALSE;
+       }
+      else if (!ok)
         {
-        dummy = read(pfd[pipe_read], (void *)&save_errno, sizeof(int));
-        dummy = read(pfd[pipe_read], (void *)&(addr->more_errno), sizeof(int));
+       int dummy = read(pfd[pipe_read], (void *)&save_errno, sizeof(int));
+        dummy = read(pfd[pipe_read], (void *)&(tctx->addr->more_errno), sizeof(int));
         yield = FALSE;
         }
       }
@@ -1398,10 +1460,9 @@ if (write_pid > 0)
       {
       yield = FALSE;
       save_errno = ERRNO_FILTER_FAIL;
-      addr->more_errno = rc;
+      tctx->addr->more_errno = rc;
       DEBUG(D_transport) debug_printf("writing process returned %d\n", rc);
       }
-    }
   }
 (void)close(pfd[pipe_read]);
 
@@ -1412,28 +1473,27 @@ filter was not NL, insert a NL to make the SMTP protocol work. */
 if (yield)
   {
   nl_check_length = nl_escape_length = 0;
-  if ((options & topt_end_dot) != 0 && (last_filter_was_NL?
-        !write_chunk(fd, US".\n", 2, use_crlf) :
-        !write_chunk(fd, US"\n.\n", 3, use_crlf)))
-    {
+  if (  tctx->options & topt_end_dot
+     && ( last_filter_was_NL
+        ? !write_chunk(fd, tctx, US".\n", 2)
+       : !write_chunk(fd, tctx, US"\n.\n", 3)
+     )  )
     yield = FALSE;
-    }
 
   /* Write out any remaining data in the buffer. */
 
   else
-    {
-    yield = (len = chunk_ptr - deliver_out_buffer) <= 0 ||
-      transport_write_block(fd, deliver_out_buffer, len);
-    }
+    yield = (len = chunk_ptr - deliver_out_buffer) <= 0
+         || transport_write_block(fd, deliver_out_buffer, len);
   }
-else errno = save_errno;      /* From some earlier error */
+else
+  errno = save_errno;      /* From some earlier error */
 
 DEBUG(D_transport)
   {
   debug_printf("end of filtering transport writing: yield=%d\n", yield);
   if (!yield)
-    debug_printf("errno=%d more_errno=%d\n", errno, addr->more_errno);
+    debug_printf("errno=%d more_errno=%d\n", errno, tctx->addr->more_errno);
   }
 
 return yield;
@@ -1482,7 +1542,7 @@ void
 transport_update_waiting(host_item *hostlist, uschar *tpname)
 {
 uschar buffer[256];
-uschar *prevname = US"";
+const uschar *prevname = US"";
 host_item *host;
 open_db dbblock;
 open_db *dbm_file;
@@ -1623,20 +1683,32 @@ Arguments:
                        as set by the caller transport
   new_message_id     set to the message id of a waiting message
   more               set TRUE if there are yet more messages waiting
+  oicf_func          function to call to validate if it is ok to send
+                     to this message_id from the current instance.
+  oicf_data          opaque data for oicf_func
 
 Returns:             TRUE if new_message_id set; FALSE otherwise
 */
 
+typedef struct msgq_s
+{
+    uschar  message_id [MESSAGE_ID_LENGTH + 1];
+    BOOL    bKeep;
+} msgq_t;
+
 BOOL
-transport_check_waiting(uschar *transport_name, uschar *hostname,
-  int local_message_max, uschar *new_message_id, BOOL *more)
+transport_check_waiting(const uschar *transport_name, const uschar *hostname,
+  int local_message_max, uschar *new_message_id, BOOL *more, oicf oicf_func, void *oicf_data)
 {
 dbdata_wait *host_record;
-int host_length, path_len;
+int host_length;
 open_db dbblock;
 open_db *dbm_file;
 uschar buffer[256];
 
+int         i;
+struct stat statbuf;
+
 *more = FALSE;
 
 DEBUG(D_transport)
@@ -1665,8 +1737,7 @@ if (dbm_file == NULL) return FALSE;
 
 /* See if there is a record for this host; if not, there's nothing to do. */
 
-host_record = dbfn_read(dbm_file, hostname);
-if (host_record == NULL)
+if (!(host_record = dbfn_read(dbm_file, hostname)))
   {
   dbfn_close(dbm_file);
   DEBUG(D_transport) debug_printf("no messages waiting for %s\n", hostname);
@@ -1689,58 +1760,106 @@ until one is found for which a spool file actually exists. If the record gets
 emptied, delete it and continue with any continuation records that may exist.
 */
 
-host_length = host_record->count * MESSAGE_ID_LENGTH;
+/* For Bug 1141, I refactored this major portion of the routine, it is risky
+but the 1 off will remain without it.  This code now allows me to SKIP over
+a message I do not want to send out on this run.  */
 
-/* Loop to handle continuation host records in the database */
+host_length = host_record->count * MESSAGE_ID_LENGTH;
 
-for (;;)
+while (1)
   {
-  BOOL found = FALSE;
+  msgq_t      *msgq;
+  int         msgq_count = 0;
+  int         msgq_actual = 0;
+  BOOL        bFound = FALSE;
+  BOOL        bContinuation = FALSE;
+
+  /* create an array to read entire message queue into memory for processing  */
 
-  sprintf(CS buffer, "%s/input/", spool_directory);
-  path_len = Ustrlen(buffer);
+  msgq = store_malloc(sizeof(msgq_t) * host_record->count);
+  msgq_count = host_record->count;
+  msgq_actual = msgq_count;
 
-  for (host_length -= MESSAGE_ID_LENGTH; host_length >= 0;
-       host_length -= MESSAGE_ID_LENGTH)
+  for (i = 0; i < host_record->count; ++i)
     {
-    struct stat statbuf;
-    Ustrncpy(new_message_id, host_record->text + host_length,
+    msgq[i].bKeep = TRUE;
+
+    Ustrncpy(msgq[i].message_id, host_record->text + (i * MESSAGE_ID_LENGTH),
       MESSAGE_ID_LENGTH);
-    new_message_id[MESSAGE_ID_LENGTH] = 0;
+    msgq[i].message_id[MESSAGE_ID_LENGTH] = 0;
+    }
 
-    if (split_spool_directory)
-      sprintf(CS(buffer + path_len), "%c/%s-D", new_message_id[5], new_message_id);
-    else
-      sprintf(CS(buffer + path_len), "%s-D", new_message_id);
+  /* first thing remove current message id if it exists */
+
+  for (i = 0; i < msgq_count; ++i)
+    if (Ustrcmp(msgq[i].message_id, message_id) == 0)
+      {
+      msgq[i].bKeep = FALSE;
+      break;
+      }
+
+  /* now find the next acceptable message_id */
+
+  for (i = msgq_count - 1; i >= 0; --i) if (msgq[i].bKeep)
+    {
+    uschar subdir[2];
 
-    /* The listed message may be the one we are currently processing. If
-    so, we want to remove it from the list without doing anything else.
-    If not, do a stat to see if it is an existing message. If it is, break
-    the loop to handle it. No need to bother about locks; as this is all
-    "hint" processing, it won't matter if it doesn't exist by the time exim
-    actually tries to deliver it. */
+    subdir[0] = split_spool_directory ? msgq[i].message_id[5] : 0;
+    subdir[1] = 0;
 
-    if (Ustrcmp(new_message_id, message_id) != 0 &&
-        Ustat(buffer, &statbuf) == 0)
+    if (Ustat(spool_fname(US"input", subdir, msgq[i].message_id, US"-D"),
+             &statbuf) != 0)
+      msgq[i].bKeep = FALSE;
+    else if (!oicf_func || oicf_func(msgq[i].message_id, oicf_data))
       {
-      found = TRUE;
+      Ustrcpy(new_message_id, msgq[i].message_id);
+      msgq[i].bKeep = FALSE;
+      bFound = TRUE;
       break;
       }
     }
 
-  /* If we have removed all the message ids from the record delete the record.
-  If there is a continuation record, fetch it and remove it from the file,
-  as it will be rewritten as the main record. Repeat in the case of an
-  empty continuation. */
+  /* re-count */
+  for (msgq_actual = 0, i = 0; i < msgq_count; ++i)
+    if (msgq[i].bKeep)
+      msgq_actual++;
+
+  /* reassemble the host record, based on removed message ids, from in
+  memory queue  */
+
+  if (msgq_actual <= 0)
+    {
+    host_length = 0;
+    host_record->count = 0;
+    }
+  else
+    {
+    host_length = msgq_actual * MESSAGE_ID_LENGTH;
+    host_record->count = msgq_actual;
+
+    if (msgq_actual < msgq_count)
+      {
+      int new_count;
+      for (new_count = 0, i = 0; i < msgq_count; ++i)
+       if (msgq[i].bKeep)
+         Ustrncpy(&host_record->text[new_count++ * MESSAGE_ID_LENGTH],
+           msgq[i].message_id, MESSAGE_ID_LENGTH);
+
+      host_record->text[new_count * MESSAGE_ID_LENGTH] = 0;
+      }
+    }
+
+/* Jeremy: check for a continuation record, this code I do not know how to
+test but the code should work */
 
   while (host_length <= 0)
     {
     int i;
-    dbdata_wait *newr = NULL;
+    dbdata_wait * newr = NULL;
 
     /* Search for a continuation */
 
-    for (i = host_record->sequence - 1; i >= 0 && newr == NULL; i--)
+    for (i = host_record->sequence - 1; i >= 0 && !newr; i--)
       {
       sprintf(CS buffer, "%.200s:%d", hostname, i);
       newr = dbfn_read(dbm_file, buffer);
@@ -1748,7 +1867,7 @@ for (;;)
 
     /* If no continuation, delete the current and break the loop */
 
-    if (newr == NULL)
+    if (!newr)
       {
       dbfn_delete(dbm_file, hostname);
       break;
@@ -1759,11 +1878,15 @@ for (;;)
     dbfn_delete(dbm_file, buffer);
     host_record = newr;
     host_length = host_record->count * MESSAGE_ID_LENGTH;
-    }
 
-  /* If we found an existing message, break the continuation loop. */
+    bContinuation = TRUE;
+    }
 
-  if (found) break;
+  if (bFound)          /* Usual exit from main loop */
+    {
+    store_free (msgq);
+    break;
+    }
 
   /* If host_length <= 0 we have emptied a record and not found a good message,
   and there are no continuation records. Otherwise there is a continuation
@@ -1775,7 +1898,20 @@ for (;;)
     DEBUG(D_transport) debug_printf("waiting messages already delivered\n");
     return FALSE;
     }
-  }
+
+  /* we were not able to find an acceptable message, nor was there a
+   * continuation record.  So bug out, outer logic will clean this up.
+   */
+
+  if (!bContinuation)
+    {
+    Ustrcpy(new_message_id, message_id);
+    dbfn_close(dbm_file);
+    return FALSE;
+    }
+
+  store_free(msgq);
+  }            /* we need to process a continuation record */
 
 /* Control gets here when an existing message has been encountered; its
 id is in new_message_id, and host_length is the revised length of the
@@ -1785,6 +1921,7 @@ record if required, close the database, and return TRUE. */
 if (host_length > 0)
   {
   host_record->count = host_length/MESSAGE_ID_LENGTH;
+
   dbfn_write(dbm_file, hostname, host_record, (int)sizeof(dbdata_wait) + host_length);
   *more = TRUE;
   }
@@ -1793,8 +1930,6 @@ dbfn_close(dbm_file);
 return TRUE;
 }
 
-
-
 /*************************************************
 *    Deliver waiting message down same socket    *
 *************************************************/
@@ -1814,8 +1949,8 @@ Returns:          FALSE if fork fails; TRUE otherwise
 */
 
 BOOL
-transport_pass_socket(uschar *transport_name, uschar *hostname,
-  uschar *hostaddress, uschar *id, int socket_fd)
+transport_pass_socket(const uschar *transport_name, const uschar *hostname,
+  const uschar *hostaddress, uschar *id, int socket_fd)
 {
 pid_t pid;
 int status;
@@ -1824,8 +1959,8 @@ DEBUG(D_transport) debug_printf("transport_pass_socket entered\n");
 
 if ((pid = fork()) == 0)
   {
-  int i = 16;
-  uschar **argv;
+  int i = 17;
+  const uschar **argv;
 
   /* Disconnect entirely from the parent process. If we are running in the
   test harness, wait for a bit to allow the previous process time to finish,
@@ -1838,21 +1973,17 @@ if ((pid = fork()) == 0)
   /* Set up the calling arguments; use the standard function for the basics,
   but we have a number of extras that may be added. */
 
-  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
+  argv = CUSS child_exec_exim(CEE_RETURN_ARGV, TRUE, &i, FALSE, 0);
 
   if (smtp_authenticated) argv[i++] = US"-MCA";
 
-  #ifdef SUPPORT_TLS
-  if (tls_offered) argv[i++] = US"-MCT";
-  #endif
-
-  if (smtp_use_size) argv[i++] = US"-MCS";
-  if (smtp_use_pipelining) argv[i++] = US"-MCP";
+  if (smtp_peer_options & PEER_OFFERED_CHUNKING) argv[i++] = US"-MCK";
+  if (smtp_peer_options & PEER_OFFERED_DSN) argv[i++] = US"-MCD";
+  if (smtp_peer_options & PEER_OFFERED_PIPE) argv[i++] = US"-MCP";
+  if (smtp_peer_options & PEER_OFFERED_SIZE) argv[i++] = US"-MCS";
+#ifdef SUPPORT_TLS
+  if (smtp_peer_options & PEER_OFFERED_TLS) argv[i++] = US"-MCT";
+#endif
 
   if (queue_run_pid != (pid_t)0)
     {
@@ -1862,9 +1993,9 @@ if ((pid = fork()) == 0)
     }
 
   argv[i++] = US"-MC";
-  argv[i++] = transport_name;
-  argv[i++] = hostname;
-  argv[i++] = hostaddress;
+  argv[i++] = US transport_name;
+  argv[i++] = US hostname;
+  argv[i++] = US hostaddress;
   argv[i++] = string_sprintf("%d", continue_sequence + 1);
   argv[i++] = id;
   argv[i++] = NULL;
@@ -1918,7 +2049,7 @@ case, no addresses are passed.
 
 Arguments:
   argvptr            pointer to anchor for argv vector
-  cmd                points to the command string
+  cmd                points to the command string (modified IN PLACE)
   expand_arguments   true if expansion is to occur
   expand_failed      error value to set if expansion fails; not relevant if
                      addr == NULL
@@ -1932,11 +2063,12 @@ Returns:             TRUE if all went well; otherwise an error will be
 */
 
 BOOL
-transport_set_up_command(uschar ***argvptr, uschar *cmd, BOOL expand_arguments,
-  int expand_failed, address_item *addr, uschar *etext, uschar **errptr)
+transport_set_up_command(const uschar ***argvptr, uschar *cmd,
+  BOOL expand_arguments, int expand_failed, address_item *addr,
+  uschar *etext, uschar **errptr)
 {
 address_item *ad;
-uschar **argv;
+const uschar **argv;
 uschar *s, *ss;
 int address_count = 0;
 int argcount = 0;
@@ -1970,7 +2102,7 @@ while (*s != 0 && argcount < max_args)
     if (*s != 0) s++;
     *ss++ = 0;
     }
-  else argv[argcount++] = string_dequote(&s);
+  else argv[argcount++] = string_copy(string_dequote(CUSS &s));
   while (isspace(*s)) s++;
   }
 
@@ -2099,7 +2231,8 @@ if (expand_arguments)
           if (*s != 0) s++;
           *ss++ = 0;
           }
-        else address_pipe_argv[address_pipe_argcount++] = string_dequote(&s);
+        else address_pipe_argv[address_pipe_argcount++] =
+             string_copy(string_dequote(CUSS &s));
         while (isspace(*s)) s++; /* strip space after arg */
         }
 
@@ -2139,7 +2272,7 @@ if (expand_arguments)
        */
       if (address_pipe_argcount > 1)
         memmove(
-          /* current position + additonal args */
+          /* current position + additional args */
           argv + i + address_pipe_argcount,
           /* current position + 1 (for the (uschar *)0 at the end) */
           argv + i + 1,
@@ -2167,9 +2300,9 @@ if (expand_arguments)
 
     else
       {
-      uschar *expanded_arg;
+      const uschar *expanded_arg;
       enable_dollar_recipients = allow_dollar_recipients;
-      expanded_arg = expand_string(argv[i]);
+      expanded_arg = expand_cstring(argv[i]);
       enable_dollar_recipients = FALSE;
 
       if (expanded_arg == NULL)
index 02cf0c8..4eea141 100644 (file)
@@ -2,7 +2,7 @@
 # calling it transports.a. This is called from the main make file, after cd'ing
 # to the transports subdirectory.
 
-OBJ = appendfile.o autoreply.o lmtp.o pipe.o smtp.o tf_maildir.o
+OBJ = appendfile.o autoreply.o lmtp.o pipe.o queuefile.o smtp.o smtp_socks.o tf_maildir.o
 
 transports.a:    $(OBJ)
                 @$(RM_COMMAND) -f transports.a
@@ -18,7 +18,9 @@ appendfile.o:    $(HDRS) appendfile.c appendfile.h tf_maildir.h
 autoreply.o:     $(HDRS) autoreply.c autoreply.h
 lmtp.o:          $(HDRS) lmtp.c lmtp.h
 pipe.o:          $(HDRS) pipe.c pipe.h
+queuefile.o:     $(HDRS) queuefile.c queuefile.h
 smtp.o:          $(HDRS) smtp.c smtp.h
+smtp_socks.o:    $(HDRS) smtp_socks.c smtp.h
 
 tf_maildir.o:    $(HDRS) tf_maildir.c tf_maildir.h appendfile.h
 
index f56862b..9b3379b 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -186,7 +186,7 @@ appendfile_transport_options_block appendfile_transport_option_defaults = {
   NULL,           /* quota_warn_threshold */
   NULL,           /* mailbox_size_string */
   NULL,           /* mailbox_filecount_string */
-  NULL,           /* expand_maildir_use_size_file */ 
+  NULL,           /* expand_maildir_use_size_file */
   US"^(?:cur|new|\\..*)$",  /* maildir_dir_regex */
   NULL,           /* maildir_tag */
   NULL,           /* maildirfolder_create_regex */
@@ -276,7 +276,7 @@ uid = uid;
 gid = gid;
 
 if (ob->expand_maildir_use_size_file)
-       ob->maildir_use_size_file = expand_check_condition(ob->expand_maildir_use_size_file, 
+       ob->maildir_use_size_file = expand_check_condition(ob->expand_maildir_use_size_file,
                US"`maildir_use_size_file` in transport", tblock->name);
 
 /* Loop for quota, quota_filecount, quota_warn_threshold, mailbox_size,
@@ -619,19 +619,18 @@ if (host_find_byname(&host, NULL, 0, NULL, FALSE) == HOST_FIND_FAILED)
 host.address = US"127.0.0.1";
 
 
-for (h = &host; h != NULL; h = h->next)
+for (h = &host; h; h = h->next)
   {
   int sock, rc;
-  int host_af = (Ustrchr(h->address, ':') != NULL)? AF_INET6 : AF_INET;
+  int host_af = Ustrchr(h->address, ':') != NULL ? AF_INET6 : AF_INET;
 
   DEBUG(D_transport) debug_printf("calling comsat on %s\n", h->address);
 
-  sock = ip_socket(SOCK_DGRAM, host_af);
-  if (sock < 0) continue;
+  if ((sock = ip_socket(SOCK_DGRAM, host_af)) < 0) continue;
 
   /* Connect never fails for a UDP socket, so don't set a timeout. */
 
-  (void)ip_connect(sock, host_af, h->address, ntohs(sp->s_port), 0);
+  (void)ip_connect(sock, host_af, h->address, ntohs(sp->s_port), 0, FALSE);
   rc = send(sock, buffer, Ustrlen(buffer) + 1, 0);
   (void)close(sock);
 
@@ -664,7 +663,7 @@ Returns:       pointer to the required transport, or NULL
 transport_instance *
 check_file_format(int cfd, transport_instance *tblock, address_item *addr)
 {
-uschar *format =
+const uschar *format =
   ((appendfile_transport_options_block *)(tblock->options_block))->file_format;
 uschar data[256];
 int len = read(cfd, data, sizeof(data));
@@ -679,15 +678,16 @@ if (len == 0) return tblock;
 
 /* Search the formats for a match */
 
-while ((s = string_nextinlist(&format,&sep,big_buffer,big_buffer_size))!= NULL)
+while ((s = string_nextinlist(&format,&sep,big_buffer,big_buffer_size)))
   {
   int slen = Ustrlen(s);
   BOOL match = len >= slen && Ustrncmp(data, s, slen) == 0;
   uschar *tp = string_nextinlist(&format, &sep, big_buffer, big_buffer_size);
-  if (match)
+
+  if (match && tp)
     {
     transport_instance *tt;
-    for (tt = transports; tt != NULL; tt = tt->next)
+    for (tt = transports; tt; tt = tt->next)
       if (Ustrcmp(tp, tt->name) == 0)
         {
         DEBUG(D_transport)
@@ -1146,7 +1146,7 @@ directory name) is given, that is, when appending to a single file:
 
      Open with O_WRONLY + O_EXCL + O_CREAT with configured mode, unless we know
      this is via a symbolic link (only possible if allow_symlinks is set), in
-     which case don't use O_EXCL, as it dosn't work.
+     which case don't use O_EXCL, as it doesn't work.
 
      If open fails because the file already exists, go to (6f). To avoid
      looping for ever in a situation where the file is continuously being
@@ -2540,8 +2540,8 @@ else
       uschar *basename;
 
       (void)gettimeofday(&msg_tv, NULL);
-      basename = string_sprintf("%lu.H%luP%lu.%s", msg_tv.tv_sec,
-        msg_tv.tv_usec, getpid(), primary_hostname);
+      basename = string_sprintf(TIME_T_FMT ".H%luP%lu.%s",
+               msg_tv.tv_sec, msg_tv.tv_usec, getpid(), primary_hostname);
 
       filename = dataname = string_sprintf("tmp/%s", basename);
       newname = string_sprintf("new/%s", basename);
@@ -2873,9 +2873,14 @@ at initialization time. */
 
 if (yield == OK)
   {
-  if (!transport_write_message(addr, fd, ob->options, 0, tblock->add_headers,
-      tblock->remove_headers, ob->check_string, ob->escape_string,
-      tblock->rewrite_rules, tblock->rewrite_existflags))
+  transport_ctx tctx = {
+    tblock,
+    addr,
+    ob->check_string,
+    ob->escape_string,
+    ob->options
+  };
+  if (!transport_write_message(fd, &tctx, 0))
     yield = DEFER;
   }
 
@@ -3123,7 +3128,7 @@ if (yield != OK)
   fcntl() call (BSDI & FreeBSD do not). */
 
   if (!isdirectory && ftruncate(fd, saved_size))
-    DEBUG(D_transport) debug_printf("Error restting file size\n");
+    DEBUG(D_transport) debug_printf("Error resetting file size\n");
   }
 
 /* Handle successful writing - we want the modification time to be now for
index 4e391b8..f07cd83 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -157,12 +157,13 @@ if (ss == NULL)
 if (type != cke_text) for (t = ss; *t != 0; t++)
   {
   int c = *t;
+  const uschar * sp;
   if (mac_isprint(c)) continue;
   if (type == cke_hdr && c == '\n' && (t[1] == ' ' || t[1] == '\t')) continue;
-  s = string_printing(s);
+  sp = string_printing(s);
   addr->transport_return = FAIL;
   addr->message = string_sprintf("Expansion of \"%s\" in %s transport "
-    "contains non-printing character %d", s, name, c);
+    "contains non-printing character %d", sp, name, c);
   return NULL;
   }
 
@@ -187,7 +188,7 @@ Returns:      nothing
 */
 
 static void
-check_never_mail(uschar **listptr, uschar *never_mail)
+check_never_mail(uschar **listptr, const uschar *never_mail)
 {
 uschar *s = *listptr;
 
@@ -266,7 +267,6 @@ autoreply_transport_entry(
 {
 int fd, pid, rc;
 int cache_fd = -1;
-int log_fd = -1;
 int cache_size = 0;
 int add_size = 0;
 EXIM_DB *dbm_file = NULL;
@@ -379,7 +379,7 @@ remove those that match. */
 
 if (ob->never_mail != NULL)
   {
-  uschar *never_mail = expand_string(ob->never_mail);
+  const uschar *never_mail = expand_string(ob->never_mail);
 
   if (never_mail == NULL)
     {
@@ -521,9 +521,10 @@ if (oncelog != NULL && *oncelog != 0 && to != NULL)
 
   if (then != 0 && (once_repeat_sec <= 0 || now - then < once_repeat_sec))
     {
+    int log_fd;
     DEBUG(D_transport) debug_printf("message previously sent to %s%s\n", to,
       (once_repeat_sec > 0)? " and repeat time not reached" : "");
-    log_fd = Uopen(logfile, O_WRONLY|O_APPEND|O_CREAT, ob->mode);
+    log_fd = logfile ? Uopen(logfile, O_WRONLY|O_APPEND|O_CREAT, ob->mode) : -1;
     if (log_fd >= 0)
       {
       uschar *ptr = log_buffer;
@@ -676,6 +677,7 @@ if (ff != NULL)
       }
     else fprintf(f, "%s", CS big_buffer);
     }
+  (void) fclose(ff);
   }
 
 /* Copy the original message if required, observing the return size
@@ -689,6 +691,16 @@ if (return_message)
     US"------ This is a copy of the body of the message, without the headers.\n"
     :
     US"------ This is a copy of the message, including all the headers.\n";
+  transport_ctx tctx = {
+    tblock,
+    addr,
+    NULL, NULL,
+    (tblock->body_only ? topt_no_headers : 0) |
+    (tblock->headers_only ? topt_no_body : 0) |
+    (tblock->return_path_add ? topt_add_return_path : 0) |
+    (tblock->delivery_date_add ? topt_add_delivery_date : 0) |
+    (tblock->envelope_to_add ? topt_add_envelope_to : 0)
+  };
 
   if (bounce_return_size_limit > 0 && !tblock->headers_only)
     {
@@ -708,14 +720,7 @@ if (return_message)
 
   fflush(f);
   transport_count = 0;
-  transport_write_message(addr, fileno(f),
-    (tblock->body_only? topt_no_headers : 0) |
-    (tblock->headers_only? topt_no_body : 0) |
-    (tblock->return_path_add? topt_add_return_path : 0) |
-    (tblock->delivery_date_add? topt_add_delivery_date : 0) |
-    (tblock->envelope_to_add? topt_add_envelope_to : 0),
-    bounce_return_size_limit, tblock->add_headers, tblock->remove_headers,
-    NULL, NULL, tblock->rewrite_rules, tblock->rewrite_existflags);
+  transport_write_message(fileno(f), &tctx, bounce_return_size_limit);
   }
 
 /* End the message and wait for the child process to end; no timeout. */
index 84bbeb9..c4606ef 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -106,7 +106,7 @@ Arguments:
   more_errno   from the top address for use with ERRNO_FILTER_FAIL
   buffer       the LMTP response buffer
   yield        where to put a one-digit LMTP response code
-  message      where to put an errror message
+  message      where to put an error message
 
 Returns:       TRUE if a "QUIT" command should be sent, else FALSE
 */
@@ -171,7 +171,7 @@ if (*errno_value == ERRNO_WRITEINCOMPLETE)
 
 if (buffer[0] != 0)
   {
-  uschar *s = string_printing(buffer);
+  const uschar *s = string_printing(buffer);
   *message = string_sprintf("LMTP error after %s: %s", big_buffer, s);
   *yield = buffer[0];
   return TRUE;
@@ -217,6 +217,7 @@ va_list ap;
 va_start(ap, format);
 if (!string_vformat(big_buffer, big_buffer_size, CS format, ap))
   {
+  va_end(ap);
   errno = ERRNO_SMTPFORMAT;
   return FALSE;
   }
@@ -460,7 +461,7 @@ BOOL yield = FALSE;
 address_item *addr;
 uschar *igquotstr = US"";
 uschar *sockname = NULL;
-uschar **argv;
+const uschar **argv;
 uschar buffer[256];
 
 DEBUG(D_transport) debug_printf("%s transport entered\n", tblock->name);
@@ -469,13 +470,29 @@ DEBUG(D_transport) debug_printf("%s transport entered\n", tblock->name);
 not both. When a command is specified, call the common function for creating an
 argument list and expanding the items. */
 
-if (ob->cmd != NULL)
+if (ob->cmd)
   {
   DEBUG(D_transport) debug_printf("using command %s\n", ob->cmd);
   sprintf(CS buffer, "%.50s transport", tblock->name);
   if (!transport_set_up_command(&argv, ob->cmd, TRUE, PANIC, addrlist, buffer,
        NULL))
     return FALSE;
+
+  /* If the -N option is set, can't do any more. Presume all has gone well. */
+  if (dont_deliver)
+    goto MINUS_N;
+
+/* As this is a local transport, we are already running with the required
+uid/gid and current directory. Request that the new process be a process group
+leader, so we can kill it and all its children on an error. */
+
+  if ((pid = child_open(USS argv, NULL, 0, &fd_in, &fd_out, TRUE)) < 0)
+    {
+    addrlist->message = string_sprintf(
+      "Failed to create child process for %s transport: %s", tblock->name,
+        strerror(errno));
+    return FALSE;
+    }
   }
 
 /* When a socket is specified, expand the string and create a socket. */
@@ -498,38 +515,11 @@ else
         ob->skt, tblock->name, strerror(errno));
     return FALSE;
     }
-  }
 
-/* If the -N option is set, can't do any more. Presume all has gone well. */
+  /* If the -N option is set, can't do any more. Presume all has gone well. */
+  if (dont_deliver)
+    goto MINUS_N;
 
-if (dont_deliver)
-  {
-  DEBUG(D_transport)
-    debug_printf("*** delivery by %s transport bypassed by -N option",
-      tblock->name);
-  addrlist->transport_return = OK;
-  return FALSE;
-  }
-
-/* As this is a local transport, we are already running with the required
-uid/gid and current directory. Request that the new process be a process group
-leader, so we can kill it and all its children on an error. */
-
-if (ob->cmd != NULL)
-  {
-  if ((pid = child_open(argv, NULL, 0, &fd_in, &fd_out, TRUE)) < 0)
-    {
-    addrlist->message = string_sprintf(
-      "Failed to create child process for %s transport: %s", tblock->name,
-        strerror(errno));
-    return FALSE;
-    }
-  }
-
-/* For a socket, try to make the connection */
-
-else
-  {
   sockun.sun_family = AF_UNIX;
   sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1), sockname);
   if(connect(fd_out, (struct sockaddr *)(&sockun), sizeof(sockun)) == -1)
@@ -541,6 +531,7 @@ else
     }
   }
 
+
 /* Make the output we are going to read into a file. */
 
 out = fdopen(fd_out, "rb");
@@ -618,6 +609,12 @@ for (addr = addrlist; addr != NULL; addr = addr->next)
 if (send_data)
   {
   BOOL ok;
+  transport_ctx tctx = {
+    tblock,
+    addrlist,
+    US".", US"..",
+    ob->options
+  };
 
   if (!lmtp_write_command(fd_in, "DATA\r\n")) goto WRITE_FAILED;
   if (!lmtp_read_response(out, buffer, sizeof(buffer), '3', timeout))
@@ -637,9 +634,7 @@ if (send_data)
     debug_printf("  LMTP>> writing message and terminating \".\"\n");
 
   transport_count = 0;
-  ok = transport_write_message(addrlist, fd_in, ob->options, 0,
-        tblock->add_headers, tblock->remove_headers, US".", US"..",
-        tblock->rewrite_rules, tblock->rewrite_existflags);
+  ok = transport_write_message(fd_in, &tctx, 0);
 
   /* Failure can either be some kind of I/O disaster (including timeout),
   or the failure of a transport filter or the expansion of added headers. */
@@ -664,10 +659,11 @@ if (send_data)
     if (lmtp_read_response(out, buffer, sizeof(buffer), '2', timeout))
       {
       addr->transport_return = OK;
-      if ((log_extra_selector & LX_smtp_confirmation) != 0)
+      if (LOGGING(smtp_confirmation))
         {
-        uschar *s = string_printing(buffer);
-        addr->message = (s == buffer)? (uschar *)string_copy(s) : s;
+        const uschar *s = string_printing(buffer);
+       /* de-const safe here as string_printing known to have alloc'n'copied */
+        addr->message = (s == buffer)? (uschar *)string_copy(s) : US s;
         }
       }
     /* If the response has failed badly, use it for all the remaining pending
@@ -784,6 +780,14 @@ DEBUG(D_transport)
   debug_printf("%s transport yields %d\n", tblock->name, yield);
 
 return yield;
+
+
+MINUS_N:
+  DEBUG(D_transport)
+    debug_printf("*** delivery by %s transport bypassed by -N option",
+      tblock->name);
+  addrlist->transport_return = OK;
+  return FALSE;
 }
 
 /* End of transport/lmtp.c */
index 3366a6d..8b87e4a 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -326,22 +326,20 @@ Returns:             TRUE if all went well; otherwise an error will be
 */
 
 static BOOL
-set_up_direct_command(uschar ***argvptr, uschar *cmd, BOOL expand_arguments,
-  int expand_fail, address_item *addr, uschar *tname,
+set_up_direct_command(const uschar ***argvptr, uschar *cmd,
+  BOOL expand_arguments, int expand_fail, address_item *addr, uschar *tname,
   pipe_transport_options_block *ob)
 {
 BOOL permitted = FALSE;
-uschar **argv;
-uschar buffer[64];
+const uschar **argv;
 
 /* Set up "transport <name>" to be put in any error messages, and then
 call the common function for creating an argument list and expanding
 the items if necessary. If it fails, this function fails (error information
 is in the addresses). */
 
-sprintf(CS buffer, "%.50s transport", tname);
 if (!transport_set_up_command(argvptr, cmd, expand_arguments, expand_fail,
-      addr, buffer, NULL))
+      addr, string_sprintf("%.50s transport", tname), NULL))
   return FALSE;
 
 /* Point to the set-up arguments. */
@@ -350,14 +348,13 @@ argv = *argvptr;
 
 /* If allow_commands is set, see if the command is in the permitted list. */
 
-if (ob->allow_commands != NULL)
+if (ob->allow_commands)
   {
   int sep = 0;
-  uschar *s, *p;
-  uschar buffer[256];
+  const uschar *s;
+  uschar *p;
 
-  s = expand_string(ob->allow_commands);
-  if (s == NULL)
+  if (!(s = expand_string(ob->allow_commands)))
     {
     addr->transport_return = DEFER;
     addr->message = string_sprintf("failed to expand string \"%s\" "
@@ -365,10 +362,8 @@ if (ob->allow_commands != NULL)
     return FALSE;
     }
 
-  while ((p = string_nextinlist(&s, &sep, buffer, sizeof(buffer))) != NULL)
-    {
+  while ((p = string_nextinlist(&s, &sep, NULL, 0)))
     if (Ustrcmp(p, argv[0]) == 0) { permitted = TRUE; break; }
-    }
   }
 
 /* If permitted is TRUE it means the command was found in the allowed list, and
@@ -391,7 +386,7 @@ if (!permitted)
       }
     }
 
-  else if (ob->allow_commands != NULL)
+  else if (ob->allow_commands)
     {
     addr->transport_return = FAIL;
     addr->message = string_sprintf("\"%s\" command not permitted by %s "
@@ -407,10 +402,9 @@ if (argv[0][0] != '/')
   {
   int sep = 0;
   uschar *p;
-  uschar *listptr = ob->path;
-  uschar buffer[1024];
+  const uschar *listptr = expand_string(ob->path);
 
-  while ((p = string_nextinlist(&listptr, &sep, buffer, sizeof(buffer))) != NULL)
+  while ((p = string_nextinlist(&listptr, &sep, NULL, 0)))
     {
     struct stat statbuf;
     sprintf(CS big_buffer, "%.256s/%.256s", p, argv[0]);
@@ -420,7 +414,7 @@ if (argv[0][0] != '/')
       break;
       }
     }
-  if (p == NULL)
+  if (!p)
     {
     addr->transport_return = FAIL;
     addr->message = string_sprintf("\"%s\" command not found for %s transport",
@@ -453,10 +447,10 @@ Returns:             TRUE if all went well; otherwise an error will be
 */
 
 static BOOL
-set_up_shell_command(uschar ***argvptr, uschar *cmd, BOOL expand_arguments,
-  int expand_fail, address_item *addr, uschar *tname)
+set_up_shell_command(const uschar ***argvptr, uschar *cmd,
+  BOOL expand_arguments, int expand_fail, address_item *addr, uschar *tname)
 {
-uschar **argv;
+const uschar **argv;
 
 *argvptr = argv = store_get((4)*sizeof(uschar *));
 
@@ -491,11 +485,12 @@ if (expand_arguments)
 
     for (ad = addr; ad != NULL; ad = ad->next)
       {
-      if (ad != addr) string_cat(s, &size, &offset, US" ", 1);
-      string_cat(s, &size, &offset, ad->address, Ustrlen(ad->address));
+      /*XXX string_append_listele() ? */
+      if (ad != addr) s = string_catn(s, &size, &offset, US" ", 1);
+      s = string_cat(s, &size, &offset, ad->address);
       }
 
-    string_cat(s, &size, &offset, q, Ustrlen(q));
+    s = string_cat(s, &size, &offset, q);
     s[offset] = 0;
     }
 
@@ -551,11 +546,18 @@ pipe_transport_options_block *ob =
 int timeout = ob->timeout;
 BOOL written_ok = FALSE;
 BOOL expand_arguments;
-uschar **argv;
+const uschar **argv;
 uschar *envp[50];
-uschar *envlist = ob->environment;
+const uschar *envlist = ob->environment;
 uschar *cmd, *ss;
-uschar *eol = (ob->use_crlf)? US"\r\n" : US"\n";
+uschar *eol = ob->use_crlf ? US"\r\n" : US"\n";
+transport_ctx tctx = {
+  tblock,
+  addr,
+  ob->check_string,
+  ob->escape_string,
+  ob->options /* set at initialization time */
+};
 
 DEBUG(D_transport) debug_printf("%s transport entered\n", tblock->name);
 
@@ -574,7 +576,7 @@ if (testflag(addr, af_pfr) && addr->local_part[0] == '|')
   {
   if (ob->force_command)
     {
-    /* Enables expansion of $address_pipe into seperate arguments */
+    /* Enables expansion of $address_pipe into separate arguments */
     setflag(addr, af_force_command);
     cmd = ob->cmd;
     expand_arguments = TRUE;
@@ -612,7 +614,7 @@ if (cmd == NULL || *cmd == '\0')
 and numerical the variables in existence. These are passed in
 addr->pipe_expandn for use here. */
 
-if (expand_arguments && addr->pipe_expandn != NULL)
+if (expand_arguments && addr->pipe_expandn)
   {
   uschar **ss = addr->pipe_expandn;
   expand_nmax = -1;
@@ -652,7 +654,7 @@ envp[envcount++] = string_sprintf("LOCAL_PART_SUFFIX=%#s",
 envp[envcount++] = string_sprintf("DOMAIN=%s", deliver_domain);
 envp[envcount++] = string_sprintf("HOME=%#s", deliver_home);
 envp[envcount++] = string_sprintf("MESSAGE_ID=%s", message_id);
-envp[envcount++] = string_sprintf("PATH=%s", ob->path);
+envp[envcount++] = string_sprintf("PATH=%s", expand_string(ob->path));
 envp[envcount++] = string_sprintf("RECIPIENT=%#s%#s%#s@%#s",
   deliver_localpart_prefix, deliver_localpart, deliver_localpart_suffix,
   deliver_domain);
@@ -669,9 +671,9 @@ else if (timezone_string != NULL && timezone_string[0] != 0)
 
 /* Add any requested items */
 
-if (envlist != NULL)
+if (envlist)
   {
-  envlist = expand_string(envlist);
+  envlist = expand_cstring(envlist);
   if (envlist == NULL)
     {
     addr->transport_return = DEFER;
@@ -729,7 +731,7 @@ reading of the output pipe. */
 uid/gid and current directory. Request that the new process be a process group
 leader, so we can kill it and all its children on a timeout. */
 
-if ((pid = child_open(argv, envp, ob->umask, &fd_in, &fd_out, TRUE)) < 0)
+if ((pid = child_open(USS argv, envp, ob->umask, &fd_in, &fd_out, TRUE)) < 0)
   {
   addr->transport_return = DEFER;
   addr->message = string_sprintf(
@@ -843,31 +845,27 @@ if (ob->use_bsmtp)
   if (!transport_write_string(fd_in, "MAIL FROM:<%s>%s", return_path, eol))
     goto END_WRITE;
 
-  for (a = addr; a != NULL; a = a->next)
-    {
+  for (a = addr; a; a = a->next)
     if (!transport_write_string(fd_in,
         "RCPT TO:<%s>%s",
         transport_rcpt_address(a, tblock->rcpt_include_affixes),
         eol))
       goto END_WRITE;
-    }
 
   if (!transport_write_string(fd_in, "DATA%s", eol)) goto END_WRITE;
   }
 
-/* Now the actual message - the options were set at initialization time */
+/* Now the actual message */
 
-if (!transport_write_message(addr, fd_in, ob->options, 0, tblock->add_headers,
-  tblock->remove_headers, ob->check_string, ob->escape_string,
-  tblock->rewrite_rules, tblock->rewrite_existflags))
+if (!transport_write_message(fd_in, &tctx, 0))
     goto END_WRITE;
 
 /* Now any configured suffix */
 
-if (ob->message_suffix != NULL)
+if (ob->message_suffix)
   {
   uschar *suffix = expand_string(ob->message_suffix);
-  if (suffix == NULL)
+  if (!suffix)
     {
     addr->transport_return = search_find_defer? DEFER : PANIC;
     addr->message = string_sprintf("Expansion of \"%s\" (suffix for %s "
@@ -1026,7 +1024,7 @@ if ((rc = child_close(pid, timeout)) != 0)
   the command that was given is a non-existent path). By default this is
   treated as just another failure, but if freeze_exec_fail is set, the reaction
   is to freeze the message rather than bounce the address. Exim used to signal
-  this failure with EX_UNAVAILABLE, which is definined in many systems as
+  this failure with EX_UNAVAILABLE, which is defined in many systems as
 
       #define EX_UNAVAILABLE  69
 
@@ -1064,7 +1062,7 @@ if ((rc = child_close(pid, timeout)) != 0)
       uschar *ss;
       int size, ptr, i;
 
-      /* If temp_errors is "*" all codes are temporary. Initializion checks
+      /* If temp_errors is "*" all codes are temporary. Initialization checks
       that it's either "*" or a list of numbers. If not "*", scan the list of
       temporary failure codes; if any match, the result is DEFER. */
 
@@ -1073,16 +1071,13 @@ if ((rc = child_close(pid, timeout)) != 0)
 
       else
         {
-        uschar *s = ob->temp_errors;
+        const uschar *s = ob->temp_errors;
         uschar *p;
-        uschar buffer[64];
         int sep = 0;
 
         addr->transport_return = FAIL;
-        while ((p = string_nextinlist(&s,&sep,buffer,sizeof(buffer))) != NULL)
-          {
+        while ((p = string_nextinlist(&s,&sep,NULL,0)))
           if (rc == Uatoi(p)) { addr->transport_return = DEFER; break; }
-          }
         }
 
       /* Ensure the message contains the expanded command and arguments. This
@@ -1104,36 +1099,33 @@ if ((rc = child_close(pid, timeout)) != 0)
 
       if (*ss != 0)
         {
-        addr->message = string_cat(addr->message, &size, &ptr, US" ", 1);
-        addr->message = string_cat(addr->message, &size, &ptr,
-          ss, Ustrlen(ss));
+        addr->message = string_catn(addr->message, &size, &ptr, US" ", 1);
+        addr->message = string_cat (addr->message, &size, &ptr, ss);
         }
 
       /* Now add the command and arguments */
 
-      addr->message = string_cat(addr->message, &size, &ptr,
+      addr->message = string_catn(addr->message, &size, &ptr,
         US" from command:", 14);
 
       for (i = 0; i < sizeof(argv)/sizeof(int *) && argv[i] != NULL; i++)
         {
         BOOL quote = FALSE;
-        addr->message = string_cat(addr->message, &size, &ptr, US" ", 1);
+        addr->message = string_catn(addr->message, &size, &ptr, US" ", 1);
         if (Ustrpbrk(argv[i], " \t") != NULL)
           {
           quote = TRUE;
-          addr->message = string_cat(addr->message, &size, &ptr, US"\"", 1);
+          addr->message = string_catn(addr->message, &size, &ptr, US"\"", 1);
           }
-        addr->message = string_cat(addr->message, &size, &ptr, argv[i],
-          Ustrlen(argv[i]));
+        addr->message = string_cat(addr->message, &size, &ptr, argv[i]);
         if (quote)
-          addr->message = string_cat(addr->message, &size, &ptr, US"\"", 1);
+          addr->message = string_catn(addr->message, &size, &ptr, US"\"", 1);
         }
 
       /* Add previous filter timeout message, if present. */
 
-      if (*tmsg != 0)
-        addr->message = string_cat(addr->message, &size, &ptr, tmsg,
-          Ustrlen(tmsg));
+      if (*tmsg)
+        addr->message = string_cat(addr->message, &size, &ptr, tmsg);
 
       addr->message[ptr] = 0;  /* Ensure concatenated string terminated */
       }
diff --git a/src/transports/queuefile.c b/src/transports/queuefile.c
new file mode 100644 (file)
index 0000000..7f10706
--- /dev/null
@@ -0,0 +1,256 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Andrew Colin Kissa <andrew@topdog.za.net> 2016 */
+/* Copyright (c) University of Cambridge 2016 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+
+#include "../exim.h"
+#include "queuefile.h"
+
+/* Options specific to the appendfile transport. They must be in alphabetic
+order (note that "_" comes before the lower case letters). Some of them are
+stored in the publicly visible instance block - these are flagged with the
+opt_public flag. */
+
+optionlist queuefile_transport_options[] = {
+  { "directory", opt_stringptr,
+    (void *)offsetof(queuefile_transport_options_block, dirname) },
+};
+
+/* Size of the options list. An extern variable has to be used so that its
+address can appear in the tables drtables.c. */
+
+int queuefile_transport_options_count =
+  sizeof(queuefile_transport_options) / sizeof(optionlist);
+
+/* Default private options block for the appendfile transport. */
+
+queuefile_transport_options_block queuefile_transport_option_defaults = {
+  NULL,           /* dirname */
+};
+
+/*************************************************
+*          Initialization entry point            *
+*************************************************/
+
+void queuefile_transport_init(transport_instance *tblock)
+{
+queuefile_transport_options_block *ob =
+  (queuefile_transport_options_block *) tblock->options_block;
+
+if (!ob->dirname)
+  log_write(0, LOG_PANIC_DIE | LOG_CONFIG,
+    "directory must be set for the %s transport", tblock->name);
+}
+
+/* This function will copy from a file to another
+
+Arguments:
+  dst        fd to write to (the destination queue file)
+  src        fd to read from (the spool queue file)
+
+Returns:       TRUE if all went well, FALSE otherwise with errno set
+*/
+
+static BOOL
+copy_spool_file(int dst, int src)
+{
+int i, j;
+uschar buffer[16384];
+uschar * s;
+
+if (lseek(src, 0, SEEK_SET) != 0)
+  return FALSE;
+
+do
+  if ((j = read(src, buffer, sizeof(buffer))) > 0)
+    for (s = buffer; (i = write(dst, s, j)) != j; s += i, j -= i)
+      if (i < 0)
+       return FALSE;
+  else if (j < 0)
+    return FALSE;
+while (j > 0);
+return TRUE;
+}
+
+/* This function performs the actual copying of the header
+and data files to the destination directory
+
+Arguments:
+  tb           the transport block
+  addr          address_item being processed
+  sdfd          int Source directory fd
+  ddfd          int Destination directory fd
+  link_file     BOOL use linkat instead of data copy
+  srcfd                fd for data file, or -1 for header file
+
+Returns:       TRUE if all went well, FALSE otherwise
+*/
+
+static BOOL
+copy_spool_files(transport_instance * tb, address_item * addr,
+  int sdfd, int ddfd, BOOL link_file, int srcfd)
+{
+BOOL is_hdr_file = srcfd < 0;
+const uschar * suffix = srcfd < 0 ? US"H" : US"D";
+int dstfd;
+const uschar * filename = string_sprintf("%s-%s", message_id, suffix);
+const uschar * srcpath = spool_fname(US"input", message_subdir, message_id, suffix);
+const uschar * dstpath = string_sprintf("%s/%s-%s",
+  ((queuefile_transport_options_block *) tb->options_block)->dirname,
+  message_id, suffix);
+const uschar * s;
+const uschar * op;
+
+if (link_file)
+  {
+  DEBUG(D_transport) debug_printf("%s transport, linking %s => %s\n",
+    tb->name, srcpath, dstpath);
+
+  if (linkat(sdfd, CCS filename, ddfd, CCS filename, 0) >= 0)
+    return TRUE;
+
+  op = US"linking";
+  s = dstpath;
+  }
+else                                   /* use data copy */
+  {
+  DEBUG(D_transport) debug_printf("%s transport, copying %s => %s\n",
+    tb->name, srcpath, dstpath);
+
+  if (  (s = dstpath,
+        (dstfd = openat(ddfd, CCS filename, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE))
+        < 0
+       )
+     ||    is_hdr_file
+       && (s = srcpath, (srcfd = openat(sdfd, CCS filename, O_RDONLY)) < 0)
+     )
+    op = US"opening";
+
+  else
+    if (s = dstpath, fchmod(dstfd, SPOOL_MODE) != 0)
+      op = US"setting perms on";
+    else
+      if (!copy_spool_file(dstfd, srcfd))
+       op = US"creating";
+      else
+       return TRUE;
+  }
+
+addr->basic_errno = errno;
+addr->message = string_sprintf("%s transport %s file: %s failed with error: %s",
+  tb->name, op, s, strerror(errno));
+addr->transport_return = DEFER;
+return FALSE;
+}
+
+/*************************************************
+*              Main entry point                  *
+*************************************************/
+
+/* This transport always returns FALSE, indicating that the status in
+the first address is the status for all addresses in a batch. */
+
+BOOL
+queuefile_transport_entry(transport_instance * tblock, address_item * addr)
+{
+queuefile_transport_options_block * ob =
+  (queuefile_transport_options_block *) tblock->options_block;
+BOOL can_link;
+uschar * sourcedir = spool_dname(US"input", message_subdir);
+uschar * s;
+struct stat dstatbuf, sstatbuf;
+int ddfd = -1, sdfd = -1;
+
+DEBUG(D_transport)
+  debug_printf("%s transport entered\n", tblock->name);
+
+#ifndef O_DIRECTORY
+# define O_DIRECTORY 0
+#endif
+#ifndef O_NOFOLLOW
+# define O_NOFOLLOW 0
+#endif
+
+if (ob->dirname[0] != '/')
+  {
+  addr->transport_return = PANIC;
+  addr->message = string_sprintf("%s transport directory: "
+    "%s is not absolute", tblock->name, ob->dirname);
+  return FALSE;
+  }
+
+/* Open the source and destination directories and check if they are
+on the same filesystem, so we can hard-link files rather than copying. */
+
+if (  (s = ob->dirname,
+       (ddfd = Uopen(s, O_RDONLY | O_DIRECTORY | O_NOFOLLOW, 0)) < 0)
+   || (s = sourcedir,
+       (sdfd = Uopen(sourcedir, O_RDONLY | O_DIRECTORY | O_NOFOLLOW, 0)) < 0)
+   )
+  {
+  addr->transport_return = PANIC;
+  addr->basic_errno = errno;
+  addr->message = string_sprintf("%s transport accessing directory: %s "
+    "failed with error: %s", tblock->name, s, strerror(errno));
+  if (ddfd >= 0) (void) close(ddfd);
+  return FALSE;
+  }
+
+if (  (s = ob->dirname, fstat(ddfd, &dstatbuf) < 0)
+   || (s = sourcedir,   fstat(sdfd, &sstatbuf) < 0)
+   )
+  {
+  addr->transport_return = PANIC;
+  addr->basic_errno = errno;
+  addr->message = string_sprintf("%s transport fstat on directory fd: "
+    "%s failed with error: %s", tblock->name, s, strerror(errno));
+  goto RETURN;
+  }
+can_link = (dstatbuf.st_dev == sstatbuf.st_dev);
+
+if (dont_deliver)
+  {
+  DEBUG(D_transport)
+    debug_printf("*** delivery by %s transport bypassed by -N option\n",
+      tblock->name);
+  addr->transport_return = OK;
+  goto RETURN;
+  }
+
+/* Link or copy the header and data spool files */
+
+DEBUG(D_transport)
+  debug_printf("%s transport, copying header file\n", tblock->name);
+
+if (!copy_spool_files(tblock, addr, sdfd, ddfd, can_link, -1))
+  goto RETURN;
+
+DEBUG(D_transport)
+  debug_printf("%s transport, copying data file\n", tblock->name);
+
+if (!copy_spool_files(tblock, addr, sdfd, ddfd, can_link, deliver_datafile))
+  {
+  DEBUG(D_transport)
+    debug_printf("%s transport, copying data file failed, "
+      "unlinking the header file\n", tblock->name);
+  Uunlink(string_sprintf("%s/%s-H", ob->dirname, message_id));
+  goto RETURN;
+  }
+
+DEBUG(D_transport)
+  debug_printf("%s transport succeeded\n", tblock->name);
+
+addr->transport_return = OK;
+
+RETURN:
+if (ddfd >= 0) (void) close(ddfd);
+if (sdfd >= 0) (void) close(sdfd);
+
+/* A return of FALSE means that if there was an error, a common error was
+put in the first address of a batch. */
+return FALSE;
+}
diff --git a/src/transports/queuefile.h b/src/transports/queuefile.h
new file mode 100644 (file)
index 0000000..0e45b51
--- /dev/null
@@ -0,0 +1,29 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Andrew Colin Kissa <andrew@topdog.za.net> 2016 */
+/* Copyright (c) University of Cambridge 2016 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* Private structure for the private options. */
+
+typedef struct {
+    uschar *dirname;
+} queuefile_transport_options_block;
+
+/* Data for reading the private options. */
+
+extern optionlist queuefile_transport_options[];
+extern int queuefile_transport_options_count;
+
+/* Block containing default values. */
+
+extern queuefile_transport_options_block queuefile_transport_option_defaults;
+
+/* The main and init entry points for the transport */
+
+extern BOOL queuefile_transport_entry(transport_instance *, address_item *);
+extern void queuefile_transport_init(transport_instance *);
+
+/* End of transports/queuefile.h */
index 40eebe8..0bbd8d9 100644 (file)
@@ -2,16 +2,12 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2017 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
 #include "smtp.h"
 
-#define PENDING          256
-#define PENDING_DEFER   (PENDING + DEFER)
-#define PENDING_OK      (PENDING + OK)
-
 
 /* Options specific to the smtp transport. This transport also supports LMTP
 over TCP/IP. The options must be in alphabetic order (note that "_" comes
@@ -19,6 +15,11 @@ before the lower case letters). Some live in the transport_instance block so as
 to be publicly visible; these are flagged with opt_public. */
 
 optionlist smtp_transport_options[] = {
+  { "*expand_multi_domain",             opt_stringptr | opt_hidden | opt_public,
+      (void *)offsetof(transport_instance, expand_multi_domain) },
+  { "*expand_retry_include_ip_address", opt_stringptr | opt_hidden,
+       (void *)(offsetof(smtp_transport_options_block, expand_retry_include_ip_address)) },
+
   { "address_retry_include_sender", opt_bool,
       (void *)offsetof(smtp_transport_options_block, address_retry_include_sender) },
   { "allow_localhost",      opt_bool,
@@ -39,26 +40,26 @@ optionlist smtp_transport_options[] = {
       (void *)offsetof(smtp_transport_options_block, delay_after_cutoff) },
 #ifndef DISABLE_DKIM
   { "dkim_canon", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, dkim_canon) },
+      (void *)offsetof(smtp_transport_options_block, dkim.dkim_canon) },
   { "dkim_domain", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, dkim_domain) },
+      (void *)offsetof(smtp_transport_options_block, dkim.dkim_domain) },
   { "dkim_private_key", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, dkim_private_key) },
+      (void *)offsetof(smtp_transport_options_block, dkim.dkim_private_key) },
   { "dkim_selector", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, dkim_selector) },
+      (void *)offsetof(smtp_transport_options_block, dkim.dkim_selector) },
   { "dkim_sign_headers", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, dkim_sign_headers) },
+      (void *)offsetof(smtp_transport_options_block, dkim.dkim_sign_headers) },
   { "dkim_strict", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, dkim_strict) },
+      (void *)offsetof(smtp_transport_options_block, dkim.dkim_strict) },
 #endif
   { "dns_qualify_single",   opt_bool,
       (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) },
+      (void *)offsetof(smtp_transport_options_block, dnssec.request) },
   { "dnssec_require_domains", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, dnssec_require_domains) },
+      (void *)offsetof(smtp_transport_options_block, dnssec.require) },
   { "dscp",                 opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, dscp) },
   { "fallback_hosts",       opt_stringptr,
@@ -67,17 +68,6 @@ optionlist smtp_transport_options[] = {
       (void *)offsetof(smtp_transport_options_block, final_timeout) },
   { "gethostbyname",        opt_bool,
       (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; 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) },
-  { "gnutls_require_mac",   opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, gnutls_require_mac) },
-  { "gnutls_require_protocols", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, gnutls_require_proto) },
-#endif
   { "helo_data",            opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, helo_data) },
   { "hosts",                opt_stringptr,
@@ -109,6 +99,10 @@ optionlist smtp_transport_options[] = {
   { "hosts_require_auth",   opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_require_auth) },
 #ifdef SUPPORT_TLS
+# ifdef EXPERIMENTAL_DANE
+  { "hosts_require_dane",   opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, hosts_require_dane) },
+# endif
 # ifndef DISABLE_OCSP
   { "hosts_require_ocsp",   opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_require_ocsp) },
@@ -118,6 +112,14 @@ optionlist smtp_transport_options[] = {
 #endif
   { "hosts_try_auth",       opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_try_auth) },
+  { "hosts_try_chunking",   opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, hosts_try_chunking) },
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
+  { "hosts_try_dane",       opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, hosts_try_dane) },
+#endif
+  { "hosts_try_fastopen",   opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, hosts_try_fastopen) },
 #ifndef DISABLE_PRDR
   { "hosts_try_prdr",       opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_try_prdr) },
@@ -134,18 +136,22 @@ optionlist smtp_transport_options[] = {
       (void *)offsetof(smtp_transport_options_block, lmtp_ignore_quota) },
   { "max_rcpt",             opt_int | opt_public,
       (void *)offsetof(transport_instance, max_addresses) },
-  { "multi_domain",         opt_bool | opt_public,
+  { "multi_domain",         opt_expand_bool | opt_public,
       (void *)offsetof(transport_instance, multi_domain) },
   { "port",                 opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, port) },
   { "protocol",             opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, protocol) },
-  { "retry_include_ip_address", opt_bool,
+  { "retry_include_ip_address", opt_expand_bool,
       (void *)offsetof(smtp_transport_options_block, retry_include_ip_address) },
   { "serialize_hosts",      opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, serialize_hosts) },
   { "size_addition",        opt_int,
       (void *)offsetof(smtp_transport_options_block, size_addition) }
+#ifdef SUPPORT_SOCKS
+ ,{ "socks_proxy",          opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, socks_proxy) }
+#endif
 #ifdef SUPPORT_TLS
  ,{ "tls_certificate",      opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, tls_certificate) },
@@ -163,19 +169,13 @@ optionlist smtp_transport_options[] = {
       (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) },
   { "tls_verify_hosts",     opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, tls_verify_hosts) }
 #endif
-#ifdef EXPERIMENTAL_TPDA
- ,{ "tpda_host_defer_action", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, tpda_host_defer_action) },
-#endif
 };
 
 /* Size of the options list. An extern variable has to be used so that its
@@ -200,16 +200,22 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   NULL,                /* serialize_hosts */
   NULL,                /* hosts_try_auth */
   NULL,                /* hosts_require_auth */
+  US"*",               /* hosts_try_chunking */
+#ifdef EXPERIMENTAL_DANE
+  NULL,                /* hosts_try_dane */
+  NULL,                /* hosts_require_dane */
+#endif
+  NULL,                /* hosts_try_fastopen */
 #ifndef DISABLE_PRDR
-  NULL,                /* hosts_try_prdr */
+  US"*",               /* hosts_try_prdr */
 #endif
 #ifndef DISABLE_OCSP
-  US"*",               /* hosts_request_ocsp */
+  US"*",               /* hosts_request_ocsp (except under DANE; tls_client_start()) */
   NULL,                /* hosts_require_ocsp */
 #endif
   NULL,                /* hosts_require_tls */
   NULL,                /* hosts_avoid_tls */
-  US"*",               /* hosts_verify_avoid_tls */
+  NULL,                /* hosts_verify_avoid_tls */
   NULL,                /* hosts_avoid_pipelining */
   NULL,                /* hosts_avoid_esmtp */
   NULL,                /* hosts_nopass_tls */
@@ -226,62 +232,58 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   FALSE,               /* gethostbyname */
   TRUE,                /* dns_qualify_single */
   FALSE,               /* dns_search_parents */
-  NULL,                /* dnssec_request_domains */
-  NULL,                /* dnssec_require_domains */
+  { NULL, NULL },      /* dnssec_domains {request,require} */
   TRUE,                /* delay_after_cutoff */
   FALSE,               /* hosts_override */
   FALSE,               /* hosts_randomize */
   TRUE,                /* keepalive */
   FALSE,               /* lmtp_ignore_quota */
+  NULL,                       /* expand_retry_include_ip_address */
   TRUE                 /* retry_include_ip_address */
+#ifdef SUPPORT_SOCKS
+ ,NULL                 /* socks_proxy */
+#endif
 #ifdef SUPPORT_TLS
  ,NULL,                /* tls_certificate */
   NULL,                /* tls_crl */
   NULL,                /* tls_privatekey */
   NULL,                /* tls_require_ciphers */
-  NULL,                /* gnutls_require_kx */
-  NULL,                /* gnutls_require_mac */
-  NULL,                /* gnutls_require_proto */
   NULL,                /* tls_sni */
-  NULL,                /* tls_verify_certificates */
+  US"system",          /* tls_verify_certificates */
   EXIM_CLIENT_DH_DEFAULT_MIN_BITS,
                        /* tls_dh_min_bits */
   TRUE,                /* tls_tempfail_tryclear */
   NULL,                /* tls_verify_hosts */
-  NULL                 /* tls_try_verify_hosts */
-# ifdef EXPERIMENTAL_CERTNAMES
- ,NULL                 /* tls_verify_cert_hostnames */
-# endif
+  US"*",               /* tls_try_verify_hosts */
+  US"*"                /* tls_verify_cert_hostnames */
 #endif
 #ifndef DISABLE_DKIM
- ,NULL,                /* dkim_canon */
-  NULL,                /* dkim_domain */
-  NULL,                /* dkim_private_key */
-  NULL,                /* dkim_selector */
-  NULL,                /* dkim_sign_headers */
-  NULL                 /* dkim_strict */
-#endif
-#ifdef EXPERIMENTAL_TPDA
- ,NULL                 /* tpda_host_defer_action */
+ , {NULL,              /* dkim_canon */
+    NULL,              /* dkim_domain */
+    NULL,              /* dkim_private_key */
+    NULL,              /* dkim_selector */
+    NULL,              /* dkim_sign_headers */
+    NULL,              /* dkim_strict */
+    FALSE}            /* dot_stuffed */
 #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
+static uschar *rf_names[] = { US"NEVER", US"SUCCESS", US"FAILURE", US"DELAY" };
 
 
 
 /* Local statics */
 
-static uschar *smtp_command;   /* Points to last cmd for error messages */
-static uschar *mail_command;   /* Points to MAIL cmd for error messages */
-static BOOL    update_waiting; /* TRUE to update the "wait" database */
+static uschar *smtp_command;           /* Points to last cmd for error messages */
+static uschar *mail_command;           /* Points to MAIL cmd for error messages */
+static uschar *data_command = US"";    /* Points to DATA cmd for error messages */
+static BOOL    update_waiting;         /* TRUE to update the "wait" database */
+static BOOL    pipelining_active;      /* current transaction is in pipe mode */
 
 
 /*************************************************
@@ -319,7 +321,7 @@ gid = gid;
 
 /* Pass back options if required. This interface is getting very messy. */
 
-if (tf != NULL)
+if (tf)
   {
   tf->interface = ob->interface;
   tf->port = ob->port;
@@ -338,11 +340,8 @@ host lists, provided that the local host wasn't present in the original host
 list. */
 
 if (!testflag(addrlist, af_local_host_removed))
-  {
-  for (; addrlist != NULL; addrlist = addrlist->next)
-    if (addrlist->fallback_hosts == NULL)
-      addrlist->fallback_hosts = ob->fallback_hostlist;
-  }
+  for (; addrlist; addrlist = addrlist->next)
+    if (!addrlist->fallback_hosts) addrlist->fallback_hosts = ob->fallback_hostlist;
 
 return OK;
 }
@@ -400,15 +399,6 @@ if (ob->hosts_override && ob->hosts != NULL) tblock->overrides_hosts = TRUE;
 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
 }
 
 
@@ -428,6 +418,9 @@ Arguments:
   msg            to put in each address's message field
   rc             to put in each address's transport_return field
   pass_message   if TRUE, set the "pass message" flag in the address
+  host           if set, mark addrs as having used this host
+  smtp_greeting  from peer
+  helo_response  from peer
 
 If errno_value has the special value ERRNO_CONNECTTIMEOUT, ETIMEDOUT is put in
 the errno field, and RTEF_CTOUT is ORed into the more_errno field, to indicate
@@ -438,7 +431,11 @@ Returns:       nothing
 
 static void
 set_errno(address_item *addrlist, int errno_value, uschar *msg, int rc,
-  BOOL pass_message)
+  BOOL pass_message, host_item * host
+#ifdef EXPERIMENTAL_DSN_INFO
+  , const uschar * smtp_greeting, const uschar * helo_response
+#endif
+  )
 {
 address_item *addr;
 int orvalue = 0;
@@ -447,20 +444,43 @@ if (errno_value == ERRNO_CONNECTTIMEOUT)
   errno_value = ETIMEDOUT;
   orvalue = RTEF_CTOUT;
   }
-for (addr = addrlist; addr != NULL; addr = addr->next)
-  {
-  if (addr->transport_return < PENDING) continue;
-  addr->basic_errno = errno_value;
-  addr->more_errno |= orvalue;
-  if (msg != NULL)
+for (addr = addrlist; addr; addr = addr->next)
+  if (addr->transport_return >= PENDING)
     {
-    addr->message = msg;
-    if (pass_message) setflag(addr, af_pass_message);
+    addr->basic_errno = errno_value;
+    addr->more_errno |= orvalue;
+    if (msg)
+      {
+      addr->message = msg;
+      if (pass_message) setflag(addr, af_pass_message);
+      }
+    addr->transport_return = rc;
+    if (host)
+      {
+      addr->host_used = host;
+#ifdef EXPERIMENTAL_DSN_INFO
+      if (smtp_greeting)
+       {uschar * s = Ustrchr(smtp_greeting, '\n'); if (s) *s = '\0';}
+      addr->smtp_greeting = smtp_greeting;
+
+      if (helo_response)
+       {uschar * s = Ustrchr(helo_response, '\n'); if (s) *s = '\0';}
+      addr->helo_response = helo_response;
+#endif
+      }
     }
-  addr->transport_return = rc;
-  }
 }
 
+static void
+set_errno_nohost(address_item *addrlist, int errno_value, uschar *msg, int rc,
+  BOOL pass_message)
+{
+set_errno(addrlist, errno_value, msg, rc, pass_message, NULL
+#ifdef EXPERIMENTAL_DSN_INFO
+         , NULL, NULL
+#endif
+         );
+}
 
 
 /*************************************************
@@ -479,90 +499,73 @@ Arguments:
   more_errno     from the top address for use with ERRNO_FILTER_FAIL
   buffer         the SMTP response buffer
   yield          where to put a one-digit SMTP response code
-  message        where to put an errror message
+  message        where to put an error message
   pass_message   set TRUE if message is an SMTP response
 
 Returns:         TRUE if an SMTP "QUIT" command should be sent, else FALSE
 */
 
-static BOOL check_response(host_item *host, int *errno_value, int more_errno,
+static BOOL
+check_response(host_item *host, int *errno_value, int more_errno,
   uschar *buffer, int *yield, uschar **message, BOOL *pass_message)
 {
-uschar *pl = US"";
-
-if (smtp_use_pipelining &&
-    (Ustrcmp(smtp_command, "MAIL") == 0 ||
-     Ustrcmp(smtp_command, "RCPT") == 0 ||
-     Ustrcmp(smtp_command, "DATA") == 0))
-  pl = US"pipelined ";
+uschar * pl = pipelining_active ? US"pipelined " : US"";
+const uschar * s;
 
 *yield = '4';    /* Default setting is to give a temporary error */
 
-/* Handle response timeout */
-
-if (*errno_value == ETIMEDOUT)
-  {
-  *message = US string_sprintf("SMTP timeout while connected to %s [%s] "
-    "after %s%s", host->name, host->address, pl, smtp_command);
-  if (transport_count > 0)
-    *message = US string_sprintf("%s (%d bytes written)", *message,
-      transport_count);
-  return FALSE;
-  }
-
-/* Handle malformed SMTP response */
-
-if (*errno_value == ERRNO_SMTPFORMAT)
-  {
-  uschar *malfresp = string_printing(buffer);
-  while (isspace(*malfresp)) malfresp++;
-  if (*malfresp == 0)
-    *message = string_sprintf("Malformed SMTP reply (an empty line) from "
-      "%s [%s] in response to %s%s", host->name, host->address, pl,
-      smtp_command);
-  else
-    *message = string_sprintf("Malformed SMTP reply from %s [%s] in response "
-      "to %s%s: %s", host->name, host->address, pl, smtp_command, malfresp);
-  return FALSE;
-  }
-
-/* Handle a failed filter process error; can't send QUIT as we mustn't
-end the DATA. */
-
-if (*errno_value == ERRNO_FILTER_FAIL)
-  {
-  *message = US string_sprintf("transport filter process failed (%d)%s",
-    more_errno,
-    (more_errno == EX_EXECFAILED)? ": unable to execute command" : "");
-  return FALSE;
-  }
-
-/* Handle a failed add_headers expansion; can't send QUIT as we mustn't
-end the DATA. */
-
-if (*errno_value == ERRNO_CHHEADER_FAIL)
+switch(*errno_value)
   {
-  *message =
-    US string_sprintf("failed to expand headers_add or headers_remove: %s",
-      expand_string_message);
-  return FALSE;
-  }
-
-/* Handle failure to write a complete data block */
-
-if (*errno_value == ERRNO_WRITEINCOMPLETE)
-  {
-  *message = US string_sprintf("failed to write a data block");
-  return FALSE;
+  case ETIMEDOUT:              /* Handle response timeout */
+    *message = US string_sprintf("SMTP timeout after %s%s",
+       pl, smtp_command);
+    if (transport_count > 0)
+      *message = US string_sprintf("%s (%d bytes written)", *message,
+       transport_count);
+    return FALSE;
+
+  case ERRNO_SMTPFORMAT:       /* Handle malformed SMTP response */
+    s = string_printing(buffer);
+    while (isspace(*s)) s++;
+    *message = *s == 0
+      ? string_sprintf("Malformed SMTP reply (an empty line) "
+         "in response to %s%s", pl, smtp_command)
+      : string_sprintf("Malformed SMTP reply in response to %s%s: %s",
+         pl, smtp_command, s);
+    return FALSE;
+
+  case ERRNO_FILTER_FAIL:      /* Handle a failed filter process error;
+                         can't send QUIT as we mustn't end the DATA. */
+    *message = string_sprintf("transport filter process failed (%d)%s",
+      more_errno,
+      more_errno == EX_EXECFAILED ? ": unable to execute command" : "");
+    return FALSE;
+
+  case ERRNO_CHHEADER_FAIL:    /* Handle a failed add_headers expansion;
+                           can't send QUIT as we mustn't end the DATA. */
+    *message =
+      string_sprintf("failed to expand headers_add or headers_remove: %s",
+       expand_string_message);
+    return FALSE;
+
+  case ERRNO_WRITEINCOMPLETE:  /* failure to write a complete data block */
+    *message = string_sprintf("failed to write a data block");
+    return FALSE;
+
+#ifdef SUPPORT_I18N
+  case ERRNO_UTF8_FWD: /* no advertised SMTPUTF8, for international message */
+    *message = US"utf8 support required but not offered for forwarding";
+    DEBUG(D_deliver|D_transport) debug_printf("%s\n", *message);
+    return TRUE;
+#endif
   }
 
 /* Handle error responses from the remote mailer. */
 
 if (buffer[0] != 0)
   {
-  uschar *s = string_printing(buffer);
-  *message = US string_sprintf("SMTP error from remote mail server after %s%s: "
-    "host %s [%s]: %s", pl, smtp_command, host->name, host->address, s);
+  *message = string_sprintf("SMTP error from remote mail server after %s%s: "
+    "%s", pl, smtp_command, s = string_printing(buffer));
   *pass_message = TRUE;
   *yield = buffer[0];
   return TRUE;
@@ -577,10 +580,11 @@ assume the connection is now dead. */
 if (*errno_value == 0 || *errno_value == ECONNRESET)
   {
   *errno_value = ERRNO_SMTPCLOSED;
-  *message = US string_sprintf("Remote host %s [%s] closed connection "
-    "in response to %s%s", host->name, host->address, pl, smtp_command);
+  *message = US string_sprintf("Remote host closed connection "
+    "in response to %s%s", pl, smtp_command);
   }
-else *message = US string_sprintf("%s [%s]", host->name, host->address);
+else
+  *message = US string_sprintf("%s [%s]", host->name, host->address);
 
 return FALSE;
 }
@@ -603,9 +607,14 @@ Returns:   nothing
 static void
 write_logs(address_item *addr, host_item *host)
 {
-if (addr->message != NULL)
+uschar * message = LOGGING(outgoing_port)
+  ? string_sprintf("H=%s [%s]:%d", host->name, host->address,
+                   host->port == PORT_NONE ? 25 : host->port)
+  : string_sprintf("H=%s [%s]", host->name, host->address);
+
+if (addr->message)
   {
-  uschar *message = addr->message;
+  message = string_sprintf("%s: %s", message, addr->message);
   if (addr->basic_errno > 0)
     message = string_sprintf("%s: %s", message, strerror(addr->basic_errno));
   log_write(0, LOG_MAIN, "%s", message);
@@ -613,21 +622,22 @@ if (addr->message != NULL)
   }
 else
   {
-  uschar *msg =
-    ((log_extra_selector & LX_outgoing_port) != 0)?
-    string_sprintf("%s [%s]:%d", host->name, host->address,
-      (host->port == PORT_NONE)? 25 : host->port)
-    :
-    string_sprintf("%s [%s]", host->name, host->address);
-  log_write(0, LOG_MAIN, "%s %s", msg, strerror(addr->basic_errno));
-  deliver_msglog("%s %s %s\n", tod_stamp(tod_log), msg,
-    strerror(addr->basic_errno));
+  const uschar * s = exim_errstr(addr->basic_errno);
+  log_write(0, LOG_MAIN, "%s %s", message, s);
+  deliver_msglog("%s %s %s\n", tod_stamp(tod_log), message, s);
   }
 }
 
+static void
+msglog_line(host_item * host, uschar * message)
+{
+deliver_msglog("%s H=%s [%s] %s\n", tod_stamp(tod_log),
+  host->name, host->address, message);
+}
+
 
 
-#ifdef EXPERIMENTAL_TPDA
+#ifndef DISABLE_EVENT
 /*************************************************
 *   Post-defer action                            *
 *************************************************/
@@ -636,7 +646,6 @@ else
    It might, for example, be used to write to the database log.
 
 Arguments:
-  ob                    transport options block
   addr                  the address item containing error information
   host                  the current host
 
@@ -644,42 +653,43 @@ Returns:   nothing
 */
 
 static void
-tpda_deferred(smtp_transport_options_block *ob, address_item *addr, host_item *host)
+deferred_event_raise(address_item *addr, host_item *host)
 {
-uschar *action = ob->tpda_host_defer_action;
+uschar * action = addr->transport->event_action;
+const uschar * save_domain;
+uschar * save_local;
+
 if (!action)
-       return;
-
-tpda_delivery_ip =         string_copy(host->address);
-tpda_delivery_port =       (host->port == PORT_NONE)? 25 : host->port;
-tpda_delivery_fqdn =       string_copy(host->name);
-tpda_delivery_local_part = string_copy(addr->local_part);
-tpda_delivery_domain =     string_copy(addr->domain);
-tpda_defer_errno =         addr->basic_errno;
-
-tpda_defer_errstr = addr->message
-  ? addr->basic_errno > 0
-    ? string_sprintf("%s: %s", addr->message, strerror(addr->basic_errno))
-    : string_copy(addr->message)
-  : addr->basic_errno > 0
-    ? string_copy(US strerror(addr->basic_errno))
-    : NULL;
+  return;
 
-DEBUG(D_transport)
-  debug_printf("  TPDA(host defer): tpda_host_defer_action=|%s| tpda_delivery_IP=%s\n",
-    action, tpda_delivery_ip);
+save_domain = deliver_domain;
+save_local = deliver_localpart;
+
+/*XXX would ip & port already be set up? */
+deliver_host_address = string_copy(host->address);
+deliver_host_port =    host->port == PORT_NONE ? 25 : host->port;
+event_defer_errno =    addr->basic_errno;
 
 router_name =    addr->router->name;
 transport_name = addr->transport->name;
-if (!expand_string(action) && *expand_string_message)
-  log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand tpda_defer_action in %s: %s\n",
-    transport_name, expand_string_message);
+deliver_domain = addr->domain;
+deliver_localpart = addr->local_part;
+
+(void) event_raise(action, US"msg:host:defer",
+    addr->message
+      ? addr->basic_errno > 0
+       ? string_sprintf("%s: %s", addr->message, strerror(addr->basic_errno))
+       : string_copy(addr->message)
+      : addr->basic_errno > 0
+       ? string_copy(US strerror(addr->basic_errno))
+       : NULL);
+
+deliver_localpart = save_local;
+deliver_domain =    save_domain;
 router_name = transport_name = NULL;
 }
 #endif
 
-
-
 /*************************************************
 *           Synchronize SMTP responses           *
 *************************************************/
@@ -706,21 +716,11 @@ subsequent general error, it will get reset accordingly. If not, it will get
 converted to OK at the end.
 
 Arguments:
-  addrlist          the complete address list
-  include_affixes   TRUE if affixes include in RCPT
-  sync_addr         ptr to the ptr of the one to start scanning at (updated)
-  host              the host we are connected to
+  sx               smtp connection context
   count             the number of responses to read
-  address_retry_
-    include_sender  true if 4xx retry is to include the sender it its key
-  pending_MAIL      true if the first response is for MAIL
   pending_DATA      0 if last command sent was not DATA
                    +1 if previously had a good recipient
                    -1 if not previously had a good recipient
-  inblock           incoming SMTP block
-  timeout           timeout value
-  buffer            buffer for reading response
-  buffsize          size of buffer
 
 Returns:      3 if at least one address had 2xx and one had 5xx
               2 if at least one address had 5xx but none had 2xx
@@ -732,43 +732,51 @@ Returns:      3 if at least one address had 2xx and one had 5xx
 */
 
 static int
-sync_responses(address_item *addrlist, BOOL include_affixes,
-  address_item **sync_addr, host_item *host, int count,
-  BOOL address_retry_include_sender, BOOL pending_MAIL,
-  int pending_DATA, smtp_inblock *inblock, int timeout, uschar *buffer,
-  int buffsize)
+sync_responses(smtp_context * sx, int count, int pending_DATA)
 {
-address_item *addr = *sync_addr;
+address_item *addr = sx->sync_addr;
+smtp_transport_options_block *ob =
+  (smtp_transport_options_block *)sx->tblock->options_block;
 int yield = 0;
 
 /* Handle the response for a MAIL command. On error, reinstate the original
 command in big_buffer for error message use, and flush any further pending
 responses before returning, except after I/O errors and timeouts. */
 
-if (pending_MAIL)
+if (sx->pending_MAIL)
   {
   count--;
-  if (!smtp_read_response(inblock, buffer, buffsize, '2', timeout))
+  if (!smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
+                         '2', ob->command_timeout))
     {
+    DEBUG(D_transport) debug_printf("bad response for MAIL\n");
     Ustrcpy(big_buffer, mail_command);  /* Fits, because it came from there! */
-    if (errno == 0 && buffer[0] != 0)
+    if (errno == 0 && sx->buffer[0] != 0)
       {
       uschar flushbuffer[4096];
       int save_errno = 0;
-      if (buffer[0] == '4')
+      if (sx->buffer[0] == '4')
         {
         save_errno = ERRNO_MAIL4XX;
-        addr->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+        addr->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
         }
       while (count-- > 0)
         {
-        if (!smtp_read_response(inblock, flushbuffer, sizeof(flushbuffer),
-                   '2', timeout)
+        if (!smtp_read_response(&sx->inblock, flushbuffer, sizeof(flushbuffer),
+                   '2', ob->command_timeout)
             && (errno != 0 || flushbuffer[0] == 0))
           break;
         }
       errno = save_errno;
       }
+
+    if (pending_DATA) count--;  /* Number of RCPT responses to come */
+    while (count-- > 0)                /* Mark any pending addrs with the host used */
+      {
+      while (addr->transport_return != PENDING_DEFER) addr = addr->next;
+      addr->host_used = sx->host;
+      addr = addr->next;
+      }
     return -3;
     }
   }
@@ -784,8 +792,10 @@ while (count-- > 0)
   while (addr->transport_return != PENDING_DEFER) addr = addr->next;
 
   /* The address was accepted */
+  addr->host_used = sx->host;
 
-  if (smtp_read_response(inblock, buffer, buffsize, '2', timeout))
+  if (smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
+                         '2', ob->command_timeout))
     {
     yield |= 1;
     addr->transport_return = PENDING_OK;
@@ -807,11 +817,9 @@ while (count-- > 0)
 
   else if (errno == ETIMEDOUT)
     {
-    int save_errno = errno;
-    uschar *message = string_sprintf("SMTP timeout while connected to %s [%s] "
-      "after RCPT TO:<%s>", host->name, host->address,
-      transport_rcpt_address(addr, include_affixes));
-    set_errno(addrlist, save_errno, message, DEFER, FALSE);
+    uschar *message = string_sprintf("SMTP timeout after RCPT TO:<%s>",
+               transport_rcpt_address(addr, sx->tblock->rcpt_include_affixes));
+    set_errno_nohost(sx->first_addr, ETIMEDOUT, message, DEFER, FALSE);
     retry_add_item(addr, addr->address_retry_key, 0);
     update_waiting = FALSE;
     return -1;
@@ -822,10 +830,10 @@ while (count-- > 0)
   big_buffer for which we are checking the response, so the error message
   makes sense. */
 
-  else if (errno != 0 || buffer[0] == 0)
+  else if (errno != 0 || sx->buffer[0] == 0)
     {
     string_format(big_buffer, big_buffer_size, "RCPT TO:<%s>",
-      transport_rcpt_address(addr, include_affixes));
+      transport_rcpt_address(addr, sx->tblock->rcpt_include_affixes));
     return -2;
     }
 
@@ -835,14 +843,15 @@ while (count-- > 0)
     {
     addr->message =
       string_sprintf("SMTP error from remote mail server after RCPT TO:<%s>: "
-        "host %s [%s]: %s", transport_rcpt_address(addr, include_affixes),
-        host->name, host->address, string_printing(buffer));
+       "%s", transport_rcpt_address(addr, sx->tblock->rcpt_include_affixes),
+       string_printing(sx->buffer));
     setflag(addr, af_pass_message);
-    deliver_msglog("%s %s\n", tod_stamp(tod_log), addr->message);
+    if (!sx->verify)
+      msglog_line(sx->host, addr->message);
 
     /* The response was 5xx */
 
-    if (buffer[0] == '5')
+    if (sx->buffer[0] == '5')
       {
       addr->transport_return = FAIL;
       yield |= 2;
@@ -854,28 +863,42 @@ while (count-- > 0)
       {
       addr->transport_return = DEFER;
       addr->basic_errno = ERRNO_RCPT4XX;
-      addr->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+      addr->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
 
-      /* Log temporary errors if there are more hosts to be tried. */
+      if (!sx->verify)
+       {
+#ifndef DISABLE_EVENT
+       event_defer_errno = addr->more_errno;
+       msg_event_raise(US"msg:rcpt:host:defer", addr);
+#endif
 
-      if (host->next != NULL) log_write(0, LOG_MAIN, "%s", addr->message);
+       /* Log temporary errors if there are more hosts to be tried.
+       If not, log this last one in the == line. */
 
-      /* Do not put this message on the list of those waiting for specific
-      hosts, as otherwise it is likely to be tried too often. */
+       if (sx->host->next)
+         log_write(0, LOG_MAIN, "H=%s [%s]: %s",
+           sx->host->name, sx->host->address, addr->message);
 
-      update_waiting = FALSE;
+#ifndef DISABLE_EVENT
+       else
+         msg_event_raise(US"msg:rcpt:defer", addr);
+#endif
 
-      /* Add a retry item for the address so that it doesn't get tried again
-      too soon. If address_retry_include_sender is true, add the sender address
-      to the retry key. */
+       /* Do not put this message on the list of those waiting for specific
+       hosts, as otherwise it is likely to be tried too often. */
 
-      if (address_retry_include_sender)
-        {
-        uschar *altkey = string_sprintf("%s:<%s>", addr->address_retry_key,
-          sender_address);
-        retry_add_item(addr, altkey, 0);
-        }
-      else retry_add_item(addr, addr->address_retry_key, 0);
+       update_waiting = FALSE;
+
+       /* Add a retry item for the address so that it doesn't get tried again
+       too soon. If address_retry_include_sender is true, add the sender address
+       to the retry key. */
+
+       retry_add_item(addr,
+         ob->address_retry_include_sender
+           ? string_sprintf("%s:<%s>", addr->address_retry_key, sender_address)
+           : addr->address_retry_key,
+         0);
+       }
       }
     }
   }       /* Loop for next RCPT response */
@@ -883,27 +906,28 @@ while (count-- > 0)
 /* Update where to start at for the next block of responses, unless we
 have already handled all the addresses. */
 
-if (addr != NULL) *sync_addr = addr->next;
+if (addr) sx->sync_addr = addr->next;
 
 /* Handle a response to DATA. If we have not had any good recipients, either
 previously or in this block, the response is ignored. */
 
 if (pending_DATA != 0 &&
-    !smtp_read_response(inblock, buffer, buffsize, '3', timeout))
+    !smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
+                       '3', ob->command_timeout))
   {
   int code;
   uschar *msg;
   BOOL pass_message;
   if (pending_DATA > 0 || (yield & 1) != 0)
     {
-    if (errno == 0 && buffer[0] == '4')
+    if (errno == 0 && sx->buffer[0] == '4')
       {
       errno = ERRNO_DATA4XX;
-      addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+      sx->first_addr->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
       }
     return -3;
     }
-  (void)check_response(host, &errno, 0, buffer, &code, &msg, &pass_message);
+  (void)check_response(sx->host, &errno, 0, sx->buffer, &code, &msg, &pass_message);
   DEBUG(D_transport) debug_printf("%s\nerror for DATA ignored: pipelining "
     "is in use and there were no good recipients\n", msg);
   }
@@ -940,147 +964,146 @@ smtp_auth(uschar *buffer, unsigned bufsize, address_item *addrlist, host_item *h
     smtp_transport_options_block *ob, BOOL is_esmtp,
     smtp_inblock *ibp, smtp_outblock *obp)
 {
-  int require_auth;
-  uschar *fail_reason = US"server did not advertise AUTH support";
+int require_auth;
+uschar *fail_reason = US"server did not advertise AUTH support";
 
-  smtp_authenticated = FALSE;
-  client_authenticator = client_authenticated_id = client_authenticated_sender = NULL;
-  require_auth = verify_check_this_host(&(ob->hosts_require_auth), NULL,
-    host->name, host->address, NULL);
+smtp_authenticated = FALSE;
+client_authenticator = client_authenticated_id = client_authenticated_sender = NULL;
+require_auth = verify_check_given_host(&ob->hosts_require_auth, host);
 
-  if (is_esmtp && !regex_AUTH) regex_AUTH =
-      regex_must_compile(US"\\n250[\\s\\-]AUTH\\s+([\\-\\w\\s]+)(?:\\n|$)",
-            FALSE, TRUE);
+if (is_esmtp && !regex_AUTH) regex_AUTH =
+    regex_must_compile(US"\\n250[\\s\\-]AUTH\\s+([\\-\\w\\s]+)(?:\\n|$)",
+         FALSE, TRUE);
 
-  if (is_esmtp && regex_match_and_setup(regex_AUTH, buffer, 0, -1))
-    {
-    uschar *names = string_copyn(expand_nstring[1], expand_nlength[1]);
-    expand_nmax = -1;                          /* reset */
+if (is_esmtp && regex_match_and_setup(regex_AUTH, buffer, 0, -1))
+  {
+  uschar *names = string_copyn(expand_nstring[1], expand_nlength[1]);
+  expand_nmax = -1;                          /* reset */
 
-    /* Must not do this check until after we have saved the result of the
-    regex match above. */
+  /* Must not do this check until after we have saved the result of the
+  regex match above. */
 
-    if (require_auth == OK ||
-        verify_check_this_host(&(ob->hosts_try_auth), NULL, host->name,
-          host->address, NULL) == OK)
-      {
-      auth_instance *au;
-      fail_reason = US"no common mechanisms were found";
+  if (require_auth == OK ||
+      verify_check_given_host(&ob->hosts_try_auth, host) == OK)
+    {
+    auth_instance *au;
+    fail_reason = US"no common mechanisms were found";
 
-      DEBUG(D_transport) debug_printf("scanning authentication mechanisms\n");
+    DEBUG(D_transport) debug_printf("scanning authentication mechanisms\n");
 
-      /* Scan the configured authenticators looking for one which is configured
-      for use as a client, which is not suppressed by client_condition, and
-      whose name matches an authentication mechanism supported by the server.
-      If one is found, attempt to authenticate by calling its client function.
-      */
+    /* Scan the configured authenticators looking for one which is configured
+    for use as a client, which is not suppressed by client_condition, and
+    whose name matches an authentication mechanism supported by the server.
+    If one is found, attempt to authenticate by calling its client function.
+    */
 
-      for (au = auths; !smtp_authenticated && au != NULL; au = au->next)
-        {
-        uschar *p = names;
-        if (!au->client ||
-            (au->client_condition != NULL &&
-             !expand_check_condition(au->client_condition, au->name,
-               US"client authenticator")))
-          {
-          DEBUG(D_transport) debug_printf("skipping %s authenticator: %s\n",
-            au->name,
-            (au->client)? "client_condition is false" :
-                          "not configured as a client");
-          continue;
-          }
+    for (au = auths; !smtp_authenticated && au != NULL; au = au->next)
+      {
+      uschar *p = names;
+      if (!au->client ||
+         (au->client_condition != NULL &&
+          !expand_check_condition(au->client_condition, au->name,
+            US"client authenticator")))
+       {
+       DEBUG(D_transport) debug_printf("skipping %s authenticator: %s\n",
+         au->name,
+         (au->client)? "client_condition is false" :
+                       "not configured as a client");
+       continue;
+       }
 
-        /* Loop to scan supported server mechanisms */
+      /* Loop to scan supported server mechanisms */
 
-        while (*p != 0)
-          {
-          int rc;
-          int len = Ustrlen(au->public_name);
-          while (isspace(*p)) p++;
+      while (*p != 0)
+       {
+       int rc;
+       int len = Ustrlen(au->public_name);
+       while (isspace(*p)) p++;
 
-          if (strncmpic(au->public_name, p, len) != 0 ||
-              (p[len] != 0 && !isspace(p[len])))
-            {
-            while (*p != 0 && !isspace(*p)) p++;
-            continue;
-            }
+       if (strncmpic(au->public_name, p, len) != 0 ||
+           (p[len] != 0 && !isspace(p[len])))
+         {
+         while (*p != 0 && !isspace(*p)) p++;
+         continue;
+         }
 
-          /* Found data for a listed mechanism. Call its client entry. Set
-          a flag in the outblock so that data is overwritten after sending so
-          that reflections don't show it. */
+       /* Found data for a listed mechanism. Call its client entry. Set
+       a flag in the outblock so that data is overwritten after sending so
+       that reflections don't show it. */
 
-          fail_reason = US"authentication attempt(s) failed";
-          obp->authenticating = TRUE;
-          rc = (au->info->clientcode)(au, ibp, obp,
-            ob->command_timeout, buffer, bufsize);
-          obp->authenticating = FALSE;
-          DEBUG(D_transport) debug_printf("%s authenticator yielded %d\n",
-            au->name, rc);
+       fail_reason = US"authentication attempt(s) failed";
+       obp->authenticating = TRUE;
+       rc = (au->info->clientcode)(au, ibp, obp,
+         ob->command_timeout, buffer, bufsize);
+       obp->authenticating = FALSE;
+       DEBUG(D_transport) debug_printf("%s authenticator yielded %d\n",
+         au->name, rc);
 
-          /* A temporary authentication failure must hold up delivery to
-          this host. After a permanent authentication failure, we carry on
-          to try other authentication methods. If all fail hard, try to
-          deliver the message unauthenticated unless require_auth was set. */
+       /* A temporary authentication failure must hold up delivery to
+       this host. After a permanent authentication failure, we carry on
+       to try other authentication methods. If all fail hard, try to
+       deliver the message unauthenticated unless require_auth was set. */
 
-          switch(rc)
-            {
-            case OK:
-            smtp_authenticated = TRUE;   /* stops the outer loop */
-           client_authenticator = au->name;
-           if (au->set_client_id != NULL)
-             client_authenticated_id = expand_string(au->set_client_id);
-            break;
-
-            /* Failure after writing a command */
-
-            case FAIL_SEND:
-            return FAIL_SEND;
-
-            /* Failure after reading a response */
-
-            case FAIL:
-            if (errno != 0 || buffer[0] != '5') return FAIL;
-            log_write(0, LOG_MAIN, "%s authenticator failed H=%s [%s] %s",
-              au->name, host->name, host->address, buffer);
-            break;
-
-            /* Failure by some other means. In effect, the authenticator
-            decided it wasn't prepared to handle this case. Typically this
-            is the result of "fail" in an expansion string. Do we need to
-            log anything here? Feb 2006: a message is now put in the buffer
-            if logging is required. */
-
-            case CANCELLED:
-            if (*buffer != 0)
-              log_write(0, LOG_MAIN, "%s authenticator cancelled "
-                "authentication H=%s [%s] %s", au->name, host->name,
-                host->address, buffer);
-            break;
-
-            /* Internal problem, message in buffer. */
-
-            case ERROR:
-            set_errno(addrlist, 0, string_copy(buffer), DEFER, FALSE);
-            return ERROR;
-            }
+       switch(rc)
+         {
+         case OK:
+         smtp_authenticated = TRUE;   /* stops the outer loop */
+         client_authenticator = au->name;
+         if (au->set_client_id != NULL)
+           client_authenticated_id = expand_string(au->set_client_id);
+         break;
+
+         /* Failure after writing a command */
+
+         case FAIL_SEND:
+         return FAIL_SEND;
+
+         /* Failure after reading a response */
+
+         case FAIL:
+         if (errno != 0 || buffer[0] != '5') return FAIL;
+         log_write(0, LOG_MAIN, "%s authenticator failed H=%s [%s] %s",
+           au->name, host->name, host->address, buffer);
+         break;
+
+         /* Failure by some other means. In effect, the authenticator
+         decided it wasn't prepared to handle this case. Typically this
+         is the result of "fail" in an expansion string. Do we need to
+         log anything here? Feb 2006: a message is now put in the buffer
+         if logging is required. */
+
+         case CANCELLED:
+         if (*buffer != 0)
+           log_write(0, LOG_MAIN, "%s authenticator cancelled "
+             "authentication H=%s [%s] %s", au->name, host->name,
+             host->address, buffer);
+         break;
+
+         /* Internal problem, message in buffer. */
+
+         case ERROR:
+         set_errno_nohost(addrlist, ERRNO_AUTHPROB, string_copy(buffer),
+                   DEFER, FALSE);
+         return ERROR;
+         }
 
-          break;  /* If not authenticated, try next authenticator */
-          }       /* Loop for scanning supported server mechanisms */
-        }         /* Loop for further authenticators */
-      }
+       break;  /* If not authenticated, try next authenticator */
+       }       /* Loop for scanning supported server mechanisms */
+      }         /* Loop for further authenticators */
     }
+  }
 
-  /* If we haven't authenticated, but are required to, give up. */
+/* If we haven't authenticated, but are required to, give up. */
 
-  if (require_auth == OK && !smtp_authenticated)
-    {
-    set_errno(addrlist, ERRNO_AUTHFAIL,
-      string_sprintf("authentication required but %s", fail_reason), DEFER,
-      FALSE);
-    return DEFER;
-    }
+if (require_auth == OK && !smtp_authenticated)
+  {
+  set_errno_nohost(addrlist, ERRNO_AUTHFAIL,
+    string_sprintf("authentication required but %s", fail_reason), DEFER,
+    FALSE);
+  return DEFER;
+  }
 
-  return OK;
+return OK;
 }
 
 
@@ -1115,7 +1138,7 @@ if (ob->authenticated_sender != NULL)
       {
       uschar *message = string_sprintf("failed to expand "
         "authenticated_sender: %s", expand_string_message);
-      set_errno(addrlist, 0, message, DEFER, FALSE);
+      set_errno_nohost(addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE);
       return TRUE;
       }
     }
@@ -1140,209 +1163,536 @@ return FALSE;
 
 
 
-/*************************************************
-*       Deliver address list to given host       *
-*************************************************/
+#ifdef EXPERIMENTAL_DANE
+/* Lookup TLSA record for host/port.
+Return:  OK            success with dnssec; DANE mode
+         DEFER         Do not use this host now, may retry later
+        FAIL_FORCED    No TLSA record; DANE not usable
+        FAIL           Do not use this connection
+*/
 
-/* If continue_hostname is not null, we get here only when continuing to
-deliver down an existing channel. The channel was passed as the standard
-input. TLS is never active on a passed channel; the previous process always
-closes it down before passing the connection on.
+int
+tlsa_lookup(const host_item * host, dns_answer * dnsa, BOOL dane_required)
+{
+/* move this out to host.c given the similarity to dns_lookup() ? */
+uschar buffer[300];
+const uschar * fullname = buffer;
 
-Otherwise, we have to make a connection to the remote host, and do the
-initial protocol exchange.
+/* TLSA lookup string */
+(void)sprintf(CS buffer, "_%d._tcp.%.256s", host->port, host->name);
 
-When running as an MUA wrapper, if the sender or any recipient is rejected,
-temporarily or permanently, we force failure for all recipients.
+switch (dns_lookup(dnsa, buffer, T_TLSA, &fullname))
+  {
+  case DNS_SUCCEED:
+    if (!dns_is_secure(dnsa))
+      {
+      log_write(0, LOG_MAIN, "DANE error: TLSA lookup not DNSSEC");
+      return DEFER;
+      }
+    return OK;
 
-Arguments:
-  addrlist        chain of potential addresses to deliver; only those whose
-                  transport_return field is set to PENDING_DEFER are currently
-                  being processed; others should be skipped - they have either
-                  been delivered to an earlier host or IP address, or been
-                  failed by one of them.
-  host            host to deliver to
-  host_af         AF_INET or AF_INET6
-  port            default TCP/IP port to use, in host byte order
-  interface       interface to bind to, or NULL
-  tblock          transport instance block
-  copy_host       TRUE if host set in addr->host_used must be copied, because
-                    it is specific to this call of the transport
-  message_defer   set TRUE if yield is OK, but all addresses were deferred
-                    because of a non-recipient, non-host failure, that is, a
-                    4xx response to MAIL FROM, DATA, or ".". This is a defer
-                    that is specific to the message.
-  suppress_tls    if TRUE, don't attempt a TLS connection - this is set for
-                    a second attempt after TLS initialization fails
+  case DNS_AGAIN:
+    return DEFER; /* just defer this TLS'd conn */
 
-Returns:          OK    - the connection was made and the delivery attempted;
-                          the result for each address is in its data block.
-                  DEFER - the connection could not be made, or something failed
-                          while setting up the SMTP session, or there was a
-                          non-message-specific error, such as a timeout.
-                  ERROR - a filter command is specified for this transport,
-                          and there was a problem setting it up; OR helo_data
-                          or add_headers or authenticated_sender is specified
-                          for this transport, and the string failed to expand
-*/
+  case DNS_NODATA:     /* no TLSA RR for this lookup */
+  case DNS_NOMATCH:    /* no records at all for this lookup */
+    return dane_required ? FAIL : FAIL_FORCED;
 
-static int
-smtp_deliver(address_item *addrlist, host_item *host, int host_af, int port,
-  uschar *interface, transport_instance *tblock, BOOL copy_host,
-  BOOL *message_defer, BOOL suppress_tls)
-{
-address_item *addr;
-address_item *sync_addr;
-address_item *first_addr = addrlist;
-int yield = OK;
-int address_count;
-int save_errno;
-int rc;
-time_t start_delivery_time = time(NULL);
-smtp_transport_options_block *ob =
-  (smtp_transport_options_block *)(tblock->options_block);
-BOOL lmtp = strcmpic(ob->protocol, US"lmtp") == 0;
-BOOL smtps = strcmpic(ob->protocol, US"smtps") == 0;
-BOOL ok = FALSE;
-BOOL send_rset = TRUE;
-BOOL send_quit = TRUE;
-BOOL setting_up = TRUE;
-BOOL completed_address = FALSE;
-BOOL esmtp = TRUE;
-BOOL pending_MAIL;
-BOOL pass_message = FALSE;
-#ifndef DISABLE_PRDR
-BOOL prdr_offered = FALSE;
-BOOL prdr_active;
-#endif
-#ifdef EXPERIMENTAL_DSN
-BOOL dsn_all_lasthop = TRUE;
+  default:
+  case DNS_FAIL:
+    return dane_required ? FAIL : DEFER;
+  }
+}
 #endif
-smtp_inblock inblock;
-smtp_outblock outblock;
-int max_rcpt = tblock->max_addresses;
-uschar *igquotstr = US"";
-uschar *helo_data = NULL;
-uschar *message = NULL;
-uschar new_message_id[MESSAGE_ID_LENGTH + 1];
-uschar *p;
-uschar buffer[4096];
-uschar inbuffer[4096];
-uschar outbuffer[4096];
 
-suppress_tls = suppress_tls;  /* stop compiler warning when no TLS support */
 
-*message_defer = FALSE;
-smtp_command = US"initial connection";
-if (max_rcpt == 0) max_rcpt = 999999;
 
-/* Set up the buffer for reading SMTP response packets. */
+typedef struct smtp_compare_s
+{
+    uschar                          *current_sender_address;
+    struct transport_instance       *tblock;
+} smtp_compare_t;
 
-inblock.buffer = inbuffer;
-inblock.buffersize = sizeof(inbuffer);
-inblock.ptr = inbuffer;
-inblock.ptrend = inbuffer;
 
-/* Set up the buffer for holding SMTP commands while pipelining */
+/* Create a unique string that identifies this message, it is based on
+sender_address, helo_data and tls_certificate if enabled.
+*/
 
-outblock.buffer = outbuffer;
-outblock.buffersize = sizeof(outbuffer);
-outblock.ptr = outbuffer;
-outblock.cmd_count = 0;
-outblock.authenticating = FALSE;
+static uschar *
+smtp_local_identity(uschar * sender, struct transport_instance * tblock)
+{
+address_item * addr1;
+uschar * if1 = US"";
+uschar * helo1 = US"";
+#ifdef SUPPORT_TLS
+uschar * tlsc1 = US"";
+#endif
+uschar * save_sender_address = sender_address;
+uschar * local_identity = NULL;
+smtp_transport_options_block * ob =
+  (smtp_transport_options_block *)tblock->options_block;
 
-/* Reset the parameters of a TLS session. */
+sender_address = sender;
 
-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;
+addr1 = deliver_make_addr (sender, TRUE);
+deliver_set_expansions(addr1);
 
-/* 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. */
+if (ob->interface)
+  if1 = expand_string(ob->interface);
 
-tls_modify_variables(&tls_out);
+if (ob->helo_data)
+  helo1 = expand_string(ob->helo_data);
 
-#ifndef SUPPORT_TLS
-if (smtps)
-  {
-  set_errno(addrlist, 0, US"TLS support not available", DEFER, FALSE);
-  return ERROR;
-  }
+#ifdef SUPPORT_TLS
+if (ob->tls_certificate)
+  tlsc1 = expand_string(ob->tls_certificate);
+local_identity = string_sprintf ("%s^%s^%s", if1, helo1, tlsc1);
+#else
+local_identity = string_sprintf ("%s^%s", if1, helo1);
 #endif
 
-/* Make a connection to the host if this isn't a continued delivery, and handle
-the initial interaction and HELO/EHLO/LHLO. Connect timeout errors are handled
-specially so they can be identified for retries. */
+deliver_set_expansions(NULL);
+sender_address = save_sender_address;
 
-if (continue_hostname == NULL)
-  {
-  inblock.sock = outblock.sock =
-    smtp_connect(host, host_af, port, interface, ob->connect_timeout,
-      ob->keepalive, ob->dscp);   /* This puts port into host->port */
+return local_identity;
+}
 
-  if (inblock.sock < 0)
-    {
-    set_errno(addrlist, (errno == ETIMEDOUT)? ERRNO_CONNECTTIMEOUT : errno,
-      NULL, DEFER, FALSE);
-    return DEFER;
-    }
 
-  /* Expand the greeting message while waiting for the initial response. (Makes
-  sense if helo_data contains ${lookup dnsdb ...} stuff). The expansion is
-  delayed till here so that $sending_interface and $sending_port are set. */
 
-  helo_data = expand_string(ob->helo_data);
+/* This routine is a callback that is called from transport_check_waiting.
+This function will evaluate the incoming message versus the previous
+message.  If the incoming message is using a different local identity then
+we will veto this new message.  */
 
-  /* The first thing is to wait for an initial OK response. The dreaded "goto"
-  is nevertheless a reasonably clean way of programming this kind of logic,
-  where you want to escape on any error. */
+static BOOL
+smtp_are_same_identities(uschar * message_id, smtp_compare_t * s_compare)
+{
+uschar * message_local_identity,
+       * current_local_identity,
+       * new_sender_address;
 
-  if (!smtps)
-    {
-    if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
-      ob->command_timeout)) goto RESPONSE_FAILED;
+current_local_identity =
+  smtp_local_identity(s_compare->current_sender_address, s_compare->tblock);
 
-    /* Now check if the helo_data expansion went well, and sign off cleanly if
-    it didn't. */
+if (!(new_sender_address = deliver_get_sender_address(message_id)))
+    return 0;
 
-    if (helo_data == NULL)
-      {
-      uschar *message = string_sprintf("failed to expand helo_data: %s",
-        expand_string_message);
-      set_errno(addrlist, 0, message, DEFER, FALSE);
-      yield = DEFER;
-      goto SEND_QUIT;
-      }
-    }
+message_local_identity =
+  smtp_local_identity(new_sender_address, s_compare->tblock);
 
-/** Debugging without sending a message
-addrlist->transport_return = DEFER;
-goto SEND_QUIT;
-**/
+return Ustrcmp(current_local_identity, message_local_identity) == 0;
+}
 
-  /* Errors that occur after this point follow an SMTP command, which is
-  left in big_buffer by smtp_write_command() for use in error messages. */
 
-  smtp_command = big_buffer;
 
-  /* Tell the remote who we are...
+static uschar
+ehlo_response(uschar * buf, uschar checks)
+{
+size_t bsize = Ustrlen(buf);
 
-  February 1998: A convention has evolved that ESMTP-speaking MTAs include the
-  string "ESMTP" in their greeting lines, so make Exim send EHLO if the
-  greeting is of this form. The assumption was that the far end supports it
-  properly... but experience shows that there are some that give 5xx responses,
-  even though the banner includes "ESMTP" (there's a bloody-minded one that
-  says "ESMTP not spoken here"). Cope with that case.
+#ifdef SUPPORT_TLS
+if (  checks & PEER_OFFERED_TLS
+   && pcre_exec(regex_STARTTLS, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+  checks &= ~PEER_OFFERED_TLS;
+#endif
 
-  September 2000: Time has passed, and it seems reasonable now to always send
-  EHLO at the start. It is also convenient to make the change while installing
+if (  checks & PEER_OFFERED_IGNQ
+   && pcre_exec(regex_IGNOREQUOTA, NULL, CS buf, bsize, 0,
+               PCRE_EOPT, NULL, 0) < 0)
+  checks &= ~PEER_OFFERED_IGNQ;
+
+if (  checks & PEER_OFFERED_CHUNKING
+   && pcre_exec(regex_CHUNKING, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+  checks &= ~PEER_OFFERED_CHUNKING;
+
+#ifndef DISABLE_PRDR
+if (  checks & PEER_OFFERED_PRDR
+   && pcre_exec(regex_PRDR, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+  checks &= ~PEER_OFFERED_PRDR;
+#endif
+
+#ifdef SUPPORT_I18N
+if (  checks & PEER_OFFERED_UTF8
+   && pcre_exec(regex_UTF8, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+  checks &= ~PEER_OFFERED_UTF8;
+#endif
+
+if (  checks & PEER_OFFERED_DSN
+   && pcre_exec(regex_DSN, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+  checks &= ~PEER_OFFERED_DSN;
+
+if (  checks & PEER_OFFERED_PIPE
+   && pcre_exec(regex_PIPELINING, NULL, CS buf, bsize, 0,
+               PCRE_EOPT, NULL, 0) < 0)
+  checks &= ~PEER_OFFERED_PIPE;
+
+if (  checks & PEER_OFFERED_SIZE
+   && pcre_exec(regex_SIZE, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+  checks &= ~PEER_OFFERED_SIZE;
+
+return checks;
+}
+
+
+
+/* Callback for emitting a BDAT data chunk header.
+
+If given a nonzero size, first flush any buffered SMTP commands
+then emit the command.
+
+Reap previous SMTP command responses if requested.
+Reap one SMTP command response if requested.
+
+Returns:       OK or ERROR
+*/
+
+static int
+smtp_chunk_cmd_callback(int fd, transport_ctx * tctx,
+  unsigned chunk_size, unsigned flags)
+{
+smtp_transport_options_block * ob =
+  (smtp_transport_options_block *)(tctx->tblock->options_block);
+smtp_context * sx = tctx->smtp_context;
+int cmd_count = 0;
+int prev_cmd_count;
+
+/* Write SMTP chunk header command */
+
+if (chunk_size > 0)
+  {
+  if((cmd_count = smtp_write_command(&sx->outblock, FALSE, "BDAT %u%s\r\n",
+                             chunk_size,
+                             flags & tc_chunk_last ? " LAST" : "")
+     ) < 0) return ERROR;
+  if (flags & tc_chunk_last)
+    data_command = string_copy(big_buffer);  /* Save for later error message */
+  }
+
+prev_cmd_count = cmd_count += sx->cmd_count;
+
+/* Reap responses for any previous, but not one we just emitted */
+
+if (chunk_size > 0)
+  prev_cmd_count--;
+if (sx->pending_BDAT)
+  prev_cmd_count--;
+
+if (flags & tc_reap_prev  &&  prev_cmd_count > 0)
+  {
+  DEBUG(D_transport) debug_printf("look for %d responses"
+    " for previous pipelined cmds\n", prev_cmd_count);
+
+  switch(sync_responses(sx, prev_cmd_count, 0))
+    {
+    case 1:                            /* 2xx (only) => OK */
+    case 3: sx->good_RCPT = TRUE;      /* 2xx & 5xx => OK & progress made */
+    case 2: sx->completed_addr = TRUE; /* 5xx (only) => progress made */
+    case 0: break;                     /* No 2xx or 5xx, but no probs */
+
+    case -1:                           /* Timeout on RCPT */
+    default: return ERROR;             /* I/O error, or any MAIL/DATA error */
+    }
+  cmd_count = 1;
+  if (!sx->pending_BDAT)
+    pipelining_active = FALSE;
+  }
+
+/* Reap response for an outstanding BDAT */
+
+if (sx->pending_BDAT)
+  {
+  DEBUG(D_transport) debug_printf("look for one response for BDAT\n");
+
+  if (!smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer), '2',
+       ob->command_timeout))
+    {
+    if (errno == 0 && sx->buffer[0] == '4')
+      {
+      errno = ERRNO_DATA4XX;   /*XXX does this actually get used? */
+      sx->addrlist->more_errno |=
+       ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
+      }
+    return ERROR;
+    }
+  cmd_count--;
+  sx->pending_BDAT = FALSE;
+  pipelining_active = FALSE;
+  }
+else if (chunk_size > 0)
+  sx->pending_BDAT = TRUE;
+
+
+sx->cmd_count = cmd_count;
+return OK;
+}
+
+
+
+/*************************************************
+*       Make connection for given message        *
+*************************************************/
+
+/*
+Arguments:
+  ctx            connection context
+  suppress_tls    if TRUE, don't attempt a TLS connection - this is set for
+                    a second attempt after TLS initialization fails
+
+Returns:          OK    - the connection was made and the delivery attempted;
+                          fd is set in the conn context, tls_out set up.
+                  DEFER - the connection could not be made, or something failed
+                          while setting up the SMTP session, or there was a
+                          non-message-specific error, such as a timeout.
+                  ERROR - helo_data or add_headers or authenticated_sender is
+                         specified for this transport, and the string failed
+                         to expand
+*/
+int
+smtp_setup_conn(smtp_context * sx, BOOL suppress_tls)
+{
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
+dns_answer tlsa_dnsa;
+#endif
+BOOL pass_message = FALSE;
+uschar * message = NULL;
+int yield = OK;
+int rc;
+
+sx->ob = (smtp_transport_options_block *) sx->tblock->options_block;
+
+sx->lmtp = strcmpic(sx->ob->protocol, US"lmtp") == 0;
+sx->smtps = strcmpic(sx->ob->protocol, US"smtps") == 0;
+sx->ok = FALSE;
+sx->send_rset = TRUE;
+sx->send_quit = TRUE;
+sx->setting_up = TRUE;
+sx->esmtp = TRUE;
+sx->esmtp_sent = FALSE;
+#ifdef SUPPORT_I18N
+sx->utf8_needed = FALSE;
+#endif
+sx->dsn_all_lasthop = TRUE;
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
+sx->dane = FALSE;
+sx->dane_required = verify_check_given_host(&sx->ob->hosts_require_dane, sx->host) == OK;
+#endif
+
+if ((sx->max_rcpt = sx->tblock->max_addresses) == 0) sx->max_rcpt = 999999;
+sx->peer_offered = 0;
+sx->igquotstr = US"";
+if (!sx->helo_data) sx->helo_data = sx->ob->helo_data;
+#ifdef EXPERIMENTAL_DSN_INFO
+sx->smtp_greeting = NULL;
+sx->helo_response = NULL;
+#endif
+
+smtp_command = US"initial connection";
+sx->buffer[0] = '\0';
+
+/* Set up the buffer for reading SMTP response packets. */
+
+sx->inblock.buffer = sx->inbuffer;
+sx->inblock.buffersize = sizeof(sx->inbuffer);
+sx->inblock.ptr = sx->inbuffer;
+sx->inblock.ptrend = sx->inbuffer;
+
+/* Set up the buffer for holding SMTP commands while pipelining */
+
+sx->outblock.buffer = sx->outbuffer;
+sx->outblock.buffersize = sizeof(sx->outbuffer);
+sx->outblock.ptr = sx->outbuffer;
+sx->outblock.cmd_count = 0;
+sx->outblock.authenticating = FALSE;
+
+/* Reset the parameters of a TLS session. */
+
+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 (when being used by a transport) as we're in a subprocess.
+For verify, unflipped once the callout is dealt with */
+
+tls_modify_variables(&tls_out);
+
+#ifndef SUPPORT_TLS
+if (sx->smtps)
+  {
+  set_errno_nohost(sx->addrlist, ERRNO_TLSFAILURE, US"TLS support not available",
+           DEFER, FALSE);
+  return ERROR;
+  }
+#endif
+
+/* Make a connection to the host if this isn't a continued delivery, and handle
+the initial interaction and HELO/EHLO/LHLO. Connect timeout errors are handled
+specially so they can be identified for retries. */
+
+if (continue_hostname == NULL)
+  {
+  if (sx->verify)
+    HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", sx->interface, sx->port);
+
+  /* This puts port into host->port */
+  sx->inblock.sock = sx->outblock.sock =
+    smtp_connect(sx->host, sx->host_af, sx->port, sx->interface,
+                 sx->ob->connect_timeout, sx->tblock);
+
+  if (sx->inblock.sock < 0)
+    {
+    uschar * msg = NULL;
+    if (sx->verify)
+      {
+      msg = US strerror(errno);
+      HDEBUG(D_verify) debug_printf("connect: %s\n", msg);
+      }
+    set_errno_nohost(sx->addrlist,
+      errno == ETIMEDOUT ? ERRNO_CONNECTTIMEOUT : errno,
+      sx->verify ? string_sprintf("could not connect: %s", msg)
+            : NULL,
+      DEFER, FALSE);
+    sx->send_quit = FALSE;
+    return DEFER;
+    }
+
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
+    {
+    tls_out.dane_verified = FALSE;
+    tls_out.tlsa_usage = 0;
+
+    if (sx->host->dnssec == DS_YES)
+      {
+      if(  sx->dane_required
+       || verify_check_given_host(&sx->ob->hosts_try_dane, sx->host) == OK
+       )
+       switch (rc = tlsa_lookup(sx->host, &tlsa_dnsa, sx->dane_required))
+         {
+         case OK:              sx->dane = TRUE; break;
+         case FAIL_FORCED:     break;
+         default:              set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
+                                 string_sprintf("DANE error: tlsa lookup %s",
+                                   rc == DEFER ? "DEFER" : "FAIL"),
+                                 rc, FALSE);
+                               return rc;
+         }
+      }
+    else if (sx->dane_required)
+      {
+      set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
+       string_sprintf("DANE error: %s lookup not DNSSEC", sx->host->name),
+       FAIL, FALSE);
+      return FAIL;
+      }
+
+    if (sx->dane)
+      sx->ob->tls_tempfail_tryclear = FALSE;
+    }
+#endif /*DANE*/
+
+  /* Expand the greeting message while waiting for the initial response. (Makes
+  sense if helo_data contains ${lookup dnsdb ...} stuff). The expansion is
+  delayed till here so that $sending_interface and $sending_port are set. */
+
+  if (sx->helo_data)
+    if (!(sx->helo_data = expand_string(sx->helo_data)))
+      if (sx->verify)
+       log_write(0, LOG_MAIN|LOG_PANIC,
+         "<%s>: failed to expand transport's helo_data value for callout: %s",
+         sx->addrlist->address, expand_string_message);
+
+#ifdef SUPPORT_I18N
+  if (sx->helo_data)
+    {
+    expand_string_message = NULL;
+    if ((sx->helo_data = string_domain_utf8_to_alabel(sx->helo_data,
+                                             &expand_string_message)),
+       expand_string_message)
+      if (sx->verify)
+       log_write(0, LOG_MAIN|LOG_PANIC,
+         "<%s>: failed to expand transport's helo_data value for callout: %s",
+         sx->addrlist->address, expand_string_message);
+      else
+       sx->helo_data = NULL;
+    }
+#endif
+
+  /* The first thing is to wait for an initial OK response. The dreaded "goto"
+  is nevertheless a reasonably clean way of programming this kind of logic,
+  where you want to escape on any error. */
+
+  if (!sx->smtps)
+    {
+    BOOL good_response;
+
+#ifdef TCP_QUICKACK
+    (void) setsockopt(sx->inblock.sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
+#endif
+    good_response = smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
+      '2', sx->ob->command_timeout);
+#ifdef EXPERIMENTAL_DSN_INFO
+    sx->smtp_greeting = string_copy(sx->buffer);
+#endif
+    if (!good_response) goto RESPONSE_FAILED;
+
+#ifndef DISABLE_EVENT
+      {
+      uschar * s;
+      lookup_dnssec_authenticated = sx->host->dnssec==DS_YES ? US"yes"
+       : sx->host->dnssec==DS_NO ? US"no" : NULL;
+      s = event_raise(sx->tblock->event_action, US"smtp:connect", sx->buffer);
+      if (s)
+       {
+       set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL,
+         string_sprintf("deferred by smtp:connect event expansion: %s", s),
+         DEFER, FALSE);
+       yield = DEFER;
+       goto SEND_QUIT;
+       }
+      }
+#endif
+
+    /* Now check if the helo_data expansion went well, and sign off cleanly if
+    it didn't. */
+
+    if (!sx->helo_data)
+      {
+      message = string_sprintf("failed to expand helo_data: %s",
+        expand_string_message);
+      set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE);
+      yield = DEFER;
+      goto SEND_QUIT;
+      }
+    }
+
+/** Debugging without sending a message
+sx->addrlist->transport_return = DEFER;
+goto SEND_QUIT;
+**/
+
+  /* Errors that occur after this point follow an SMTP command, which is
+  left in big_buffer by smtp_write_command() for use in error messages. */
+
+  smtp_command = big_buffer;
+
+  /* Tell the remote who we are...
+
+  February 1998: A convention has evolved that ESMTP-speaking MTAs include the
+  string "ESMTP" in their greeting lines, so make Exim send EHLO if the
+  greeting is of this form. The assumption was that the far end supports it
+  properly... but experience shows that there are some that give 5xx responses,
+  even though the banner includes "ESMTP" (there's a bloody-minded one that
+  says "ESMTP not spoken here"). Cope with that case.
+
+  September 2000: Time has passed, and it seems reasonable now to always send
+  EHLO at the start. It is also convenient to make the change while installing
   the TLS stuff.
 
   July 2003: Joachim Wieland met a broken server that advertises "PIPELINING"
@@ -1357,88 +1707,113 @@ goto SEND_QUIT;
   mailers use upper case for some reason (the RFC is quite clear about case
   independence) so, for peace of mind, I gave in. */
 
-  esmtp = verify_check_this_host(&(ob->hosts_avoid_esmtp), NULL,
-     host->name, host->address, NULL) != OK;
+  sx->esmtp = verify_check_given_host(&sx->ob->hosts_avoid_esmtp, sx->host) != OK;
 
   /* Alas; be careful, since this goto is not an error-out, so conceivably
   we might set data between here and the target which we assume to exist
   and be usable.  I can see this coming back to bite us. */
-  #ifdef SUPPORT_TLS
-  if (smtps)
+#ifdef SUPPORT_TLS
+  if (sx->smtps)
     {
-    tls_offered = TRUE;
+    smtp_peer_options |= PEER_OFFERED_TLS;
     suppress_tls = FALSE;
-    ob->tls_tempfail_tryclear = FALSE;
+    sx->ob->tls_tempfail_tryclear = FALSE;
     smtp_command = US"SSL-on-connect";
     goto TLS_NEGOTIATE;
     }
-  #endif
+#endif
 
-  if (esmtp)
+  if (sx->esmtp)
     {
-    if (smtp_write_command(&outblock, FALSE, "%s %s\r\n",
-         lmtp? "LHLO" : "EHLO", helo_data) < 0)
+    if (smtp_write_command(&sx->outblock, FALSE, "%s %s\r\n",
+         sx->lmtp ? "LHLO" : "EHLO", sx->helo_data) < 0)
       goto SEND_FAILED;
-    if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
-           ob->command_timeout))
+    sx->esmtp_sent = TRUE;
+    if (!smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer), '2',
+           sx->ob->command_timeout))
       {
-      if (errno != 0 || buffer[0] == 0 || lmtp) goto RESPONSE_FAILED;
-      esmtp = FALSE;
+      if (errno != 0 || sx->buffer[0] == 0 || sx->lmtp)
+       {
+#ifdef EXPERIMENTAL_DSN_INFO
+       sx->helo_response = string_copy(sx->buffer);
+#endif
+       goto RESPONSE_FAILED;
+       }
+      sx->esmtp = FALSE;
       }
+#ifdef EXPERIMENTAL_DSN_INFO
+    sx->helo_response = string_copy(sx->buffer);
+#endif
     }
   else
-    {
     DEBUG(D_transport)
       debug_printf("not sending EHLO (host matches hosts_avoid_esmtp)\n");
-    }
 
-  if (!esmtp)
+  if (!sx->esmtp)
     {
-    if (smtp_write_command(&outblock, FALSE, "HELO %s\r\n", helo_data) < 0)
+    BOOL good_response;
+    int n = sizeof(sx->buffer);
+    uschar * rsp = sx->buffer;
+
+    if (sx->esmtp_sent && (n = Ustrlen(sx->buffer)) < sizeof(sx->buffer)/2)
+      { rsp = sx->buffer + n + 1; n = sizeof(sx->buffer) - n; }
+
+    if (smtp_write_command(&sx->outblock, FALSE, "HELO %s\r\n", sx->helo_data) < 0)
       goto SEND_FAILED;
-    if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
-      ob->command_timeout)) goto RESPONSE_FAILED;
+    good_response = smtp_read_response(&sx->inblock, rsp, n,
+      '2', sx->ob->command_timeout);
+#ifdef EXPERIMENTAL_DSN_INFO
+    sx->helo_response = string_copy(rsp);
+#endif
+    if (!good_response)
+      {
+      /* Handle special logging for a closed connection after HELO
+      when had previously sent EHLO */
+
+      if (rsp != sx->buffer && rsp[0] == 0 && (errno == 0 || errno == ECONNRESET))
+       {
+       errno = ERRNO_SMTPCLOSED;
+       goto EHLOHELO_FAILED;
+       }
+      Ustrncpy(sx->buffer, rsp, sizeof(sx->buffer)/2);
+      goto RESPONSE_FAILED;
+      }
     }
 
-  /* Set IGNOREQUOTA if the response to LHLO specifies support and the
-  lmtp_ignore_quota option was set. */
+  sx->peer_offered = smtp_peer_options = 0;
 
-  igquotstr = (lmtp && ob->lmtp_ignore_quota &&
-    pcre_exec(regex_IGNOREQUOTA, NULL, CS buffer, Ustrlen(CS buffer), 0,
-      PCRE_EOPT, NULL, 0) >= 0)? US" IGNOREQUOTA" : US"";
+  if (sx->esmtp || sx->lmtp)
+    {
+    sx->peer_offered = ehlo_response(sx->buffer,
+      PEER_OFFERED_TLS /* others checked later */
+      );
 
   /* Set tls_offered if the response to EHLO specifies support for STARTTLS. */
 
-  #ifdef SUPPORT_TLS
-  tls_offered = esmtp &&
-    pcre_exec(regex_STARTTLS, NULL, CS buffer, Ustrlen(buffer), 0,
-      PCRE_EOPT, NULL, 0) >= 0;
-  #endif
-
-  #ifndef DISABLE_PRDR
-  prdr_offered = esmtp &&
-    (pcre_exec(regex_PRDR, NULL, CS buffer, Ustrlen(buffer), 0,
-      PCRE_EOPT, NULL, 0) >= 0) &&
-    (verify_check_this_host(&(ob->hosts_try_prdr), NULL, host->name,
-      host->address, NULL) == OK);
-
-  if (prdr_offered)
-    {DEBUG(D_transport) debug_printf("PRDR usable\n");}
-  #endif
+#ifdef SUPPORT_TLS
+    smtp_peer_options |= sx->peer_offered & PEER_OFFERED_TLS;
+#endif
+    }
   }
 
 /* For continuing deliveries down the same channel, the socket is the standard
 input, and we don't need to redo EHLO here (but may need to do so for TLS - see
 below). Set up the pointer to where subsequent commands will be left, for
-error messages. Note that smtp_use_size and smtp_use_pipelining will have been
+error messages. Note that smtp_peer_options will have been
 set from the command line if they were set in the process that passed the
 connection on. */
 
+/*XXX continue case needs to propagate DSN_INFO, prob. in deliver.c
+as the continue goes via transport_pass_socket() and doublefork and exec.
+It does not wait.  Unclear how we keep separate host's responses
+separate - we could match up by host ip+port as a bodge. */
+
 else
   {
-  inblock.sock = outblock.sock = fileno(stdin);
+  sx->inblock.sock = sx->outblock.sock = fileno(stdin);
   smtp_command = big_buffer;
-  host->port = port;    /* Record the port that was used */
+  sx->host->port = sx->port;    /* Record the port that was used */
+  sx->helo_data = NULL;                /* ensure we re-expand ob->helo_data */
   }
 
 /* If TLS is available on this connection, whether continued or not, attempt to
@@ -1450,12 +1825,15 @@ the client not be required to use TLS. If the response is bad, copy the buffer
 for error analysis. */
 
 #ifdef SUPPORT_TLS
-if (tls_offered && !suppress_tls &&
-      verify_check_this_host(&(ob->hosts_avoid_tls), NULL, host->name,
-        host->address, NULL) != OK)
+if (  smtp_peer_options & PEER_OFFERED_TLS
+   && !suppress_tls
+   && verify_check_given_host(&sx->ob->hosts_avoid_tls, sx->host) != OK
+   && (  !sx->verify
+      || verify_check_given_host(&sx->ob->hosts_verify_avoid_tls, sx->host) != OK
+   )  )
   {
   uschar buffer2[4096];
-  if (smtp_write_command(&outblock, FALSE, "STARTTLS\r\n") < 0)
+  if (smtp_write_command(&sx->outblock, FALSE, "STARTTLS\r\n") < 0)
     goto SEND_FAILED;
 
   /* If there is an I/O error, transmission of this message is deferred. If
@@ -1465,13 +1843,16 @@ if (tls_offered && !suppress_tls &&
   STARTTLS, we carry on. This means we will try to send the message in clear,
   unless the host is in hosts_require_tls (tested below). */
 
-  if (!smtp_read_response(&inblock, buffer2, sizeof(buffer2), '2',
-      ob->command_timeout))
+  if (!smtp_read_response(&sx->inblock, buffer2, sizeof(buffer2), '2',
+      sx->ob->command_timeout))
     {
-    if (errno != 0 || buffer2[0] == 0 ||
-         (buffer2[0] == '4' && !ob->tls_tempfail_tryclear))
+    if (  errno != 0
+       || buffer2[0] == 0
+       || (buffer2[0] == '4' && !sx->ob->tls_tempfail_tryclear)
+       )
       {
-      Ustrncpy(buffer, buffer2, sizeof(buffer));
+      Ustrncpy(sx->buffer, buffer2, sizeof(sx->buffer));
+      sx->buffer[sizeof(sx->buffer)-1] = '\0';
       goto RESPONSE_FAILED;
       }
     }
@@ -1481,7 +1862,12 @@ if (tls_offered && !suppress_tls &&
   else
   TLS_NEGOTIATE:
     {
-    int rc = tls_client_start(inblock.sock, host, addrlist, ob);
+    address_item * addr;
+    int rc = tls_client_start(sx->inblock.sock, sx->host, sx->addrlist, sx->tblock
+# ifdef EXPERIMENTAL_DANE
+                            , sx->dane ? &tlsa_dnsa : NULL
+# endif
+                            );
 
     /* 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
@@ -1489,16 +1875,22 @@ if (tls_offered && !suppress_tls &&
 
     if (rc != OK)
       {
-      save_errno = ERRNO_TLSFAILURE;
+# ifdef EXPERIMENTAL_DANE
+      if (sx->dane) log_write(0, LOG_MAIN,
+         "DANE attempt failed; no TLS connection to %s [%s]",
+         sx->host->name, sx->host->address);
+# endif
+
+      errno = ERRNO_TLSFAILURE;
       message = US"failure while setting up TLS session";
-      send_quit = FALSE;
+      sx->send_quit = FALSE;
       goto TLS_FAILED;
       }
 
     /* TLS session is set up */
 
-    for (addr = addrlist; addr != NULL; addr = addr->next)
-      {
+    smtp_peer_options_wrap = smtp_peer_options;
+    for (addr = sx->addrlist; addr; addr = addr->next)
       if (addr->transport_return == PENDING_DEFER)
         {
         addr->cipher = tls_out.cipher;
@@ -1507,7 +1899,6 @@ if (tls_offered && !suppress_tls &&
         addr->peerdn = tls_out.peerdn;
        addr->ocsp = tls_out.ocsp;
         }
-      }
     }
   }
 
@@ -1524,27 +1915,29 @@ start of the Exim process (in exim.c). */
 if (tls_out.active >= 0)
   {
   char *greeting_cmd;
-  if (helo_data == NULL)
+  BOOL good_response;
+
+  if (!sx->helo_data && !(sx->helo_data = expand_string(sx->ob->helo_data)))
     {
-    helo_data = expand_string(ob->helo_data);
-    if (helo_data == NULL)
-      {
-      uschar *message = string_sprintf("failed to expand helo_data: %s",
-        expand_string_message);
-      set_errno(addrlist, 0, message, DEFER, FALSE);
-      yield = DEFER;
-      goto SEND_QUIT;
-      }
+    uschar *message = string_sprintf("failed to expand helo_data: %s",
+      expand_string_message);
+    set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE);
+    yield = DEFER;
+    goto SEND_QUIT;
     }
 
   /* For SMTPS we need to wait for the initial OK response. */
-  if (smtps)
+  if (sx->smtps)
     {
-    if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
-      ob->command_timeout)) goto RESPONSE_FAILED;
+    good_response = smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
+      '2', sx->ob->command_timeout);
+#ifdef EXPERIMENTAL_DSN_INFO
+    sx->smtp_greeting = string_copy(sx->buffer);
+#endif
+    if (!good_response) goto RESPONSE_FAILED;
     }
 
-  if (esmtp)
+  if (sx->esmtp)
     greeting_cmd = "EHLO";
   else
     {
@@ -1553,28 +1946,35 @@ if (tls_out.active >= 0)
       debug_printf("not sending EHLO (host matches hosts_avoid_esmtp)\n");
     }
 
-  if (smtp_write_command(&outblock, FALSE, "%s %s\r\n",
-        lmtp? "LHLO" : greeting_cmd, helo_data) < 0)
+  if (smtp_write_command(&sx->outblock, FALSE, "%s %s\r\n",
+        sx->lmtp ? "LHLO" : greeting_cmd, sx->helo_data) < 0)
     goto SEND_FAILED;
-  if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
-       ob->command_timeout))
-    goto RESPONSE_FAILED;
+  good_response = smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
+    '2', sx->ob->command_timeout);
+#ifdef EXPERIMENTAL_DSN_INFO
+  sx->helo_response = string_copy(sx->buffer);
+#endif
+  if (!good_response) goto RESPONSE_FAILED;
+  smtp_peer_options = 0;
   }
 
 /* If the host is required to use a secure channel, ensure that we
 have one. */
 
-else if (verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name,
-          host->address, NULL) == OK)
+else if (  sx->smtps
+# ifdef EXPERIMENTAL_DANE
+       || sx->dane
+# endif
+       || verify_check_given_host(&sx->ob->hosts_require_tls, sx->host) == OK
+       )
   {
-  save_errno = ERRNO_TLSREQUIRED;
-  message = string_sprintf("a TLS session is required for %s [%s], but %s",
-    host->name, host->address,
-    tls_offered? "an attempt to start TLS failed" :
-                 "the server did not offer TLS support");
+  errno = ERRNO_TLSREQUIRED;
+  message = string_sprintf("a TLS session is required, but %s",
+    smtp_peer_options & PEER_OFFERED_TLS
+    ? "an attempt to start TLS failed" : "the server did not offer TLS support");
   goto TLS_FAILED;
   }
-#endif
+#endif /*SUPPORT_TLS*/
 
 /* If TLS is active, we have just started it up and re-done the EHLO command,
 so its response needs to be analyzed. If TLS is not active and this is a
@@ -1582,184 +1982,298 @@ continued session down a previously-used socket, we haven't just done EHLO, so
 we skip this. */
 
 if (continue_hostname == NULL
-    #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
     || tls_out.active >= 0
-    #endif
+#endif
     )
   {
-  /* Set for IGNOREQUOTA if the response to LHLO specifies support and the
-  lmtp_ignore_quota option was set. */
+  if (sx->esmtp || sx->lmtp)
+    {
+    sx->peer_offered = ehlo_response(sx->buffer,
+       0 /* no TLS */
+       | (sx->lmtp && sx->ob->lmtp_ignore_quota ? PEER_OFFERED_IGNQ : 0)
+       | PEER_OFFERED_CHUNKING
+       | PEER_OFFERED_PRDR
+#ifdef SUPPORT_I18N
+       | (sx->addrlist->prop.utf8_msg ? PEER_OFFERED_UTF8 : 0)
+         /*XXX if we hand peercaps on to continued-conn processes,
+               must not depend on this addr */
+#endif
+       | PEER_OFFERED_DSN
+       | PEER_OFFERED_PIPE
+       | (sx->ob->size_addition >= 0 ? PEER_OFFERED_SIZE : 0)
+      );
+
+    /* Set for IGNOREQUOTA if the response to LHLO specifies support and the
+    lmtp_ignore_quota option was set. */
+
+    sx->igquotstr = sx->peer_offered & PEER_OFFERED_IGNQ ? US" IGNOREQUOTA" : US"";
+
+    /* If the response to EHLO specified support for the SIZE parameter, note
+    this, provided size_addition is non-negative. */
 
-  igquotstr = (lmtp && ob->lmtp_ignore_quota &&
-    pcre_exec(regex_IGNOREQUOTA, NULL, CS buffer, Ustrlen(CS buffer), 0,
-      PCRE_EOPT, NULL, 0) >= 0)? US" IGNOREQUOTA" : US"";
+    smtp_peer_options |= sx->peer_offered & PEER_OFFERED_SIZE;
 
-  /* If the response to EHLO specified support for the SIZE parameter, note
-  this, provided size_addition is non-negative. */
+    /* Note whether the server supports PIPELINING. If hosts_avoid_esmtp matched
+    the current host, esmtp will be false, so PIPELINING can never be used. If
+    the current host matches hosts_avoid_pipelining, don't do it. */
 
-  smtp_use_size = esmtp && ob->size_addition >= 0 &&
-    pcre_exec(regex_SIZE, NULL, CS buffer, Ustrlen(CS buffer), 0,
-      PCRE_EOPT, NULL, 0) >= 0;
+    if (  sx->peer_offered & PEER_OFFERED_PIPE
+       && verify_check_given_host(&sx->ob->hosts_avoid_pipelining, sx->host) != OK)
+      smtp_peer_options |= PEER_OFFERED_PIPE;
 
-  /* Note whether the server supports PIPELINING. If hosts_avoid_esmtp matched
-  the current host, esmtp will be false, so PIPELINING can never be used. If
-  the current host matches hosts_avoid_pipelining, don't do it. */
+    DEBUG(D_transport) debug_printf("%susing PIPELINING\n",
+      smtp_peer_options & PEER_OFFERED_PIPE ? "" : "not ");
 
-  smtp_use_pipelining = esmtp &&
-    verify_check_this_host(&(ob->hosts_avoid_pipelining), NULL, host->name,
-      host->address, NULL) != OK &&
-    pcre_exec(regex_PIPELINING, NULL, CS buffer, Ustrlen(CS buffer), 0,
-      PCRE_EOPT, NULL, 0) >= 0;
+    if (  sx->peer_offered & PEER_OFFERED_CHUNKING
+       && verify_check_given_host(&sx->ob->hosts_try_chunking, sx->host) != OK)
+      sx->peer_offered &= ~PEER_OFFERED_CHUNKING;
 
-  DEBUG(D_transport) debug_printf("%susing PIPELINING\n",
-    smtp_use_pipelining? "" : "not ");
+    if (sx->peer_offered & PEER_OFFERED_CHUNKING)
+      {DEBUG(D_transport) debug_printf("CHUNKING usable\n");}
 
 #ifndef DISABLE_PRDR
-  prdr_offered = esmtp &&
-    pcre_exec(regex_PRDR, NULL, CS buffer, Ustrlen(CS buffer), 0,
-      PCRE_EOPT, NULL, 0) >= 0 &&
-    verify_check_this_host(&(ob->hosts_try_prdr), NULL, host->name,
-      host->address, NULL) == OK;
+    if (  sx->peer_offered & PEER_OFFERED_PRDR
+       && verify_check_given_host(&sx->ob->hosts_try_prdr, sx->host) != OK)
+      sx->peer_offered &= ~PEER_OFFERED_PRDR;
 
-  if (prdr_offered)
-    {DEBUG(D_transport) debug_printf("PRDR usable\n");}
+    if (sx->peer_offered & PEER_OFFERED_PRDR)
+      {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 server supports DSN */
+    smtp_peer_options |= sx->peer_offered & PEER_OFFERED_DSN;
+    DEBUG(D_transport) debug_printf("%susing DSN\n",
+                       sx->peer_offered & PEER_OFFERED_DSN ? "" : "not ");
 
-  /* 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
-  authenticator's client driver is running. */
+    /* 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
+    authenticator's client driver is running. */
 
-  switch (yield = smtp_auth(buffer, sizeof(buffer), addrlist, host,
-                           ob, esmtp, &inblock, &outblock))
-    {
-    default:           goto SEND_QUIT;
-    case OK:           break;
-    case FAIL_SEND:    goto SEND_FAILED;
-    case FAIL:         goto RESPONSE_FAILED;
+    switch (yield = smtp_auth(sx->buffer, sizeof(sx->buffer), sx->addrlist, sx->host,
+                             sx->ob, sx->esmtp, &sx->inblock, &sx->outblock))
+      {
+      default:         goto SEND_QUIT;
+      case OK:         break;
+      case FAIL_SEND:  goto SEND_FAILED;
+      case FAIL:       goto RESPONSE_FAILED;
+      }
     }
   }
+pipelining_active = !!(smtp_peer_options & PEER_OFFERED_PIPE);
 
 /* The setting up of the SMTP call is now complete. Any subsequent errors are
 message-specific. */
 
-setting_up = FALSE;
+sx->setting_up = FALSE;
 
-/* If there is a filter command specified for this transport, we can now
-set it up. This cannot be done until the identify of the host is known. */
+#ifdef SUPPORT_I18N
+if (sx->addrlist->prop.utf8_msg)
+  {
+  sx->utf8_needed = !sx->addrlist->prop.utf8_downcvt
+                   && !sx->addrlist->prop.utf8_downcvt_maybe;
+  DEBUG(D_transport) if (!sx->utf8_needed)
+    debug_printf("utf8: %s downconvert\n",
+      sx->addrlist->prop.utf8_downcvt ? "mandatory" : "optional");
+  }
 
-if (tblock->filter_command != NULL)
+/* If this is an international message we need the host to speak SMTPUTF8 */
+if (sx->utf8_needed && !(sx->peer_offered & PEER_OFFERED_UTF8))
   {
-  BOOL rc;
-  uschar buffer[64];
-  sprintf(CS buffer, "%.50s transport", tblock->name);
-  rc = transport_set_up_command(&transport_filter_argv, tblock->filter_command,
-    TRUE, DEFER, addrlist, buffer, NULL);
-  transport_filter_timeout = tblock->filter_timeout;
+  errno = ERRNO_UTF8_FWD;
+  goto RESPONSE_FAILED;
+  }
+#endif
 
-  /* On failure, copy the error to all addresses, abandon the SMTP call, and
-  yield ERROR. */
+return OK;
 
-  if (!rc)
-    {
-    set_errno(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER,
-      FALSE);
-    yield = ERROR;
-    goto SEND_QUIT;
-    }
+
+  {
+  int code;
+
+  RESPONSE_FAILED:
+    message = NULL;
+    sx->send_quit = check_response(sx->host, &errno, sx->addrlist->more_errno,
+      sx->buffer, &code, &message, &pass_message);
+    goto FAILED;
+
+  SEND_FAILED:
+    code = '4';
+    message = US string_sprintf("send() to %s [%s] failed: %s",
+      sx->host->name, sx->host->address, strerror(errno));
+    sx->send_quit = FALSE;
+    goto FAILED;
+
+  /* This label is jumped to directly when a TLS negotiation has failed,
+  or was not done for a host for which it is required. Values will be set
+  in message and errno, and setting_up will always be true. Treat as
+  a temporary error. */
+
+  EHLOHELO_FAILED:
+    code = '4';
+    message = string_sprintf("Remote host closed connection in response to %s"
+      " (EHLO response was: %s)", smtp_command, sx->buffer);
+    sx->send_quit = FALSE;
+    goto FAILED;
+
+#ifdef SUPPORT_TLS
+  TLS_FAILED:
+    code = '4';
+    goto FAILED;
+#endif
+
+  /* The failure happened while setting up the call; see if the failure was
+  a 5xx response (this will either be on connection, or following HELO - a 5xx
+  after EHLO causes it to try HELO). If so, fail all addresses, as this host is
+  never going to accept them. For other errors during setting up (timeouts or
+  whatever), defer all addresses, and yield DEFER, so that the host is not
+  tried again for a while. */
+
+FAILED:
+  sx->ok = FALSE;                /* For when reached by GOTO */
+
+  yield = code == '5'
+#ifdef SUPPORT_I18N
+         || errno == ERRNO_UTF8_FWD
+#endif
+    ? FAIL : DEFER;
+
+  set_errno(sx->addrlist, errno, message, yield, pass_message, sx->host
+#ifdef EXPERIMENTAL_DSN_INFO
+           , sx->smtp_greeting, sx->helo_response
+#endif
+           );
   }
 
 
-/* For messages that have more than the maximum number of envelope recipients,
-we want to send several transactions down the same SMTP connection. (See
-comments in deliver.c as to how this reconciles, heuristically, with
-remote_max_parallel.) This optimization was added to Exim after the following
-code was already working. The simplest way to put it in without disturbing the
-code was to use a goto to jump back to this point when there is another
-transaction to handle. */
+SEND_QUIT:
 
-SEND_MESSAGE:
-sync_addr = first_addr;
-address_count = 0;
-ok = FALSE;
-send_rset = TRUE;
-completed_address = FALSE;
+if (sx->send_quit)
+  (void)smtp_write_command(&sx->outblock, FALSE, "QUIT\r\n");
 
+#ifdef SUPPORT_TLS
+tls_close(FALSE, TRUE);
+#endif
 
-/* Initiate a message transfer. If we know the receiving MTA supports the SIZE
-qualification, send it, adding something to the message size to allow for
-imprecision and things that get added en route. Exim keeps the number of lines
+/* Close the socket, and return the appropriate value, first setting
+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,
+
+If all went well and continue_more is set, we shouldn't actually get here if
+there are further addresses, as the return above will be taken. However,
+writing RSET might have failed, or there may be other addresses whose hosts are
+specified in the transports, and therefore not visible at top level, in which
+case continue_more won't get set. */
+
+HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
+if (sx->send_quit)
+  {
+  shutdown(sx->outblock.sock, SHUT_WR);
+  if (fcntl(sx->inblock.sock, F_SETFL, O_NONBLOCK) == 0)
+    for (rc = 16; read(sx->inblock.sock, sx->inbuffer, sizeof(sx->inbuffer)) > 0 && rc > 0;)
+      rc--;                            /* drain socket */
+  sx->send_quit = FALSE;
+  }
+(void)close(sx->inblock.sock);
+sx->inblock.sock = sx->outblock.sock = -1;
+
+#ifndef DISABLE_EVENT
+(void) event_raise(sx->tblock->event_action, US"tcp:close", NULL);
+#endif
+
+continue_transport = NULL;
+continue_hostname = NULL;
+return yield;
+}
+
+
+
+
+/* Create the string of options that will be appended to the MAIL FROM:
+in the connection context buffer */
+
+static int
+build_mailcmd_options(smtp_context * sx, address_item * addrlist)
+{
+uschar * p = sx->buffer;
+address_item * addr;
+int address_count;
+
+*p = 0;
+
+/* If we know the receiving MTA supports the SIZE qualification,
+send it, adding something to the message size to allow for imprecision
+and things that get added en route. Exim keeps the number of lines
 in a message, so we can give an accurate value for the original message, but we
 need some additional to handle added headers. (Double "." characters don't get
 included in the count.) */
 
-p = buffer;
-*p = 0;
-
-if (smtp_use_size)
+if (sx->peer_offered & PEER_OFFERED_SIZE)
   {
-  sprintf(CS p, " SIZE=%d", message_size+message_linecount+ob->size_addition);
+  sprintf(CS p, " SIZE=%d", message_size+message_linecount+sx->ob->size_addition);
   while (*p) p++;
   }
 
 #ifndef DISABLE_PRDR
-prdr_active = FALSE;
-if (prdr_offered)
-  {
-  for (addr = first_addr; addr; addr = addr->next)
+/* If it supports Per-Recipient Data Reponses, and we have omre than one recipient,
+request that */
+
+sx->prdr_active = FALSE;
+if (sx->peer_offered & PEER_OFFERED_PRDR)
+  for (addr = addrlist; addr; addr = addr->next)
     if (addr->transport_return == PENDING_DEFER)
       {
       for (addr = addr->next; addr; addr = addr->next)
         if (addr->transport_return == PENDING_DEFER)
          {                     /* at least two recipients to send */
-         prdr_active = TRUE;
+         sx->prdr_active = TRUE;
          sprintf(CS p, " PRDR"); p += 5;
-         goto prdr_is_active;
+         break;
          }
       break;
       }
-  }
-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;
+#ifdef SUPPORT_I18N
+/* If it supports internationalised messages, and this meesage need that,
+request it */
 
-/* Add any DSN flags to the mail command */
+if (  sx->peer_offered & PEER_OFFERED_UTF8
+   && addrlist->prop.utf8_msg
+   && !addrlist->prop.utf8_downcvt
+   )
+  Ustrcpy(p, " SMTPUTF8"), p += 9;
+#endif
 
-if ((smtp_use_dsn) && (dsn_all_lasthop == FALSE))
+/* check if all addresses have DSN-lasthop flag; do not send RET and ENVID if so */
+for (sx->dsn_all_lasthop = TRUE, addr = addrlist, address_count = 0;
+     addr && address_count < sx->max_rcpt;
+     addr = addr->next) if (addr->transport_return == PENDING_DEFER)
   {
-  if (dsn_ret == dsn_ret_hdrs)
+  address_count++;
+  if (!(addr->dsn_flags & rf_dsnlasthop))
     {
-    strcpy(p, " RET=HDRS");
-    while (*p) p++;
+    sx->dsn_all_lasthop = FALSE;
+    break;
     }
+  }
+
+/* Add any DSN flags to the mail command */
+
+if (sx->peer_offered & PEER_OFFERED_DSN && !sx->dsn_all_lasthop)
+  {
+  if (dsn_ret == dsn_ret_hdrs)
+    { Ustrcpy(p, " RET=HDRS"); p += 9; }
   else if (dsn_ret == dsn_ret_full)
+    { Ustrcpy(p, " RET=FULL"); p += 9; }
+
+  if (dsn_envid)
     {
-    strcpy(p, " RET=FULL");
-    while (*p) p++;
-    }
-  if (dsn_envid != NULL)
-    {
-    string_format(p, sizeof(buffer) - (p-buffer), " ENVID=%s", dsn_envid);
+    string_format(p, sizeof(sx->buffer) - (p-sx->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
@@ -1768,8 +2282,72 @@ Other expansion failures are serious. An empty result is ignored, but there is
 otherwise no check - this feature is expected to be used with LMTP and other
 cases where non-standard addresses (e.g. without domains) might be required. */
 
-if (smtp_mail_auth_str(p, sizeof(buffer) - (p-buffer), addrlist, ob))
-    return ERROR;
+if (smtp_mail_auth_str(p, sizeof(sx->buffer) - (p-sx->buffer), addrlist, sx->ob))
+  return ERROR;
+
+return OK;
+}
+
+
+static void
+build_rcptcmd_options(smtp_context * sx, const address_item * addr)
+{
+uschar * p = sx->buffer;
+*p = 0;
+
+/* Add any DSN flags to the rcpt command */
+
+if (sx->peer_offered & PEER_OFFERED_DSN && !(addr->dsn_flags & rf_dsnlasthop))
+  {
+  if (addr->dsn_flags & rf_dsnflags)
+    {
+    int i;
+    BOOL first = TRUE;
+
+    Ustrcpy(p, " NOTIFY=");
+    while (*p) p++;
+    for (i = 0; i < nelem(rf_list); i++) if (addr->dsn_flags & rf_list[i])
+      {
+      if (!first) *p++ = ',';
+      first = FALSE;
+      Ustrcpy(p, rf_names[i]);
+      while (*p) p++;
+      }
+    }
+
+  if (addr->dsn_orcpt)
+    {
+    string_format(p, sizeof(sx->buffer) - (p-sx->buffer), " ORCPT=%s",
+      addr->dsn_orcpt);
+    while (*p) p++;
+    }
+  }
+}
+
+
+
+/*
+Return:
+ 0     good, rcpt results in addr->transport_return (PENDING_OK, DEFER, FAIL)
+ -1    MAIL response error
+ -2    any non-MAIL read i/o error
+ -3    non-MAIL response timeout
+ -4    internal error; channel still usable
+ -5    transmit failed
+ */
+
+int
+smtp_write_mail_and_rcpt_cmds(smtp_context * sx, int * yield)
+{
+address_item * addr;
+int address_count;
+int rc;
+
+if (build_mailcmd_options(sx, sx->first_addr) != OK)
+  {
+  *yield = ERROR;
+  return -4;
+  }
 
 /* From here until we send the DATA command, we can make use of PIPELINING
 if the server host supports it. The code has to be able to check the responses
@@ -1777,135 +2355,280 @@ at any point, for when the buffer fills up, so we write it totally generally.
 When PIPELINING is off, each command written reports that it has flushed the
 buffer. */
 
-pending_MAIL = TRUE;     /* The block starts with MAIL */
+sx->pending_MAIL = TRUE;     /* The block starts with MAIL */
+
+  {
+  uschar * s = sx->from_addr;
+#ifdef SUPPORT_I18N
+  uschar * errstr = NULL;
+
+  /* If we must downconvert, do the from-address here.  Remember we had to
+  for the to-addresses (done below), and also (ugly) for re-doing when building
+  the delivery log line. */
+
+  if (  sx->addrlist->prop.utf8_msg
+     && (sx->addrlist->prop.utf8_downcvt || !(sx->peer_offered & PEER_OFFERED_UTF8))
+     )
+    {
+    if (s = string_address_utf8_to_alabel(s, &errstr), errstr)
+      {
+      set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, errstr, DEFER, FALSE);
+      *yield = ERROR;
+      return -4;
+      }
+    setflag(sx->addrlist, af_utf8_downcvt);
+    }
+#endif
+
+  rc = smtp_write_command(&sx->outblock, pipelining_active,
+         "MAIL FROM:<%s>%s\r\n", s, sx->buffer);
+  }
+
+mail_command = string_copy(big_buffer);  /* Save for later error message */
+
+switch(rc)
+  {
+  case -1:                /* Transmission error */
+    return -5;
+
+  case +1:                /* Cmd was sent */
+    if (!smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer), '2',
+       sx->ob->command_timeout))
+      {
+      if (errno == 0 && sx->buffer[0] == '4')
+       {
+       errno = ERRNO_MAIL4XX;
+       sx->addrlist->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
+       }
+      return -1;
+      }
+    sx->pending_MAIL = FALSE;
+    break;
+
+  /* otherwise zero: command queued for pipeline */
+  }
+
+/* Pass over all the relevant recipient addresses for this host, which are the
+ones that have status PENDING_DEFER. If we are using PIPELINING, we can send
+several before we have to read the responses for those seen so far. This
+checking is done by a subroutine because it also needs to be done at the end.
+Send only up to max_rcpt addresses at a time, leaving next_addr pointing to
+the next one if not all are sent.
+
+In the MUA wrapper situation, we want to flush the PIPELINING buffer for the
+last address because we want to abort if any recipients have any kind of
+problem, temporary or permanent. We know that all recipient addresses will have
+the PENDING_DEFER status, because only one attempt is ever made, and we know
+that max_rcpt will be large, so all addresses will be done at once.
+
+For verify we flush the pipeline after any (the only) rcpt address. */
+
+for (addr = sx->first_addr, address_count = 0;
+     addr  &&  address_count < sx->max_rcpt;
+     addr = addr->next) if (addr->transport_return == PENDING_DEFER)
+  {
+  int count;
+  BOOL no_flush;
+  uschar * rcpt_addr;
+
+  addr->dsn_aware = sx->peer_offered & PEER_OFFERED_DSN
+    ? dsn_support_yes : dsn_support_no;
+
+  address_count++;
+  no_flush = pipelining_active && !sx->verify && (!mua_wrapper || addr->next);
+
+  build_rcptcmd_options(sx, addr);
+
+  /* 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. */
+
+  rcpt_addr = transport_rcpt_address(addr, sx->tblock->rcpt_include_affixes);
+
+#ifdef SUPPORT_I18N
+  if (  testflag(sx->addrlist, af_utf8_downcvt)
+     && !(rcpt_addr = string_address_utf8_to_alabel(rcpt_addr, NULL))
+     )
+    {
+    /*XXX could we use a per-address errstr here? Not fail the whole send? */
+    errno = ERRNO_EXPANDFAIL;
+    return -5;         /*XXX too harsh? */
+    }
+#endif
+
+  count = smtp_write_command(&sx->outblock, no_flush, "RCPT TO:<%s>%s%s\r\n",
+    rcpt_addr, sx->igquotstr, sx->buffer);
+
+  if (count < 0) return -5;
+  if (count > 0)
+    {
+    switch(sync_responses(sx, count, 0))
+      {
+      case 3: sx->ok = TRUE;                   /* 2xx & 5xx => OK & progress made */
+      case 2: sx->completed_addr = TRUE;       /* 5xx (only) => progress made */
+             break;
+
+      case 1: sx->ok = TRUE;                   /* 2xx (only) => OK, but if LMTP, */
+             if (!sx->lmtp)                    /*  can't tell about progress yet */
+               sx->completed_addr = TRUE;
+      case 0:                                  /* No 2xx or 5xx, but no probs */
+             break;
+
+      case -1: return -3;                      /* Timeout on RCPT */
+      case -2: return -2;                      /* non-MAIL read i/o error */
+      default: return -1;                      /* any MAIL error */
+      }
+    sx->pending_MAIL = FALSE;            /* Dealt with MAIL */
+    }
+  }      /* Loop for next address */
+
+sx->next_addr = addr;
+return 0;
+}
+
+
+/*************************************************
+*       Deliver address list to given host       *
+*************************************************/
+
+/* If continue_hostname is not null, we get here only when continuing to
+deliver down an existing channel. The channel was passed as the standard
+input. TLS is never active on a passed channel; the previous process always
+closes it down before passing the connection on.
+
+Otherwise, we have to make a connection to the remote host, and do the
+initial protocol exchange.
+
+When running as an MUA wrapper, if the sender or any recipient is rejected,
+temporarily or permanently, we force failure for all recipients.
+
+Arguments:
+  addrlist        chain of potential addresses to deliver; only those whose
+                  transport_return field is set to PENDING_DEFER are currently
+                  being processed; others should be skipped - they have either
+                  been delivered to an earlier host or IP address, or been
+                  failed by one of them.
+  host            host to deliver to
+  host_af         AF_INET or AF_INET6
+  port            default TCP/IP port to use, in host byte order
+  interface       interface to bind to, or NULL
+  tblock          transport instance block
+  message_defer   set TRUE if yield is OK, but all addresses were deferred
+                    because of a non-recipient, non-host failure, that is, a
+                    4xx response to MAIL FROM, DATA, or ".". This is a defer
+                    that is specific to the message.
+  suppress_tls    if TRUE, don't attempt a TLS connection - this is set for
+                    a second attempt after TLS initialization fails
 
-rc = smtp_write_command(&outblock, smtp_use_pipelining,
-       "MAIL FROM:<%s>%s\r\n", return_path, buffer);
-mail_command = string_copy(big_buffer);  /* Save for later error message */
+Returns:          OK    - the connection was made and the delivery attempted;
+                          the result for each address is in its data block.
+                  DEFER - the connection could not be made, or something failed
+                          while setting up the SMTP session, or there was a
+                          non-message-specific error, such as a timeout.
+                  ERROR - a filter command is specified for this transport,
+                          and there was a problem setting it up; OR helo_data
+                          or add_headers or authenticated_sender is specified
+                          for this transport, and the string failed to expand
+*/
 
-switch(rc)
-  {
-  case -1:                /* Transmission error */
-  goto SEND_FAILED;
+static int
+smtp_deliver(address_item *addrlist, host_item *host, int host_af, int port,
+  uschar *interface, transport_instance *tblock,
+  BOOL *message_defer, BOOL suppress_tls)
+{
+address_item *addr;
+int yield = OK;
+int save_errno;
+int rc;
+time_t start_delivery_time = time(NULL);
 
-  case +1:                /* Block was sent */
-  if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
-       ob->command_timeout))
-    {
-    if (errno == 0 && buffer[0] == '4')
-      {
-      errno = ERRNO_MAIL4XX;
-      addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
-      }
-    goto RESPONSE_FAILED;
-    }
-  pending_MAIL = FALSE;
-  break;
-  }
+BOOL pass_message = FALSE;
+uschar *message = NULL;
+uschar new_message_id[MESSAGE_ID_LENGTH + 1];
+uschar *p;
 
-/* Pass over all the relevant recipient addresses for this host, which are the
-ones that have status PENDING_DEFER. If we are using PIPELINING, we can send
-several before we have to read the responses for those seen so far. This
-checking is done by a subroutine because it also needs to be done at the end.
-Send only up to max_rcpt addresses at a time, leaving first_addr pointing to
-the next one if not all are sent.
+smtp_context sx;
 
-In the MUA wrapper situation, we want to flush the PIPELINING buffer for the
-last address because we want to abort if any recipients have any kind of
-problem, temporary or permanent. We know that all recipient addresses will have
-the PENDING_DEFER status, because only one attempt is ever made, and we know
-that max_rcpt will be large, so all addresses will be done at once. */
+suppress_tls = suppress_tls;  /* stop compiler warning when no TLS support */
+*message_defer = FALSE;
 
-for (addr = first_addr;
-     address_count < max_rcpt && addr != NULL;
-     addr = addr->next)
-  {
-  int count;
-  BOOL no_flush;
+sx.addrlist = addrlist;
+sx.host = host;
+sx.host_af = host_af,
+sx.port = port;
+sx.interface = interface;
+sx.helo_data = NULL;
+sx.tblock = tblock;
+sx.verify = FALSE;
 
-  #ifdef EXPERIMENTAL_DSN
-  if(smtp_use_dsn)
-    addr->dsn_aware = dsn_support_yes;
-  else
-    addr->dsn_aware = dsn_support_no;
-  #endif
+/* Get the channel set up ready for a message (MAIL FROM being the next
+SMTP command to send */
 
-  if (addr->transport_return != PENDING_DEFER) continue;
+if ((rc = smtp_setup_conn(&sx, suppress_tls)) != OK)
+  return rc;
 
-  address_count++;
-  no_flush = smtp_use_pipelining && (!mua_wrapper || addr->next != NULL);
+/* If there is a filter command specified for this transport, we can now
+set it up. This cannot be done until the identify of the host is known. */
 
-  #ifdef EXPERIMENTAL_DSN
-  /* Add any DSN flags to the rcpt command and add to the sent string */
+if (tblock->filter_command)
+  {
+  BOOL rc;
+  uschar fbuf[64];
+  sprintf(CS fbuf, "%.50s transport", tblock->name);
+  rc = transport_set_up_command(&transport_filter_argv, tblock->filter_command,
+    TRUE, DEFER, addrlist, fbuf, NULL);
+  transport_filter_timeout = tblock->filter_timeout;
 
-  p = buffer;
-  *p = 0;
+  /* On failure, copy the error to all addresses, abandon the SMTP call, and
+  yield ERROR. */
 
-  if ((smtp_use_dsn) && ((addr->dsn_flags & rf_dsnlasthop) != 1))
+  if (!rc)
     {
-    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++;
-          }
-        }
-      }
+    set_errno_nohost(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER,
+      FALSE);
+    yield = ERROR;
+    goto SEND_QUIT;
+    }
 
-    if (addr->dsn_orcpt != NULL) {
-      string_format(p, sizeof(buffer) - (p-buffer), " ORCPT=%s",
-        addr->dsn_orcpt);
-      while (*p) p++;
-      }
+  if (  transport_filter_argv
+     && *transport_filter_argv
+     && **transport_filter_argv
+     && sx.peer_offered & PEER_OFFERED_CHUNKING
+     )
+    {
+    sx.peer_offered &= ~PEER_OFFERED_CHUNKING;
+    DEBUG(D_transport) debug_printf("CHUNKING not usable due to transport filter\n");
     }
-  #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. */
+/* For messages that have more than the maximum number of envelope recipients,
+we want to send several transactions down the same SMTP connection. (See
+comments in deliver.c as to how this reconciles, heuristically, with
+remote_max_parallel.) This optimization was added to Exim after the following
+code was already working. The simplest way to put it in without disturbing the
+code was to use a goto to jump back to this point when there is another
+transaction to handle. */
 
-  #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
+SEND_MESSAGE:
+sx.from_addr = return_path;
+sx.first_addr = sx.sync_addr = addrlist;
+sx.ok = FALSE;
+sx.send_rset = TRUE;
+sx.completed_addr = FALSE;
 
-  if (count < 0) goto SEND_FAILED;
-  if (count > 0)
-    {
-    switch(sync_responses(first_addr, tblock->rcpt_include_affixes,
-             &sync_addr, host, count, ob->address_retry_include_sender,
-             pending_MAIL, 0, &inblock, ob->command_timeout, buffer,
-             sizeof(buffer)))
-      {
-      case 3: ok = TRUE;                   /* 2xx & 5xx => OK & progress made */
-      case 2: completed_address = TRUE;    /* 5xx (only) => progress made */
-      break;
 
-      case 1: ok = TRUE;                   /* 2xx (only) => OK, but if LMTP, */
-      if (!lmtp) completed_address = TRUE; /* can't tell about progress yet */
-      case 0:                              /* No 2xx or 5xx, but no probs */
-      break;
+/* Initiate a message transfer. */
 
-      case -1: goto END_OFF;               /* Timeout on RCPT */
-      default: goto RESPONSE_FAILED;       /* I/O error, or any MAIL error */
-      }
-    pending_MAIL = FALSE;                  /* Dealt with MAIL */
-    }
-  }      /* Loop for next address */
+switch(smtp_write_mail_and_rcpt_cmds(&sx, &yield))
+  {
+  case 0:              break;
+  case -1: case -2:    goto RESPONSE_FAILED;
+  case -3:             goto END_OFF;
+  case -4:             goto SEND_QUIT;
+  default:             goto SEND_FAILED;
+  }
 
 /* If we are an MUA wrapper, abort if any RCPTs were rejected, either
 permanently or temporarily. We should have flushed and synced after the last
@@ -1914,49 +2637,48 @@ RCPT. */
 if (mua_wrapper)
   {
   address_item *badaddr;
-  for (badaddr = first_addr; badaddr != NULL; badaddr = badaddr->next)
-    {
-    if (badaddr->transport_return != PENDING_OK) break;
-    }
-  if (badaddr != NULL)
-    {
-    set_errno(addrlist, 0, badaddr->message, FAIL,
-      testflag(badaddr, af_pass_message));
-    ok = FALSE;
-    }
+  for (badaddr = sx.first_addr; badaddr; badaddr = badaddr->next)
+    if (badaddr->transport_return != PENDING_OK)
+      {
+      /*XXX could we find a better errno than 0 here? */
+      set_errno_nohost(addrlist, 0, badaddr->message, FAIL,
+       testflag(badaddr, af_pass_message));
+      sx.ok = FALSE;
+      break;
+      }
   }
 
 /* If ok is TRUE, we know we have got at least one good recipient, and must now
 send DATA, but if it is FALSE (in the normal, non-wrapper case), we may still
 have a good recipient buffered up if we are pipelining. We don't want to waste
 time sending DATA needlessly, so we only send it if either ok is TRUE or if we
-are pipelining. The responses are all handled by sync_responses(). */
+are pipelining. The responses are all handled by sync_responses().
+If using CHUNKING, do not send a BDAT until we know how big a chunk we want
+to send is. */
 
-if (ok || (smtp_use_pipelining && !mua_wrapper))
+if (  !(sx.peer_offered & PEER_OFFERED_CHUNKING)
+   && (sx.ok || (pipelining_active && !mua_wrapper)))
   {
-  int count = smtp_write_command(&outblock, FALSE, "DATA\r\n");
+  int count = smtp_write_command(&sx.outblock, FALSE, "DATA\r\n");
+
   if (count < 0) goto SEND_FAILED;
-  switch(sync_responses(first_addr, tblock->rcpt_include_affixes, &sync_addr,
-           host, count, ob->address_retry_include_sender, pending_MAIL,
-           ok? +1 : -1, &inblock, ob->command_timeout, buffer, sizeof(buffer)))
+  switch(sync_responses(&sx, count, sx.ok ? +1 : -1))
     {
-    case 3: ok = TRUE;                   /* 2xx & 5xx => OK & progress made */
-    case 2: completed_address = TRUE;    /* 5xx (only) => progress made */
+    case 3: sx.ok = TRUE;            /* 2xx & 5xx => OK & progress made */
+    case 2: sx.completed_addr = TRUE;    /* 5xx (only) => progress made */
     break;
 
-    case 1: ok = TRUE;                   /* 2xx (only) => OK, but if LMTP, */
-    if (!lmtp) completed_address = TRUE; /* can't tell about progress yet */
+    case 1: sx.ok = TRUE;            /* 2xx (only) => OK, but if LMTP, */
+    if (!sx.lmtp) sx.completed_addr = TRUE; /* can't tell about progress yet */
     case 0: break;                       /* No 2xx or 5xx, but no probs */
 
     case -1: goto END_OFF;               /* Timeout on RCPT */
     default: goto RESPONSE_FAILED;       /* I/O error, or any MAIL/DATA error */
     }
+  pipelining_active = FALSE;
+  data_command = string_copy(big_buffer);  /* Save for later error message */
   }
 
-/* Save the first address of the next batch. */
-
-first_addr = addr;
-
 /* If there were no good recipients (but otherwise there have been no
 problems), just set ok TRUE, since we have handled address-specific errors
 already. Otherwise, it's OK to send the message. Use the check/escape mechanism
@@ -1964,41 +2686,67 @@ for handling the SMTP dot-handling protocol, flagging to apply to headers as
 well as body. Set the appropriate timeout value to be used for each chunk.
 (Haven't been able to make it work using select() for writing yet.) */
 
-if (!ok) ok = TRUE; else
+if (!(sx.peer_offered & PEER_OFFERED_CHUNKING) && !sx.ok)
+  {
+  /* Save the first address of the next batch. */
+  sx.first_addr = sx.next_addr;
+
+  sx.ok = TRUE;
+  }
+else
   {
+  transport_ctx tctx = {
+    tblock,
+    addrlist,
+    US".", US"..",    /* Escaping strings */
+    topt_use_crlf | topt_escape_headers
+    | (tblock->body_only       ? topt_no_headers : 0)
+    | (tblock->headers_only    ? topt_no_body : 0)
+    | (tblock->return_path_add ? topt_add_return_path : 0)
+    | (tblock->delivery_date_add ? topt_add_delivery_date : 0)
+    | (tblock->envelope_to_add ? topt_add_envelope_to : 0)
+  };
+
+  /* If using CHUNKING we need a callback from the generic transport
+  support to us, for the sending of BDAT smtp commands and the reaping
+  of responses.  The callback needs a whole bunch of state so set up
+  a transport-context structure to be passed around. */
+
+  if (sx.peer_offered & PEER_OFFERED_CHUNKING)
+    {
+    tctx.check_string = tctx.escape_string = NULL;
+    tctx.options |= topt_use_bdat;
+    tctx.chunk_cb = smtp_chunk_cmd_callback;
+    sx.pending_BDAT = FALSE;
+    sx.good_RCPT = sx.ok;
+    sx.cmd_count = 0;
+    tctx.smtp_context = &sx;
+    }
+  else
+    tctx.options |= topt_end_dot;
+
+  /* Save the first address of the next batch. */
+  sx.first_addr = sx.next_addr;
+
+  /* Responses from CHUNKING commands go in buffer.  Otherwise,
+  there has not been a response. */
+
+  sx.buffer[0] = 0;
+
   sigalrm_seen = FALSE;
-  transport_write_timeout = ob->data_timeout;
+  transport_write_timeout = sx.ob->data_timeout;
   smtp_command = US"sending data block";   /* For error messages */
   DEBUG(D_transport|D_v)
-    debug_printf("  SMTP>> writing message and terminating \".\"\n");
+    if (sx.peer_offered & PEER_OFFERED_CHUNKING)
+      debug_printf("         will write message using CHUNKING\n");
+    else
+      debug_printf("  SMTP>> writing message and terminating \".\"\n");
   transport_count = 0;
+
 #ifndef DISABLE_DKIM
-  ok = dkim_transport_write_message(addrlist, inblock.sock,
-    topt_use_crlf | topt_end_dot | topt_escape_headers |
-      (tblock->body_only? topt_no_headers : 0) |
-      (tblock->headers_only? topt_no_body : 0) |
-      (tblock->return_path_add? topt_add_return_path : 0) |
-      (tblock->delivery_date_add? topt_add_delivery_date : 0) |
-      (tblock->envelope_to_add? topt_add_envelope_to : 0),
-    0,            /* No size limit */
-    tblock->add_headers, tblock->remove_headers,
-    US".", US"..",    /* Escaping strings */
-    tblock->rewrite_rules, tblock->rewrite_existflags,
-    ob->dkim_private_key, ob->dkim_domain, ob->dkim_selector,
-    ob->dkim_canon, ob->dkim_strict, ob->dkim_sign_headers
-    );
+  sx.ok = dkim_transport_write_message(sx.inblock.sock, &tctx, &sx.ob->dkim);
 #else
-  ok = transport_write_message(addrlist, inblock.sock,
-    topt_use_crlf | topt_end_dot | topt_escape_headers |
-      (tblock->body_only? topt_no_headers : 0) |
-      (tblock->headers_only? topt_no_body : 0) |
-      (tblock->return_path_add? topt_add_return_path : 0) |
-      (tblock->delivery_date_add? topt_add_delivery_date : 0) |
-      (tblock->envelope_to_add? topt_add_envelope_to : 0),
-    0,            /* No size limit */
-    tblock->add_headers, tblock->remove_headers,
-    US".", US"..",    /* Escaping strings */
-    tblock->rewrite_rules, tblock->rewrite_existflags);
+  sx.ok = transport_write_message(sx.inblock.sock, &tctx, 0);
 #endif
 
   /* transport_write_message() uses write() because it is called from other
@@ -2009,13 +2757,11 @@ if (!ok) ok = TRUE; else
   transport_write_timeout = 0;   /* for subsequent transports */
 
   /* Failure can either be some kind of I/O disaster (including timeout),
-  or the failure of a transport filter or the expansion of added headers. */
+  or the failure of a transport filter or the expansion of added headers.
+  Or, when CHUNKING, it can be a protocol-detected failure. */
 
-  if (!ok)
-    {
-    buffer[0] = 0;              /* There hasn't been a response */
+  if (!sx.ok)
     goto RESPONSE_FAILED;
-    }
 
   /* We used to send the terminating "." explicitly here, but because of
   buffering effects at both ends of TCP/IP connections, you don't gain
@@ -2025,25 +2771,43 @@ if (!ok) ok = TRUE; else
 
   smtp_command = US"end of data";
 
+  if (sx.peer_offered & PEER_OFFERED_CHUNKING && sx.cmd_count > 1)
+    {
+    /* Reap any outstanding MAIL & RCPT commands, but not a DATA-go-ahead */
+    switch(sync_responses(&sx, sx.cmd_count-1, 0))
+      {
+      case 3: sx.ok = TRUE;            /* 2xx & 5xx => OK & progress made */
+      case 2: sx.completed_addr = TRUE;    /* 5xx (only) => progress made */
+      break;
+
+      case 1: sx.ok = TRUE;            /* 2xx (only) => OK, but if LMTP, */
+      if (!sx.lmtp) sx.completed_addr = TRUE; /* can't tell about progress yet */
+      case 0: break;                       /* No 2xx or 5xx, but no probs */
+
+      case -1: goto END_OFF;               /* Timeout on RCPT */
+      default: goto RESPONSE_FAILED;       /* I/O error, or any MAIL/DATA error */
+      }
+    }
+
 #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
    * with per non-PRDR. */
-  if(prdr_active)
+  if(sx.prdr_active)
     {
-    ok = smtp_read_response(&inblock, buffer, sizeof(buffer), '3',
-      ob->final_timeout);
-    if (!ok && errno == 0)
-      switch(buffer[0])
-        {
-       case '2': prdr_active = FALSE;
-                 ok = TRUE;
-                 break;
-       case '4': errno = ERRNO_DATA4XX;
-                  addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
-                 break;
-        }
+    sx.ok = smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer), '3',
+      sx.ob->final_timeout);
+    if (!sx.ok && errno == 0) switch(sx.buffer[0])
+      {
+      case '2': sx.prdr_active = FALSE;
+               sx.ok = TRUE;
+               break;
+      case '4': errno = ERRNO_DATA4XX;
+               addrlist->more_errno |=
+                 ((sx.buffer[1] - '0')*10 + sx.buffer[2] - '0') << 8;
+               break;
+      }
     }
   else
 #endif
@@ -2051,14 +2815,14 @@ if (!ok) ok = TRUE; else
   /* For non-PRDR SMTP, we now read a single response that applies to the
   whole message.  If it is OK, then all the addresses have been delivered. */
 
-  if (!lmtp)
+  if (!sx.lmtp)
     {
-    ok = smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
-      ob->final_timeout);
-    if (!ok && errno == 0 && buffer[0] == '4')
+    sx.ok = smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer), '2',
+      sx.ob->final_timeout);
+    if (!sx.ok && errno == 0 && sx.buffer[0] == '4')
       {
       errno = ERRNO_DATA4XX;
-      addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+      addrlist->more_errno |= ((sx.buffer[1] - '0')*10 + sx.buffer[2] - '0') << 8;
       }
     }
 
@@ -2074,44 +2838,34 @@ if (!ok) ok = TRUE; else
   software before the spool gets updated. Also record the final SMTP
   confirmation if needed (for SMTP only). */
 
-  if (ok)
+  if (sx.ok)
     {
     int flag = '=';
     int delivery_time = (int)(time(NULL) - start_delivery_time);
     int len;
-    host_item *thost;
     uschar *conf = NULL;
-    send_rset = FALSE;
 
-    /* Make a copy of the host if it is local to this invocation
-    of the transport. */
-
-    if (copy_host)
-      {
-      thost = store_get(sizeof(host_item));
-      *thost = *host;
-      thost->name = string_copy(host->name);
-      thost->address = string_copy(host->address);
-      }
-    else thost = host;
+    sx.send_rset = FALSE;
+    pipelining_active = FALSE;
 
     /* Set up confirmation if needed - applies only to SMTP */
 
     if (
-        #ifndef EXPERIMENTAL_TPDA
-          (log_extra_selector & LX_smtp_confirmation) != 0 &&
-        #endif
-          !lmtp
+#ifdef DISABLE_EVENT
+          LOGGING(smtp_confirmation) &&
+#endif
+          !sx.lmtp
        )
       {
-      uschar *s = string_printing(buffer);
-      conf = (s == buffer)? (uschar *)string_copy(s) : s;
+      const uschar *s = string_printing(sx.buffer);
+      /* deconst cast ok here as string_printing was checked to have alloc'n'copied */
+      conf = (s == sx.buffer)? (uschar *)string_copy(s) : US s;
       }
 
     /* Process all transported addresses - for LMTP or PRDR, read a status for
     each one. */
 
-    for (addr = addrlist; addr != first_addr; addr = addr->next)
+    for (addr = addrlist; addr != sx.first_addr; addr = addr->next)
       {
       if (addr->transport_return != PENDING_OK) continue;
 
@@ -2121,42 +2875,43 @@ if (!ok) ok = TRUE; else
       it doesn't get tried again too soon. */
 
 #ifndef DISABLE_PRDR
-      if (lmtp || prdr_active)
+      if (sx.lmtp || sx.prdr_active)
 #else
-      if (lmtp)
+      if (sx.lmtp)
 #endif
         {
-        if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
-            ob->final_timeout))
+        if (!smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer), '2',
+            sx.ob->final_timeout))
           {
-          if (errno != 0 || buffer[0] == 0) goto RESPONSE_FAILED;
+          if (errno != 0 || sx.buffer[0] == 0) goto RESPONSE_FAILED;
           addr->message = string_sprintf(
 #ifndef DISABLE_PRDR
-           "%s error after %s: %s", prdr_active ? "PRDR":"LMTP",
+           "%s error after %s: %s", sx.prdr_active ? "PRDR":"LMTP",
 #else
            "LMTP error after %s: %s",
 #endif
-            big_buffer, string_printing(buffer));
+           data_command, string_printing(sx.buffer));
           setflag(addr, af_pass_message);   /* Allow message to go to user */
-          if (buffer[0] == '5')
+          if (sx.buffer[0] == '5')
             addr->transport_return = FAIL;
           else
             {
             errno = ERRNO_DATA4XX;
-            addr->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+            addr->more_errno |= ((sx.buffer[1] - '0')*10 + sx.buffer[2] - '0') << 8;
             addr->transport_return = DEFER;
 #ifndef DISABLE_PRDR
-            if (!prdr_active)
+            if (!sx.prdr_active)
 #endif
               retry_add_item(addr, addr->address_retry_key, 0);
             }
           continue;
           }
-        completed_address = TRUE;   /* NOW we can set this flag */
-        if ((log_extra_selector & LX_smtp_confirmation) != 0)
+        sx.completed_addr = TRUE;   /* NOW we can set this flag */
+        if (LOGGING(smtp_confirmation))
           {
-          uschar *s = string_printing(buffer);
-          conf = (s == buffer)? (uschar *)string_copy(s) : s;
+          const uschar *s = string_printing(sx.buffer);
+         /* deconst cast ok here as string_printing was checked to have alloc'n'copied */
+          conf = (s == sx.buffer) ? US string_copy(s) : US s;
           }
         }
 
@@ -2165,16 +2920,17 @@ if (!ok) ok = TRUE; else
 
       addr->transport_return = OK;
       addr->more_errno = delivery_time;
-      addr->host_used = thost;
+      addr->host_used = host;
       addr->special_action = flag;
       addr->message = conf;
 #ifndef DISABLE_PRDR
-      if (prdr_active) addr->flags |= af_prdr_used;
+      if (sx.prdr_active) addr->flags |= af_prdr_used;
 #endif
+      if (sx.peer_offered & PEER_OFFERED_CHUNKING) addr->flags |= af_chunking_used;
       flag = '-';
 
 #ifndef DISABLE_PRDR
-      if (!prdr_active)
+      if (!sx.prdr_active)
 #endif
         {
         /* Update the journal. For homonymic addresses, use the base address plus
@@ -2183,55 +2939,55 @@ if (!ok) ok = TRUE; else
         write error, as it may prove possible to update the spool file later. */
 
         if (testflag(addr, af_homonym))
-          sprintf(CS buffer, "%.500s/%s\n", addr->unique + 3, tblock->name);
+          sprintf(CS sx.buffer, "%.500s/%s\n", addr->unique + 3, tblock->name);
         else
-          sprintf(CS buffer, "%.500s\n", addr->unique);
+          sprintf(CS sx.buffer, "%.500s\n", addr->unique);
 
-        DEBUG(D_deliver) debug_printf("journalling %s", buffer);
-        len = Ustrlen(CS buffer);
-        if (write(journal_fd, buffer, len) != len)
+        DEBUG(D_deliver) debug_printf("journalling %s\n", sx.buffer);
+        len = Ustrlen(CS sx.buffer);
+        if (write(journal_fd, sx.buffer, len) != len)
           log_write(0, LOG_MAIN|LOG_PANIC, "failed to write journal for "
-            "%s: %s", buffer, strerror(errno));
+            "%s: %s", sx.buffer, strerror(errno));
         }
       }
 
 #ifndef DISABLE_PRDR
-      if (prdr_active)
+      if (sx.prdr_active)
         {
        /* PRDR - get the final, overall response.  For any non-success
        upgrade all the address statuses. */
-        ok = smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
-          ob->final_timeout);
-        if (!ok)
+        sx.ok = smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer), '2',
+          sx.ob->final_timeout);
+        if (!sx.ok)
          {
-         if(errno == 0 && buffer[0] == '4')
+         if(errno == 0 && sx.buffer[0] == '4')
             {
             errno = ERRNO_DATA4XX;
-            addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+            addrlist->more_errno |= ((sx.buffer[1] - '0')*10 + sx.buffer[2] - '0') << 8;
             }
-         for (addr = addrlist; addr != first_addr; addr = addr->next)
-            if (buffer[0] == '5' || addr->transport_return == OK)
+         for (addr = addrlist; addr != sx.first_addr; addr = addr->next)
+            if (sx.buffer[0] == '5' || addr->transport_return == OK)
               addr->transport_return = PENDING_OK; /* allow set_errno action */
          goto RESPONSE_FAILED;
          }
 
        /* Update the journal, or setup retry. */
-        for (addr = addrlist; addr != first_addr; addr = addr->next)
+        for (addr = addrlist; addr != sx.first_addr; addr = addr->next)
          if (addr->transport_return == OK)
-         {
-          if (testflag(addr, af_homonym))
-            sprintf(CS buffer, "%.500s/%s\n", addr->unique + 3, tblock->name);
-          else
-            sprintf(CS buffer, "%.500s\n", addr->unique);
-
-          DEBUG(D_deliver) debug_printf("journalling(PRDR) %s", buffer);
-          len = Ustrlen(CS buffer);
-          if (write(journal_fd, buffer, len) != len)
-            log_write(0, LOG_MAIN|LOG_PANIC, "failed to write journal for "
-              "%s: %s", buffer, strerror(errno));
-         }
-       else if (addr->transport_return == DEFER)
-          retry_add_item(addr, addr->address_retry_key, -2);
+           {
+           if (testflag(addr, af_homonym))
+             sprintf(CS sx.buffer, "%.500s/%s\n", addr->unique + 3, tblock->name);
+           else
+             sprintf(CS sx.buffer, "%.500s\n", addr->unique);
+
+           DEBUG(D_deliver) debug_printf("journalling(PRDR) %s\n", sx.buffer);
+           len = Ustrlen(CS sx.buffer);
+           if (write(journal_fd, sx.buffer, len) != len)
+             log_write(0, LOG_MAIN|LOG_PANIC, "failed to write journal for "
+               "%s: %s", sx.buffer, strerror(errno));
+           }
+         else if (addr->transport_return == DEFER)
+           retry_add_item(addr, addr->address_retry_key, -2);
        }
 #endif
 
@@ -2252,57 +3008,36 @@ assumed if errno == 0 and there is no text in the buffer. If control reaches
 here during the setting up phase (i.e. before MAIL FROM) then always defer, as
 the problem is not related to this specific message. */
 
-if (!ok)
+if (!sx.ok)
   {
-  int code;
+  int code, set_rc;
+  uschar * set_message;
 
   RESPONSE_FAILED:
-  save_errno = errno;
-  message = NULL;
-  send_quit = check_response(host, &save_errno, addrlist->more_errno,
-    buffer, &code, &message, &pass_message);
-  goto FAILED;
+    {
+    save_errno = errno;
+    message = NULL;
+    sx.send_quit = check_response(host, &save_errno, addrlist->more_errno,
+      sx.buffer, &code, &message, &pass_message);
+    goto FAILED;
+    }
 
   SEND_FAILED:
-  save_errno = errno;
-  code = '4';
-  message = US string_sprintf("send() to %s [%s] failed: %s",
-    host->name, host->address, strerror(save_errno));
-  send_quit = FALSE;
-  goto FAILED;
-
-  /* This label is jumped to directly when a TLS negotiation has failed,
-  or was not done for a host for which it is required. Values will be set
-  in message and save_errno, and setting_up will always be true. Treat as
-  a temporary error. */
-
-  #ifdef SUPPORT_TLS
-  TLS_FAILED:
-  code = '4';
-  #endif
-
-  /* If the failure happened while setting up the call, see if the failure was
-  a 5xx response (this will either be on connection, or following HELO - a 5xx
-  after EHLO causes it to try HELO). If so, fail all addresses, as this host is
-  never going to accept them. For other errors during setting up (timeouts or
-  whatever), defer all addresses, and yield DEFER, so that the host is not
-  tried again for a while. */
+    {
+    save_errno = errno;
+    code = '4';
+    message = US string_sprintf("send() to %s [%s] failed: %s",
+      host->name, host->address, strerror(save_errno));
+    sx.send_quit = FALSE;
+    goto FAILED;
+    }
 
   FAILED:
-  ok = FALSE;                /* For when reached by GOTO */
-
-  if (setting_up)
     {
-    if (code == '5')
-      {
-      set_errno(addrlist, save_errno, message, FAIL, pass_message);
-      }
-    else
-      {
-      set_errno(addrlist, save_errno, message, DEFER, pass_message);
-      yield = DEFER;
-      }
-    }
+    BOOL message_error;
+
+    sx.ok = FALSE;                /* For when reached by GOTO */
+    set_message = message;
 
   /* We want to handle timeouts after MAIL or "." and loss of connection after
   "." specially. They can indicate a problem with the sender address or with
@@ -2310,30 +3045,26 @@ if (!ok)
   cases are treated in the same way as a 4xx response. This next bit of code
   does the classification. */
 
-  else
-    {
-    BOOL message_error;
-
     switch(save_errno)
       {
       case 0:
       case ERRNO_MAIL4XX:
       case ERRNO_DATA4XX:
-      message_error = TRUE;
-      break;
+       message_error = TRUE;
+       break;
 
       case ETIMEDOUT:
-      message_error = Ustrncmp(smtp_command,"MAIL",4) == 0 ||
-                      Ustrncmp(smtp_command,"end ",4) == 0;
-      break;
+       message_error = Ustrncmp(smtp_command,"MAIL",4) == 0 ||
+                       Ustrncmp(smtp_command,"end ",4) == 0;
+       break;
 
       case ERRNO_SMTPCLOSED:
-      message_error = Ustrncmp(smtp_command,"end ",4) == 0;
-      break;
+       message_error = Ustrncmp(smtp_command,"end ",4) == 0;
+       break;
 
       default:
-      message_error = FALSE;
-      break;
+       message_error = FALSE;
+       break;
       }
 
     /* Handle the cases that are treated as message errors. These are:
@@ -2341,6 +3072,7 @@ if (!ok)
       (a) negative response or timeout after MAIL
       (b) negative response after DATA
       (c) negative response or timeout or dropped connection after "."
+      (d) utf8 support required and not offered
 
     It won't be a negative response or timeout after RCPT, as that is dealt
     with separately above. The action in all cases is to set an appropriate
@@ -2354,18 +3086,19 @@ if (!ok)
     if (message_error)
       {
       if (mua_wrapper) code = '5';  /* Force hard failure in wrapper mode */
-      set_errno(addrlist, save_errno, message, (code == '5')? FAIL : DEFER,
-        pass_message);
 
       /* If there's an errno, the message contains just the identity of
       the host. */
 
-      if (code != '5')     /* Anything other than 5 is treated as temporary */
+      if (code == '5')
+       set_rc = FAIL;
+      else             /* Anything other than 5 is treated as temporary */
         {
+       set_rc = DEFER;
         if (save_errno > 0)
           message = US string_sprintf("%s: %s", message, strerror(save_errno));
         if (host->next != NULL) log_write(0, LOG_MAIN, "%s", message);
-        deliver_msglog("%s %s\n", tod_stamp(tod_log), message);
+       msglog_line(host, message);
         *message_defer = TRUE;
         }
       }
@@ -2378,11 +3111,17 @@ if (!ok)
 
     else
       {
+      set_rc = DEFER;
       yield = (save_errno == ERRNO_CHHEADER_FAIL ||
                save_errno == ERRNO_FILTER_FAIL)? ERROR : DEFER;
-      set_errno(addrlist, save_errno, message, DEFER, pass_message);
       }
     }
+
+  set_errno(addrlist, save_errno, set_message, set_rc, pass_message, host
+#ifdef EXPERIMENTAL_DSN_INFO
+           , sx.smtp_greeting, sx.helo_response
+#endif
+           );
   }
 
 
@@ -2416,51 +3155,56 @@ hosts_nopass_tls. */
 
 DEBUG(D_transport)
   debug_printf("ok=%d send_quit=%d send_rset=%d continue_more=%d "
-    "yield=%d first_address is %sNULL\n", ok, send_quit, send_rset,
-    continue_more, yield, (first_addr == NULL)? "":"not ");
+    "yield=%d first_address is %sNULL\n", sx.ok, sx.send_quit,
+    sx.send_rset, continue_more, yield, sx.first_addr ? "not " : "");
 
-if (completed_address && ok && send_quit)
+if (sx.completed_addr && sx.ok && sx.send_quit)
   {
   BOOL more;
-  if (first_addr != NULL || continue_more ||
-        (
-           (tls_out.active < 0 ||
-           verify_check_this_host(&(ob->hosts_nopass_tls), NULL, host->name,
-             host->address, NULL) != OK)
+  smtp_compare_t t_compare;
+
+  t_compare.tblock = tblock;
+  t_compare.current_sender_address = sender_address;
+
+  if (  sx.first_addr != NULL
+     || continue_more
+     || (  (  tls_out.active < 0
+           || verify_check_given_host(&sx.ob->hosts_nopass_tls, host) != OK
+          )
         &&
            transport_check_waiting(tblock->name, host->name,
-             tblock->connection_max_messages, new_message_id, &more)
-        ))
+             tblock->connection_max_messages, new_message_id, &more,
+            (oicf)smtp_are_same_identities, (void*)&t_compare)
+     )  )
     {
     uschar *msg;
     BOOL pass_message;
 
-    if (send_rset)
-      {
-      if (! (ok = smtp_write_command(&outblock, FALSE, "RSET\r\n") >= 0))
+    if (sx.send_rset)
+      if (! (sx.ok = smtp_write_command(&sx.outblock, FALSE, "RSET\r\n") >= 0))
         {
         msg = US string_sprintf("send() to %s [%s] failed: %s", host->name,
-          host->address, strerror(save_errno));
-        send_quit = FALSE;
+          host->address, strerror(errno));
+        sx.send_quit = FALSE;
         }
-      else if (! (ok = smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
-                  ob->command_timeout)))
+      else if (! (sx.ok = smtp_read_response(&sx.inblock, sx.buffer,
+                 sizeof(sx.buffer), '2', sx.ob->command_timeout)))
         {
         int code;
-        send_quit = check_response(host, &errno, 0, buffer, &code, &msg,
+        sx.send_quit = check_response(host, &errno, 0, sx.buffer, &code, &msg,
           &pass_message);
-        if (!send_quit)
+        if (!sx.send_quit)
           {
-          DEBUG(D_transport) debug_printf("%s\n", msg);
+          DEBUG(D_transport) debug_printf("H=%s [%s] %s\n",
+           host->name, host->address, msg);
           }
         }
-      }
 
     /* Either RSET was not needed, or it succeeded */
 
-    if (ok)
+    if (sx.ok)
       {
-      if (first_addr != NULL)            /* More addresses still to be sent */
+      if (sx.first_addr != NULL)            /* More addresses still to be sent */
         {                                /*   in this run of the transport */
         continue_sequence++;             /* Causes * in logging */
         goto SEND_MESSAGE;
@@ -2472,32 +3216,37 @@ if (completed_address && ok && send_quit)
       when TLS is shut down. We test for this by sending a new EHLO. If we
       don't get a good response, we don't attempt to pass the socket on. */
 
-      #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
       if (tls_out.active >= 0)
         {
         tls_close(FALSE, TRUE);
-        if (smtps)
-          ok = FALSE;
-        else
-          ok = smtp_write_command(&outblock,FALSE,"EHLO %s\r\n",helo_data) >= 0 &&
-               smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
-                 ob->command_timeout);
+       smtp_peer_options = smtp_peer_options_wrap;
+       sx.ok = !sx.smtps
+          && smtp_write_command(&sx.outblock, FALSE,
+                                   "EHLO %s\r\n", sx.helo_data) >= 0
+         && smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer),
+                                   '2', sx.ob->command_timeout);
         }
-      #endif
+#endif
 
-      /* If the socket is successfully passed, we musn't send QUIT (or
+      /* If the socket is successfully passed, we mustn't send QUIT (or
       indeed anything!) from here. */
 
-      if (ok && transport_pass_socket(tblock->name, host->name, host->address,
-            new_message_id, inblock.sock))
-        {
-        send_quit = FALSE;
-        }
+/*XXX DSN_INFO: assume likely to do new HELO; but for greet we'll want to
+propagate it from the initial
+*/
+      if (sx.ok && transport_pass_socket(tblock->name, host->name,
+           host->address, new_message_id, sx.inblock.sock))
+        sx.send_quit = FALSE;
       }
 
     /* If RSET failed and there are addresses left, they get deferred. */
 
-    else set_errno(first_addr, errno, msg, DEFER, FALSE);
+    else set_errno(sx.first_addr, errno, msg, DEFER, FALSE, host
+#ifdef EXPERIMENTAL_DSN_INFO
+                 , sx.smtp_greeting, sx.helo_response
+#endif
+                 );
     }
   }
 
@@ -2520,7 +3269,7 @@ This change is being made on 31-Jul-98. After over a year of trouble-free
 operation, the old commented-out code was removed on 17-Sep-99. */
 
 SEND_QUIT:
-if (send_quit) (void)smtp_write_command(&outblock, FALSE, "QUIT\r\n");
+if (sx.send_quit) (void)smtp_write_command(&sx.outblock, FALSE, "QUIT\r\n");
 
 END_OFF:
 
@@ -2538,7 +3287,20 @@ writing RSET might have failed, or there may be other addresses whose hosts are
 specified in the transports, and therefore not visible at top level, in which
 case continue_more won't get set. */
 
-(void)close(inblock.sock);
+HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
+if (sx.send_quit)
+  {
+  shutdown(sx.outblock.sock, SHUT_WR);
+  if (fcntl(sx.inblock.sock, F_SETFL, O_NONBLOCK) == 0)
+    for (rc = 16; read(sx.inblock.sock, sx.inbuffer, sizeof(sx.inbuffer)) > 0 && rc > 0;)
+      rc--;                            /* drain socket */
+  }
+(void)close(sx.inblock.sock);
+
+#ifndef DISABLE_EVENT
+(void) event_raise(tblock->event_action, US"tcp:close", NULL);
+#endif
+
 continue_transport = NULL;
 continue_hostname = NULL;
 return yield;
@@ -2568,7 +3330,7 @@ void
 smtp_transport_closedown(transport_instance *tblock)
 {
 smtp_transport_options_block *ob =
-  (smtp_transport_options_block *)(tblock->options_block);
+  (smtp_transport_options_block *)tblock->options_block;
 smtp_inblock inblock;
 smtp_outblock outblock;
 uschar buffer[256];
@@ -2619,22 +3381,26 @@ prepare_addresses(address_item *addrlist, host_item *host)
 {
 address_item *first_addr = NULL;
 address_item *addr;
-for (addr = addrlist; addr != NULL; addr = addr->next)
-  {
-  if (addr->transport_return != DEFER) continue;
-  if (first_addr == NULL) first_addr = addr;
-  addr->transport_return = PENDING_DEFER;
-  addr->basic_errno = 0;
-  addr->more_errno = (host->mx >= 0)? 'M' : 'A';
-  addr->message = NULL;
-  #ifdef SUPPORT_TLS
-  addr->cipher = NULL;
-  addr->ourcert = NULL;
-  addr->peercert = NULL;
-  addr->peerdn = NULL;
-  addr->ocsp = OCSP_NOT_REQ;
-  #endif
-  }
+for (addr = addrlist; addr; addr = addr->next)
+  if (addr->transport_return == DEFER)
+    {
+    if (!first_addr) first_addr = addr;
+    addr->transport_return = PENDING_DEFER;
+    addr->basic_errno = 0;
+    addr->more_errno = (host->mx >= 0)? 'M' : 'A';
+    addr->message = NULL;
+#ifdef SUPPORT_TLS
+    addr->cipher = NULL;
+    addr->ourcert = NULL;
+    addr->peercert = NULL;
+    addr->peerdn = NULL;
+    addr->ocsp = OCSP_NOT_REQ;
+#endif
+#ifdef EXPERIMENTAL_DSN_INFO
+    addr->smtp_greeting = NULL;
+    addr->helo_response = NULL;
+#endif
+    }
 return first_addr;
 }
 
@@ -2666,7 +3432,6 @@ int hosts_total = 0;
 int total_hosts_tried = 0;
 address_item *addr;
 BOOL expired = TRUE;
-BOOL continuing = continue_hostname != NULL;
 uschar *expanded_hosts = NULL;
 uschar *pistring;
 uschar *tid = string_sprintf("%s transport", tblock->name);
@@ -2678,9 +3443,15 @@ host_item *host = NULL;
 DEBUG(D_transport)
   {
   debug_printf("%s transport entered\n", tblock->name);
-  for (addr = addrlist; addr != NULL; addr = addr->next)
+  for (addr = addrlist; addr; addr = addr->next)
     debug_printf("  %s\n", addr->address);
-  if (continuing) debug_printf("already connected to %s [%s]\n",
+  if (hostlist)
+    {
+    debug_printf("hostlist:\n");
+    for (host = hostlist; host; host = host->next)
+      debug_printf("  %s:%d\n", host->name, host->port);
+    }
+  if (continue_hostname) debug_printf("already connected to %s [%s]\n",
       continue_hostname, continue_host_address);
   }
 
@@ -2696,9 +3467,9 @@ same one in order to be passed to a single transport - or if the transport has
 a host list with hosts_override set, use the host list supplied with the
 transport. It is an error for this not to exist. */
 
-if (hostlist == NULL || (ob->hosts_override && ob->hosts != NULL))
+if (!hostlist || (ob->hosts_override && ob->hosts))
   {
-  if (ob->hosts == NULL)
+  if (!ob->hosts)
     {
     addrlist->message = string_sprintf("%s transport called with no hosts set",
       tblock->name);
@@ -2717,18 +3488,17 @@ if (hostlist == NULL || (ob->hosts_override && ob->hosts != NULL))
   as the hosts string will never be used again, it doesn't matter that we
   replace all the : characters with zeros. */
 
-  if (ob->hostlist == NULL)
+  if (!ob->hostlist)
     {
     uschar *s = ob->hosts;
 
     if (Ustrchr(s, '$') != NULL)
       {
-      expanded_hosts = expand_string(s);
-      if (expanded_hosts == NULL)
+      if (!(expanded_hosts = expand_string(s)))
         {
         addrlist->message = string_sprintf("failed to expand list of hosts "
           "\"%s\" in %s transport: %s", s, tblock->name, expand_string_message);
-        addrlist->transport_return = search_find_defer? DEFER : PANIC;
+        addrlist->transport_return = search_find_defer ? DEFER : PANIC;
         return FALSE;     /* Only top address has status */
         }
       DEBUG(D_transport) debug_printf("expanded list of hosts \"%s\" to "
@@ -2741,7 +3511,7 @@ if (hostlist == NULL || (ob->hosts_override && ob->hosts != NULL))
     host_build_hostlist(&hostlist, s, ob->hosts_randomize);
 
     /* Check that the expansion yielded something useful. */
-    if (hostlist == NULL)
+    if (!hostlist)
       {
       addrlist->message =
         string_sprintf("%s transport has empty hosts setting", tblock->name);
@@ -2752,13 +3522,14 @@ if (hostlist == NULL || (ob->hosts_override && ob->hosts != NULL))
     /* If there was no expansion of hosts, save the host list for
     next time. */
 
-    if (expanded_hosts == NULL) ob->hostlist = hostlist;
+    if (!expanded_hosts) ob->hostlist = hostlist;
     }
 
   /* This is not the first time this transport has been run in this delivery;
   the host list was built previously. */
 
-  else hostlist = ob->hostlist;
+  else
+    hostlist = ob->hostlist;
   }
 
 /* The host list was supplied with the address. If hosts_randomize is set, we
@@ -2766,17 +3537,17 @@ must sort it into a random order if it did not come from MX records and has not
 already been randomized (but don't bother if continuing down an existing
 connection). */
 
-else if (ob->hosts_randomize && hostlist->mx == MX_NONE && !continuing)
+else if (ob->hosts_randomize && hostlist->mx == MX_NONE && !continue_hostname)
   {
   host_item *newlist = NULL;
-  while (hostlist != NULL)
+  while (hostlist)
     {
     host_item *h = hostlist;
     hostlist = hostlist->next;
 
     h->sort_key = random_number(100);
 
-    if (newlist == NULL)
+    if (!newlist)
       {
       h->next = NULL;
       newlist = h;
@@ -2789,7 +3560,7 @@ else if (ob->hosts_randomize && hostlist->mx == MX_NONE && !continuing)
     else
       {
       host_item *hh = newlist;
-      while (hh->next != NULL)
+      while (hh->next)
         {
         if (h->sort_key < hh->next->sort_key) break;
         hh = hh->next;
@@ -2802,12 +3573,10 @@ else if (ob->hosts_randomize && hostlist->mx == MX_NONE && !continuing)
   hostlist = addrlist->host_list = newlist;
   }
 
-
 /* Sort out the default port.  */
 
 if (!smtp_get_port(ob->port, addrlist, &port, tid)) return FALSE;
 
-
 /* For each host-plus-IP-address on the list:
 
 .  If this is a continued delivery and the host isn't the one with the
@@ -2853,26 +3622,24 @@ the current message. To cope with this, we have to go round the loop a second
 time. After that, set the status and error data for any addresses that haven't
 had it set already. */
 
-for (cutoff_retry = 0; expired &&
-     cutoff_retry < ((ob->delay_after_cutoff)? 1 : 2);
+for (cutoff_retry = 0;
+     expired && cutoff_retry < (ob->delay_after_cutoff ? 1 : 2);
      cutoff_retry++)
   {
   host_item *nexthost = NULL;
   int unexpired_hosts_tried = 0;
 
   for (host = hostlist;
-       host != NULL &&
-         unexpired_hosts_tried < ob->hosts_max_try &&
-         total_hosts_tried < ob->hosts_max_try_hardlimit;
+          host
+       && unexpired_hosts_tried < ob->hosts_max_try
+       && total_hosts_tried < ob->hosts_max_try_hardlimit;
        host = nexthost)
     {
     int rc;
     int host_af;
     uschar *rs;
-    BOOL serialized = FALSE;
     BOOL host_is_expired = FALSE;
     BOOL message_defer = FALSE;
-    BOOL ifchanges = FALSE;
     BOOL some_deferred = FALSE;
     address_item *first_addr = NULL;
     uschar *interface = NULL;
@@ -2900,11 +3667,10 @@ for (cutoff_retry = 0; expired &&
     Note that we mustn't skip unusable hosts if the address is not unset; they
     may be needed as expired hosts on the 2nd time round the cutoff loop. */
 
-    if (host->address == NULL)
+    if (!host->address)
       {
       int new_port, flags;
       host_item *hh;
-      uschar *canonical_name;
 
       if (host->status >= hstatus_unusable)
         {
@@ -2932,11 +3698,11 @@ for (cutoff_retry = 0; expired &&
       if (ob->dns_search_parents) flags |= HOST_FIND_SEARCH_PARENTS;
 
       if (ob->gethostbyname || string_is_ip_address(host->name, NULL) != 0)
-        rc = host_find_byname(host, NULL, flags, &canonical_name, TRUE);
+        rc = host_find_byname(host, NULL, flags, NULL, TRUE);
       else
         rc = host_find_bydns(host, NULL, flags, NULL, NULL, NULL,
-         ob->dnssec_request_domains, ob->dnssec_require_domains,
-          &canonical_name, NULL);
+         &ob->dnssec,          /* domains for request/require */
+          NULL, NULL);
 
       /* Update the host (and any additional blocks, resulting from
       multihoming) with a host-specific port, if any. */
@@ -2960,7 +3726,7 @@ for (cutoff_retry = 0; expired &&
           "HOST_FIND_AGAIN" : "HOST_FIND_FAILED", host->name);
         host->status = hstatus_unusable;
 
-        for (addr = addrlist; addr != NULL; addr = addr->next)
+        for (addr = addrlist; addr; addr = addr->next)
           {
           if (addr->transport_return != DEFER) continue;
           addr->basic_errno = ERRNO_UNKNOWNHOST;
@@ -2976,7 +3742,7 @@ for (cutoff_retry = 0; expired &&
 
       if (rc == HOST_FOUND_LOCAL && !ob->allow_localhost)
         {
-        for (addr = addrlist; addr != NULL; addr = addr->next)
+        for (addr = addrlist; addr; addr = addr->next)
           {
           addr->basic_errno = 0;
           addr->message = string_sprintf("%s transport found host %s to be "
@@ -2992,8 +3758,10 @@ for (cutoff_retry = 0; expired &&
     result of the lookup. Set expired FALSE, to save the outer loop executing
     twice. */
 
-    if (continuing && (Ustrcmp(continue_hostname, host->name) != 0 ||
-                       Ustrcmp(continue_host_address, host->address) != 0))
+    if (  continue_hostname
+       && (  Ustrcmp(continue_hostname, host->name) != 0
+          || Ustrcmp(continue_host_address, host->address) != 0
+       )  )
       {
       expired = FALSE;
       continue;      /* With next host */
@@ -3012,15 +3780,14 @@ for (cutoff_retry = 0; expired &&
     doing a two-stage queue run, don't do this if forcing. */
 
     if ((!deliver_force || queue_2stage) && (queue_smtp ||
-        match_isinlist(addrlist->domain, &queue_smtp_domains, 0,
+        match_isinlist(addrlist->domain,
+         (const uschar **)&queue_smtp_domains, 0,
           &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK))
       {
       expired = FALSE;
-      for (addr = addrlist; addr != NULL; addr = addr->next)
-        {
-        if (addr->transport_return != DEFER) continue;
-        addr->message = US"domain matches queue_smtp_domains, or -odqs set";
-        }
+      for (addr = addrlist; addr; addr = addr->next)
+        if (addr->transport_return == DEFER)
+         addr->message = US"domain matches queue_smtp_domains, or -odqs set";
       continue;      /* With next host */
       }
 
@@ -3043,20 +3810,23 @@ for (cutoff_retry = 0; expired &&
     the standard SMTP port. A host may have its own port setting that overrides
     the default. */
 
-    pistring = string_sprintf(":%d", (host->port == PORT_NONE)?
-      port : host->port);
+    pistring = string_sprintf(":%d", host->port == PORT_NONE
+      port : host->port);
     if (Ustrcmp(pistring, ":25") == 0) pistring = US"";
 
     /* Select IPv4 or IPv6, and choose an outgoing interface. If the interface
-    string changes upon expansion, we must add it to the key that is used for
-    retries, because connections to the same host from a different interface
-    should be treated separately. */
+    string is set, even if constant (as different transports can have different
+    constant settings), we must add it to the key that is used for retries,
+    because connections to the same host from a different interface should be
+    treated separately. */
 
-    host_af = (Ustrchr(host->address, ':') == NULL)? AF_INET : AF_INET6;
-    if (!smtp_get_interface(ob->interface, host_af, addrlist, &ifchanges,
-         &interface, tid))
-      return FALSE;
-    if (ifchanges) pistring = string_sprintf("%s/%s", pistring, interface);
+    host_af = Ustrchr(host->address, ':') == NULL ? AF_INET : AF_INET6;
+    if ((rs = ob->interface) && *rs)
+      {
+      if (!smtp_get_interface(rs, host_af, addrlist, &interface, tid))
+       return FALSE;
+      pistring = string_sprintf("%s/%s", pistring, interface);
+      }
 
     /* The first time round the outer loop, check the status of the host by
     inspecting the retry data. The second time round, we are interested only
@@ -3064,14 +3834,20 @@ for (cutoff_retry = 0; expired &&
 
     if (cutoff_retry == 0)
       {
+      BOOL incl_ip;
       /* Ensure the status of the address is set by checking retry data if
-      necessary. There maybe host-specific retry data (applicable to all
+      necessary. There may be host-specific retry data (applicable to all
       messages) and also data for retries of a specific message at this host.
       If either of these retry records are actually read, the keys used are
       returned to save recomputing them later. */
 
+      if (exp_bool(addrlist, US"transport", tblock->name, D_transport,
+               US"retry_include_ip_address", ob->retry_include_ip_address,
+               ob->expand_retry_include_ip_address, &incl_ip) != OK)
+       continue;       /* with next host */
+
       host_is_expired = retry_check_address(addrlist->domain, host, pistring,
-        ob->retry_include_ip_address, &retry_host_key, &retry_message_key);
+        incl_ip, &retry_host_key, &retry_message_key);
 
       DEBUG(D_transport) debug_printf("%s [%s]%s status = %s\n", host->name,
         (host->address == NULL)? US"" : host->address, pistring,
@@ -3085,24 +3861,24 @@ for (cutoff_retry = 0; expired &&
       switch (host->status)
         {
         case hstatus_unusable:
-        expired = FALSE;
-        setflag(addrlist, af_retry_skipped);
-        /* Fall through */
+         expired = FALSE;
+         setflag(addrlist, af_retry_skipped);
+         /* Fall through */
 
         case hstatus_unusable_expired:
-        switch (host->why)
-          {
-          case hwhy_retry: hosts_retry++; break;
-          case hwhy_failed:  hosts_fail++; break;
-          case hwhy_deferred: hosts_defer++; break;
-          }
-
-        /* If there was a retry message key, implying that previously there
-        was a message-specific defer, we don't want to update the list of
-        messages waiting for these hosts. */
-
-        if (retry_message_key != NULL) update_waiting = FALSE;
-        continue;   /* With the next host or IP address */
+         switch (host->why)
+           {
+           case hwhy_retry: hosts_retry++; break;
+           case hwhy_failed:  hosts_fail++; break;
+           case hwhy_deferred: hosts_defer++; break;
+           }
+
+         /* If there was a retry message key, implying that previously there
+         was a message-specific defer, we don't want to update the list of
+         messages waiting for these hosts. */
+
+         if (retry_message_key) update_waiting = FALSE;
+         continue;   /* With the next host or IP address */
         }
       }
 
@@ -3111,12 +3887,11 @@ for (cutoff_retry = 0; expired &&
 
     else
       {
-      if (host->address == NULL ||
-          host->status != hstatus_unusable_expired ||
-          host->last_try > received_time)
+      if (  !host->address
+         || host->status != hstatus_unusable_expired
+        || host->last_try > received_time)
         continue;
-      DEBUG(D_transport)
-        debug_printf("trying expired host %s [%s]%s\n",
+      DEBUG(D_transport) debug_printf("trying expired host %s [%s]%s\n",
           host->name, host->address, pistring);
       host_is_expired = TRUE;
       }
@@ -3133,12 +3908,11 @@ for (cutoff_retry = 0; expired &&
     and remember this for later deletion. Do not do any of this if we are
     sending the message down a pre-existing connection. */
 
-    if (!continuing &&
-        verify_check_this_host(&(ob->serialize_hosts), NULL, host->name,
-          host->address, NULL) == OK)
+    if (  !continue_hostname
+       && verify_check_given_host(&ob->serialize_hosts, host) == OK)
       {
       serialize_key = string_sprintf("host-serialize-%s", host->name);
-      if (!enq_start(serialize_key))
+      if (!enq_start(serialize_key, 1))
         {
         DEBUG(D_transport)
           debug_printf("skipping host %s because another Exim process "
@@ -3146,7 +3920,6 @@ for (cutoff_retry = 0; expired &&
         hosts_serial++;
         continue;
         }
-      serialized = TRUE;
       }
 
     /* OK, we have an IP address that is not waiting for its retry time to
@@ -3160,11 +3933,11 @@ for (cutoff_retry = 0; expired &&
 
     DEBUG(D_transport) debug_printf("delivering %s to %s [%s] (%s%s)\n",
       message_id, host->name, host->address, addrlist->address,
-      (addrlist->next == NULL)? "" : ", ...");
+      addrlist->next ? ", ..." : "");
 
     set_process_info("delivering %s to %s [%s] (%s%s)",
       message_id, host->name, host->address, addrlist->address,
-      (addrlist->next == NULL)? "" : ", ...");
+      addrlist->next ? ", ..." : "");
 
     /* This is not for real; don't do the delivery. If there are
     any remaining hosts, list them. */
@@ -3172,8 +3945,8 @@ for (cutoff_retry = 0; expired &&
     if (dont_deliver)
       {
       host_item *host2;
-      set_errno(addrlist, 0, NULL, OK, FALSE);
-      for (addr = addrlist; addr != NULL; addr = addr->next)
+      set_errno_nohost(addrlist, 0, NULL, OK, FALSE);
+      for (addr = addrlist; addr; addr = addr->next)
         {
         addr->host_used = host;
         addr->special_action = '*';
@@ -3183,9 +3956,9 @@ for (cutoff_retry = 0; expired &&
         {
         debug_printf("*** delivery by %s transport bypassed by -N option\n"
                      "*** host and remaining hosts:\n", tblock->name);
-        for (host2 = host; host2 != NULL; host2 = host2->next)
+        for (host2 = host; host2; host2 = host2->next)
           debug_printf("    %s [%s]\n", host2->name,
-            (host2->address == NULL)? US"unset" : host2->address);
+            host2->address ? host2->address : US"unset");
         }
       rc = OK;
       }
@@ -3205,27 +3978,40 @@ for (cutoff_retry = 0; expired &&
 
     else
       {
+      host_item * thost;
+      /* Make a copy of the host if it is local to this invocation
+       of the transport. */
+
+      if (expanded_hosts)
+       {
+       thost = store_get(sizeof(host_item));
+       *thost = *host;
+       thost->name = string_copy(host->name);
+       thost->address = string_copy(host->address);
+       }
+      else
+        thost = host;
+
       if (!host_is_expired && ++unexpired_hosts_tried >= ob->hosts_max_try)
         {
         host_item *h;
         DEBUG(D_transport)
           debug_printf("hosts_max_try limit reached with this host\n");
-        for (h = host; h != NULL; h = h->next)
-          if (h->mx != host->mx) break;
-        if (h != NULL)
-          {
-          nexthost = h;
-          unexpired_hosts_tried--;
-          DEBUG(D_transport) debug_printf("however, a higher MX host exists "
-            "and will be tried\n");
-          }
+        for (h = host; h; h = h->next) if (h->mx != host->mx)
+         {
+         nexthost = h;
+         unexpired_hosts_tried--;
+         DEBUG(D_transport) debug_printf("however, a higher MX host exists "
+           "and will be tried\n");
+         break;
+         }
         }
 
       /* Attempt the delivery. */
 
       total_hosts_tried++;
-      rc = smtp_deliver(addrlist, host, host_af, port, interface, tblock,
-        expanded_hosts != NULL, &message_defer, FALSE);
+      rc = smtp_deliver(addrlist, thost, host_af, port, interface, tblock,
+        &message_defer, FALSE);
 
       /* Yield is one of:
          OK     => connection made, each address contains its result;
@@ -3242,14 +4028,14 @@ for (cutoff_retry = 0; expired &&
       failures, where the log has already been written. If all hosts defer a
       general message is written at the end. */
 
-      if (rc == DEFER && first_addr->basic_errno != ERRNO_AUTHFAIL &&
-                         first_addr->basic_errno != ERRNO_TLSFAILURE)
+      if (rc == DEFER && first_addr->basic_errno != ERRNO_AUTHFAIL
+                     && first_addr->basic_errno != ERRNO_TLSFAILURE)
         write_logs(first_addr, host);
 
-      #ifdef EXPERIMENTAL_TPDA
+#ifndef DISABLE_EVENT
       if (rc == DEFER)
-        tpda_deferred(ob, first_addr, host);
-      #endif
+        deferred_event_raise(first_addr, host);
+#endif
 
       /* If STARTTLS was accepted, but there was a failure in setting up the
       TLS session (usually a certificate screwup), and the host is not in
@@ -3260,39 +4046,42 @@ for (cutoff_retry = 0; expired &&
       session, so the in-clear transmission after those errors, if permitted,
       happens inside smtp_deliver().] */
 
-      #ifdef SUPPORT_TLS
-      if (rc == DEFER && first_addr->basic_errno == ERRNO_TLSFAILURE &&
-           ob->tls_tempfail_tryclear &&
-           verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name,
-             host->address, NULL) != OK)
+#ifdef SUPPORT_TLS
+      if (  rc == DEFER
+        && first_addr->basic_errno == ERRNO_TLSFAILURE
+        && ob->tls_tempfail_tryclear
+        && verify_check_given_host(&ob->hosts_require_tls, host) != OK
+        )
         {
         log_write(0, LOG_MAIN, "TLS session failure: delivering unencrypted "
           "to %s [%s] (not in hosts_require_tls)", host->name, host->address);
         first_addr = prepare_addresses(addrlist, host);
-        rc = smtp_deliver(addrlist, host, host_af, port, interface, tblock,
-          expanded_hosts != NULL, &message_defer, TRUE);
+        rc = smtp_deliver(addrlist, thost, host_af, port, interface, tblock,
+          &message_defer, TRUE);
         if (rc == DEFER && first_addr->basic_errno != ERRNO_AUTHFAIL)
           write_logs(first_addr, host);
-        #ifdef EXPERIMENTAL_TPDA
+# ifndef DISABLE_EVENT
         if (rc == DEFER)
-          tpda_deferred(ob, first_addr, host);
-        #endif
+          deferred_event_raise(first_addr, host);
+endif
         }
-      #endif
+#endif /*SUPPORT_TLS*/
       }
 
     /* Delivery attempt finished */
 
-    rs = (rc == OK)? US"OK" : (rc == DEFER)? US"DEFER" : (rc == ERROR)?
-      US"ERROR" : US"?";
+    rs = rc == OK ? US"OK"
+       : rc == DEFER ? US"DEFER"
+       : rc == ERROR ? US"ERROR"
+       : US"?";
 
     set_process_info("delivering %s: just tried %s [%s] for %s%s: result %s",
       message_id, host->name, host->address, addrlist->address,
-      (addrlist->next == NULL)? "" : " (& others)", rs);
+      addrlist->next ? " (& others)" : "", rs);
 
     /* Release serialization if set up */
 
-    if (serialized) enq_end(serialize_key);
+    if (serialize_key) enq_end(serialize_key);
 
     /* If the result is DEFER, or if a host retry record is known to exist, we
     need to add an item to the retry chain for updating the retry database
@@ -3302,14 +4091,20 @@ for (cutoff_retry = 0; expired &&
     the unusable tree at the outer level, so even if different address blocks
     contain the same address, it still won't get tried again.) */
 
-    if (rc == DEFER || retry_host_key != NULL)
+    if (rc == DEFER || retry_host_key)
       {
-      int delete_flag = (rc != DEFER)? rf_delete : 0;
-      if (retry_host_key == NULL)
+      int delete_flag = rc != DEFER ? rf_delete : 0;
+      if (!retry_host_key)
         {
-        retry_host_key = ob->retry_include_ip_address?
-          string_sprintf("T:%S:%s%s", host->name, host->address, pistring) :
-          string_sprintf("T:%S%s", host->name, pistring);
+       BOOL incl_ip;
+       if (exp_bool(addrlist, US"transport", tblock->name, D_transport,
+                 US"retry_include_ip_address", ob->retry_include_ip_address,
+                 ob->expand_retry_include_ip_address, &incl_ip) != OK)
+         incl_ip = TRUE;       /* error; use most-specific retry record */
+
+        retry_host_key = incl_ip
+         ? string_sprintf("T:%S:%s%s", host->name, host->address, pistring)
+         : string_sprintf("T:%S%s", host->name, pistring);
         }
 
       /* If a delivery of another message over an existing SMTP connection
@@ -3322,7 +4117,7 @@ for (cutoff_retry = 0; expired &&
       host is genuinely down, another non-continued message delivery will
       notice it soon enough. */
 
-      if (delete_flag != 0 || !continuing)
+      if (delete_flag != 0 || !continue_hostname)
         retry_add_item(first_addr, retry_host_key, rf_host | delete_flag);
 
       /* We may have tried an expired host, if its retry time has come; ensure
@@ -3330,8 +4125,8 @@ for (cutoff_retry = 0; expired &&
 
       if (rc == DEFER)
         {
-        host->status = (host_is_expired)?
-          hstatus_unusable_expired : hstatus_unusable;
+        host->status = host_is_expired
+         ? hstatus_unusable_expired : hstatus_unusable;
         host->why = hwhy_deferred;
         }
       }
@@ -3344,15 +4139,21 @@ for (cutoff_retry = 0; expired &&
     reasonable. Also, stop the message from being remembered as waiting
     for specific hosts. */
 
-    if (message_defer || retry_message_key != NULL)
+    if (message_defer || retry_message_key)
       {
-      int delete_flag = message_defer? 0 : rf_delete;
-      if (retry_message_key == NULL)
+      int delete_flag = message_defer ? 0 : rf_delete;
+      if (!retry_message_key)
         {
-        retry_message_key = ob->retry_include_ip_address?
-          string_sprintf("T:%S:%s%s:%s", host->name, host->address, pistring,
-            message_id) :
-          string_sprintf("T:%S%s:%s", host->name, pistring, message_id);
+       BOOL incl_ip;
+       if (exp_bool(addrlist, US"transport", tblock->name, D_transport,
+                 US"retry_include_ip_address", ob->retry_include_ip_address,
+                 ob->expand_retry_include_ip_address, &incl_ip) != OK)
+         incl_ip = TRUE;       /* error; use most-specific retry record */
+
+        retry_message_key = incl_ip
+         ? string_sprintf("T:%S:%s%s:%s", host->name, host->address, pistring,
+             message_id)
+         : string_sprintf("T:%S%s:%s", host->name, pistring, message_id);
         }
       retry_add_item(addrlist, retry_message_key,
         rf_message | rf_host | delete_flag);
@@ -3364,16 +4165,12 @@ for (cutoff_retry = 0; expired &&
     case, see if any of them are deferred. */
 
     if (rc == OK)
-      {
-      for (addr = addrlist; addr != NULL; addr = addr->next)
-        {
+      for (addr = addrlist; addr; addr = addr->next)
         if (addr->transport_return == DEFER)
           {
           some_deferred = TRUE;
           break;
           }
-        }
-      }
 
     /* If no addresses deferred or the result was ERROR, return. We do this for
     ERROR because a failing filter set-up or add_headers expansion is likely to
@@ -3390,7 +4187,7 @@ for (cutoff_retry = 0; expired &&
     case when we were trying to deliver down an existing channel and failed.
     Don't try any other hosts in this case. */
 
-    if (continuing) break;
+    if (continue_hostname) break;
 
     /* If the whole delivery, or some individual addresses, were deferred and
     there are more hosts that could be tried, do not count this host towards
@@ -3400,16 +4197,16 @@ for (cutoff_retry = 0; expired &&
     important because if we don't try all hosts, the address will never time
     out. NOTE: this does not apply to hosts_max_try_hardlimit. */
 
-    if ((rc == DEFER || some_deferred) && nexthost != NULL)
+    if ((rc == DEFER || some_deferred) && nexthost)
       {
       BOOL timedout;
       retry_config *retry = retry_find_config(host->name, NULL, 0, 0);
 
-      if (retry != NULL && retry->rules != NULL)
+      if (retry && retry->rules)
         {
         retry_rule *last_rule;
         for (last_rule = retry->rules;
-             last_rule->next != NULL;
+             last_rule->next;
              last_rule = last_rule->next);
         timedout = time(NULL) - received_time > last_rule->timeout;
         }
@@ -3443,7 +4240,7 @@ specific failures. Force the delivery status for all addresses to FAIL. */
 
 if (mua_wrapper)
   {
-  for (addr = addrlist; addr != NULL; addr = addr->next)
+  for (addr = addrlist; addr; addr = addr->next)
     addr->transport_return = FAIL;
   goto END_TRANSPORT;
   }
@@ -3460,7 +4257,7 @@ If queue_smtp is set, or this transport was called to send a subsequent message
 down an existing TCP/IP connection, and something caused the host not to be
 found, we end up here, but can detect these cases and handle them specially. */
 
-for (addr = addrlist; addr != NULL; addr = addr->next)
+for (addr = addrlist; addr; addr = addr->next)
   {
   /* If host is not NULL, it means that we stopped processing the host list
   because of hosts_max_try or hosts_max_try_hardlimit. In the former case, this
@@ -3469,8 +4266,7 @@ for (addr = addrlist; addr != NULL; addr = addr->next)
   However, if we have hit hosts_max_try_hardlimit, we want to behave as if all
   hosts were tried. */
 
-  if (host != NULL)
-    {
+  if (host)
     if (total_hosts_tried >= ob->hosts_max_try_hardlimit)
       {
       DEBUG(D_transport)
@@ -3483,7 +4279,6 @@ for (addr = addrlist; addr != NULL; addr = addr->next)
         debug_printf("hosts_max_try limit caused some hosts to be skipped\n");
       setflag(addr, af_retry_skipped);
       }
-    }
 
   if (queue_smtp)    /* no deliveries attempted */
     {
@@ -3492,44 +4287,49 @@ for (addr = addrlist; addr != NULL; addr = addr->next)
     addr->message = US"SMTP delivery explicitly queued";
     }
 
-  else if (addr->transport_return == DEFER &&
-       (addr->basic_errno == ERRNO_UNKNOWNERROR || addr->basic_errno == 0) &&
-       addr->message == NULL)
+  else if (  addr->transport_return == DEFER
+         && (addr->basic_errno == ERRNO_UNKNOWNERROR || addr->basic_errno == 0)
+         && !addr->message
+         )
     {
     addr->basic_errno = ERRNO_HRETRY;
-    if (continue_hostname != NULL)
-      {
+    if (continue_hostname)
       addr->message = US"no host found for existing SMTP connection";
-      }
     else if (expired)
       {
       setflag(addr, af_pass_message);   /* This is not a security risk */
-      addr->message = (ob->delay_after_cutoff)?
-        US"retry time not reached for any host after a long failure period" :
-        US"all hosts have been failing for a long time and were last tried "
-          "after this message arrived";
+      addr->message = string_sprintf(
+       "all hosts%s have been failing for a long time %s",
+       addr->domain ? string_sprintf(" for '%s'", addr->domain) : US"",
+        ob->delay_after_cutoff
+       ? US"(and retry time not reached)"
+       : US"and were last tried after this message arrived");
 
       /* If we are already using fallback hosts, or there are no fallback hosts
       defined, convert the result to FAIL to cause a bounce. */
 
-      if (addr->host_list == addr->fallback_hosts ||
-          addr->fallback_hosts == NULL)
+      if (addr->host_list == addr->fallback_hosts || !addr->fallback_hosts)
         addr->transport_return = FAIL;
       }
     else
       {
+      const char * s;
       if (hosts_retry == hosts_total)
-        addr->message = US"retry time not reached for any host";
+        s = "retry time not reached for any host%s";
       else if (hosts_fail == hosts_total)
-        addr->message = US"all host address lookups failed permanently";
+        s = "all host address lookups%s failed permanently";
       else if (hosts_defer == hosts_total)
-        addr->message = US"all host address lookups failed temporarily";
+        s = "all host address lookups%s failed temporarily";
       else if (hosts_serial == hosts_total)
-        addr->message = US"connection limit reached for all hosts";
+        s = "connection limit reached for all hosts%s";
       else if (hosts_fail+hosts_defer == hosts_total)
-        addr->message = US"all host address lookups failed";
-      else addr->message = US"some host address lookups failed and retry time "
-        "not reached for other hosts or connection limit reached";
+        s = "all host address lookups%s failed";
+      else
+        s = "some host address lookups failed and retry time "
+        "not reached for other hosts or connection limit reached%s";
+
+      addr->message = string_sprintf(s,
+       addr->domain ? string_sprintf(" for '%s'", addr->domain) : US"");
       }
     }
   }
index dd41e1f..4bb6d6d 100644 (file)
@@ -2,9 +2,16 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2017 */
 /* See the file NOTICE for conditions of use and distribution. */
 
+#define DELIVER_BUFFER_SIZE 4096
+
+#define PENDING          256
+#define PENDING_DEFER   (PENDING + DEFER)
+#define PENDING_OK      (PENDING + OK)
+
+
 /* Private structure for the private options and other private data. */
 
 typedef struct {
@@ -21,6 +28,12 @@ typedef struct {
   uschar *serialize_hosts;
   uschar *hosts_try_auth;
   uschar *hosts_require_auth;
+  uschar *hosts_try_chunking;
+#ifdef EXPERIMENTAL_DANE
+  uschar *hosts_try_dane;
+  uschar *hosts_require_dane;
+#endif
+  uschar *hosts_try_fastopen;
 #ifndef DISABLE_PRDR
   uschar *hosts_try_prdr;
 #endif
@@ -47,45 +60,98 @@ typedef struct {
   BOOL    gethostbyname;
   BOOL    dns_qualify_single;
   BOOL    dns_search_parents;
-  uschar *dnssec_request_domains;
-  uschar *dnssec_require_domains;
+  dnssec_domains dnssec;
   BOOL    delay_after_cutoff;
   BOOL    hosts_override;
   BOOL    hosts_randomize;
   BOOL    keepalive;
   BOOL    lmtp_ignore_quota;
+  uschar *expand_retry_include_ip_address;
   BOOL    retry_include_ip_address;
+#ifdef SUPPORT_SOCKS
+  uschar *socks_proxy;
+#endif
 #ifdef SUPPORT_TLS
   uschar *tls_certificate;
   uschar *tls_crl;
   uschar *tls_privatekey;
   uschar *tls_require_ciphers;
-  uschar *gnutls_require_kx;
-  uschar *gnutls_require_mac;
-  uschar *gnutls_require_proto;
   uschar *tls_sni;
   uschar *tls_verify_certificates;
   int     tls_dh_min_bits;
   BOOL    tls_tempfail_tryclear;
   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
-  uschar *tpda_host_defer_action;
+  struct ob_dkim dkim;
 #endif
 } smtp_transport_options_block;
 
+/* smtp connect context */
+typedef struct {
+  uschar *             from_addr;
+  address_item *       addrlist;
+  host_item *          host;
+  int                  host_af;
+  int                  port;
+  uschar *             interface;
+
+  BOOL verify:1;
+  BOOL lmtp:1;
+  BOOL smtps:1;
+  BOOL ok:1;
+  BOOL setting_up:1;
+  BOOL esmtp:1;
+  BOOL esmtp_sent:1;
+#ifndef DISABLE_PRDR
+  BOOL prdr_active:1;
+#endif
+#ifdef SUPPORT_I18N
+  BOOL utf8_needed:1;
+#endif
+  BOOL dsn_all_lasthop:1;
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
+  BOOL dane:1;
+  BOOL dane_required:1;
+#endif
+  BOOL pending_MAIL:1;
+  BOOL pending_BDAT:1;
+  BOOL good_RCPT:1;
+  BOOL completed_addr:1;
+  BOOL send_rset:1;
+  BOOL send_quit:1;
+
+  int          max_rcpt;
+  int          cmd_count;
+
+  uschar       peer_offered;
+  uschar *     igquotstr;
+  uschar *     helo_data;
+#ifdef EXPERIMENTAL_DSN_INFO
+  uschar *     smtp_greeting;
+  uschar *     helo_response;
+#endif
+
+  address_item *       first_addr;
+  address_item *       next_addr;
+  address_item *       sync_addr;
+
+  smtp_inblock  inblock;
+  smtp_outblock outblock;
+  uschar       buffer[DELIVER_BUFFER_SIZE];
+  uschar       inbuffer[4096];
+  uschar       outbuffer[4096];
+
+  transport_instance *                 tblock;
+  smtp_transport_options_block *       ob;
+} smtp_context;
+
+extern int smtp_setup_conn(smtp_context *, BOOL);
+extern int smtp_write_mail_and_rcpt_cmds(smtp_context *, int *);
+
+
 /* Data for reading the private options. */
 
 extern optionlist smtp_transport_options[];
@@ -109,4 +175,9 @@ extern int     smtp_auth(uschar *, unsigned, address_item *, host_item *,
 extern BOOL    smtp_mail_auth_str(uschar *, unsigned,
                 address_item *, smtp_transport_options_block *);
 
+#ifdef SUPPORT_SOCKS
+extern int     socks_sock_connect(host_item *, int, int, uschar *,
+                transport_instance *, int);
+#endif
+
 /* End of transports/smtp.h */
diff --git a/src/transports/smtp_socks.c b/src/transports/smtp_socks.c
new file mode 100644 (file)
index 0000000..5558430
--- /dev/null
@@ -0,0 +1,412 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Jeremy Harris 2015 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* SOCKS version 5 proxy, client-mode */
+
+#include "../exim.h"
+#include "smtp.h"
+
+#ifdef SUPPORT_SOCKS /* entire file */
+
+#ifndef nelem
+# define nelem(arr) (sizeof(arr)/sizeof(*arr))
+#endif
+
+
+/* Defaults */
+#define SOCKS_PORT     1080
+#define SOCKS_TIMEOUT  5
+#define SOCKS_WEIGHT   1
+#define SOCKS_PRIORITY 1
+
+#define AUTH_NONE      0
+#define AUTH_NAME      2               /* user/password per RFC 1929 */
+#define AUTH_NAME_VER  1
+
+struct socks_err
+  {
+  uschar *     reason;
+  int          errcode;
+  } socks_errs[] =
+  {
+    {NULL, 0},
+    {US"general SOCKS server failure",         EIO},
+    {US"connection not allowed by ruleset",    EACCES},
+    {US"Network unreachable",                  ENETUNREACH},
+    {US"Host unreachable",                     EHOSTUNREACH},
+    {US"Connection refused",                   ECONNREFUSED},
+    {US"TTL expired",                          ECANCELED},
+    {US"Command not supported",                        EOPNOTSUPP},
+    {US"Address type not supported",           EAFNOSUPPORT}
+  };
+
+typedef struct
+  {
+  const uschar *       proxy_host;
+  uschar               auth_type;      /* RFC 1928 encoding */
+  const uschar *       auth_name;
+  const uschar *       auth_pwd;
+  short                        port;
+  BOOL                 is_failed;
+  unsigned             timeout;
+  unsigned             weight;
+  unsigned             priority;
+  } socks_opts;
+
+static void
+socks_option_defaults(socks_opts * sob)
+{
+sob->proxy_host = NULL;
+sob->auth_type =  AUTH_NONE;
+sob->auth_name =  US"";
+sob->auth_pwd =   US"";
+sob->is_failed =  FALSE;
+sob->port =      SOCKS_PORT;
+sob->timeout =   SOCKS_TIMEOUT;
+sob->weight =    SOCKS_WEIGHT;
+sob->priority =   SOCKS_PRIORITY;
+}
+
+static void
+socks_option(socks_opts * sob, const uschar * opt)
+{
+const uschar * s;
+
+if (Ustrncmp(opt, "auth=", 5) == 0)
+  {
+  opt += 5;
+  if (Ustrcmp(opt, "none") == 0)       sob->auth_type = AUTH_NONE;
+  else if (Ustrcmp(opt, "name") == 0)  sob->auth_type = AUTH_NAME;
+  }
+else if (Ustrncmp(opt, "name=", 5) == 0)
+  sob->auth_name = opt + 5;
+else if (Ustrncmp(opt, "pass=", 5) == 0)
+  sob->auth_pwd = opt + 5;
+else if (Ustrncmp(opt, "port=", 5) == 0)
+  sob->port = atoi(opt + 5);
+else if (Ustrncmp(opt, "tmo=", 4) == 0)
+  sob->timeout = atoi(opt + 4);
+else if (Ustrncmp(opt, "pri=", 4) == 0)
+  sob->priority = atoi(opt + 4);
+else if (Ustrncmp(opt, "weight=", 7) == 0)
+  sob->weight = atoi(opt + 7);
+return;
+}
+
+static int
+socks_auth(int fd, int method, socks_opts * sob, time_t tmo)
+{
+uschar * s;
+int len, i, j;
+
+switch(method)
+  {
+  default:
+    log_write(0, LOG_MAIN|LOG_PANIC,
+      "Unrecognised socks auth method %d", method);
+    return FAIL;
+  case AUTH_NONE:
+    return OK;
+  case AUTH_NAME:
+    HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  socks auth NAME '%s' '%s'\n",
+      sob->auth_name, sob->auth_pwd);
+    i = Ustrlen(sob->auth_name);
+    j = Ustrlen(sob->auth_pwd);
+    s = string_sprintf("%c%c%.255s%c%.255s", AUTH_NAME_VER,
+      i, sob->auth_name, j, sob->auth_pwd);
+    len = i + j + 3;
+    HDEBUG(D_transport|D_acl|D_v)
+      {
+      int i;
+      debug_printf_indent("  SOCKS>>");
+      for (i = 0; i<len; i++) debug_printf(" %02x", s[i]);
+      debug_printf("\n");
+      }
+    if (  send(fd, s, len, 0) < 0
+       || !fd_ready(fd, tmo-time(NULL))
+       || read(fd, s, 2) != 2
+       )
+      return FAIL;
+    HDEBUG(D_transport|D_acl|D_v)
+      debug_printf_indent("  SOCKS<< %02x %02x\n", s[0], s[1]);
+    if (s[0] == AUTH_NAME_VER && s[1] == 0)
+      {
+      HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  socks auth OK\n");
+      return OK;
+      }
+
+    log_write(0, LOG_MAIN|LOG_PANIC, "socks auth failed");
+    errno = EPROTO;
+    return FAIL;
+  }
+}
+
+
+
+/* Find a suitable proxy to use from the list.
+Possible common code with spamd_get_server() ?
+
+Return: index into proxy spec array, or -1
+*/
+
+static int
+socks_get_proxy(socks_opts * proxies, unsigned nproxies)
+{
+unsigned int i;
+socks_opts * sd;
+socks_opts * lim = &proxies[nproxies];
+long rnd, weights;
+unsigned pri;
+static BOOL srandomed = FALSE;
+
+if (nproxies == 1)             /* shortcut, if we have only 1 server */
+  return (proxies[0].is_failed ? -1 : 0);
+
+/* init random */
+if (!srandomed)
+  {
+  struct timeval tv;
+  gettimeofday(&tv, NULL);
+  srandom((unsigned int)(tv.tv_usec/1000));
+  srandomed = TRUE;
+  }
+
+/* scan for highest pri */
+for (pri = 0, sd = proxies; sd < lim; sd++)
+  if (!sd->is_failed && sd->priority > pri)
+    pri = sd->priority;
+
+/* get sum of weights at this pri */
+for (weights = 0, sd = proxies; sd < lim; sd++)
+  if (!sd->is_failed && sd->priority == pri)
+    weights += sd->weight;
+if (weights == 0)       /* all servers failed */
+  return -1;
+
+for (rnd = random() % weights, i = 0; i < nproxies; i++)
+  {
+  sd = &proxies[i];
+  if (!sd->is_failed && sd->priority == pri)
+    if ((rnd -= sd->weight) <= 0)
+      return i;
+  }
+
+log_write(0, LOG_MAIN|LOG_PANIC,
+  "%s unknown error (memory/cpu corruption?)", __FUNCTION__);
+return -1;
+}
+
+
+
+/* Make a connection via a socks proxy
+
+Arguments:
+ host          smtp target host
+ host_af       address family
+ port          remote tcp port number
+ interface     local interface
+ tb            transport
+ timeout       connection timeout (zero for indefinite)
+
+Return value:
+ 0 on success; -1 on failure, with errno set
+*/
+
+int
+socks_sock_connect(host_item * host, int host_af, int port, uschar * interface,
+  transport_instance * tb, int timeout)
+{
+smtp_transport_options_block * ob =
+  (smtp_transport_options_block *)tb->options_block;
+const uschar * proxy_list;
+const uschar * proxy_spec;
+int sep = 0;
+int fd;
+time_t tmo;
+const uschar * state;
+uschar buf[24];
+socks_opts proxies[32];                        /* max #proxies handled */
+unsigned nproxies;
+socks_opts * sob;
+unsigned size;
+
+if (!timeout) timeout = 24*60*60;      /* use 1 day for "indefinite" */
+tmo = time(NULL) + timeout;
+
+if (!(proxy_list = expand_string(ob->socks_proxy)))
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC, "Bad expansion for socks_proxy in %s",
+    tb->name);
+  return -1;
+  }
+
+/* Read proxy list */
+
+for (nproxies = 0;
+        nproxies < nelem(proxies)
+     && (proxy_spec = string_nextinlist(&proxy_list, &sep, NULL, 0));
+     nproxies++)
+  {
+  int subsep = -' ';
+  const uschar * option;
+
+  socks_option_defaults(sob = &proxies[nproxies]);
+
+  if (!(sob->proxy_host = string_nextinlist(&proxy_spec, &subsep, NULL, 0)))
+    {
+    /* paniclog config error */
+    return -1;
+    }
+
+  /*XXX consider global options eg. "hide socks_password = wibble" on the tpt */
+  /* extract any further per-proxy options */
+  while ((option = string_nextinlist(&proxy_spec, &subsep, NULL, 0)))
+    socks_option(sob, option);
+  }
+
+/* Try proxies until a connection succeeds */
+
+for(;;)
+  {
+  int idx;
+  host_item proxy;
+  int proxy_af;
+
+  if ((idx = socks_get_proxy(proxies, nproxies)) < 0)
+    {
+    HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  no proxies left\n");
+    errno = EBUSY;
+    return -1;
+    }
+  sob = &proxies[idx];
+
+  /* bodge up a host struct for the proxy */
+  proxy.address = sob->proxy_host;
+  proxy_af = Ustrchr(sob->proxy_host, ':') ? AF_INET6 : AF_INET;
+
+  if ((fd = smtp_sock_connect(&proxy, proxy_af, sob->port,
+             interface, tb, sob->timeout)) >= 0)
+    {
+    proxy_local_address = string_copy(proxy.address);
+    proxy_local_port = sob->port;
+    break;
+    }
+
+  log_write(0, LOG_MAIN, "%s: %s", __FUNCTION__, strerror(errno));
+  sob->is_failed = TRUE;
+  }
+
+/* Do the socks protocol stuff */
+/* Send method-selection */
+
+state = US"method select";
+HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SOCKS>> 05 01 %02x\n", sob->auth_type);
+buf[0] = 5; buf[1] = 1; buf[2] = sob->auth_type;
+if (send(fd, buf, 3, 0) < 0)
+  goto snd_err;
+
+/* expect method response */
+
+if (  !fd_ready(fd, tmo-time(NULL))
+   || read(fd, buf, 2) != 2
+   )
+  goto rcv_err;
+HDEBUG(D_transport|D_acl|D_v)
+  debug_printf_indent("  SOCKS<< %02x %02x\n", buf[0], buf[1]);
+if (  buf[0] != 5
+   || socks_auth(fd, buf[1], sob, tmo) != OK
+   )
+  goto proxy_err;
+
+  {
+  union sockaddr_46 sin;
+  (void) ip_addr(&sin, host_af, host->address, port);
+
+  /* send connect (ipver, ipaddr, port) */
+
+  buf[0] = 5; buf[1] = 1; buf[2] = 0; buf[3] = host_af == AF_INET6 ? 4 : 1;
+  #if HAVE_IPV6
+  if (host_af == AF_INET6)
+    {
+    memcpy(buf+4, &sin.v6.sin6_addr,       sizeof(sin.v6.sin6_addr));
+    memcpy(buf+4+sizeof(sin.v6.sin6_addr),
+      &sin.v6.sin6_port, sizeof(sin.v6.sin6_port));
+    size = 4+sizeof(sin.v6.sin6_addr)+sizeof(sin.v6.sin6_port);
+    }
+  else
+  #endif
+    {
+    memcpy(buf+4, &sin.v4.sin_addr.s_addr, sizeof(sin.v4.sin_addr.s_addr));
+    memcpy(buf+4+sizeof(sin.v4.sin_addr.s_addr),
+      &sin.v4.sin_port, sizeof(sin.v4.sin_port));
+    size = 4+sizeof(sin.v4.sin_addr.s_addr)+sizeof(sin.v4.sin_port);
+    }
+  }
+
+state = US"connect";
+HDEBUG(D_transport|D_acl|D_v)
+  {
+  int i;
+  debug_printf_indent("  SOCKS>>");
+  for (i = 0; i<size; i++) debug_printf(" %02x", buf[i]);
+  debug_printf("\n");
+  }
+if (send(fd, buf, size, 0) < 0)
+  goto snd_err;
+
+/* expect conn-reply (success, local(ipver, addr, port))
+of same length as conn-request, or non-success fail code */
+
+if (  !fd_ready(fd, tmo-time(NULL))
+   || (size = read(fd, buf, size)) < 2
+   )
+  goto rcv_err;
+HDEBUG(D_transport|D_acl|D_v)
+  {
+  int i;
+  debug_printf_indent("  SOCKS>>");
+  for (i = 0; i<size; i++) debug_printf(" %02x", buf[i]);
+  debug_printf("\n");
+  }
+if (  buf[0] != 5
+   || buf[1] != 0
+   )
+  goto proxy_err;
+
+proxy_external_address = string_copy(
+  host_ntoa(buf[3] == 4 ? AF_INET6 : AF_INET, buf+4, NULL, NULL));
+proxy_external_port = ntohs(*((uint16_t *)(buf + (buf[3] == 4 ? 20 : 8))));
+proxy_session = TRUE;
+
+HDEBUG(D_transport|D_acl|D_v)
+  debug_printf_indent("  proxy farside: [%s]:%d\n", proxy_external_address, proxy_external_port);
+
+return fd;
+
+snd_err:
+  HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  proxy snd_err %s: %s\n", state, strerror(errno));
+  return -1;
+
+proxy_err:
+  {
+  struct socks_err * se =
+    buf[1] > nelem(socks_errs) ? NULL : socks_errs + buf[1];
+  HDEBUG(D_transport|D_acl|D_v)
+    debug_printf_indent("  proxy %s: %s\n", state, se ? se->reason : US"unknown error code received");
+  errno = se ? se->errcode : EPROTO;
+  }
+
+rcv_err:
+  HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  proxy rcv_err %s: %s\n", state, strerror(errno));
+  if (!errno) errno = EPROTO;
+  else if (errno == ENOENT) errno = ECONNABORTED;
+  return -1;
+}
+
+#endif /* entire file */
+/* vi: aw ai sw=2
+*/
index cfe7cb2..7be7289 100644 (file)
@@ -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 in support of the use of maildirsize files for handling quotas in
@@ -356,7 +356,7 @@ Or, at least, it is supposed to!
 
 Arguments:
   path             the path to the maildir directory; this is already backed-up
-                     to the parent if the delivery diretory is a maildirfolder
+                     to the parent if the delivery directory is a maildirfolder
   ob               the appendfile options block
   regex            a compiled regex for getting a file's size from its name
   dir_regex        a compiled regex for selecting maildir directories
@@ -554,8 +554,8 @@ else
     FALSE);
 
   (void)gettimeofday(&tv, NULL);
-  tempname = string_sprintf("%s/tmp/%lu.H%luP%lu.%s", path, tv.tv_sec,
-    tv.tv_usec, (long unsigned) getpid(), primary_hostname);
+  tempname = string_sprintf("%s/tmp/" TIME_T_FMT ".H%luP%lu.%s",
+    path, tv.tv_sec, tv.tv_usec, (long unsigned) getpid(), primary_hostname);
 
   fd = Uopen(tempname, O_RDWR|O_CREAT|O_EXCL, ob->mode ? ob->mode : 0600);
   if (fd >= 0)
index 2a9d90a..72c084a 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for maintaining binary balanced trees and some associated
@@ -328,7 +328,7 @@ Returns:    pointer to node, or NULL if not found
 */
 
 tree_node *
-tree_search(tree_node *p, uschar *name)
+tree_search(tree_node *p, const uschar *name)
 {
 while (p != NULL)
   {
diff --git a/src/utf8.c b/src/utf8.c
new file mode 100644 (file)
index 0000000..7b7b88f
--- /dev/null
@@ -0,0 +1,273 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Jeremy Harris 2015, 2016 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+
+#include "exim.h"
+
+#ifdef SUPPORT_I18N
+
+#ifdef SUPPORT_I18N_2008
+# include <idn2.h>
+#else
+# include <idna.h>
+#endif
+
+#include <punycode.h>
+#include <stringprep.h>
+
+static uschar *
+string_localpart_alabel_to_utf8_(const uschar * alabel, uschar ** err);
+
+/**************************************************/
+
+BOOL
+string_is_utf8(const uschar * s)
+{
+uschar c;
+if (s) while ((c = *s++)) if (c & 0x80) return TRUE;
+return FALSE;
+}
+
+static BOOL
+string_is_alabel(const uschar * s)
+{
+return s[0] == 'x' && s[1] == 'n' && s[2] == '-' && s[3] == '-';
+}
+
+/**************************************************/
+/* Domain conversions.
+The *err string pointer should be null before the call
+
+Return NULL for error, with optional errstr pointer filled in
+*/
+
+uschar *
+string_domain_utf8_to_alabel(const uschar * utf8, uschar ** err)
+{
+uschar * s1, * s;
+int rc;
+
+#ifdef SUPPORT_I18N_2008
+/* Avoid lowercasing plain-ascii domains */
+if (!string_is_utf8(utf8))
+  return string_copy(utf8);
+
+/* Only lowercase is accepted by the library call.  A pity since we lose
+any mixed-case annotation.  This does not really matter for a domain. */
+  {
+  uschar c;
+  for (s1 = s = US utf8; (c = *s1); s1++) if (!(c & 0x80) && isupper(c))
+    {
+    s = string_copy(utf8);
+    for (s1 = s + (s1 - utf8); (c = *s1); s1++) if (!(c & 0x80) && isupper(c))
+      *s1 = tolower(c);
+    break;
+    }
+  }
+if ((rc = idn2_lookup_u8(CCS s, &s1, IDN2_NFC_INPUT)) != IDN2_OK)
+  {
+  if (err) *err = US idn2_strerror(rc);
+  return NULL;
+  }
+#else
+s = US stringprep_utf8_nfkc_normalize(CCS utf8, -1);
+if (  (rc = idna_to_ascii_8z(CCS s, CSS &s1, IDNA_ALLOW_UNASSIGNED))
+   != IDNA_SUCCESS)
+  {
+  free(s);
+  if (err) *err = US idna_strerror(rc);
+  return NULL;
+  }
+free(s);
+#endif
+s = string_copy(s1);
+free(s1);
+return s;
+}
+
+
+
+uschar *
+string_domain_alabel_to_utf8(const uschar * alabel, uschar ** err)
+{
+#ifdef SUPPORT_I18N_2008
+const uschar * label;
+int sep = '.';
+uschar * s = NULL;
+
+while (label = string_nextinlist(&alabel, &sep, NULL, 0))
+  if (  string_is_alabel(label)
+     && !(label = string_localpart_alabel_to_utf8_(label, err))
+     )
+    return NULL;
+  else
+    s = string_append_listele(s, '.', label);
+return s;
+
+#else
+
+uschar * s1, * s;
+int rc;
+
+if (  (rc = idna_to_unicode_8z8z(CCS alabel, CSS &s1, IDNA_USE_STD3_ASCII_RULES))
+   != IDNA_SUCCESS)
+  {
+  if (err) *err = US idna_strerror(rc);
+  return NULL;
+  }
+s = string_copy(s1);
+free(s1);
+return s;
+#endif
+}
+
+/**************************************************/
+/* localpart conversions */
+/* the *err string pointer should be null before the call */
+
+
+uschar *
+string_localpart_utf8_to_alabel(const uschar * utf8, uschar ** err)
+{
+size_t ucs4_len;
+punycode_uint * p;
+size_t p_len;
+uschar * res;
+int rc;
+
+if (!string_is_utf8(utf8)) return string_copy(utf8);
+
+p = (punycode_uint *) stringprep_utf8_to_ucs4(CCS utf8, -1, &ucs4_len);
+p_len = ucs4_len*4;    /* this multiplier is pure guesswork */
+res = store_get(p_len+5);
+
+res[0] = 'x'; res[1] = 'n'; res[2] = res[3] = '-';
+
+if ((rc = punycode_encode(ucs4_len, p, NULL, &p_len, CS res+4)) != PUNYCODE_SUCCESS)
+  {
+  DEBUG(D_expand) debug_printf("l_u2a: bad '%s'\n", punycode_strerror(rc));
+  free(p);
+  if (err) *err = US punycode_strerror(rc);
+  return NULL;
+  }
+p_len += 4;
+free(p);
+res[p_len] = '\0';
+return res;
+}
+
+
+static uschar *
+string_localpart_alabel_to_utf8_(const uschar * alabel, uschar ** err)
+{
+size_t p_len;
+punycode_uint * p;
+int rc;
+uschar * s, * res;
+
+DEBUG(D_expand) debug_printf("l_a2u: '%s'\n", alabel);
+alabel += 4;
+p_len = Ustrlen(alabel);
+p = (punycode_uint *) store_get((p_len+1) * sizeof(*p));
+
+if ((rc = punycode_decode(p_len, CCS alabel, &p_len, p, NULL)) != PUNYCODE_SUCCESS)
+  {
+  if (err) *err = US punycode_strerror(rc);
+  return NULL;
+  }
+
+s = US stringprep_ucs4_to_utf8(p, p_len, NULL, &p_len);
+res = string_copyn(s, p_len);
+free(s);
+return res;
+}
+
+
+uschar *
+string_localpart_alabel_to_utf8(const uschar * alabel, uschar ** err)
+{
+if (string_is_alabel(alabel))
+  return string_localpart_alabel_to_utf8_(alabel, err);
+
+if (err) *err = US"bad alabel prefix";
+return NULL;
+}
+
+
+/**************************************************/
+/* Whole address conversion.
+The *err string pointer should be null before the call.
+
+Return NULL on error, with (optional) errstring pointer filled in
+*/
+
+uschar *
+string_address_utf8_to_alabel(const uschar * utf8, uschar ** err)
+{
+const uschar * s;
+uschar * l;
+uschar * d;
+
+if (!*utf8) return string_copy(utf8);
+
+DEBUG(D_expand) debug_printf("addr from utf8 <%s>", utf8);
+
+for (s = utf8; *s; s++)
+  if (*s == '@')
+    {
+    l = string_copyn(utf8, s - utf8);
+    if (  !(l = string_localpart_utf8_to_alabel(l, err))
+       || !(d = string_domain_utf8_to_alabel(++s, err))
+       )
+      return NULL;
+    l = string_sprintf("%s@%s", l, d);
+    DEBUG(D_expand) debug_printf(" -> <%s>\n", l);
+    return l;
+    }
+
+l =  string_localpart_utf8_to_alabel(utf8, err);
+DEBUG(D_expand) debug_printf(" -> <%s>\n", l);
+return l;
+}
+
+
+
+/*************************************************
+*         Report the library versions.           *
+*************************************************/
+
+/* See a description in tls-openssl.c for an explanation of why this exists.
+
+Arguments:   a FILE* to print the results to
+Returns:     nothing
+*/
+
+void
+utf8_version_report(FILE *f)
+{
+#ifdef SUPPORT_I18N_2008
+fprintf(f, "Library version: IDN2: Compile: %s\n"
+           "                       Runtime: %s\n",
+       IDN2_VERSION,
+       idn2_check_version(NULL));
+fprintf(f, "Library version: Stringprep: Compile: %s\n"
+           "                             Runtime: %s\n",
+       STRINGPREP_VERSION,
+       stringprep_check_version(NULL));
+#else
+fprintf(f, "Library version: IDN: Compile: %s\n"
+           "                      Runtime: %s\n",
+       STRINGPREP_VERSION,
+       stringprep_check_version(NULL));
+#endif
+}
+
+#endif /* whole file */
+
+/* vi: aw ai sw=2
+*/
+/* End of utf8.c */
index 4d41690..01c49da 100644 (file)
    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.
 
-   2. The origin of this software must not be misrepresented; you must 
-      not claim that you wrote the original software.  If you use this 
-      software in a product, an acknowledgment in the product 
+   2. The origin of this software must not be misrepresented; you must
+      not claim that you wrote the original software.  If you use this
+      software in a product, an acknowledgment in the product
       documentation would be appreciated but is not required.
 
    3. Altered source versions must be plainly marked as such, and must
       not be misrepresented as being the original software.
 
-   4. The name of the author may not be used to endorse or promote 
-      products derived from this software without specific prior written 
+   4. The name of the author may not be used to endorse or promote
+      products derived from this software without specific prior written
       permission.
 
    THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
    the terms of the GNU General Public License, version 2.  See the
    COPYING file in the source distribution for details.
 
-   ---------------------------------------------------------------- 
+   ----------------------------------------------------------------
 */
 
 
 /* This file is for inclusion into client (your!) code.
 
-   You can use these macros to manipulate and query Valgrind's 
+   You can use these macros to manipulate and query Valgrind's
    execution inside your own programs.
 
    The resulting executables will still run without Valgrind, just a
    this is executed not under Valgrind.  Args are passed in a memory
    block, and so there's no intrinsic limit to the number that could
    be passed, but it's currently five.
-   
-   The macro args are: 
+
+   The macro args are:
       _zzq_rlval    result lvalue
       _zzq_default  default value (result returned when running on real CPU)
       _zzq_request  request code
     ||  (defined(PLAT_x86_win32) && defined(__GNUC__))
 
 typedef
-   struct { 
+   struct {
       unsigned int nraddr; /* where's the code? */
    }
    OrigFn;
@@ -262,7 +262,7 @@ typedef
 #if defined(PLAT_x86_win32) && !defined(__GNUC__)
 
 typedef
-   struct { 
+   struct {
       unsigned int nraddr; /* where's the code? */
    }
    OrigFn;
@@ -317,7 +317,7 @@ typedef
 #if defined(PLAT_amd64_linux)  ||  defined(PLAT_amd64_darwin)
 
 typedef
-   struct { 
+   struct {
       unsigned long long int nraddr; /* where's the code? */
    }
    OrigFn;
@@ -371,7 +371,7 @@ typedef
 #if defined(PLAT_ppc32_linux)
 
 typedef
-   struct { 
+   struct {
       unsigned int nraddr; /* where's the code? */
    }
    OrigFn;
@@ -431,7 +431,7 @@ typedef
 #if defined(PLAT_ppc64_linux)
 
 typedef
-   struct { 
+   struct {
       unsigned long long int nraddr; /* where's the code? */
       unsigned long long int r2;  /* what tocptr do we need? */
    }
@@ -497,7 +497,7 @@ typedef
 #if defined(PLAT_arm_linux)
 
 typedef
-   struct { 
+   struct {
       unsigned int nraddr; /* where's the code? */
    }
    OrigFn;
@@ -556,7 +556,7 @@ typedef
 #if defined(PLAT_ppc32_aix5)
 
 typedef
-   struct { 
+   struct {
       unsigned int nraddr; /* where's the code? */
       unsigned int r2;  /* what tocptr do we need? */
    }
@@ -628,7 +628,7 @@ typedef
 #if defined(PLAT_ppc64_aix5)
 
 typedef
-   struct { 
+   struct {
       unsigned long long int nraddr; /* where's the code? */
       unsigned long long int r2;  /* what tocptr do we need? */
    }
@@ -1271,7 +1271,7 @@ typedef
 /* NB 9 Sept 07.  There is a nasty kludge here in all these CALL_FN_
    macros.  In order not to trash the stack redzone, we need to drop
    %rsp by 128 before the hidden call, and restore afterwards.  The
-   nastyness is that it is only by luck that the stack still appears
+   nastiness is that it is only by luck that the stack still appears
    to be unwindable during the hidden call - since then the behaviour
    of any routine using this macro does not match what the CFI data
    says.  Sigh.
@@ -1753,7 +1753,7 @@ typedef
    "r0", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10",   \
    "r11", "r12", "r13"
 
-/* These CALL_FN_ macros assume that on ppc32-linux, 
+/* These CALL_FN_ macros assume that on ppc32-linux,
    sizeof(unsigned long) == 4. */
 
 #define CALL_FN_W_v(lval, orig)                                   \
@@ -4269,7 +4269,7 @@ typedef
 #define VG_IS_TOOL_USERREQ(a, b, v) \
    (VG_USERREQ_TOOL_BASE(a,b) == ((v) & 0xffff0000))
 
-/* !! ABIWARNING !! ABIWARNING !! ABIWARNING !! ABIWARNING !! 
+/* !! ABIWARNING !! ABIWARNING !! ABIWARNING !! ABIWARNING !!
    This enum comprises an ABI exported by Valgrind to programs
    which use client requests.  DO NOT CHANGE THE ORDER OF THESE
    ENTRIES, NOR DELETE ANY -- add new ones at the end. */
@@ -4452,7 +4452,7 @@ VALGRIND_PRINTF(const char *format, ...)
    VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0,
                               VG_USERREQ__PRINTF_VALIST_BY_REF,
                               (unsigned long)format,
-                              (unsigned long)&vargs, 
+                              (unsigned long)&vargs,
                               0, 0, 0);
 #endif
    va_end(vargs);
@@ -4482,7 +4482,7 @@ VALGRIND_PRINTF_BACKTRACE(const char *format, ...)
    VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0,
                               VG_USERREQ__PRINTF_BACKTRACE_VALIST_BY_REF,
                               (unsigned long)format,
-                              (unsigned long)&vargs, 
+                              (unsigned long)&vargs,
                               0, 0, 0);
 #endif
    va_end(vargs);
@@ -4493,8 +4493,8 @@ VALGRIND_PRINTF_BACKTRACE(const char *format, ...)
 
 
 /* These requests allow control to move from the simulated CPU to the
-   real CPU, calling an arbitary function.
-   
+   real CPU, calling an arbitrary function.
+
    Note that the current ThreadId is inserted as the first argument.
    So this call:
 
@@ -4599,7 +4599,7 @@ VALGRIND_PRINTF_BACKTRACE(const char *format, ...)
    - It marks the block as being addressable and undefined (if 'is_zeroed' is
      not set), or addressable and defined (if 'is_zeroed' is set).  This
      controls how accesses to the block by the program are handled.
-   
+
    'addr' is the start of the usable block (ie. after any
    redzone), 'sizeB' is its size.  'rzB' is the redzone size if the allocator
    can apply redzones -- these are blocks of padding at the start and end of
@@ -4607,7 +4607,7 @@ VALGRIND_PRINTF_BACKTRACE(const char *format, ...)
    Valgrind will spot block overruns.  `is_zeroed' indicates if the memory is
    zeroed (or filled with another predictable value), as is the case for
    calloc().
-   
+
    VALGRIND_MALLOCLIKE_BLOCK should be put immediately after the point where a
    heap block -- that will be used by the client program -- is allocated.
    It's best to put it at the outermost level of the allocator if possible;
@@ -4653,7 +4653,7 @@ VALGRIND_PRINTF_BACKTRACE(const char *format, ...)
 
    Note: there is currently no VALGRIND_REALLOCLIKE_BLOCK client request;  it
    has to be emulated with MALLOCLIKE/FREELIKE and memory copying.
-   
+
    Ignored if addr == 0.
 */
 #define VALGRIND_MALLOCLIKE_BLOCK(addr, sizeB, rzB, is_zeroed)    \
index b1b9f29..9c4776b 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2017 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions concerned with verifying things. The original code for callout
@@ -14,7 +14,6 @@ caching was contributed by Kevin Fleming (but I hacked it around a bit). */
 
 #define CUTTHROUGH_CMD_TIMEOUT  30     /* timeout for cutthrough-routing calls */
 #define CUTTHROUGH_DATA_TIMEOUT 60     /* timeout for cutthrough-routing calls */
-address_item cutthrough_addr;
 static smtp_outblock ctblock;
 uschar ctbuffer[8192];
 
@@ -22,6 +21,7 @@ uschar ctbuffer[8192];
 /* Structure for caching DNSBL lookups */
 
 typedef struct dnsbl_cache_block {
+  time_t expiry;
   dns_address *rhs;
   uschar *text;
   int rc;
@@ -39,6 +39,8 @@ static tree_node *dnsbl_cache = NULL;
 #define MT_NOT 1
 #define MT_ALL 2
 
+static uschar cutthrough_response(char, uschar **, int);
+
 
 
 /*************************************************
@@ -58,7 +60,7 @@ Returns:            the cache record if a non-expired one exists, else NULL
 */
 
 static dbdata_callout_cache *
-get_callout_cache_record(open_db *dbm_file, uschar *key, uschar *type,
+get_callout_cache_record(open_db *dbm_file, const uschar *key, uschar *type,
   int positive_expire, int negative_expire)
 {
 BOOL negative;
@@ -70,7 +72,7 @@ cache_record = dbfn_read_with_length(dbm_file, key, &length);
 
 if (cache_record == NULL)
   {
-  HDEBUG(D_verify) debug_printf("callout cache: no %s record found\n", type);
+  HDEBUG(D_verify) debug_printf("callout cache: no %s record found for %s\n", type, key);
   return NULL;
   }
 
@@ -84,7 +86,7 @@ now = time(NULL);
 
 if (now - cache_record->time_stamp > expire)
   {
-  HDEBUG(D_verify) debug_printf("callout cache: %s record expired\n", type);
+  HDEBUG(D_verify) debug_printf("callout cache: %s record expired for %s\n", type, key);
   return NULL;
   }
 
@@ -111,134 +113,53 @@ if (type[0] == 'd' && cache_record->result != ccache_reject)
     cache_record->random_result = ccache_unknown;
   }
 
-HDEBUG(D_verify) debug_printf("callout cache: found %s record\n", type);
+HDEBUG(D_verify) debug_printf("callout cache: found %s record for %s\n", type, key);
 return cache_record;
 }
 
 
 
-/*************************************************
-*      Do callout verification for an address    *
-*************************************************/
-
-/* This function is called from verify_address() when the address has routed to
-a host list, and a callout has been requested. Callouts are expensive; that is
-why a cache is used to improve the efficiency.
-
-Arguments:
-  addr              the address that's been routed
-  host_list         the list of hosts to try
-  tf                the transport feedback block
+/* Check the callout cache.
+Options * pm_mailfrom may be modified by cache partial results.
 
-  ifstring          "interface" option from transport, or NULL
-  portstring        "port" option from transport, or NULL
-  protocolstring    "protocol" option from transport, or NULL
-  callout           the per-command callout timeout
-  callout_overall   the overall callout timeout (if < 0 use 4*callout)
-  callout_connect   the callout connection timeout (if < 0 use callout)
-  options           the verification options - these bits are used:
-                      vopt_is_recipient => this is a recipient address
-                      vopt_callout_no_cache => don't use callout cache
-                      vopt_callout_fullpm => if postmaster check, do full one
-                      vopt_callout_random => do the "random" thing
-                      vopt_callout_recipsender => use real sender for recipient
-                      vopt_callout_recippmaster => use postmaster for recipient
-  se_mailfrom         MAIL FROM address for sender verify; NULL => ""
-  pm_mailfrom         if non-NULL, do the postmaster check with this sender
-
-Returns:            OK/FAIL/DEFER
+Return: TRUE if result found
 */
 
-static int
-do_callout(address_item *addr, host_item *host_list, transport_feedback *tf,
-  int callout, int callout_overall, int callout_connect, int options,
-  uschar *se_mailfrom, uschar *pm_mailfrom)
+static BOOL
+cached_callout_lookup(address_item * addr, uschar * address_key,
+  uschar * from_address, int * opt_ptr, uschar ** pm_ptr,
+  int * yield, uschar ** failure_ptr,
+  dbdata_callout_cache * new_domain_record, int * old_domain_res)
 {
-BOOL is_recipient = (options & vopt_is_recipient) != 0;
-BOOL callout_no_cache = (options & vopt_callout_no_cache) != 0;
-BOOL callout_random = (options & vopt_callout_random) != 0;
-
-int yield = OK;
-int old_domain_cache_result = ccache_accept;
-BOOL done = FALSE;
-uschar *address_key;
-uschar *from_address;
-uschar *random_local_part = NULL;
-uschar *save_deliver_domain = deliver_domain;
-uschar **failure_ptr = is_recipient?
-  &recipient_verify_failure : &sender_verify_failure;
+int options = *opt_ptr;
 open_db dbblock;
 open_db *dbm_file = NULL;
-dbdata_callout_cache new_domain_record;
-dbdata_callout_cache_address new_address_record;
-host_item *host;
-time_t callout_start_time;
-
-new_domain_record.result = ccache_unknown;
-new_domain_record.postmaster_result = ccache_unknown;
-new_domain_record.random_result = ccache_unknown;
-
-memset(&new_address_record, 0, sizeof(new_address_record));
-
-/* For a recipient callout, the key used for the address cache record must
-include the sender address if we are using the real sender in the callout,
-because that may influence the result of the callout. */
-
-address_key = addr->address;
-from_address = US"";
-
-if (is_recipient)
-  {
-  if ((options & vopt_callout_recipsender) != 0)
-    {
-    address_key = string_sprintf("%s/<%s>", addr->address, sender_address);
-    from_address = sender_address;
-    }
-  else if ((options & vopt_callout_recippmaster) != 0)
-    {
-    address_key = string_sprintf("%s/<postmaster@%s>", addr->address,
-      qualify_domain_sender);
-    from_address = string_sprintf("postmaster@%s", qualify_domain_sender);
-    }
-  }
-
-/* For a sender callout, we must adjust the key if the mailfrom address is not
-empty. */
-
-else
-  {
-  from_address = (se_mailfrom == NULL)? US"" : se_mailfrom;
-  if (from_address[0] != 0)
-    address_key = string_sprintf("%s/<%s>", addr->address, from_address);
-  }
 
 /* Open the callout cache database, it it exists, for reading only at this
 stage, unless caching has been disabled. */
 
-if (callout_no_cache)
+if (options & vopt_callout_no_cache)
   {
   HDEBUG(D_verify) debug_printf("callout cache: disabled by no_cache\n");
   }
-else if ((dbm_file = dbfn_open(US"callout", O_RDWR, &dbblock, FALSE)) == NULL)
+else if (!(dbm_file = dbfn_open(US"callout", O_RDWR, &dbblock, FALSE)))
   {
   HDEBUG(D_verify) debug_printf("callout cache: not available\n");
   }
-
-/* If a cache database is available see if we can avoid the need to do an
-actual callout by making use of previously-obtained data. */
-
-if (dbm_file != NULL)
+else
   {
-  dbdata_callout_cache_address *cache_address_record;
-  dbdata_callout_cache *cache_record = get_callout_cache_record(dbm_file,
-    addr->domain, US"domain",
-    callout_cache_domain_positive_expire,
-    callout_cache_domain_negative_expire);
+  /* If a cache database is available see if we can avoid the need to do an
+  actual callout by making use of previously-obtained data. */
+
+  dbdata_callout_cache_address * cache_address_record;
+  dbdata_callout_cache * cache_record = get_callout_cache_record(dbm_file,
+      addr->domain, US"domain",
+      callout_cache_domain_positive_expire, callout_cache_domain_negative_expire);
 
   /* If an unexpired cache record was found for this domain, see if the callout
   process can be short-circuited. */
 
-  if (cache_record != NULL)
+  if (cache_record)
     {
     /* In most cases, if an early command (up to and including MAIL FROM:<>)
     was rejected, there is no point carrying on. The callout fails. However, if
@@ -248,20 +169,21 @@ if (dbm_file != NULL)
     not to disturb the cached domain value if this whole verification succeeds
     (we don't want it turning into "accept"). */
 
-    old_domain_cache_result = cache_record->result;
+    *old_domain_res = cache_record->result;
 
-    if (cache_record->result == ccache_reject ||
-         (*from_address == 0 && cache_record->result == ccache_reject_mfnull))
+    if (  cache_record->result == ccache_reject
+       || *from_address == 0 && cache_record->result == ccache_reject_mfnull)
       {
       setflag(addr, af_verify_nsfail);
       HDEBUG(D_verify)
-        debug_printf("callout cache: domain gave initial rejection, or "
-          "does not accept HELO or MAIL FROM:<>\n");
+       debug_printf("callout cache: domain gave initial rejection, or "
+         "does not accept HELO or MAIL FROM:<>\n");
       setflag(addr, af_verify_nsfail);
       addr->user_message = US"(result of an earlier callout reused).";
-      yield = FAIL;
+      *yield = FAIL;
       *failure_ptr = US"mail";
-      goto END_CALLOUT;
+      dbfn_close(dbm_file);
+      return TRUE;
       }
 
     /* If a previous check on a "random" local part was accepted, we assume
@@ -271,26 +193,28 @@ if (dbm_file != NULL)
     the data in the new record. If a random check is required but hasn't been
     done, skip the remaining cache processing. */
 
-    if (callout_random) switch(cache_record->random_result)
+    if (options & vopt_callout_random) switch(cache_record->random_result)
       {
       case ccache_accept:
-      HDEBUG(D_verify)
-        debug_printf("callout cache: domain accepts random addresses\n");
-      goto END_CALLOUT;     /* Default yield is OK */
+       HDEBUG(D_verify)
+         debug_printf("callout cache: domain accepts random addresses\n");
+       dbfn_close(dbm_file);
+       return TRUE;     /* Default yield is OK */
 
       case ccache_reject:
-      HDEBUG(D_verify)
-        debug_printf("callout cache: domain rejects random addresses\n");
-      callout_random = FALSE;
-      new_domain_record.random_result = ccache_reject;
-      new_domain_record.random_stamp = cache_record->random_stamp;
-      break;
+       HDEBUG(D_verify)
+         debug_printf("callout cache: domain rejects random addresses\n");
+       *opt_ptr = options & ~vopt_callout_random;
+       new_domain_record->random_result = ccache_reject;
+       new_domain_record->random_stamp = cache_record->random_stamp;
+       break;
 
       default:
-      HDEBUG(D_verify)
-        debug_printf("callout cache: need to check random address handling "
-          "(not cached or cache expired)\n");
-      goto END_CACHE;
+       HDEBUG(D_verify)
+         debug_printf("callout cache: need to check random address handling "
+           "(not cached or cache expired)\n");
+       dbfn_close(dbm_file);
+       return FALSE;
       }
 
     /* If a postmaster check is requested, but there was a previous failure,
@@ -298,27 +222,29 @@ if (dbm_file != NULL)
     but has not been done before, we are going to have to do a callout, so skip
     remaining cache processing. */
 
-    if (pm_mailfrom != NULL)
+    if (*pm_ptr)
       {
       if (cache_record->postmaster_result == ccache_reject)
-        {
-        setflag(addr, af_verify_pmfail);
-        HDEBUG(D_verify)
-          debug_printf("callout cache: domain does not accept "
-            "RCPT TO:<postmaster@domain>\n");
-        yield = FAIL;
-        *failure_ptr = US"postmaster";
-        setflag(addr, af_verify_pmfail);
-        addr->user_message = US"(result of earlier verification reused).";
-        goto END_CALLOUT;
-        }
+       {
+       setflag(addr, af_verify_pmfail);
+       HDEBUG(D_verify)
+         debug_printf("callout cache: domain does not accept "
+           "RCPT TO:<postmaster@domain>\n");
+       *yield = FAIL;
+       *failure_ptr = US"postmaster";
+       setflag(addr, af_verify_pmfail);
+       addr->user_message = US"(result of earlier verification reused).";
+       dbfn_close(dbm_file);
+       return TRUE;
+       }
       if (cache_record->postmaster_result == ccache_unknown)
-        {
-        HDEBUG(D_verify)
-          debug_printf("callout cache: need to check RCPT "
-            "TO:<postmaster@domain> (not cached or cache expired)\n");
-        goto END_CACHE;
-        }
+       {
+       HDEBUG(D_verify)
+         debug_printf("callout cache: need to check RCPT "
+           "TO:<postmaster@domain> (not cached or cache expired)\n");
+       dbfn_close(dbm_file);
+       return FALSE;
+       }
 
       /* If cache says OK, set pm_mailfrom NULL to prevent a redundant
       postmaster check if the address itself has to be checked. Also ensure
@@ -326,10 +252,10 @@ if (dbm_file != NULL)
       */
 
       HDEBUG(D_verify) debug_printf("callout cache: domain accepts RCPT "
-        "TO:<postmaster@domain>\n");
-      pm_mailfrom = NULL;
-      new_domain_record.postmaster_result = ccache_accept;
-      new_domain_record.postmaster_stamp = cache_record->postmaster_stamp;
+       "TO:<postmaster@domain>\n");
+      *pm_ptr = NULL;
+      new_domain_record->postmaster_result = ccache_accept;
+      new_domain_record->postmaster_stamp = cache_record->postmaster_stamp;
       }
     }
 
@@ -338,37 +264,300 @@ if (dbm_file != NULL)
   sender address if we are doing a recipient callout with a non-empty sender).
   */
 
-  cache_address_record = (dbdata_callout_cache_address *)
-    get_callout_cache_record(dbm_file,
-      address_key, US"address",
-      callout_cache_positive_expire,
-      callout_cache_negative_expire);
+  if (!(cache_address_record = (dbdata_callout_cache_address *)
+    get_callout_cache_record(dbm_file, address_key, US"address",
+      callout_cache_positive_expire, callout_cache_negative_expire)))
+    {
+    dbfn_close(dbm_file);
+    return FALSE;
+    }
 
-  if (cache_address_record != NULL)
+  if (cache_address_record->result == ccache_accept)
     {
-    if (cache_address_record->result == ccache_accept)
-      {
-      HDEBUG(D_verify)
-        debug_printf("callout cache: address record is positive\n");
-      }
-    else
-      {
-      HDEBUG(D_verify)
-        debug_printf("callout cache: address record is negative\n");
-      addr->user_message = US"Previous (cached) callout verification failure";
-      *failure_ptr = US"recipient";
-      yield = FAIL;
-      }
-    goto END_CALLOUT;
+    HDEBUG(D_verify)
+      debug_printf("callout cache: address record is positive\n");
+    }
+  else
+    {
+    HDEBUG(D_verify)
+      debug_printf("callout cache: address record is negative\n");
+    addr->user_message = US"Previous (cached) callout verification failure";
+    *failure_ptr = US"recipient";
+    *yield = FAIL;
     }
 
   /* Close the cache database while we actually do the callout for real. */
 
-  END_CACHE:
   dbfn_close(dbm_file);
-  dbm_file = NULL;
+  return TRUE;
+  }
+return FALSE;
+}
+
+
+/* Write results to callout cache
+*/
+static void
+cache_callout_write(dbdata_callout_cache * dom_rec, const uschar * domain,
+  int done, dbdata_callout_cache_address * addr_rec, uschar * address_key)
+{
+open_db dbblock;
+open_db *dbm_file = NULL;
+
+/* If we get here with done == TRUE, a successful callout happened, and yield
+will be set OK or FAIL according to the response to the RCPT command.
+Otherwise, we looped through the hosts but couldn't complete the business.
+However, there may be domain-specific information to cache in both cases.
+
+The value of the result field in the new_domain record is ccache_unknown if
+there was an error before or with MAIL FROM:, and errno was not zero,
+implying some kind of I/O error. We don't want to write the cache in that case.
+Otherwise the value is ccache_accept, ccache_reject, or ccache_reject_mfnull. */
+
+if (dom_rec->result != ccache_unknown)
+  if (!(dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE)))
+    {
+    HDEBUG(D_verify) debug_printf("callout cache: not available\n");
+    }
+  else
+    {
+    (void)dbfn_write(dbm_file, domain, dom_rec,
+      (int)sizeof(dbdata_callout_cache));
+    HDEBUG(D_verify) debug_printf("wrote callout cache domain record for %s:\n"
+      "  result=%d postmaster=%d random=%d\n",
+      domain,
+      dom_rec->result,
+      dom_rec->postmaster_result,
+      dom_rec->random_result);
+    }
+
+/* If a definite result was obtained for the callout, cache it unless caching
+is disabled. */
+
+if (done  &&  addr_rec->result != ccache_unknown)
+  {
+  if (!dbm_file)
+    dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE);
+  if (!dbm_file)
+    {
+    HDEBUG(D_verify) debug_printf("no callout cache available\n");
+    }
+  else
+    {
+    (void)dbfn_write(dbm_file, address_key, addr_rec,
+      (int)sizeof(dbdata_callout_cache_address));
+    HDEBUG(D_verify) debug_printf("wrote %s callout cache address record for %s\n",
+      addr_rec->result == ccache_accept ? "positive" : "negative",
+      address_key);
+    }
+  }
+
+if (dbm_file) dbfn_close(dbm_file);
+}
+
+
+/* Cutthrough-multi.  If the existing cached cutthrough connection matches
+the one we would make for a subsequent recipient, use it.  Send the RCPT TO
+and check the result, nonpipelined as it may be wanted immediately for
+recipient-verification.
+
+It seems simpler to deal with this case separately from the main callout loop.
+We will need to remember it has sent, or not, so that rcpt-acl tail code
+can do it there for the non-rcpt-verify case.  For this we keep an addresscount.
+
+Return: TRUE for a definitive result for the recipient
+*/
+static int
+cutthrough_multi(address_item * addr, host_item * host_list,
+  transport_feedback * tf, int * yield)
+{
+BOOL done = FALSE;
+host_item * host;
+
+if (addr->transport == cutthrough.addr.transport)
+  for (host = host_list; host; host = host->next)
+    if (Ustrcmp(host->address, cutthrough.host.address) == 0)
+      {
+      int host_af;
+      uschar *interface = NULL;  /* Outgoing interface to use; NULL => any */
+      int port = 25;
+
+      deliver_host = host->name;
+      deliver_host_address = host->address;
+      deliver_host_port = host->port;
+      deliver_domain = addr->domain;
+      transport_name = addr->transport->name;
+
+      host_af = (Ustrchr(host->address, ':') == NULL)? AF_INET:AF_INET6;
+
+      if (!smtp_get_interface(tf->interface, host_af, addr, &interface,
+             US"callout") ||
+         !smtp_get_port(tf->port, addr, &port, US"callout"))
+       log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address,
+         addr->message);
+
+      if (  (  interface == cutthrough.interface
+           || (  interface
+              && cutthrough.interface
+              && Ustrcmp(interface, cutthrough.interface) == 0
+           )  )
+        && port == cutthrough.host.port
+        )
+       {
+       uschar * resp = NULL;
+
+       /* Match!  Send the RCPT TO, set done from the response */
+       done =
+         smtp_write_command(&ctblock, FALSE, "RCPT TO:<%.1000s>\r\n",
+           transport_rcpt_address(addr,
+              addr->transport->rcpt_include_affixes)) >= 0 &&
+         cutthrough_response('2', &resp, CUTTHROUGH_DATA_TIMEOUT) == '2';
+
+       /* This would go horribly wrong if a callout fail was ignored by ACL.
+       We punt by abandoning cutthrough on a reject, like the
+       first-rcpt does. */
+
+       if (done)
+         {
+         address_item * na = store_get(sizeof(address_item));
+         *na = cutthrough.addr;
+         cutthrough.addr = *addr;
+         cutthrough.addr.host_used = &cutthrough.host;
+         cutthrough.addr.next = na;
+
+         cutthrough.nrcpt++;
+         }
+       else
+         {
+         cancel_cutthrough_connection("recipient rejected");
+         if (!resp || errno == ETIMEDOUT)
+           {
+           HDEBUG(D_verify) debug_printf("SMTP timeout\n");
+           }
+         else if (errno == 0)
+           {
+           if (*resp == 0)
+             Ustrcpy(resp, US"connection dropped");
+
+           addr->message =
+             string_sprintf("response to \"%s\" was: %s",
+               big_buffer, string_printing(resp));
+
+           addr->user_message =
+             string_sprintf("Callout verification failed:\n%s", resp);
+
+           /* Hard rejection ends the process */
+
+           if (resp[0] == '5')   /* Address rejected */
+             {
+             *yield = FAIL;
+             done = TRUE;
+             }
+           }
+         }
+       }
+      break;   /* host_list */
+      }
+if (!done)
+  cancel_cutthrough_connection("incompatible connection");
+return done;
+}
+
+
+/*************************************************
+*      Do callout verification for an address    *
+*************************************************/
+
+/* This function is called from verify_address() when the address has routed to
+a host list, and a callout has been requested. Callouts are expensive; that is
+why a cache is used to improve the efficiency.
+
+Arguments:
+  addr              the address that's been routed
+  host_list         the list of hosts to try
+  tf                the transport feedback block
+
+  ifstring          "interface" option from transport, or NULL
+  portstring        "port" option from transport, or NULL
+  protocolstring    "protocol" option from transport, or NULL
+  callout           the per-command callout timeout
+  callout_overall   the overall callout timeout (if < 0 use 4*callout)
+  callout_connect   the callout connection timeout (if < 0 use callout)
+  options           the verification options - these bits are used:
+                      vopt_is_recipient => this is a recipient address
+                      vopt_callout_no_cache => don't use callout cache
+                      vopt_callout_fullpm => if postmaster check, do full one
+                      vopt_callout_random => do the "random" thing
+                      vopt_callout_recipsender => use real sender for recipient
+                      vopt_callout_recippmaster => use postmaster for recipient
+  se_mailfrom         MAIL FROM address for sender verify; NULL => ""
+  pm_mailfrom         if non-NULL, do the postmaster check with this sender
+
+Returns:            OK/FAIL/DEFER
+*/
+
+static int
+do_callout(address_item *addr, host_item *host_list, transport_feedback *tf,
+  int callout, int callout_overall, int callout_connect, int options,
+  uschar *se_mailfrom, uschar *pm_mailfrom)
+{
+int yield = OK;
+int old_domain_cache_result = ccache_accept;
+BOOL done = FALSE;
+uschar *address_key;
+uschar *from_address;
+uschar *random_local_part = NULL;
+const uschar *save_deliver_domain = deliver_domain;
+uschar **failure_ptr = options & vopt_is_recipient
+  ? &recipient_verify_failure : &sender_verify_failure;
+dbdata_callout_cache new_domain_record;
+dbdata_callout_cache_address new_address_record;
+time_t callout_start_time;
+
+new_domain_record.result = ccache_unknown;
+new_domain_record.postmaster_result = ccache_unknown;
+new_domain_record.random_result = ccache_unknown;
+
+memset(&new_address_record, 0, sizeof(new_address_record));
+
+/* For a recipient callout, the key used for the address cache record must
+include the sender address if we are using the real sender in the callout,
+because that may influence the result of the callout. */
+
+if (options & vopt_is_recipient)
+  if (options & vopt_callout_recipsender)
+    {
+    from_address = sender_address;
+    address_key = string_sprintf("%s/<%s>", addr->address, sender_address);
+    if (cutthrough.delivery) options |= vopt_callout_no_cache;
+    }
+  else if (options & vopt_callout_recippmaster)
+    {
+    from_address = string_sprintf("postmaster@%s", qualify_domain_sender);
+    address_key = string_sprintf("%s/<postmaster@%s>", addr->address,
+      qualify_domain_sender);
+    }
+  else
+    {
+    from_address = US"";
+    address_key = addr->address;
+    }
+
+/* For a sender callout, we must adjust the key if the mailfrom address is not
+empty. */
+
+else
+  {
+  from_address = se_mailfrom ? se_mailfrom : US"";
+  address_key = *from_address
+    ? string_sprintf("%s/<%s>", addr->address, from_address) : addr->address;
   }
 
+if (cached_callout_lookup(addr, address_key, from_address,
+      &options, &pm_mailfrom, &yield, failure_ptr,
+      &new_domain_record, &old_domain_cache_result))
+  goto END_CALLOUT;
+
 if (!addr->transport)
   {
   HDEBUG(D_verify) debug_printf("cannot callout via null transport\n");
@@ -380,20 +569,18 @@ else
   {
   smtp_transport_options_block *ob =
     (smtp_transport_options_block *)addr->transport->options_block;
+  host_item * host;
 
   /* 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,
   or unless we have a previously cached negative random result. If we are to test
   with a random local part, ensure that such a local part is available. If not,
-  log the fact, but carry on without randomming. */
+  log the fact, but carry on without randomising. */
 
-  if (callout_random && callout_random_local_part != NULL)
-    {
-    random_local_part = expand_string(callout_random_local_part);
-    if (random_local_part == NULL)
+  if (options & vopt_callout_random  &&  callout_random_local_part)
+    if (!(random_local_part = expand_string(callout_random_local_part)))
       log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
         "callout_random_local_part: %s", expand_string_message);
-    }
 
   /* Default the connect and overall callout timeouts if not set, and record the
   time we are starting so that we can enforce it. */
@@ -408,34 +595,36 @@ else
   and cause the client to time out. So in this case we forgo the PIPELINING
   optimization. */
 
-  if (smtp_out != NULL && !disable_callout_flush) mac_smtp_fflush();
+  if (smtp_out && !disable_callout_flush) mac_smtp_fflush();
+
+  clearflag(addr, af_verify_pmfail);  /* postmaster callout flag */
+  clearflag(addr, af_verify_nsfail);  /* null sender callout flag */
+
+/* cutthrough-multi: if a nonfirst rcpt has the same routing as the first,
+and we are holding a cutthrough conn open, we can just append the rcpt to
+that conn for verification purposes (and later delivery also).  Simplest
+coding means skipping this whole loop and doing the append separately.  */
+
+  /* Can we re-use an open cutthrough connection? */
+  if (  cutthrough.fd >= 0
+     && (options & (vopt_callout_recipsender | vopt_callout_recippmaster))
+       == vopt_callout_recipsender
+     && !random_local_part
+     && !pm_mailfrom
+     )
+    done = cutthrough_multi(addr, host_list, tf, &yield);
 
-  /* Now make connections to the hosts and do real callouts. The list of hosts
-  is passed in as an argument. */
+  /* If we did not use a cached connection, make connections to the hosts
+  and do real callouts. The list of hosts is passed in as an argument. */
 
-  for (host = host_list; host != NULL && !done; host = host->next)
+  for (host = host_list; host && !done; host = host->next)
     {
-    smtp_inblock inblock;
-    smtp_outblock outblock;
     int host_af;
     int port = 25;
-    BOOL send_quit = TRUE;
-    uschar *active_hostname = smtp_active_hostname;
-    BOOL lmtp;
-    BOOL smtps;
-    BOOL esmtp;
-    BOOL suppress_tls = FALSE;
     uschar *interface = NULL;  /* Outgoing interface to use; NULL => any */
-    uschar inbuffer[4096];
-    uschar outbuffer[1024];
-    uschar responsebuffer[4096];
+    smtp_context sx;
 
-    clearflag(addr, af_verify_pmfail);  /* postmaster callout flag */
-    clearflag(addr, af_verify_nsfail);  /* null sender callout flag */
-
-    /* Skip this host if we don't have an IP address for it. */
-
-    if (host->address == NULL)
+    if (!host->address)
       {
       DEBUG(D_verify) debug_printf("no IP address for host name %s: skipping\n",
         host->name);
@@ -452,7 +641,7 @@ else
 
     /* Set IPv4 or IPv6 */
 
-    host_af = (Ustrchr(host->address, ':') == NULL)? AF_INET:AF_INET6;
+    host_af = Ustrchr(host->address, ':') ? AF_INET6 : AF_INET;
 
     /* Expand and interpret the interface and port strings. The latter will not
     be used if there is a host-specific port (e.g. from a manualroute router).
@@ -462,460 +651,291 @@ else
 
     deliver_host = host->name;
     deliver_host_address = host->address;
+    deliver_host_port = host->port;
     deliver_domain = addr->domain;
+    transport_name = addr->transport->name;
 
-    if (!smtp_get_interface(tf->interface, host_af, addr, NULL, &interface,
-            US"callout") ||
-        !smtp_get_port(tf->port, addr, &port, US"callout"))
+    if (  !smtp_get_interface(tf->interface, host_af, addr, &interface,
+            US"callout")
+       || !smtp_get_port(tf->port, addr, &port, US"callout")
+       )
       log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address,
         addr->message);
 
-    /* Set HELO string according to the protocol */
-    lmtp= Ustrcmp(tf->protocol, "lmtp") == 0;
-    smtps= Ustrcmp(tf->protocol, "smtps") == 0;
-
-
-    HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", interface, port);
-
-    /* Set up the buffer for reading SMTP response packets. */
-
-    inblock.buffer = inbuffer;
-    inblock.buffersize = sizeof(inbuffer);
-    inblock.ptr = inbuffer;
-    inblock.ptrend = inbuffer;
-
-    /* Set up the buffer for holding SMTP commands while pipelining */
+    sx.addrlist = addr;
+    sx.host = host;
+    sx.host_af = host_af,
+    sx.port = port;
+    sx.interface = interface;
+    sx.helo_data = tf->helo_data;
+    sx.tblock = addr->transport;
+    sx.verify = TRUE;
 
-    outblock.buffer = outbuffer;
-    outblock.buffersize = sizeof(outbuffer);
-    outblock.ptr = outbuffer;
-    outblock.cmd_count = 0;
-    outblock.authenticating = FALSE;
+tls_retry_connection:
+    /* Set the address state so that errors are recorded in it */
 
-    /* Reset the parameters of a TLS session */
-    tls_out.cipher = tls_out.peerdn = NULL;
+    addr->transport_return = PENDING_DEFER;
+    ob->connect_timeout = callout_connect;
+    ob->command_timeout = callout;
 
-    /* Connect to the host; on failure, just loop for the next one, but we
-    set the error for the last one. Use the callout_connect timeout. */
+    /* Get the channel set up ready for a message (MAIL FROM being the next
+    SMTP command to send.  If we tried TLS but it failed, try again without
+    if permitted */
 
-    tls_retry_connection:
-
-    inblock.sock = outblock.sock =
-      smtp_connect(host, host_af, port, interface, callout_connect, TRUE, NULL);
-    /* reconsider DSCP here */
-    if (inblock.sock < 0)
+    yield = smtp_setup_conn(&sx, FALSE);
+#ifdef SUPPORT_TLS
+    if (  yield == DEFER
+       && addr->basic_errno == ERRNO_TLSFAILURE
+       && ob->tls_tempfail_tryclear
+       && verify_check_given_host(&ob->hosts_require_tls, host) != OK
+       )
+      {
+      log_write(0, LOG_MAIN, "TLS session failure:"
+       " callout unencrypted to %s [%s] (not in hosts_require_tls)",
+       host->name, host->address);
+      addr->transport_return = PENDING_DEFER;
+      yield = smtp_setup_conn(&sx, TRUE);
+      }
+#endif
+    if (yield != OK)
       {
-      addr->message = string_sprintf("could not connect to %s [%s]: %s",
-          host->name, host->address, strerror(errno));
+      errno = addr->basic_errno;
+      transport_name = NULL;
       deliver_host = deliver_host_address = NULL;
       deliver_domain = save_deliver_domain;
-      continue;
-      }
 
-    /* Expand the helo_data string to find the host name to use. */
+      /* Failure to accept HELO is cached; this blocks the whole domain for all
+      senders. I/O errors and defer responses are not cached. */
 
-    if (tf->helo_data != NULL)
-      {
-      uschar *s = expand_string(tf->helo_data);
-      if (s == NULL)
-        log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: failed to expand transport's "
-          "helo_data value for callout: %s", addr->address,
-          expand_string_message);
-      else active_hostname = s;
+      if (yield == FAIL && (errno == 0 || errno == ERRNO_SMTPCLOSED))
+       {
+       setflag(addr, af_verify_nsfail);
+       new_domain_record.result = ccache_reject;
+       done = TRUE;
+       }
+      else
+       done = FALSE;
+      goto no_conn;
       }
 
-    /* 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. */
-
-    Ustrcpy(big_buffer, "initial connection");
+    /* If we needed to authenticate, smtp_setup_conn() did that.  Copy
+    the AUTH info for logging */
 
-    /* Unless ssl-on-connect, wait for the initial greeting */
-    smtps_redo_greeting:
+    addr->authenticator = client_authenticator;
+    addr->auth_id = client_authenticated_id;
 
-    #ifdef SUPPORT_TLS
-    if (!smtps || (smtps && tls_out.active >= 0))
-    #endif
-      if (!(done= smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout)))
-        goto RESPONSE_FAILED;
+    sx.from_addr = from_address;
+    sx.first_addr = sx.sync_addr = addr;
+    sx.ok = FALSE;                     /*XXX these 3 last might not be needed for verify? */
+    sx.send_rset = TRUE;
+    sx.completed_addr = FALSE;
 
-    /* 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))
-      DEBUG(D_transport)
-        debug_printf("not sending EHLO (host matches hosts_avoid_esmtp)\n");
+    new_domain_record.result =
+      old_domain_cache_result == ccache_reject_mfnull
+      ? ccache_reject_mfnull : ccache_accept;
 
-    tls_redo_helo:
+    /* Do the random local part check first. Temporarily replace the recipient
+    with the "random" value */
 
-    #ifdef SUPPORT_TLS
-    if (smtps  &&  tls_out.active < 0) /* ssl-on-connect, first pass */
+    if (random_local_part)
       {
-      tls_offered = TRUE;
-      ob->tls_tempfail_tryclear = FALSE;
-      }
-      else                             /* all other cases */
-    #endif
-
-      { esmtp_retry:
-
-      if (!(done= smtp_write_command(&outblock, FALSE, "%s %s\r\n",
-        !esmtp? "HELO" : lmtp? "LHLO" : "EHLO", active_hostname) >= 0))
-        goto SEND_FAILED;
-      if (!smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout))
-        {
-       if (errno != 0 || responsebuffer[0] == 0 || lmtp || !esmtp || tls_out.active >= 0)
-         {
-         done= FALSE;
-         goto RESPONSE_FAILED;
-         }
-        #ifdef SUPPORT_TLS
-        tls_offered = FALSE;
-        #endif
-        esmtp = FALSE;
-        goto esmtp_retry;                      /* fallback to HELO */
-        }
-
-      /* Set tls_offered if the response to EHLO specifies support for STARTTLS. */
-      #ifdef SUPPORT_TLS
-      if (esmtp && !suppress_tls &&  tls_out.active < 0)
-        {
-          if (regex_STARTTLS == NULL) regex_STARTTLS =
-           regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
-
-          tls_offered = pcre_exec(regex_STARTTLS, NULL, CS responsebuffer,
-                       Ustrlen(responsebuffer), 0, PCRE_EOPT, NULL, 0) >= 0;
+      uschar * main_address = addr->address;
+      const uschar * rcpt_domain = addr->domain;
+
+#ifdef SUPPORT_I18N
+      uschar * errstr = NULL;
+      if (  testflag(addr, af_utf8_downcvt)
+        && (rcpt_domain = string_domain_utf8_to_alabel(rcpt_domain,
+                                   &errstr), errstr)
+        )
+       {
+       addr->message = errstr;
+       errno = ERRNO_EXPANDFAIL;
+       setflag(addr, af_verify_nsfail);
+       done = FALSE;
+       rcpt_domain = US"";  /*XXX errorhandling! */
        }
-      else
-        tls_offered = FALSE;
-      #endif
-      }
-
-    /* If TLS is available on this connection attempt to
-    start up a TLS session, unless the host is in hosts_avoid_tls. If successful,
-    send another EHLO - the server may give a different answer in secure mode. We
-    use a separate buffer for reading the response to STARTTLS so that if it is
-    negative, the original EHLO data is available for subsequent analysis, should
-    the client not be required to use TLS. If the response is bad, copy the buffer
-    for error analysis. */
-
-    #ifdef SUPPORT_TLS
-    if (tls_offered &&
-       verify_check_this_host(&(ob->hosts_avoid_tls), NULL, host->name,
-         host->address, NULL) != OK &&
-       verify_check_this_host(&(ob->hosts_verify_avoid_tls), NULL, host->name,
-         host->address, NULL) != OK
-       )
-      {
-      uschar buffer2[4096];
-      if (  !smtps
-         && !(done= smtp_write_command(&outblock, FALSE, "STARTTLS\r\n") >= 0))
-        goto SEND_FAILED;
-
-      /* If there is an I/O error, transmission of this message is deferred. If
-      there is a temporary rejection of STARRTLS and tls_tempfail_tryclear is
-      false, we also defer. However, if there is a temporary rejection of STARTTLS
-      and tls_tempfail_tryclear is true, or if there is an outright rejection of
-      STARTTLS, we carry on. This means we will try to send the message in clear,
-      unless the host is in hosts_require_tls (tested below). */
-
-      if (!smtps && !smtp_read_response(&inblock, buffer2, sizeof(buffer2), '2',
-                       ob->command_timeout))
-        {
-        if (errno != 0 || buffer2[0] == 0 ||
-               (buffer2[0] == '4' && !ob->tls_tempfail_tryclear))
-       {
-       Ustrncpy(responsebuffer, buffer2, sizeof(responsebuffer));
-       done= FALSE;
-       goto RESPONSE_FAILED;
-       }
-        }
-
-       /* STARTTLS accepted or ssl-on-connect: try to negotiate a TLS session. */
-      else
-        {
-       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. */
-        if (rc != OK)
-          {
-       if (rc == DEFER && ob->tls_tempfail_tryclear && !smtps &&
-          verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name,
-            host->address, NULL) != OK)
-         {
-            (void)close(inblock.sock);
-         log_write(0, LOG_MAIN, "TLS session failure: delivering unencrypted "
-           "to %s [%s] (not in hosts_require_tls)", host->name, host->address);
-         suppress_tls = TRUE;
-         goto tls_retry_connection;
-         }
-       /*save_errno = ERRNO_TLSFAILURE;*/
-       /*message = US"failure while setting up TLS session";*/
-       send_quit = FALSE;
-       done= FALSE;
-       goto TLS_FAILED;
-       }
-
-        /* TLS session is set up.  Copy info for logging. */
-        addr->cipher = tls_out.cipher;
-        addr->peerdn = tls_out.peerdn;
-
-        /* For SMTPS we need to wait for the initial OK response, then do HELO. */
-        if (smtps)
-        goto smtps_redo_greeting;
-
-        /* For STARTTLS we need to redo EHLO */
-        goto tls_redo_helo;
-        }
-      }
-
-    /* If the host is required to use a secure channel, ensure that we have one. */
-    if (tls_out.active < 0)
-      if (verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name,
-       host->address, NULL) == OK)
-        {
-        /*save_errno = ERRNO_TLSREQUIRED;*/
-        log_write(0, LOG_MAIN, "a TLS session is required for %s [%s], but %s",
-          host->name, host->address,
-       tls_offered? "an attempt to start TLS failed" : "the server did not offer TLS support");
-        done= FALSE;
-        goto TLS_FAILED;
-        }
+#endif
 
-    #endif /*SUPPORT_TLS*/
+      /* This would be ok for 1st rcpt of a cutthrough (XXX do we have a count?) , but no way to
+      handle a subsequent because of the RSET.  So refuse to support any. */
+      cancel_cutthrough_connection("random-recipient");
+
+      addr->address = string_sprintf("%s@%.1000s",
+                                   random_local_part, rcpt_domain);
+      done = FALSE;
+
+      /* If accepted, we aren't going to do any further tests below.
+      Otherwise, cache a real negative response, and get back to the right
+      state to send RCPT. Unless there's some problem such as a dropped
+      connection, we expect to succeed, because the commands succeeded above.
+      However, some servers drop the connection after responding to an
+      invalid recipient, so on (any) error we drop and remake the connection.
+      XXX We don't care about that for postmaster_full.  Should we?
+
+      XXX could we add another flag to the context, and have the common
+      code emit the RSET too?  Even pipelined after the RCPT...
+      Then the main-verify call could use it if there's to be a subsequent
+      postmaster-verify.
+      The sync_responses() would need to be taught about it and we'd
+      need another return code filtering out to here.
+      */
 
-    done = TRUE; /* so far so good; have response to HELO */
+      /* Remember when we last did a random test */
+      new_domain_record.random_stamp = time(NULL);
 
-    /*XXX the EHLO response would be analyzed here for IGNOREQUOTA, SIZE, PIPELINING */
+      if (smtp_write_mail_and_rcpt_cmds(&sx, &yield) == 0)
+       switch(addr->transport_return)
+         {
+         case PENDING_OK:
+           new_domain_record.random_result = ccache_accept;
+           break;
+         case FAIL:
+           new_domain_record.random_result = ccache_reject;
+
+           /* Between each check, issue RSET, because some servers accept only
+           one recipient after MAIL FROM:<>.
+           XXX We don't care about that for postmaster_full.  Should we? */
+
+           if ((done =
+             smtp_write_command(&sx.outblock, FALSE, "RSET\r\n") >= 0 &&
+             smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer),
+               '2', callout)))
+             break;
+
+           HDEBUG(D_acl|D_v)
+             debug_printf_indent("problem after random/rset/mfrom; reopen conn\n");
+           random_local_part = NULL;
+#ifdef SUPPORT_TLS
+           tls_close(FALSE, TRUE);
+#endif
+           HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
+           (void)close(sx.inblock.sock);
+           sx.inblock.sock = sx.outblock.sock = -1;
+#ifndef DISABLE_EVENT
+           (void) event_raise(addr->transport->event_action,
+                             US"tcp:close", NULL);
+#endif
+           addr->address = main_address;
+           addr->transport_return = PENDING_DEFER;
+           sx.first_addr = sx.sync_addr = addr;
+           sx.ok = FALSE;
+           sx.send_rset = TRUE;
+           sx.completed_addr = FALSE;
+           goto tls_retry_connection;
+         }
 
-    /* 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
+      /* Re-setup for main verify, or for the error message when failing */
+      addr->address = main_address;
+      addr->transport_return = PENDING_DEFER;
+      sx.first_addr = sx.sync_addr = addr;
+      sx.ok = FALSE;
+      sx.send_rset = TRUE;
+      sx.completed_addr = FALSE;
       }
+    else
+      done = TRUE;
 
-    SEND_FAILED:
-    RESPONSE_FAILED:
-    TLS_FAILED:
-    ;
-    /* 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. */
+    /* Main verify. If the host is accepting all local parts, as determined
+    by the "random" check, we don't need to waste time doing any further
+    checking. */
 
-    if (!done)
+    if (done)
       {
-      *failure_ptr = US"mail";     /* At or before MAIL */
-      if (errno == 0 && responsebuffer[0] == '5')
-        {
-        setflag(addr, af_verify_nsfail);
-        new_domain_record.result = ccache_reject;
-        }
+      done = FALSE;
+      switch(smtp_write_mail_and_rcpt_cmds(&sx, &yield))
+       {
+       case 0:  switch(addr->transport_return) /* ok so far */
+                   {
+                   case PENDING_OK:  done = TRUE;
+                                     new_address_record.result = ccache_accept;
+                                     break;
+                   case FAIL:        done = TRUE;
+                                     yield = FAIL;
+                                     *failure_ptr = US"recipient";
+                                     new_address_record.result = ccache_reject;
+                                     break;
+                   default:          break;
+                   }
+                 break;
+
+       case -1:                                /* MAIL response error */
+                 *failure_ptr = US"mail";
+                 if (errno == 0 && sx.buffer[0] == '5')
+                   {
+                   setflag(addr, af_verify_nsfail);
+                   if (from_address[0] == 0)
+                     new_domain_record.result = ccache_reject_mfnull;
+                   }
+                 break;
+                                               /* non-MAIL read i/o error */
+                                               /* non-MAIL response timeout */
+                                               /* internal error; channel still usable */
+       default:  break;                        /* transmit failed */
+       }
       }
 
-    /* If we haven't authenticated, but are required to, give up. */
-    /* Try to AUTH */
-
-    else done = smtp_auth(responsebuffer, sizeof(responsebuffer),
-       addr, host, ob, esmtp, &inblock, &outblock) == OK  &&
-
-               /* Copy AUTH info for logging */
-      ( (addr->authenticator = client_authenticator),
-        (addr->auth_id = client_authenticated_id),
-
-    /* Build a mail-AUTH string (re-using responsebuffer for convenience */
-        !smtp_mail_auth_str(responsebuffer, sizeof(responsebuffer), addr, ob)
-      )  &&
-
-      ( (addr->auth_sndr = client_authenticated_sender),
-
-    /* Send the MAIL command */
-        (smtp_write_command(&outblock, FALSE, "MAIL FROM:<%s>%s\r\n",
-          from_address, responsebuffer) >= 0)
-      )  &&
-
-      smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
-        '2', callout);
+    addr->auth_sndr = client_authenticated_sender;
 
     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
-    that blocks the whole domain for all senders. */
+    /* Do postmaster check if requested; if a full check is required, we
+    check for RCPT TO:<postmaster> (no domain) in accordance with RFC 821. */
 
-    if (!done)
+    if (done && pm_mailfrom)
       {
-      *failure_ptr = US"mail";     /* At or before MAIL */
-      if (errno == 0 && responsebuffer[0] == '5')
-        {
-        setflag(addr, af_verify_nsfail);
-        if (from_address[0] == 0)
-          new_domain_record.result = ccache_reject_mfnull;
-        }
-      }
-
-    /* Otherwise, proceed to check a "random" address (if required), then the
-    given address, and the postmaster address (if required). Between each check,
-    issue RSET, because some servers accept only one recipient after MAIL
-    FROM:<>.
-
-    Before doing this, set the result in the domain cache record to "accept",
-    unless its previous value was ccache_reject_mfnull. In that case, the domain
-    rejects MAIL FROM:<> and we want to continue to remember that. When that is
-    the case, we have got here only in the case of a recipient verification with
-    a non-null sender. */
-
-    else
-      {
-      new_domain_record.result =
-        (old_domain_cache_result == ccache_reject_mfnull)?
-          ccache_reject_mfnull: ccache_accept;
-
-      /* Do the random local part check first */
-
-      if (random_local_part != NULL)
-        {
-        uschar randombuffer[1024];
-        BOOL random_ok =
-          smtp_write_command(&outblock, FALSE,
-            "RCPT TO:<%.1000s@%.1000s>\r\n", random_local_part,
-            addr->domain) >= 0 &&
-          smtp_read_response(&inblock, randombuffer,
-            sizeof(randombuffer), '2', callout);
-
-        /* Remember when we last did a random test */
-
-        new_domain_record.random_stamp = time(NULL);
-
-        /* If accepted, we aren't going to do any further tests below. */
-
-        if (random_ok)
-          {
-          new_domain_record.random_result = ccache_accept;
-          }
-
-        /* Otherwise, cache a real negative response, and get back to the right
-        state to send RCPT. Unless there's some problem such as a dropped
-        connection, we expect to succeed, because the commands succeeded above. */
-
-        else if (errno == 0)
-          {
-          if (randombuffer[0] == '5')
-            new_domain_record.random_result = ccache_reject;
-
-          done =
-            smtp_write_command(&outblock, FALSE, "RSET\r\n") >= 0 &&
-            smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
-              '2', callout) &&
-
-            smtp_write_command(&outblock, FALSE, "MAIL FROM:<%s>\r\n",
-              from_address) >= 0 &&
-            smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
-              '2', callout);
-          }
-        else done = FALSE;    /* Some timeout/connection problem */
-        }                     /* Random check */
-
-      /* If the host is accepting all local parts, as determined by the "random"
-      check, we don't need to waste time doing any further checking. */
-
-      if (new_domain_record.random_result != ccache_accept && done)
-        {
-        /* Get the rcpt_include_affixes flag from the transport if there is one,
-        but assume FALSE if there is not. */
-
-        done =
-          smtp_write_command(&outblock, FALSE, "RCPT TO:<%.1000s>\r\n",
-            transport_rcpt_address(addr,
-              (addr->transport == NULL)? FALSE :
-               addr->transport->rcpt_include_affixes)) >= 0 &&
-          smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
-            '2', callout);
-
-        if (done)
-          new_address_record.result = ccache_accept;
-        else if (errno == 0 && responsebuffer[0] == '5')
-          {
-          *failure_ptr = US"recipient";
-          new_address_record.result = ccache_reject;
-          }
-
-        /* Do postmaster check if requested; if a full check is required, we
-        check for RCPT TO:<postmaster> (no domain) in accordance with RFC 821. */
-
-        if (done && pm_mailfrom != NULL)
-          {
-          /*XXX not suitable for cutthrough - sequencing problems */
-       cutthrough_delivery= FALSE;
-       HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of postmaster verify\n");
-
-          done =
-            smtp_write_command(&outblock, FALSE, "RSET\r\n") >= 0 &&
-            smtp_read_response(&inblock, responsebuffer,
-              sizeof(responsebuffer), '2', callout) &&
-
-            smtp_write_command(&outblock, FALSE,
-              "MAIL FROM:<%s>\r\n", pm_mailfrom) >= 0 &&
-            smtp_read_response(&inblock, responsebuffer,
-              sizeof(responsebuffer), '2', callout) &&
-
-            /* First try using the current domain */
-
-            ((
-            smtp_write_command(&outblock, FALSE,
-              "RCPT TO:<postmaster@%.1000s>\r\n", addr->domain) >= 0 &&
-            smtp_read_response(&inblock, responsebuffer,
-              sizeof(responsebuffer), '2', callout)
-            )
-
-            ||
-
-            /* If that doesn't work, and a full check is requested,
-            try without the domain. */
-
-            (
-            (options & vopt_callout_fullpm) != 0 &&
-            smtp_write_command(&outblock, FALSE,
-              "RCPT TO:<postmaster>\r\n") >= 0 &&
-            smtp_read_response(&inblock, responsebuffer,
-              sizeof(responsebuffer), '2', callout)
-            ));
-
-          /* Sort out the cache record */
-
-          new_domain_record.postmaster_stamp = time(NULL);
-
-          if (done)
-            new_domain_record.postmaster_result = ccache_accept;
-          else if (errno == 0 && responsebuffer[0] == '5')
-            {
-            *failure_ptr = US"postmaster";
-            setflag(addr, af_verify_pmfail);
-            new_domain_record.postmaster_result = ccache_reject;
-            }
-          }
-        }           /* Random not accepted */
-      }             /* MAIL FROM: accepted */
+      /* Could possibly shift before main verify, just above, and be ok
+      for cutthrough.  But no way to handle a subsequent rcpt, so just
+      refuse any */
+      cancel_cutthrough_connection("postmaster verify");
+      HDEBUG(D_acl|D_v) debug_printf_indent("Cutthrough cancelled by presence of postmaster verify\n");
+
+      done = smtp_write_command(&sx.outblock, FALSE, "RSET\r\n") >= 0
+          && smtp_read_response(&sx.inblock, sx.buffer,
+                               sizeof(sx.buffer), '2', callout);
+
+      if (done)
+       {
+       uschar * main_address = addr->address;
+
+       /*XXX oops, affixes */
+       addr->address = string_sprintf("postmaster@%.1000s", addr->domain);
+       addr->transport_return = PENDING_DEFER;
+
+       sx.from_addr = pm_mailfrom;
+       sx.first_addr = sx.sync_addr = addr;
+       sx.ok = FALSE;
+       sx.send_rset = TRUE;
+       sx.completed_addr = FALSE;
+
+       if(  smtp_write_mail_and_rcpt_cmds(&sx, &yield) == 0
+         && addr->transport_return == PENDING_OK
+         )
+         done = TRUE;
+       else
+         done = (options & vopt_callout_fullpm) != 0
+             && smtp_write_command(&sx.outblock, FALSE,
+                           "RCPT TO:<postmaster>\r\n") >= 0
+             && smtp_read_response(&sx.inblock, sx.buffer,
+                           sizeof(sx.buffer), '2', callout);
+
+       /* Sort out the cache record */
+
+       new_domain_record.postmaster_stamp = time(NULL);
+
+       if (done)
+         new_domain_record.postmaster_result = ccache_accept;
+       else if (errno == 0 && sx.buffer[0] == '5')
+         {
+         *failure_ptr = US"postmaster";
+         setflag(addr, af_verify_pmfail);
+         new_domain_record.postmaster_result = ccache_reject;
+         }
 
+       addr->address = main_address;
+       }
+      }
     /* For any failure of the main check, other than a negative response, we just
     close the connection and carry on. We can identify a negative response by the
     fact that errno is zero. For I/O errors it will be non-zero
@@ -926,158 +946,162 @@ else
     don't give the IP address because this may be an internal host whose identity
     is not to be widely broadcast. */
 
-    if (!done)
+no_conn:
+    switch(errno)
       {
-      if (errno == ETIMEDOUT)
-        {
-        HDEBUG(D_verify) debug_printf("SMTP timeout\n");
-        send_quit = FALSE;
-        }
-      else if (errno == 0)
-        {
-        if (*responsebuffer == 0) Ustrcpy(responsebuffer, US"connection dropped");
-
-        addr->message =
-          string_sprintf("response to \"%s\" from %s [%s] was: %s",
-            big_buffer, host->name, host->address,
-            string_printing(responsebuffer));
-
-        addr->user_message = is_recipient?
-          string_sprintf("Callout verification failed:\n%s", responsebuffer)
-          :
-          string_sprintf("Called:   %s\nSent:     %s\nResponse: %s",
-            host->address, big_buffer, responsebuffer);
-
-        /* Hard rejection ends the process */
-
-        if (responsebuffer[0] == '5')   /* Address rejected */
-          {
-          yield = FAIL;
-          done = TRUE;
-          }
-        }
+      case ETIMEDOUT:
+       HDEBUG(D_verify) debug_printf("SMTP timeout\n");
+       sx.send_quit = FALSE;
+       break;
+
+#ifdef SUPPORT_I18N
+      case ERRNO_UTF8_FWD:
+       {
+       extern int acl_where;   /* src/acl.c */
+       errno = 0;
+       addr->message = string_sprintf(
+           "response to \"EHLO\" did not include SMTPUTF8");
+       addr->user_message = acl_where == ACL_WHERE_RCPT
+         ? US"533 no support for internationalised mailbox name"
+         : US"550 mailbox unavailable";
+       yield = FAIL;
+       done = TRUE;
+       }
+       break;
+#endif
+      case ECONNREFUSED:
+       sx.send_quit = FALSE;
+       break;
+
+      case 0:
+       if (*sx.buffer == 0) Ustrcpy(sx.buffer, US"connection dropped");
+
+       /*XXX test here is ugly; seem to have a split of responsibility for
+       building this message.  Need to reationalise.  Where is it done
+       before here, and when not?
+       Not == 5xx resp to MAIL on main-verify
+       */
+       if (!addr->message) addr->message =
+         string_sprintf("response to \"%s\" was: %s",
+                         big_buffer, string_printing(sx.buffer));
+
+       addr->user_message = options & vopt_is_recipient
+         ? string_sprintf("Callout verification failed:\n%s", sx.buffer)
+         : string_sprintf("Called:   %s\nSent:     %s\nResponse: %s",
+           host->address, big_buffer, sx.buffer);
+
+       /* Hard rejection ends the process */
+
+       if (sx.buffer[0] == '5')   /* Address rejected */
+         {
+         yield = FAIL;
+         done = TRUE;
+         }
+       break;
       }
 
     /* End the SMTP conversation and close the connection. */
 
-    /* Cutthrough - on a successfull connect and recipient-verify with use-sender
-    and we have no cutthrough conn so far
+    /* Cutthrough - on a successful connect and recipient-verify with
+    use-sender and we are 1st rcpt and have no cutthrough conn so far
     here is where we want to leave the conn open */
-    if (  cutthrough_delivery
+    if (  cutthrough.delivery
+       && rcpt_count == 1
        && done
        && yield == OK
-       && (options & (vopt_callout_recipsender|vopt_callout_recippmaster)) == vopt_callout_recipsender
+       &&    (options & (vopt_callout_recipsender|vopt_callout_recippmaster|vopt_success_on_redirect))
+          == vopt_callout_recipsender
        && !random_local_part
        && !pm_mailfrom
-       && cutthrough_fd < 0
+       && cutthrough.fd < 0
+       && !sx.lmtp
        )
       {
-      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;
-      cutthrough_addr.host_used->port =    port;
+      HDEBUG(D_acl|D_v) debug_printf_indent("holding verify callout open for cutthrough delivery\n");
+
+      cutthrough.fd = sx.outblock.sock;        /* We assume no buffer in use in the outblock */
+      cutthrough.nrcpt = 1;
+      cutthrough.interface = interface;
+      cutthrough.host = *host;
+      cutthrough.addr = *addr;         /* Save the address_item for later logging */
+      cutthrough.addr.next =     NULL;
+      cutthrough.addr.host_used = &cutthrough.host;
       if (addr->parent)
-        *(cutthrough_addr.parent = store_get(sizeof(address_item)))= *addr->parent;
+        *(cutthrough.addr.parent = store_get(sizeof(address_item))) =
+         *addr->parent;
       ctblock.buffer = ctbuffer;
       ctblock.buffersize = sizeof(ctbuffer);
       ctblock.ptr = ctbuffer;
       /* ctblock.cmd_count = 0; ctblock.authenticating = FALSE; */
-      ctblock.sock = cutthrough_fd;
+      ctblock.sock = cutthrough.fd;
       }
     else
       {
       /* Ensure no cutthrough on multiple address verifies */
       if (options & vopt_callout_recipsender)
-        cancel_cutthrough_connection("multiple verify calls");
-      if (send_quit) (void)smtp_write_command(&outblock, FALSE, "QUIT\r\n");
+        cancel_cutthrough_connection("not usable for cutthrough");
+      if (sx.send_quit)
+       {
+       (void) smtp_write_command(&sx.outblock, FALSE, "QUIT\r\n");
+
+       /* Wait a short time for response, and discard it */
+       smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer),
+         '2', 1);
+       }
 
-      #ifdef SUPPORT_TLS
-      tls_close(FALSE, TRUE);
-      #endif
-      (void)close(inblock.sock);
+      if (sx.inblock.sock >= 0)
+       {
+#ifdef SUPPORT_TLS
+       tls_close(FALSE, TRUE);
+#endif
+       HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
+       (void)close(sx.inblock.sock);
+       sx.inblock.sock = sx.outblock.sock = -1;
+#ifndef DISABLE_EVENT
+       (void) event_raise(addr->transport->event_action, US"tcp:close", NULL);
+#endif
+       }
       }
 
+    if (!done || yield != OK)
+      addr->message = string_sprintf("%s [%s] : %s", host->name, host->address,
+                                   addr->message);
     }    /* Loop through all hosts, while !done */
   }
 
 /* If we get here with done == TRUE, a successful callout happened, and yield
 will be set OK or FAIL according to the response to the RCPT command.
 Otherwise, we looped through the hosts but couldn't complete the business.
-However, there may be domain-specific information to cache in both cases.
-
-The value of the result field in the new_domain record is ccache_unknown if
-there was an error before or with MAIL FROM:, and errno was not zero,
-implying some kind of I/O error. We don't want to write the cache in that case.
-Otherwise the value is ccache_accept, ccache_reject, or ccache_reject_mfnull. */
-
-if (!callout_no_cache && new_domain_record.result != ccache_unknown)
-  {
-  if ((dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE))
-       == NULL)
-    {
-    HDEBUG(D_verify) debug_printf("callout cache: not available\n");
-    }
-  else
-    {
-    (void)dbfn_write(dbm_file, addr->domain, &new_domain_record,
-      (int)sizeof(dbdata_callout_cache));
-    HDEBUG(D_verify) debug_printf("wrote callout cache domain record:\n"
-      "  result=%d postmaster=%d random=%d\n",
-      new_domain_record.result,
-      new_domain_record.postmaster_result,
-      new_domain_record.random_result);
-    }
-  }
+However, there may be domain-specific information to cache in both cases. */
 
-/* If a definite result was obtained for the callout, cache it unless caching
-is disabled. */
-
-if (done)
-  {
-  if (!callout_no_cache && new_address_record.result != ccache_unknown)
-    {
-    if (dbm_file == NULL)
-      dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE);
-    if (dbm_file == NULL)
-      {
-      HDEBUG(D_verify) debug_printf("no callout cache available\n");
-      }
-    else
-      {
-      (void)dbfn_write(dbm_file, address_key, &new_address_record,
-        (int)sizeof(dbdata_callout_cache_address));
-      HDEBUG(D_verify) debug_printf("wrote %s callout cache address record\n",
-        (new_address_record.result == ccache_accept)? "positive" : "negative");
-      }
-    }
-  }    /* done */
+if (!(options & vopt_callout_no_cache))
+  cache_callout_write(&new_domain_record, addr->domain,
+    done, &new_address_record, address_key);
 
 /* Failure to connect to any host, or any response other than 2xx or 5xx is a
 temporary error. If there was only one host, and a response was received, leave
 it alone if supplying details. Otherwise, give a generic response. */
 
-else   /* !done */
+if (!done)
   {
-  uschar *dullmsg = string_sprintf("Could not complete %s verify callout",
-    is_recipient? "recipient" : "sender");
+  uschar * dullmsg = string_sprintf("Could not complete %s verify callout",
+    options & vopt_is_recipient ? "recipient" : "sender");
   yield = DEFER;
 
-  if (host_list->next != NULL || addr->message == NULL) addr->message = dullmsg;
+  addr->message = host_list->next || !addr->message
+    ? dullmsg : string_sprintf("%s: %s", dullmsg, addr->message);
 
-  addr->user_message = (!smtp_return_error_details)? dullmsg :
-    string_sprintf("%s for <%s>.\n"
+  addr->user_message = smtp_return_error_details
+    string_sprintf("%s for <%s>.\n"
       "The mail server(s) for the domain may be temporarily unreachable, or\n"
       "they may be permanently unreachable from this server. In the latter case,\n%s",
       dullmsg, addr->address,
-      is_recipient?
-        "the address will never be accepted."
-        :
-        "you need to change the address or create an MX record for its domain\n"
-        "if it is supposed to be generally accessible from the Internet.\n"
-        "Talk to your mail administrator for details.");
+      options & vopt_is_recipient
+       ? "the address will never be accepted."
+        : "you need to change the address or create an MX record for its domain\n"
+         "if it is supposed to be generally accessible from the Internet.\n"
+         "Talk to your mail administrator for details.")
+    : dullmsg;
 
   /* Force a specific error code */
 
@@ -1087,7 +1111,7 @@ else   /* !done */
 /* Come here from within the cache-reading code on fast-track exit. */
 
 END_CALLOUT:
-if (dbm_file != NULL) dbfn_close(dbm_file);
+tls_modify_variables(&tls_in);
 return yield;
 }
 
@@ -1096,23 +1120,27 @@ return yield;
 /* Called after recipient-acl to get a cutthrough connection open when
    one was requested and a recipient-verify wasn't subsequently done.
 */
-void
+int
 open_cutthrough_connection( address_item * addr )
 {
 address_item addr2;
+int rc;
 
 /* Use a recipient-verify-callout to set up the cutthrough connection. */
 /* We must use a copy of the address for verification, because it might
 get rewritten. */
 
 addr2 = *addr;
-HDEBUG(D_acl) debug_printf("----------- start cutthrough setup ------------\n");
-(void) verify_address(&addr2, NULL,
+HDEBUG(D_acl) debug_printf_indent("----------- %s cutthrough setup ------------\n",
+  rcpt_count > 1 ? "more" : "start");
+rc = verify_address(&addr2, NULL,
        vopt_is_recipient | vopt_callout_recipsender | vopt_callout_no_cache,
        CUTTHROUGH_CMD_TIMEOUT, -1, -1,
        NULL, NULL, NULL);
-HDEBUG(D_acl) debug_printf("----------- end cutthrough setup ------------\n");
-return;
+addr->message = addr2.message;
+addr->user_message = addr2.user_message;
+HDEBUG(D_acl) debug_printf_indent("----------- end cutthrough setup ------------\n");
+return rc;
 }
 
 
@@ -1121,14 +1149,14 @@ return;
 static BOOL
 cutthrough_send(int n)
 {
-if(cutthrough_fd < 0)
+if(cutthrough.fd < 0)
   return TRUE;
 
 if(
 #ifdef SUPPORT_TLS
-   (tls_out.active == cutthrough_fd) ? tls_write(FALSE, ctblock.buffer, n) :
+   (tls_out.active == cutthrough.fd) ? tls_write(FALSE, ctblock.buffer, n) :
 #endif
-   send(cutthrough_fd, ctblock.buffer, n, 0) > 0
+   send(cutthrough.fd, ctblock.buffer, n, 0) > 0
   )
 {
   transport_count += n;
@@ -1136,7 +1164,7 @@ if(
   return TRUE;
 }
 
-HDEBUG(D_transport|D_acl) debug_printf("cutthrough_send failed: %s\n", strerror(errno));
+HDEBUG(D_transport|D_acl) debug_printf_indent("cutthrough_send failed: %s\n", strerror(errno));
 return FALSE;
 }
 
@@ -1160,7 +1188,7 @@ return TRUE;
 BOOL
 cutthrough_puts(uschar * cp, int n)
 {
-if (cutthrough_fd < 0)       return TRUE;
+if (cutthrough.fd < 0)       return TRUE;
 if (_cutthrough_puts(cp, n)) return TRUE;
 cancel_cutthrough_connection("transmit failed");
 return FALSE;
@@ -1168,7 +1196,7 @@ return FALSE;
 
 
 static BOOL
-_cutthrough_flush_send( void )
+_cutthrough_flush_send(void)
 {
 int n= ctblock.ptr-ctblock.buffer;
 
@@ -1181,7 +1209,7 @@ return TRUE;
 
 /* Send out any bufferred output.  Return boolean success. */
 BOOL
-cutthrough_flush_send( void )
+cutthrough_flush_send(void)
 {
 if (_cutthrough_flush_send()) return TRUE;
 cancel_cutthrough_connection("transmit failed");
@@ -1190,7 +1218,7 @@ return FALSE;
 
 
 BOOL
-cutthrough_put_nl( void )
+cutthrough_put_nl(void)
 {
 return cutthrough_puts(US"\r\n", 2);
 }
@@ -1198,7 +1226,7 @@ return cutthrough_puts(US"\r\n", 2);
 
 /* Get and check response from cutthrough target */
 static uschar
-cutthrough_response(char expect, uschar ** copy)
+cutthrough_response(char expect, uschar ** copy, int timeout)
 {
 smtp_inblock inblock;
 uschar inbuffer[4096];
@@ -1208,15 +1236,15 @@ inblock.buffer = inbuffer;
 inblock.buffersize = sizeof(inbuffer);
 inblock.ptr = inbuffer;
 inblock.ptrend = inbuffer;
-inblock.sock = cutthrough_fd;
+inblock.sock = cutthrough.fd;
 /* this relies on (inblock.sock == tls_out.active) */
-if(!smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), expect, CUTTHROUGH_DATA_TIMEOUT))
+if(!smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), expect, timeout))
   cancel_cutthrough_connection("target timeout on read");
 
 if(copy != NULL)
   {
   uschar * cp;
-  *copy= cp= string_copy(responsebuffer);
+  *copy = cp = string_copy(responsebuffer);
   /* Trim the trailing end of line */
   cp += Ustrlen(responsebuffer);
   if(cp > *copy  &&  cp[-1] == '\n') *--cp = '\0';
@@ -1229,23 +1257,23 @@ return responsebuffer[0];
 
 /* Negotiate dataphase with the cutthrough target, returning success boolean */
 BOOL
-cutthrough_predata( void )
+cutthrough_predata(void)
 {
-if(cutthrough_fd < 0)
+if(cutthrough.fd < 0)
   return FALSE;
 
-HDEBUG(D_transport|D_acl|D_v) debug_printf("  SMTP>> DATA\n");
+HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP>> DATA\n");
 cutthrough_puts(US"DATA\r\n", 6);
 cutthrough_flush_send();
 
 /* Assume nothing buffered.  If it was it gets ignored. */
-return cutthrough_response('3', NULL) == '3';
+return cutthrough_response('3', NULL, CUTTHROUGH_DATA_TIMEOUT) == '3';
 }
 
 
-/* fd and use_crlf args only to match write_chunk() */
+/* fd and tctx args only to match write_chunk() */
 static BOOL
-cutthrough_write_chunk(int fd, uschar * s, int len, BOOL use_crlf)
+cutthrough_write_chunk(int fd, transport_ctx * tctx, uschar * s, int len)
 {
 uschar * s2;
 while(s && (s2 = Ustrchr(s, '\n')))
@@ -1262,57 +1290,65 @@ return TRUE;
 /* Expands newlines to wire format (CR,NL).           */
 /* Also sends header-terminating blank line.          */
 BOOL
-cutthrough_headers_send( void )
+cutthrough_headers_send(void)
 {
-if(cutthrough_fd < 0)
+transport_ctx tctx;
+
+if(cutthrough.fd < 0)
   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");
+HDEBUG(D_acl) debug_printf_indent("----------- 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))
+tctx.tblock = cutthrough.addr.transport;
+tctx.addr = &cutthrough.addr;
+tctx.check_string = US".";
+tctx.escape_string = US"..";
+tctx.options = topt_use_crlf;
+
+if (!transport_headers_send(cutthrough.fd, &tctx, &cutthrough_write_chunk))
   return FALSE;
 
-HDEBUG(D_acl) debug_printf("----------- done cutthrough headers send ------------\n");
+HDEBUG(D_acl) debug_printf_indent("----------- done cutthrough headers send ------------\n");
 return TRUE;
 }
 
 
 static void
-close_cutthrough_connection( const char * why )
+close_cutthrough_connection(const char * why)
 {
-if(cutthrough_fd >= 0)
+if(cutthrough.fd >= 0)
   {
   /* We could be sending this after a bunch of data, but that is ok as
      the only way to cancel the transfer in dataphase is to drop the tcp
      conn before the final dot.
   */
   ctblock.ptr = ctbuffer;
-  HDEBUG(D_transport|D_acl|D_v) debug_printf("  SMTP>> QUIT\n");
+  HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP>> QUIT\n");
   _cutthrough_puts(US"QUIT\r\n", 6);   /* avoid recursion */
   _cutthrough_flush_send();
-  /* No wait for response */
+
+  /* Wait a short time for response, and discard it */
+  cutthrough_response('2', NULL, 1);
 
   #ifdef SUPPORT_TLS
   tls_close(FALSE, TRUE);
   #endif
-  (void)close(cutthrough_fd);
-  cutthrough_fd= -1;
-  HDEBUG(D_acl) debug_printf("----------- cutthrough shutdown (%s) ------------\n", why);
+  HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
+  (void)close(cutthrough.fd);
+  cutthrough.fd = -1;
+  HDEBUG(D_acl) debug_printf_indent("----------- cutthrough shutdown (%s) ------------\n", why);
   }
 ctblock.ptr = ctbuffer;
 }
 
 void
-cancel_cutthrough_connection( const char * why )
+cancel_cutthrough_connection(const char * why)
 {
 close_cutthrough_connection(why);
-cutthrough_delivery= FALSE;
+cutthrough.delivery = FALSE;
 }
 
 
@@ -1324,33 +1360,45 @@ cutthrough_delivery= FALSE;
    Return smtp response-class digit.
 */
 uschar *
-cutthrough_finaldot( void )
+cutthrough_finaldot(void)
 {
-HDEBUG(D_transport|D_acl|D_v) debug_printf("  SMTP>> .\n");
+uschar res;
+address_item * addr;
+HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP>> .\n");
 
 /* Assume data finshed with new-line */
-if(!cutthrough_puts(US".", 1) || !cutthrough_put_nl() || !cutthrough_flush_send())
-  return cutthrough_addr.message;
+if(  !cutthrough_puts(US".", 1)
+  || !cutthrough_put_nl()
+  || !cutthrough_flush_send()
+  )
+  return cutthrough.addr.message;
 
-switch(cutthrough_response('2', &cutthrough_addr.message))
+res = cutthrough_response('2', &cutthrough.addr.message, CUTTHROUGH_DATA_TIMEOUT);
+for (addr = &cutthrough.addr; addr; addr = addr->next)
   {
-  case '2':
-    delivery_log(LOG_MAIN, &cutthrough_addr, (int)'>', NULL);
-    close_cutthrough_connection("delivered");
-    break;
+  addr->message = cutthrough.addr.message;
+  switch(res)
+    {
+    case '2':
+      delivery_log(LOG_MAIN, addr, (int)'>', NULL);
+      close_cutthrough_connection("delivered");
+      break;
 
-  case '4':
-    delivery_log(LOG_MAIN, &cutthrough_addr, 0, US"tmp-reject from cutthrough after DATA:");
-    break;
+    case '4':
+      delivery_log(LOG_MAIN, addr, 0,
+       US"tmp-reject from cutthrough after DATA:");
+      break;
 
-  case '5':
-    delivery_log(LOG_MAIN|LOG_REJECT, &cutthrough_addr, 0, US"rejected after DATA:");
-    break;
+    case '5':
+      delivery_log(LOG_MAIN|LOG_REJECT, addr, 0,
+       US"rejected after DATA:");
+      break;
 
-  default:
-    break;
+    default:
+      break;
+    }
   }
-  return cutthrough_addr.message;
+return cutthrough.addr.message;
 }
 
 
@@ -1381,7 +1429,7 @@ if (addr != vaddr)
   vaddr->user_message = addr->user_message;
   vaddr->basic_errno = addr->basic_errno;
   vaddr->more_errno = addr->more_errno;
-  vaddr->p.address_data = addr->p.address_data;
+  vaddr->prop.address_data = addr->prop.address_data;
   copyflag(vaddr, addr, af_pass_message);
   }
 return yield;
@@ -1483,21 +1531,20 @@ verify_address(address_item *vaddr, FILE *f, int options, int callout,
 {
 BOOL allok = TRUE;
 BOOL full_info = (f == NULL)? FALSE : (debug_selector != 0);
-BOOL is_recipient = (options & vopt_is_recipient) != 0;
 BOOL expn         = (options & vopt_expn) != 0;
 BOOL success_on_redirect = (options & vopt_success_on_redirect) != 0;
 int i;
 int yield = OK;
 int verify_type = expn? v_expn :
      address_test_mode? v_none :
-          is_recipient? v_recipient : v_sender;
+          options & vopt_is_recipient? v_recipient : v_sender;
 address_item *addr_list;
 address_item *addr_new = NULL;
 address_item *addr_remote = NULL;
 address_item *addr_local = NULL;
 address_item *addr_succeed = NULL;
-uschar **failure_ptr = is_recipient?
-  &recipient_verify_failure : &sender_verify_failure;
+uschar **failure_ptr = options & vopt_is_recipient
+  &recipient_verify_failure : &sender_verify_failure;
 uschar *ko_prefix, *cr;
 uschar *address = vaddr->address;
 uschar *save_sender;
@@ -1530,7 +1577,7 @@ if (parse_find_at(address) == NULL)
     *failure_ptr = US"qualify";
     return FAIL;
     }
-  address = rewrite_address_qualify(address, is_recipient);
+  address = rewrite_address_qualify(address, options & vopt_is_recipient);
   }
 
 DEBUG(D_verify)
@@ -1545,7 +1592,7 @@ may have been set by domains and local part tests during an ACL. */
 if (global_rewrite_rules != NULL)
   {
   uschar *old = address;
-  address = rewrite_address(address, is_recipient, FALSE,
+  address = rewrite_address(address, options & vopt_is_recipient, FALSE,
     global_rewrite_rules, rewrite_existflags);
   if (address != old)
     {
@@ -1569,7 +1616,7 @@ if (address[0] == 0) return OK;
 
 /* Flip the legacy TLS-related variables over to the outbound set in case
 they're used in the context of a transport used by verification. Reset them
-at exit from this routine. */
+at exit from this routine (so no returns allowed from here on). */
 
 tls_modify_variables(&tls_out);
 
@@ -1578,6 +1625,10 @@ while verifying a sender address (a nice bit of self-reference there). */
 
 save_sender = sender_address;
 
+/* Observability variable for router/transport use */
+
+verify_mode = options & vopt_is_recipient ? US"R" : US"S";
+
 /* Update the address structure with the possibly qualified and rewritten
 address. Set it up as the starting address on the chain of new addresses. */
 
@@ -1593,7 +1644,7 @@ If an address generates more than one child, the loop is used only when
 full_info is set, and this can only be set locally. Remote enquiries just get
 information about the top level address, not anything that it generated. */
 
-while (addr_new != NULL)
+while (addr_new)
   {
   int rc;
   address_item *addr = addr_new;
@@ -1642,18 +1693,18 @@ while (addr_new != NULL)
 
   /* Just in case some router parameter refers to it. */
 
-  return_path = (addr->p.errors_address != NULL)?
-    addr->p.errors_address : sender_address;
+  return_path = addr->prop.errors_address
+    ? addr->prop.errors_address : sender_address;
 
   /* Split the address into domain and local part, handling the %-hack if
   necessary, and then route it. While routing a sender address, set
   $sender_address to <> because that is what it will be if we were trying to
   send a bounce to the sender. */
 
-  if (routed != NULL) *routed = FALSE;
+  if (routed) *routed = FALSE;
   if ((rc = deliver_split_address(addr)) == OK)
     {
-    if (!is_recipient) sender_address = null_sender;
+    if (!(options & vopt_is_recipient)) sender_address = null_sender;
     rc = route_address(addr, &addr_local, &addr_remote, &addr_new,
       &addr_succeed, verify_type);
     sender_address = save_sender;     /* Put back the real sender */
@@ -1667,10 +1718,11 @@ while (addr_new != NULL)
 
   if (rc == OK)
     {
-    if (routed != NULL) *routed = TRUE;
+    if (routed) *routed = TRUE;
     if (callout > 0)
       {
-      host_item *host_list = addr->host_list;
+      transport_instance * tp;
+      host_item * host_list = addr->host_list;
 
       /* Make up some data for use in the case where there is no remote
       transport. */
@@ -1692,18 +1744,18 @@ while (addr_new != NULL)
       transport's options, so as to mimic what would happen if we were really
       sending a message to this address. */
 
-      if (addr->transport != NULL && !addr->transport->info->local)
+      if ((tp = addr->transport) && !tp->info->local)
         {
-        (void)(addr->transport->setup)(addr->transport, addr, &tf, 0, 0, NULL);
+        (void)(tp->setup)(tp, addr, &tf, 0, 0, NULL);
 
         /* If the transport has hosts and the router does not, or if the
         transport is configured to override the router's hosts, we must build a
         host list of the transport's hosts, and find the IP addresses */
 
-        if (tf.hosts != NULL && (host_list == NULL || tf.hosts_override))
+        if (tf.hosts && (!host_list || tf.hosts_override))
           {
           uschar *s;
-          uschar *save_deliver_domain = deliver_domain;
+          const uschar *save_deliver_domain = deliver_domain;
           uschar *save_deliver_localpart = deliver_localpart;
 
           host_list = NULL;    /* Ignore the router's hosts */
@@ -1714,16 +1766,15 @@ while (addr_new != NULL)
           deliver_domain = save_deliver_domain;
           deliver_localpart = save_deliver_localpart;
 
-          if (s == NULL)
+          if (!s)
             {
             log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand list of hosts "
               "\"%s\" in %s transport for callout: %s", tf.hosts,
-              addr->transport->name, expand_string_message);
+              tp->name, expand_string_message);
             }
           else
             {
             int flags;
-            uschar *canonical_name;
             host_item *host, *nexthost;
             host_build_hostlist(&host_list, s, tf.hosts_randomize);
 
@@ -1737,26 +1788,24 @@ while (addr_new != NULL)
             if (tf.qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE;
             if (tf.search_parents) flags |= HOST_FIND_SEARCH_PARENTS;
 
-            for (host = host_list; host != NULL; host = nexthost)
+            for (host = host_list; host; host = nexthost)
               {
               nexthost = host->next;
               if (tf.gethostbyname ||
                   string_is_ip_address(host->name, NULL) != 0)
-                (void)host_find_byname(host, NULL, flags, &canonical_name, TRUE);
+                (void)host_find_byname(host, NULL, flags, NULL, TRUE);
               else
                {
-               uschar * d_request = NULL, * d_require = NULL;
-               if (Ustrcmp(addr->transport->driver_name, "smtp") == 0)
+               dnssec_domains * dnssec_domains = NULL;
+               if (Ustrcmp(tp->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;
+                     (smtp_transport_options_block *) tp->options_block;
+                 dnssec_domains = &ob->dnssec;
                  }
 
                 (void)host_find_bydns(host, NULL, flags, NULL, NULL, NULL,
-                 d_request, d_require, &canonical_name, NULL);
+                 dnssec_domains, NULL, NULL);
                }
               }
             }
@@ -1766,7 +1815,7 @@ while (addr_new != NULL)
       /* Can only do a callout if we have at least one host! If the callout
       fails, it will have set ${sender,recipient}_verify_failure. */
 
-      if (host_list != NULL)
+      if (host_list)
         {
         HDEBUG(D_verify) debug_printf("Attempting full verification using callout\n");
         if (host_checking && !host_checking_callout)
@@ -1807,24 +1856,24 @@ while (addr_new != NULL)
   if (rc == FAIL)
     {
     allok = FALSE;
-    if (f != NULL)
+    if (f)
       {
       address_item *p = addr->parent;
 
       respond_printf(f, "%s%s %s", ko_prefix,
-        full_info? addr->address : address,
-        address_test_mode? "is undeliverable" : "failed to verify");
+        full_info ? addr->address : address,
+        address_test_mode ? "is undeliverable" : "failed to verify");
       if (!expn && admin_user)
         {
         if (addr->basic_errno > 0)
           respond_printf(f, ": %s", strerror(addr->basic_errno));
-        if (addr->message != NULL)
+        if (addr->message)
           respond_printf(f, ": %s", addr->message);
         }
 
       /* Show parents iff doing full info */
 
-      if (full_info) while (p != NULL)
+      if (full_info) while (p)
         {
         respond_printf(f, "%s\n    <-- %s", cr, p->address);
         p = p->parent;
@@ -1834,11 +1883,11 @@ while (addr_new != NULL)
     cancel_cutthrough_connection("routing hard fail");
 
     if (!full_info)
-    {
+      {
       yield = copy_error(vaddr, addr, FAIL);
       goto out;
-    }
-    else yield = FAIL;
+      }
+    yield = FAIL;
     }
 
   /* Soft failure */
@@ -1846,7 +1895,7 @@ while (addr_new != NULL)
   else if (rc == DEFER)
     {
     allok = FALSE;
-    if (f != NULL)
+    if (f)
       {
       address_item *p = addr->parent;
       respond_printf(f, "%s%s cannot be resolved at this time", ko_prefix,
@@ -1855,7 +1904,7 @@ while (addr_new != NULL)
         {
         if (addr->basic_errno > 0)
           respond_printf(f, ": %s", strerror(addr->basic_errno));
-        if (addr->message != NULL)
+        if (addr->message)
           respond_printf(f, ": %s", addr->message);
         else if (addr->basic_errno <= 0)
           respond_printf(f, ": unknown error");
@@ -1863,7 +1912,7 @@ while (addr_new != NULL)
 
       /* Show parents iff doing full info */
 
-      if (full_info) while (p != NULL)
+      if (full_info) while (p)
         {
         respond_printf(f, "%s\n    <-- %s", cr, p->address);
         p = p->parent;
@@ -1877,7 +1926,7 @@ while (addr_new != NULL)
       yield = copy_error(vaddr, addr, DEFER);
       goto out;
       }
-    else if (yield == OK) yield = DEFER;
+    if (yield == OK) yield = DEFER;
     }
 
   /* If we are handling EXPN, we do not want to continue to route beyond
@@ -1886,20 +1935,20 @@ while (addr_new != NULL)
   else if (expn)
     {
     uschar *ok_prefix = US"250-";
-    if (addr_new == NULL)
-      {
-      if (addr_local == NULL && addr_remote == NULL)
+
+    if (!addr_new)
+      if (!addr_local && !addr_remote)
         respond_printf(f, "250 mail to <%s> is discarded\r\n", address);
       else
         respond_printf(f, "250 <%s>\r\n", address);
-      }
-    else while (addr_new != NULL)
+
+    else do
       {
       address_item *addr2 = addr_new;
       addr_new = addr2->next;
-      if (addr_new == NULL) ok_prefix = US"250 ";
+      if (!addr_new) ok_prefix = US"250 ";
       respond_printf(f, "%s<%s>\r\n", ok_prefix, addr2->address);
-      }
+      } while (addr_new);
     yield = OK;
     goto out;
     }
@@ -1921,21 +1970,30 @@ while (addr_new != NULL)
     just a single new address as a special case, and continues on to verify the
     generated address. */
 
-    if (!full_info &&                    /* Stop if short info wanted AND */
-         (((addr_new == NULL ||          /* No new address OR */
-           addr_new->next != NULL ||     /* More than one new address OR */
-           testflag(addr_new, af_pfr)))  /* New address is pfr */
-         ||                              /* OR */
-         (addr_new != NULL &&            /* At least one new address AND */
-          success_on_redirect)))         /* success_on_redirect is set */
+    if (  !full_info                   /* Stop if short info wanted AND */
+       && (  (  !addr_new              /* No new address OR */
+             || addr_new->next         /* More than one new address OR */
+            || testflag(addr_new, af_pfr)      /* New address is pfr */
+            )
+          ||                           /* OR */
+             (  addr_new               /* At least one new address AND */
+             && success_on_redirect    /* success_on_redirect is set */
+         )  )
+       )
       {
-      if (f != NULL) fprintf(f, "%s %s\n", address,
-        address_test_mode? "is deliverable" : "verified");
+      if (f) fprintf(f, "%s %s\n",
+        address, address_test_mode ? "is deliverable" : "verified");
 
       /* If we have carried on to verify a child address, we want the value
       of $address_data to be that of the child */
 
-      vaddr->p.address_data = addr->p.address_data;
+      vaddr->prop.address_data = addr->prop.address_data;
+
+      /* If stopped because more than one new address, cannot cutthrough */
+
+      if (addr_new && addr_new->next)
+       cancel_cutthrough_connection("multiple addresses from routing");
+
       yield = OK;
       goto out;
       }
@@ -1951,24 +2009,25 @@ If there are no local and no remote addresses, and there were no pipes, files,
 or autoreplies, and there were no errors or deferments, the message is to be
 discarded, usually because of the use of :blackhole: in an alias file. */
 
-if (allok && addr_local == NULL && addr_remote == NULL)
+if (allok && !addr_local && !addr_remote)
   {
   fprintf(f, "mail to %s is discarded\n", address);
   goto out;
   }
 
 for (addr_list = addr_local, i = 0; i < 2; addr_list = addr_remote, i++)
-  {
-  while (addr_list != NULL)
+  while (addr_list)
     {
     address_item *addr = addr_list;
     address_item *p = addr->parent;
+    transport_instance * tp = addr->transport;
+
     addr_list = addr->next;
 
     fprintf(f, "%s", CS addr->address);
 #ifdef EXPERIMENTAL_SRS
-    if(addr->p.srs_sender)
-      fprintf(f, "    [srs = %s]", addr->p.srs_sender);
+    if(addr->prop.srs_sender)
+      fprintf(f, "    [srs = %s]", addr->prop.srs_sender);
 #endif
 
     /* If the address is a duplicate, show something about it. */
@@ -1976,72 +2035,62 @@ for (addr_list = addr_local, i = 0; i < 2; addr_list = addr_remote, i++)
     if (!testflag(addr, af_pfr))
       {
       tree_node *tnode;
-      if ((tnode = tree_search(tree_duplicates, addr->unique)) != NULL)
+      if ((tnode = tree_search(tree_duplicates, addr->unique)))
         fprintf(f, "   [duplicate, would not be delivered]");
       else tree_add_duplicate(addr->unique, addr);
       }
 
     /* Now show its parents */
 
-    while (p != NULL)
-      {
+    for (p = addr->parent; p; p = p->parent)
       fprintf(f, "\n    <-- %s", p->address);
-      p = p->parent;
-      }
     fprintf(f, "\n  ");
 
     /* Show router, and transport */
 
-    fprintf(f, "router = %s, ", addr->router->name);
-    fprintf(f, "transport = %s\n", (addr->transport == NULL)? US"unset" :
-      addr->transport->name);
+    fprintf(f, "router = %s, transport = %s\n",
+      addr->router->name, tp ? tp->name : US"unset");
 
     /* Show any hosts that are set up by a router unless the transport
     is going to override them; fiddle a bit to get a nice format. */
 
-    if (addr->host_list != NULL && addr->transport != NULL &&
-        !addr->transport->overrides_hosts)
+    if (addr->host_list && tp && !tp->overrides_hosts)
       {
       host_item *h;
       int maxlen = 0;
       int maxaddlen = 0;
-      for (h = addr->host_list; h != NULL; h = h->next)
-        {
+      for (h = addr->host_list; h; h = h->next)
+        {                              /* get max lengths of host names, addrs */
         int len = Ustrlen(h->name);
         if (len > maxlen) maxlen = len;
-        len = (h->address != NULL)? Ustrlen(h->address) : 7;
+        len = h->address ? Ustrlen(h->address) : 7;
         if (len > maxaddlen) maxaddlen = len;
         }
-      for (h = addr->host_list; h != NULL; h = h->next)
-        {
-        int len = Ustrlen(h->name);
-        fprintf(f, "  host %s ", h->name);
-        while (len++ < maxlen) fprintf(f, " ");
-        if (h->address != NULL)
-          {
-          fprintf(f, "[%s] ", h->address);
-          len = Ustrlen(h->address);
-          }
-        else if (!addr->transport->info->local)  /* Omit [unknown] for local */
-          {
-          fprintf(f, "[unknown] ");
-          len = 7;
-          }
-        else len = -3;
-        while (len++ < maxaddlen) fprintf(f," ");
-        if (h->mx >= 0) fprintf(f, "MX=%d", h->mx);
+      for (h = addr->host_list; h; h = h->next)
+       {
+       fprintf(f, "  host %-*s ", maxlen, h->name);
+
+       if (h->address)
+         fprintf(f, "[%s%-*c", h->address, maxaddlen+1 - Ustrlen(h->address), ']');
+       else if (tp->info->local)
+         fprintf(f, " %-*s ", maxaddlen, "");  /* Omit [unknown] for local */
+       else
+         fprintf(f, "[%s%-*c", "unknown", maxaddlen+1 - 7, ']');
+
+        if (h->mx >= 0) fprintf(f, " MX=%d", h->mx);
         if (h->port != PORT_NONE) fprintf(f, " port=%d", h->port);
-        if (h->status == hstatus_unusable) fprintf(f, " ** unusable **");
-        fprintf(f, "\n");
+        if (running_in_test_harness  &&  h->dnssec == DS_YES) fputs(" AD", f);
+        if (h->status == hstatus_unusable) fputs(" ** unusable **", f);
+       fputc('\n', f);
         }
       }
     }
-  }
 
 /* Yield will be DEFER or FAIL if any one address has, only for full_info (which is
 the -bv or -bt case). */
 
 out:
+verify_mode = NULL;
 tls_modify_variables(&tls_in);
 
 return yield;
@@ -2150,7 +2199,8 @@ for (h = header_list; h != NULL && yield == OK; h = h->next)
         verb = US"begins";
         }
 
-      *msgptr = string_printing(
+      /* deconst cast ok as we're passing a non-const to string_printing() */
+      *msgptr = US string_printing(
         string_sprintf("%s: failing address in \"%.*s:\" header %s: %.*s",
           errmess, tt - h->text, h->text, verb, len, s));
 
@@ -2176,7 +2226,7 @@ return yield;
 *      Check header names for 8-bit characters   *
 *************************************************/
 
-/* This function checks for invalid charcters in header names. See
+/* This function checks for invalid characters in header names. See
 RFC 5322, 2.2. and RFC 6532, 3.
 
 Arguments:
@@ -2563,9 +2613,8 @@ DEBUG(D_ident) debug_printf("doing ident callback\n");
 to the incoming interface address. If the sender host address is an IPv6
 address, the incoming interface address will also be IPv6. */
 
-host_af = (Ustrchr(sender_host_address, ':') == NULL)? AF_INET : AF_INET6;
-sock = ip_socket(SOCK_STREAM, host_af);
-if (sock < 0) return;
+host_af = Ustrchr(sender_host_address, ':') == NULL ? AF_INET : AF_INET6;
+if ((sock = ip_socket(SOCK_STREAM, host_af)) < 0) return;
 
 if (ip_bind(sock, host_af, interface_address, 0) < 0)
   {
@@ -2574,19 +2623,15 @@ if (ip_bind(sock, host_af, interface_address, 0) < 0)
   goto END_OFF;
   }
 
-if (ip_connect(sock, host_af, sender_host_address, port, rfc1413_query_timeout)
-     < 0)
+if (ip_connect(sock, host_af, sender_host_address, port,
+               rfc1413_query_timeout, TRUE) < 0)
   {
-  if (errno == ETIMEDOUT && (log_extra_selector & LX_ident_timeout) != 0)
-    {
+  if (errno == ETIMEDOUT && LOGGING(ident_timeout))
     log_write(0, LOG_MAIN, "ident connection to %s timed out",
       sender_host_address);
-    }
   else
-    {
     DEBUG(D_ident) debug_printf("ident connection to %s failed: %s\n",
       sender_host_address, strerror(errno));
-    }
   goto END_OFF;
   }
 
@@ -2671,9 +2716,9 @@ if (*p == 0) goto END_OFF;
 /* The rest of the line is the data we want. We turn it into printing
 characters when we save it, so that it cannot mess up the format of any logging
 or Received: lines into which it gets inserted. We keep a maximum of 127
-characters. */
+characters. The deconst cast is ok as we fed a nonconst to string_printing() */
 
-sender_ident = string_printing(string_copyn(p, 127));
+sender_ident = US string_printing(string_copyn(p, 127));
 DEBUG(D_ident) debug_printf("sender_ident = %s\n", sender_ident);
 
 END_OFF:
@@ -2718,7 +2763,7 @@ Returns:         OK      matched
 */
 
 int
-check_host(void *arg, uschar *ss, uschar **valueptr, uschar **error)
+check_host(void *arg, const uschar *ss, const uschar **valueptr, uschar **error)
 {
 check_host_block *cb = (check_host_block *)arg;
 int mlen = -1;
@@ -2726,7 +2771,7 @@ int maskoffset;
 BOOL iplookup = FALSE;
 BOOL isquery = FALSE;
 BOOL isiponly = cb->host_name != NULL && cb->host_name[0] == 0;
-uschar *t;
+const uschar *t;
 uschar *semicolon;
 uschar **aliases;
 
@@ -2762,7 +2807,7 @@ if (*ss == '@')
   }
 
 /* If the pattern is an IP address, optionally followed by a bitmask count, do
-a (possibly masked) comparision with the current IP address. */
+a (possibly masked) comparison with the current IP address. */
 
 if (string_is_ip_address(ss, &maskoffset) != 0)
   return (host_is_in_net(cb->host_address, ss, maskoffset)? OK : FAIL);
@@ -2867,9 +2912,9 @@ if (iplookup)
   /* Now do the actual lookup; note that there is no search_close() because
   of the caching arrangements. */
 
-  handle = search_open(filename, search_type, 0, NULL, NULL);
-  if (handle == NULL) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s",
-    search_error_message);
+  if (!(handle = search_open(filename, search_type, 0, NULL, NULL)))
+    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", search_error_message);
+
   result = search_find(handle, filename, key, -1, NULL, 0, 0, NULL);
   if (valueptr != NULL) *valueptr = result;
   return (result != NULL)? OK : search_find_defer? DEFER: FAIL;
@@ -2907,6 +2952,10 @@ if (*t == 0)
   h.address = NULL;
   h.mx = MX_NONE;
 
+  /* Using byname rather than bydns here means we cannot determine dnssec
+  status.  On the other hand it is unclear how that could be either
+  propagated up or enforced. */
+
   rc = host_find_byname(&h, NULL, HOST_FIND_QUALIFY_SINGLE, NULL, FALSE);
   if (rc == HOST_FOUND || rc == HOST_FOUND_LOCAL)
     {
@@ -2939,7 +2988,7 @@ on spec. */
 
 if ((semicolon = Ustrchr(ss, ';')) != NULL)
   {
-  uschar *affix;
+  const uschar *affix;
   int partial, affixlen, starflags, id;
 
   *semicolon = 0;
@@ -3040,12 +3089,12 @@ determined from the IP address, the result is FAIL unless the item
 "+allow_unknown" was met earlier in the list, in which case OK is returned. */
 
 int
-verify_check_this_host(uschar **listptr, unsigned int *cache_bits,
-  uschar *host_name, uschar *host_address, uschar **valueptr)
+verify_check_this_host(const uschar **listptr, unsigned int *cache_bits,
+  const uschar *host_name, const uschar *host_address, const uschar **valueptr)
 {
 int rc;
 unsigned int *local_cache_bits = cache_bits;
-uschar *save_host_address = deliver_host_address;
+const uschar *save_host_address = deliver_host_address;
 check_host_block cb;
 cb.host_name = host_name;
 cb.host_address = host_address;
@@ -3084,6 +3133,15 @@ return rc;
 
 
 
+/*************************************************
+*      Check the given host item matches a list  *
+*************************************************/
+int
+verify_check_given_host(uschar **listptr, host_item *host)
+{
+return verify_check_this_host(CUSS listptr, NULL, host->name, host->address, NULL);
+}
+
 /*************************************************
 *      Check the remote host matches a list      *
 *************************************************/
@@ -3103,7 +3161,7 @@ Returns:               the yield of verify_check_this_host(),
 int
 verify_check_host(uschar **listptr)
 {
-return verify_check_this_host(listptr, sender_host_cache, NULL,
+return verify_check_this_host(CUSS listptr, sender_host_cache, NULL,
   (sender_host_address == NULL)? US"" : sender_host_address, NULL);
 }
 
@@ -3232,23 +3290,39 @@ if (!string_format(query, sizeof(query), "%s.%s", prepend, domain))
 
 /* Look for this query in the cache. */
 
-t = tree_search(dnsbl_cache, query);
+if (  (t = tree_search(dnsbl_cache, query))
+   && (cb = t->data.ptr)->expiry > time(NULL)
+   )
+
+/* Previous lookup was cached */
+
+  {
+  HDEBUG(D_dnsbl) debug_printf("using result of previous DNS lookup\n");
+  }
 
 /* If not cached from a previous lookup, we must do a DNS lookup, and
 cache the result in permanent memory. */
 
-if (t == NULL)
+else
   {
+  uint ttl = 3600;
+
   store_pool = POOL_PERM;
 
-  /* Set up a tree entry to cache the lookup */
+  if (t)
+    {
+    HDEBUG(D_dnsbl) debug_printf("cached data found but past valid time; ");
+    }
 
-  t = store_get(sizeof(tree_node) + Ustrlen(query));
-  Ustrcpy(t->name, query);
-  t->data.ptr = cb = store_get(sizeof(dnsbl_cache_block));
-  (void)tree_insertnode(&dnsbl_cache, t);
+  else
+    {  /* Set up a tree entry to cache the lookup */
+    t = store_get(sizeof(tree_node) + Ustrlen(query));
+    Ustrcpy(t->name, query);
+    t->data.ptr = cb = store_get(sizeof(dnsbl_cache_block));
+    (void)tree_insertnode(&dnsbl_cache, t);
+    }
 
-  /* Do the DNS loopup . */
+  /* Do the DNS lookup . */
 
   HDEBUG(D_dnsbl) debug_printf("new DNS lookup for %s\n", query);
   cb->rc = dns_basic_lookup(&dnsa, query, T_A);
@@ -3264,24 +3338,28 @@ if (t == NULL)
 
   Quite apart from one A6 RR generating multiple addresses, there are DNS
   lists that return more than one A record, so we must handle multiple
-  addresses generated in that way as well. */
+  addresses generated in that way as well.
+
+  Mark the cache entry with the "now" plus the minimum of the address TTLs,
+  or some suitably far-future time if none were found. */
 
   if (cb->rc == DNS_SUCCEED)
     {
     dns_record *rr;
     dns_address **addrp = &(cb->rhs);
     for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
-         rr != NULL;
+         rr;
          rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
       {
       if (rr->type == T_A)
         {
         dns_address *da = dns_address_from_rr(&dnsa, rr);
-        if (da != NULL)
+        if (da)
           {
           *addrp = da;
           while (da->next != NULL) da = da->next;
           addrp = &(da->next);
+         if (ttl > rr->ttl) ttl = rr->ttl;
           }
         }
       }
@@ -3293,17 +3371,10 @@ if (t == NULL)
     if (cb->rhs == NULL) cb->rc = DNS_NODATA;
     }
 
+  cb->expiry = time(NULL)+ttl;
   store_pool = old_pool;
   }
 
-/* Previous lookup was cached */
-
-else
-  {
-  HDEBUG(D_dnsbl) debug_printf("using result of previous DNS lookup\n");
-  cb = t->data.ptr;
-  }
-
 /* We now have the result of the DNS lookup, either newly done, or cached
 from a previous call. If the lookup succeeded, check against the address
 list if there is one. This may be a positive equality list (introduced by
@@ -3334,7 +3405,7 @@ if (cb->rc == DNS_SUCCEED)
       {
       int ipsep = ',';
       uschar ip[46];
-      uschar *ptr = iplist;
+      const uschar *ptr = iplist;
       uschar *res;
 
       /* Handle exact matching */
@@ -3527,7 +3598,9 @@ Note: an address for testing DUL is 192.203.178.4
 Note: a domain for testing RFCI is example.tld.dsn.rfc-ignorant.org
 
 Arguments:
+  where        the acl type
   listptr      the domain/address/data list
+  log_msgptr   log message on error
 
 Returns:    OK      successful lookup (i.e. the address is on the list), or
                       lookup deferred after +include_unknown
@@ -3537,11 +3610,11 @@ Returns:    OK      successful lookup (i.e. the address is on the list), or
 */
 
 int
-verify_check_dnsbl(uschar **listptr)
+verify_check_dnsbl(int where, const uschar ** listptr, uschar ** log_msgptr)
 {
 int sep = 0;
 int defer_return = FAIL;
-uschar *list = *listptr;
+const uschar *list = *listptr;
 uschar *domain;
 uschar *s;
 uschar buffer[1024];
@@ -3584,21 +3657,19 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
 
   /* See if there's explicit data to be looked up */
 
-  key = Ustrchr(domain, '/');
-  if (key != NULL) *key++ = 0;
+  if ((key = Ustrchr(domain, '/'))) *key++ = 0;
 
   /* See if there's a list of addresses supplied after the domain name. This is
   introduced by an = or a & character; if preceded by = we require all matches
   and if preceded by ! we invert the result. */
 
-  iplist = Ustrchr(domain, '=');
-  if (iplist == NULL)
+  if (!(iplist = Ustrchr(domain, '=')))
     {
     bitmask = TRUE;
     iplist = Ustrchr(domain, '&');
     }
 
-  if (iplist != NULL)                          /* Found either = or & */
+  if (iplist)                                 /* Found either = or & */
     {
     if (iplist > domain && iplist[-1] == '!')  /* Handle preceding ! */
       {
@@ -3617,6 +3688,7 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
       }
     }
 
+
   /* If there is a comma in the domain, it indicates that a second domain for
   looking up TXT records is provided, before the main domain. Otherwise we must
   set domain_txt == domain. */
@@ -3662,6 +3734,13 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
 
   if (key == NULL)
     {
+    if (where == ACL_WHERE_NOTSMTP_START || where == ACL_WHERE_NOTSMTP)
+      {
+      *log_msgptr = string_sprintf
+       ("cannot test auto-keyed dnslists condition in %s ACL",
+         acl_wherenames[where]);
+      return ERROR;
+      }
     if (sender_host_address == NULL) return FAIL;    /* can never match */
     if (revadd[0] == 0) invert_address(revadd, sender_host_address);
     rc = one_check_dnsbl(domain, domain_txt, sender_host_address, revadd,
@@ -3687,7 +3766,7 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
     uschar keybuffer[256];
     uschar keyrevadd[128];
 
-    while ((keydomain = string_nextinlist(&key, &keysep, keybuffer,
+    while ((keydomain = string_nextinlist(CUSS &key, &keysep, keybuffer,
             sizeof(keybuffer))) != NULL)
       {
       uschar *prepend = keydomain;
index dbf18d7..dd94089 100644 (file)
@@ -1,4 +1,4 @@
-# automatically generated file - see ../scripts/reversion
-EXIM_RELEASE_VERSION="4.84"
-EXIM_VARIANT_VERSION=""
-EXIM_COMPILE_NUMBER="1"
+# initial version automatically generated from ./release-process/scripts/mk_exim_release
+EXIM_RELEASE_VERSION=4.89
+EXIM_VARIANT_VERSION=
+EXIM_COMPILE_NUMBER=0
diff --git a/util/.gitignore b/util/.gitignore
new file mode 100644 (file)
index 0000000..5d49724
--- /dev/null
@@ -0,0 +1,2 @@
+# Compiled programs:
+gen_pkcs3
diff --git a/util/chunking_fixqueue_finalnewlines.pl b/util/chunking_fixqueue_finalnewlines.pl
new file mode 100755 (executable)
index 0000000..5dddfa5
--- /dev/null
@@ -0,0 +1,160 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+BEGIN { pop @INC if $INC[-1] eq '.' };
+
+use Fcntl qw(:DEFAULT :flock :seek);
+use File::Find;
+use File::Spec;
+
+use constant MIN_AGE => 60; # seconds
+my $exim = exists $ENV{'EXIM_BINARY'} ? $ENV{'EXIM_BINARY'} : 'exim';
+
+my %known_okay = map {$_=>1} qw( linux darwin freebsd );
+unless (exists $known_okay{$^O}) {
+  warn "for ease, this perl uses flock, not fcntl, assuming they're the same\n";
+  warn "this is not known by this author to be the case on $^O\n";
+  warn "please investigate and either add to allowed-list in script, or rewrite\n";
+  die "bailing out";
+
+  # Another approach to rewriting script: stop all exim receivers and
+  # queue-runners, prevent them from starting, then add your OS to the list and
+  # run, even though the locking type is wrong, relying upon not actually
+  # contending.
+}
+
+my $spool_dir = `$exim -n -bP spool_directory`;
+chomp $spool_dir;
+
+chdir(File::Spec->catfile($spool_dir, 'input'))
+    or die "chdir($spool_dir/input) failed: $!\n";
+
+my $exim_msgid_r = qr/(?:[0-9A-Za-z]{6}-[0-9A-Za-z]{6}-[0-9A-Za-z]{2})/;
+my $spool_dfile_r = qr/^(($exim_msgid_r)-D)\z/o;
+
+sub fh_ends_newline {
+  my ($fh, $dfn, $verbose) = @_;
+  seek($fh, -1, 2) or do { warn "seek(file($dfn)) failed: $!\n"; return -1 };
+  my $count = read $fh, my $ch, 1;
+  if ($count == -1) { warn "failed to read last byte of $dfn\n"; return -1 };
+  if ($count == 0) { warn "file shrunk by one??  problem with $dfn\n"; return -1 };
+  if ($ch eq "\n") { print "okay!\n" if $verbose; return 1 }
+  print "PROBLEM: $dfn missing final newline (got $ch)\n" if $verbose;
+  return 0;
+}
+
+
+sub each_found_file {
+  return unless $_ =~ $spool_dfile_r;
+  my ($msgid, $dfn) = ($2, $1);
+
+  # We should have already upgraded Exim before invoking us, thus any spool
+  # files will be old and we can reduce spending time trying to lock files
+  # still being written to, etc.
+  my @st = lstat($dfn) or return;
+  if ($^T - $st[9] < MIN_AGE) { return };
+  -f "./${msgid}-H" || return;
+
+  print "consider: $dfn\n";
+  open(my $fh, '+<:raw', $dfn) or do {
+    warn "open($dfn) failed: $!\n";
+    return;
+  };
+  # return with a lexical FH in modern Perl should guarantee close, AIUI
+
+  # we do our first check without a lock, so that we can scan past messages
+  # being handled by Exim quickly, and only lock up on those which Exim is
+  # trying and failing to deliver.  However, since Exim will be hung on remote
+  # hosts, this is likely.  Thus best to kill queue-runners first.
+
+  return if fh_ends_newline($fh, $dfn, 0); # also returns on error
+  print "Problem? $msgid probably missing newline, locking to be sure ...\n";
+  flock($fh, LOCK_EX) or do { warn "flock(file($dfn)) failed: $!\n"; return };
+  return if fh_ends_newline($fh, $dfn, 1); # also returns on error
+
+  fixup_message($msgid, $dfn, $fh);
+
+  close($fh) or warn "close($dfn) failed: $!\n";
+};
+
+sub fixup_message {
+  my ($msgid, $dfn, $fh) = @_;
+  # we can't freeze the message, our lock stops that, which is good!
+
+  seek($fh, 0, 2) or do { warn "seek(file($dfn)) failed: $!\n"; return -1 };
+
+  my $r = inc_message_header_linecount($msgid);
+  if ($r < 0) {
+    warn "failed to fix message headers in ${msgid}-H so not editing message\n";
+    return;
+  }
+
+  print {$fh} "\n";
+
+  print "${msgid}: added newline\n";
+};
+
+sub inc_message_header_linecount {
+  my ($msgid) = @_;
+  my $name_in = "${msgid}-H";
+  my $name_out = "${msgid}-chunkfix";
+
+  open(my $in, '<:perlio', $name_in) or do { warn "open(${name_in}) failed: $!\n"; return -1 };
+  open(my $out, '>:perlio', $name_out) or do { warn "write-open(${name_out}) failed: $!\n"; return -1 };
+  my $seen = 0;
+  my $lc;
+  foreach (<$in>) {
+    if ($seen) {
+      print {$out} $_;
+      next;
+    }
+    if (/^(-body_linecount\s+)(\d+)(\s*)$/) {
+      $lc = $2 + 1;
+      print {$out} "${1}${lc}${3}";
+      $seen = 1;
+      next;
+    }
+    print {$out} $_;
+  }
+  close($in) or do {
+    warn "read-close(${msgid}-H) failed, assuming incomplete: $!\n";
+    close($out);
+    unlink $name_out;
+    return -1;
+  };
+  close($out) or do {
+    warn "write-close(${msgid}-chunkfix) failed, aborting: $!\n";
+    unlink $name_out;
+    return -1;
+  };
+
+  my @target = stat($name_in) or do { warn "stat($name_in) failed: $!\n"; unlink $name_out; return -1 };
+  my @created = stat($name_out) or do { warn "stat($name_out) failed: $!\n"; unlink $name_out; return -1 };
+  # 4=uid, 5=gid, 2=mode
+  if (($created[5] != $target[5]) or ($created[4] != $target[4])) {
+    chown $target[4], $target[5], $name_out or do {
+      warn "chown($name_out) failed: $!\n";
+      unlink $name_out;
+      return -1;
+    };
+  }
+  if (($created[2]&07777) != ($target[2]&0x7777)) {
+    chmod $target[2]&0x7777, $name_out or do {
+      warn "chmod($name_out) failed: $!\n";
+      unlink $name_out;
+      return -1;
+    };
+  }
+
+  rename $name_out, $name_in or do {
+    warn "rename '${msgid}-chunkfix' -> '${msgid}-H' failed: $!\n";
+    unlink $name_out;
+    return -1;
+  };
+
+  print "${msgid}: linecount set to $lc\n";
+  return 1;
+}
+
+find({wanted => \&each_found_file}, '.');
index cb70eb4..48f989a 100755 (executable)
@@ -24,6 +24,8 @@
 #       Vadim Vygonets <vadik-exim@vygo.net>.  All rights reserved.
 # Public domain is OK with me.
 
+BEGIN { pop @INC if $INC[-1] eq '.' };
+
 use MIME::Base64;
 use Digest::MD5;
 
index 4be2c58..6a467e0 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2012 Phil Pennock.
+/* Copyright (C) 2012,2016 Phil Pennock.
  * This is distributed as part of Exim and licensed under the GPL.
  * See the file "NOTICE" for more details.
  */
@@ -86,7 +86,7 @@ bn_from_text(const char *text)
   rc = BN_hex2bn(&b, spaceless);
 
   if (rc != p - spaceless)
-    die("BN_hex2bn did not convert entire input; took %d of %z bytes",
+    die("BN_hex2bn did not convert entire input; took %d of %zu bytes",
         rc, p - spaceless);
 
   return b;
@@ -134,7 +134,7 @@ emit_c_format_dh(FILE *stream, DH *dh)
       break;
     }
     *nl = '\0';
-    fprintf(stream, "\"%s\\n\"\n", p);
+    fprintf(stream, "\"%s\\n\"%s\n", p, (nl == end - 1 ? ";" : ""));
     p = nl + 1;
   }
 }
@@ -143,9 +143,11 @@ emit_c_format_dh(FILE *stream, DH *dh)
 void __attribute__((__noreturn__))
 usage(FILE *stream, int exitcode)
 {
-  fprintf(stream, "Usage: %s [-CPcst] <dh_p> <dh_g>\n"
+  fprintf(stream, "Usage: %s [-CPcst] <dh_p> <dh_g> [<dh_q>]\n"
 "Both dh_p and dh_g should be hex strings representing the numbers\n"
+"The same applies to the optional dh_q (prime-order subgroup).\n"
 "They may contain whitespace.\n"
+"Older values, dh_g is often just '2', not a long string.\n"
 "\n"
 " -C      show C string form of PEM result\n"
 " -P      do not show PEM\n"
@@ -161,7 +163,7 @@ usage(FILE *stream, int exitcode)
 int
 main(int argc, char *argv[])
 {
-  BIGNUM *p, *g;
+  BIGNUM *p, *g, *q;
   DH *dh;
   int ch;
   bool perform_dh_check = false;
@@ -169,6 +171,7 @@ main(int argc, char *argv[])
   bool show_numbers = false;
   bool show_pem = true;
   bool show_text = false;
+  bool given_q = false;
 
   while ((ch = getopt(argc, argv, "CPcsth")) != -1) {
     switch (ch) {
@@ -201,25 +204,49 @@ main(int argc, char *argv[])
   argc -= optind;
   argv += optind;
 
-  if (argc != 3) {
+  if ((argc < 3) || (argc > 4)) {
     fprintf(stderr, "argc: %d\n", argc);
     usage(stderr, 1);
   }
 
+  // If we use DH_set0_pqg instead of setting dh fields directly; the q value
+  // is optional and may be NULL.
+  // Just blank them all.
+  p = g = q = NULL;
+
   p = bn_from_text(argv[1]);
   g = bn_from_text(argv[2]);
+  if (argc >= 4) {
+    q = bn_from_text(argv[3]);
+    given_q = true;
+  }
 
   if (show_numbers) {
     printf("p = ");
     BN_print_fp(stdout, p);
     printf("\ng = ");
     BN_print_fp(stdout, g);
+    if (given_q) {
+      printf("\nq = ");
+      BN_print_fp(stdout, q);
+    }
     printf("\n");
   }
 
   dh = DH_new();
+  // The documented method for setting q appeared in OpenSSL 1.1.0.
+#if OPENSSL_VERSION_NUMBER >= 0x1010000f
+  // NULL okay for q; yes, the optional value is in the middle.
+  if (DH_set0_pqg(dh, p, q, g) != 1) {
+    die_openssl_err("initialising DH pqg values failed");
+  }
+#else
   dh->p = p;
   dh->g = g;
+  if (given_q) {
+    dh->q = q;
+  }
+#endif
 
   if (perform_dh_check)
     our_dh_check(dh);
@@ -234,6 +261,6 @@ main(int argc, char *argv[])
       PEM_write_DHparams(stdout, dh);
   }
 
-  DH_free(dh); /* should free p & g too */
+  DH_free(dh); /* should free p,g (& q if non-NULL) too */
   return 0;
 }
index 2486e8f..691849d 100755 (executable)
@@ -9,12 +9,13 @@
 # Little Perl script to convert flat file into CDB file. Two advantages over
 # cdbmake-12 awk script that is distributed with CDB:
 #  1) Handles 'dpc22:dpc22@hermes' as well as 'dpc22 dpc22@hermes'
-#  2) Perl works with arbitary length strings: awk chokes at 1,024 chars
+#  2) Perl works with arbitrary length strings: awk chokes at 1,024 chars
 #
 # Cambridge: hermes/src/admin/mkcdb,v 1.9 2005/02/15 18:14:12 fanf2 Exp
 
 use strict;
 
+BEGIN { pop @INC if $INC[-1] eq '.' };
 $ENV{'PATH'} = "";
 umask(022);
 
index 0d214d6..08ca4cb 100755 (executable)
@@ -2,6 +2,7 @@
 # Copyright (C) 2012 Wizards Internet Ltd
 # License GPLv2: GNU GPL version 2 <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>
 use strict;
+BEGIN { pop @INC if $INC[-1] eq '.' };
 use Getopt::Std;
 $Getopt::Std::STANDARD_HELP_VERSION=1;
 use IO::Handle;
index feae3ca..67a171d 100644 (file)
@@ -21,6 +21,7 @@
 #
 use strict;
 use warnings;
+BEGIN { pop @INC if $INC[-1] eq '.' };
 use IO::Select;
 use IO::Socket;
 use Getopt::Long;
index d7fd43a..e212fa2 100644 (file)
@@ -2,6 +2,8 @@
 
 use strict;
 
+BEGIN { pop @INC if $INC[-1] eq '.' };
+
 sub usage () {
   print <<END;
 usage: ratelimit.pl [options] <period> <regex> <logfile>